This document is about: SERVER 4
SWITCH TO

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.

We are phasing out "Persistent Games" support. New titles will not be able to rely on the `IsPersistent` setting, WebFlags and related features. At this point, using WebRPCs is discouraged. Clients could simply call your backend via https.

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\Photon.LoadBalancing.dll.config" for MasterServer
  • "deploy\Loadbalancing\GameServer\bin\Photon.LoadBalancing.dll.config" for GameServer

It requires a BaseURL value and can be disabled.
Here is an example of configuration:

XML

<WebRpcSettings Enabled="true">
    <BaseUrl Value="https://example.com/WebRPC" />
</WebRpcSettings>

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

This part explains the particular example 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': GetGamesList.
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 saved State object by looping on the ActorList and comparing UserIds.

  • Properties: The key:value pairs of CustomRoomProperties visible to the lobby that can be retrieved from CustomProperties property contained in the respective room's saved State.

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:

  1. RESTful way:

    Relative URI: /GetGameList/{opponentId}

  2. POST data:

    Relative URI: /GetGameList

    JSON

    {
      "AppId": "00000000-0000-0000-0000-000000000000",
      "AppVersion": "client-x.y.z",
      "Region": "EU",
      "UserId": "userXYZ",
      "OpponentId": "opponentId"
    }
    
  3. 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"
    ]
}
Back to top