This document is about: SERVER 4
SWITCH TO

WebRPCs

WebRPC是一種靈活的方式,可以將外部服務與Photon server結合起來。
例如,通過WebRPC,Photon客戶端可以要求伺服器從外部網絡服務中獲取數據。

網絡服務基礎知識

WebRPC的架構很簡單。
Photon伺服器在客戶端和網絡伺服器之間扮演一個_代理_或_中繼_的角色。
WebRPC也可以看作是Webhooks的延伸,甚至可以看作是自定義的客戶端驅動的Webhooks。

請注意以下開發提示:

  • 您可以使用任何語言、框架或網絡伺服器來實現WebRPCs的服務。
  • 在任何情況下,盡量減少您發送的數據,以保持事情的精簡和對玩家的便宜。

配置

在Photon伺服器上,WebRPC的配置可以在:

  • "deploy\Loadbalancing\Master\bin\Photon.LoadBalancing.dll.config "用於主伺服器
  • "deploy\Loadbalancing\GameServer\bin\Photon.LoadBalancing.dll.config "用於遊戲伺服器

它需要一個 BaseURL 值,並且可以被禁用。
下面是一個配置的例子:

XML

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

請求

在客戶端,Photon WebRPC是一個Photon操作,在連接到主伺服器或遊戲伺服器時允許使用。
它需要一個URI路徑字符串(又稱WebRPC方法名稱)和一個包含要發送給網絡服務的數據的第二個參數(又稱WebRPC參數)。
這兩個參數可以傳遞給LoadBalancingClient.OpWebRpc方法。

UriPath是WebRPC的相對路徑,應該與遠程過程的名稱相匹配。
也可以在URL本身中發送參數。
在這種情況下,_query string_應該包含在存儲過程的名稱中,並附加到相對路徑上。

當Photon伺服器收到請求時,存儲過程的名稱將與BaseURL連接,形成WebRPC的URL的絕對路徑,然後將其轉發給同一URL的網絡服務。

由於使用的HTTP請求方法是POST,如果客戶端發送了任何參數,Photon將把它們作為JSON POST數據與默認屬性一起傳遞過去。

  • AppVersion:
    您的應用程序的版本,由遊戲客戶端設置。

  • UserId:
    進行WebRPC的Actor的ID。

找到的任何其他屬性都是客戶端發送的參數的一部分。
如果客戶端使用的WebRpcParameters類型是Dictionary<string, object>,那麼所有的鍵/值對將被發送到數據的根對象中。
否則,如果它是任何其他有效的JSON類型,它將作為一個新的屬性RpcParams的值被包含。

Example 1

您的BaseURL是https://my.service.org,客戶端調用WebRPC("method?id=1", parameters)
參數是一個Dictionary<string, object>,它有一個{{"key1": 1}, {"key2": "yx"}, {"key3": true}} 作為內容。
在這種情況下,Photon將調用https://my.service.org/method?id=1,並額外發送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

您的BaseURL是https://my.service.org,客戶端調用WebRPC("method?id=1", parameters)
參數是一個對象的數組。object[] { 0, "xy", false }
在這種情況下,Photon將調用https://my.service.org/method?id=1,並另外發送 JSON:

JSON

{
    "AppId": "00000000-0000-0000-0000-000000000000",
    "AppVersion": "client-x.y.z",
    "Region": "EU",
    "UserId": "userXYZ",
    "RpcParams": [0, "xy", false]
}

Example 3

您的BaseURL是https://my.service.org,客戶端調用WebRPC("method?id=1", parameters)
參數是一個原始的簡單類型。比方說一個字符串 "test"。
在這種情況下,Photon將調用https://my.service.org/method?id=1,並另外發送JSON:

JSON

{
    "AppId": "00000000-0000-0000-0000-000000000000",
    "AppVersion": "client-x.y.z",
    "Region": "EU",
    "UserId": "userXYZ",
    "RpcParams": "test"
}

發送安全數據

AuthCookie可以在針對自定義認証提供者的成功認証後被檢索到。
欲了解更多信息,請訪問
自定義認証文檔頁面

響應

WebRPCs的網絡服務必須用一個JSON對象來響應,以便Photon將結果發回給客戶端。
預期的響應必須包括 ResultCodeData 和可選的 Message

ResultCode應該是0表示OK,任何其他代碼表示錯誤。
您可以編造它們來幫助客戶端處理錯誤,添加一個可讀的字符串信息總是一個最佳做法。

Data可以是空的,但如果您發送任何東西,它必須是一個有效的JSON對象。

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
    }
}

為什麼選擇WebRPC

發送HTTP請求可以不用Photon。
您可以自己寫一個HTTP客戶端,或者使用SDK或庫中的客戶端。
然而Photon WebRPCs提供的功能不僅僅是這些。
以下是它的一些優點:

  • 把所有的邏輯放在一個連接的客戶端內,處理一個 "生命周期 "和 "狀態機 "可能會更好。在這種情況下,Photon客戶端就是您所需要的。
  • WebRPC操作以兩種方式為您處理JSON序列化。
  • 您可以使用AuthCookie驗証發送WebRPC的客戶端的身份,或者在伺服器之間交換更敏感的數據。閱讀更多關於如何利用這個功能的信息點此

問題排除

以下是您在進行WebRPC調用時可能得到的錯誤代碼列表,以及如何處理每個錯誤:

  • OperationInvalid (-2)

這通常意味著您沒有為您的應用程序配置或啟用WebRPCs伺服器端。

  • HttpLimitReached (32745)

這意味著您每秒鐘發出太多的WebRPC請求。
檢查錯誤信息以了解您所超過的限制。

  • ExternalHttpCallFailed (32744)

這意味著在與配置的外部網絡服務進行通信時出了問題。
在錯誤信息中找到更多細節,並採取相應行動。

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