WebRPCs
WebRPCs are a flexible way to integrate external services with Photon server.
For instance with a WebRPC, Photon clients can ask the server to fetch data from an external web service.
Web Service Basics
The WebRPC architecture is simple.
Photon Servers play a Proxy or Relay role between client and web server.
WebRPCs can also be seen as an extension of webhooks or even custom client-driven webhooks.
Please take the following development tips into consideration:
- You can use any language or framework or web server to implement the service for the WebRPCs.
- In all cases, try to minimize the data you send to keep things lean and cheap for players.
Configuration
On Photon server, WebRPC configuration can be done in:
- "deploy\LoadBalancing\Master\bin\Master.xml.config" for MasterServer
- "deploy\LoadBalancing\GameServer\bin\GameServer.xml.config" for GameServer
It requires a BaseURL
value and can be disabled.
Here is an example of configuration:
XML
<WebRpc Enabled="true">
<BaseUrl Value="https://example.com/WebRPC" />
</WebRpc>
Request
From client side, Photon WebRPC is a Photon operation that is allowed when connected to either Master or Game servers.
It requires a URI path string (a.k.a WebRPC method name) and optionally a second argument which contains the data to be sent to the web service (a.k.a WebRPC parameters).
Those two arguments can be passed to the LoadBalancingClient.OpWebRpc
method.
UriPath
is the relative path of the WebRPC which should match the remote procedure name.
It is also possible to send parameters in the URL itself.
In this case, the query string should be included in the procedure name, appended to the relative path.
When the request is received by Photon servers, the procedure's name will be concatenated to the BaseURL to form the absolute path of the WebRPC's URL before forwarding it to the web service at that same URL.
Since the HTTP request method used is POST, if there are any parameters sent by client, Photon will pass them over as JSON POST data along with default properties:
AppVersion
:
version of your application as set from the game client.UserId
:
ID of the actor making the WebRPC.
Any other property found is part of the parameters sent by the client.
If the WebRpcParameters
type used in client is Dictionary<string, object>
then all the key/value pairs will be sent in the root object of the post data.
Otherwise, if it is any other JSON valid type, it will be included as a value of a new property RpcParams
.
Example 1
Your BaseURL is https://my.service.org
and the client calls WebRPC("method?id=1", parameters)
.
The parameters are a Dictionary<string, object>
that has a {{"key1": 1}, {"key2": "yx"}, {"key3": true}}
as content.
In this case, Photon will call https://my.service.org/method?id=1
and additionally POST the JSON:
JSON
{
"AppId": "00000000-0000-0000-0000-000000000000",
"AppVersion": "client-x.y.z",
"Region": "EU",
"UserId": "userXYZ",
"key1": 1,
"key2": "yx",
"key3": true
}
Example 2
Your BaseURL is https://my.service.org
and the client calls WebRPC("method?id=1", parameters)
.
The parameters are an array of objects: object[] { 0, "xy", false }
.
In this case, Photon will call https://my.service.org/method?id=1
and additionally POST the JSON:
JSON
{
"AppId": "00000000-0000-0000-0000-000000000000",
"AppVersion": "client-x.y.z",
"Region": "EU",
"UserId": "userXYZ",
"RpcParams": [0, "xy", false]
}
Example 3
Your BaseURL is https://my.service.org
and the client calls WebRPC("method?id=1", parameters)
.
The parameters are is a primitive simple type. Let's say a string "test".
In this case, Photon will call https://my.service.org/method?id=1
and additionally POST the JSON:
JSON
{
"AppId": "00000000-0000-0000-0000-000000000000",
"AppVersion": "client-x.y.z",
"Region": "EU",
"UserId": "userXYZ",
"RpcParams": "test"
}
Sending Secure Data
WebRPC also offers an option to securely transmit the encrypted object AuthCookie
to the web service when available.
This can be done by setting the appropriate webflag (SendAuthCookie = 0x02
) when calling the WebRPC operation method from client code.
The AuthCookie
could be retrieved after a successful authentication against a custom authentication provider.
For more information please visit the
custom authentication documentation page
.
Response
A web service for WebRPCs must respond with a JSON object to allow Photon to send the result back to the clients.
The expected response has to include ResultCode
, Data
and optionally a Message
.
The ResultCode
should be 0 for OK and any other code for errors.
You can make them up to help the client handle the error and adding a readable string message is always a best practice.
The Data
can be empty but if you send anything, it must be a valid JSON object.
Example 1
JSON
{
"ResultCode": "0"
}
Example 2
JSON
{
"ResultCode": "1",
"Message": "Self-explanatory error message"
}
Example 3<!--
JSON
{
"ResultCode": "1",
"Data": {
"Key1": "V",
"Key2": 1,
"Key3": true
}
}
Why Choose WebRPC
Sending HTTP requests can be done without Photon.
You can write a HTTP client yourself or use one available from an SDK or a library.
However Photon WebRPCs offer more than just that.
Here are some of its advantages:
It may be better to keep all logic inside a single connected client and handle a single "lifecycle" and "state machine". Photon client is what you need in this case.
WebRPC operation takes care of the JSON serialization for you in both ways.
You can verify the identity of the client sending the WebRPC using
AuthCookie
or exchange more sensitive data server-to-server. Read more about how to make use of this feature here.
Troubleshooting
Here is the list of the error codes you could get when making WebRPC calls and how to handle each one:
OperationInvalid
(-2)
This generally means that you did not configure or enable WebRPCs server side for your application.
HttpLimitReached
(32745)
This means that you are making too many WebRPC requests per second.
Check the error message to know the limit you have exceeded.
ExternalHttpCallFailed
(32744)
This means that something went wrong when communicating with the configured external web service.
Find more details in the error message and act accordingly.
GetGameList
GetGameList
which is not a built-in nor an out-of-box feature in any of the Photon products.If you still want to use one of the available samples provided in our GitHub page, pay attention to the spelling of the remote procedure name. It does not include an 's':
Otherwise, you are free to choose any name you want or even any WebRPC implementation that suits you.
GetGameList
is a special WebRPC as it was first introduced in the "Memory Demo" to demonstrate Photon's WebRPC feature.
It is also special because almost any game need to fetch a list of previously saved games.
Its basic idea is to send a request to the web service and expect a list of games which could be rejoined by the calling user and continue to play.
This presumes that the application should be properly configured: the BaseUrl
should point to your web service and in order to persist games data IsPersistent
should be set to true
and
PathClose webhook should be activated.
The latter is important as the games data that should be returned by the web service rely on the Photon's serialized room State
.
On the other hand, the web service should have access to the saved game data and should be ready to receive and process HTTP POST requests sent to the WebRPC's path (here GetGameList
).
Since Photon sends the UserId
in all WebRPC requests data, there is no need to add any custom parameter. The request is sent without extra arguments.
The web service should return the list of saved games in Data
as a JSON object. Each saved game's GameId will have a key.
The respective value is also a JSON object composed of two properties:
ActorNr
: The exact same original ActorNr of the actor calling the WebRPC that was assigned to him/her when he/she joined that room for the first time. This could be loaded easily in case it was intentionally and separately saved earlier or it could be retrieved from the respective room's savedState
object by looping on the ActorList and comparing UserIds.Properties
: The key:value pairs of CustomRoomProperties visible to the lobby that can be retrieved fromCustomProperties
property contained in the respective room's savedState
.
JSON
{
"ResultCode":0,
"Message":"",
"Data":{
"RoomA":{
"ActorNr":3,
"Properties":{
"Map":"USA",
"Mode":"FFA",
"Respawn":true
}
},
"RoomB":{
"ActorNr":5,
"Properties":{
"Map":"Germany",
"Mode":"TDM",
"TeamA":1,
"TeamB":5
}
},
"RoomC":{
"ActorNr":1,
"Properties":{
"Map":"Russia",
"Mode":"CTF",
"TeamA":1,
"TeamB":5,
"Flag":20
}
}
}
}
Although WebRPC calls are permitted to both Master and Game servers, it makes more sense to call GetGameList
when not joined to a room, i.e. from Master server.
Passing data to Web Service
WebRPC offers three different ways of passing data from client to web service. The three different types can either be used separately or combined.
For instance if you want to extend GetGameList
and retrieve a filtered list of saved games based on a specific opponent, you can do this in three different ways:
RESTful way:
Relative URI:
/GetGameList/{opponentId}
POST data:
Relative URI:
/GetGameList
JSON
{ "AppId": "00000000-0000-0000-0000-000000000000", "AppVersion": "client-x.y.z", "Region": "EU", "UserId": "userXYZ", "OpponentId": "opponentId" }
Query string (a.k.a. GET parameters):
Relative URI:
/GetGameList?opponentId={opponentId}
Of course the web service should be updated also according to the method chosen.
Data Types Conversion
In this section, only the type of data exchanged between Photon server and the web service is explained.
For more information about data types between clients and Photon servers please refer to serialization in Photon page.
Photon Server -> Web Service
C# / .NET (Photon supported types) | JavaScript / JSON |
---|---|
byte
|
number |
short
|
|
int
|
|
long
|
|
double | |
bool
|
bool |
string
|
string |
byte[] (byte array length < short.MaxValue )
|
string (Base64 encoded) |
T[] (array of supported type T, length < short.MaxValue )
|
array |
Hashtable (of supported types, count < short.MaxValue , preferably Photon implementation)
|
object
|
Dictionary (keys and values of supported types, count < short.MaxValue )
|
object
|
null
|
null
|
Sample request data (types are concatenated)
As sent from Photon Server:
JSON
{
"(Dictionary<String,Object>)Dictionary":{
"(Int32)dk_int":"1",
"(String)dk_str":"dv2",
"(Boolean)dk_bool":"True"
},
"(Hashtable)Hashtable":{
"(Byte)hk_byte":"255",
"(Object[])hk_array":[
"(Int32)0",
"(String)xy",
"(Boolean)False"
],
"hk_null":"null"
},
"null":"null",
"(String[])string[]":[
"PUN",
"TB",
"RT",
"Bolt",
"Chat"
],
"(Byte[])byte[]":[
"255",
"0"
],
"(Int16[])short[]":[
"-32768",
"32767"
],
"(Int32[])int[]":[
"-2147483648",
"2147483647"
],
"(Int64[])long[]":[
"-9223372036854775808",
"9223372036854775807"
],
"(Single[])float[]":[
"-3.402823E+38",
"3.402823E+38"
],
"(Double[])double[]":[
"-1.79769313486232E+308",
"1.79769313486232E+308"
],
"(Boolean[])bool[]":[
"True",
"False"
]
}
As read by Web Service:
JSON
{
"(object)Dictionary":{
"dk_int":"(number)1",
"dk_str":"(string)dv2",
"dk_bool":"(boolean)true"
},
"(object)Hashtable":{
"(number)hk_byte":"255",
"(array)hk_array":[
"(number)0",
"(string)xy",
"(boolean)false"
],
"hk_null":null
},
"null":null,
"(array)string[]":[
"(string)PUN",
"(string)TB",
"(string)RT",
"(string)Bolt",
"(string)Chat"
],
"byte[]":"(string)/wA=",
"(array)short[]":[
"(number)-32768",
"(number)32767"
],
"(array)int[]":[
"(number)-2147483648",
"(number)2147483647"
],
"(array)long[]":[
"(number)-9223372036854776000",
"(number)9223372036854776000"
],
"(array)float[]":[
"(number)-3.40282347e+38",
"(number)3.40282347e+38"
],
"(array)double[]":[
"(number)-1.7976931348623157e+308",
"(number)1.7976931348623157e+308"
],
"(array)bool[]":[
"(boolean)true",
"(boolean)false"
]
}
Web Service -> Photon Server
Here is a table that matches each JavaScript/JSON type to its equivalent one in C#/.Net :
JavaScript / JSON | C# / .Net |
---|---|
object |
Dictionary
|
array |
object[] (array of objects)
|
number (integral) |
long
|
number (floating) |
double
|
string |
string
|
boolean |
bool
|
null (not a type)
|
null
|
undefined (when sent)
|
null
|
Sample response data (types are concatenated)
As sent from Web Service:
JSON
{
"(object)number": {
"(number)MAX_VALUE": "1.7976931348623157e+308",
"(number)MIN_VALUE": "5e-324"
},
"(object)object": {
"(string)string": "xyz",
"null": null,
"(boolean)bool": "false",
"(undefined)undefined": "undefined",
"(number)float": "-3.14",
"(number)integer": "123456"
},
"(array)array": [
"(string)xyz",
"(number)0",
"(boolean)true",
null,
"(undefined)undefined"
]
}
As read from Photon Server:
JSON
{
"(Dictionary<String,Object>)number":{
"(Double)MAX_VALUE":"1.79769313486232E+308",
"(Double)MIN_VALUE":"4.94065645841247E-324"
},
"(Dictionary<String,Object>)object":{
"(String)string":"xyz",
"null":"null",
"(Boolean)bool":"False",
"(Double)float":"-3.14",
"(Int64)integer":"123456"
},
"(Object[])array":[
"(String)xyz",
"(Int64)0",
"(Boolean)True",
"null",
"null"
]
}