REST SDK - edcasillas/unity-common-utils GitHub Wiki
The REST SDK is a collection of of classes that help simplifying web requests in Unity.
The main component of the REST SDK is the RestClient
which allows interaction with a web API through the most common HTTP methods: GET
, POST
, PUT
and DELETE
.
Methods within the RestClient
hit the requested API and return a RestResponse
object in their callback, which indicates whether the call was successful and can provide all the details from the response. These details include, in some cases, objects returned by the API in the form of a JSON document which are automatically deserialized to a POCO by the REST SDK.
Using the REST SDK, you don't have to worry about the usage of UnityWebRequests, WWW objects or creating coroutines to receive responses, and you can focus on requesting and retrieving the data you want.
Using the REST SDK requires a little amount of code. In the following example, we'll use it to retrieve the details of a player from an API located at http://www.my-sample-api.com and show the name of the player in a Unity UI text component.
public class PlayerData { // Declare the POCO DTO* where the data received by the server will be deserialized.
public int Id; // Please note fields in the DTO must be fields, not properties with getters and setters. This is a requirement for the JsonUtility to be able to put data into them.
public string Name;
public int Age;
// You can have as many fields as necessary here.
}
[RequireComponent(typeof(Text)]
public class PlayerDataDisplay : MonoBehaviour { // A component to be added to a Game Object to retrieve the data from the API and display them in a label. You would normally do something fancier.
private IRestClient restClient = new RestClient("http://www.my-sample-api.com"); // Declare a field for the REST client. We use it's interface (IRestClient) instead of the implementation (RestClient) as the type of the field so we can extend the functionality of the client in case it's necessary; this will also provide the ability to mock the client for unit tests purposes.
private void Awake() {
var label = GetComponent<Text>();
restClient.Get<PlayerData>("players/1", response => { // Send a request to the API; the full URL to be requested will be http://www.my-sample-api.com/players/1". The second parameter is a callback where the 'response' object is a RestResponse including a Data property, which in this case is of type 'PlayerData' because we specified that in the generic parameter of the 'Get' method.
if(response.IsSuccess) {
label.text = $"{response.Data.Name} ({response.Data.Age} years old)";
}
else {
label.text = "ERROR";
}
});
}
}
* You can read more about the DTO pattern here: https://en.wikipedia.org/wiki/Data_transfer_object
The above example assumes the endpoint at http://www.my-sample-api.com/players/1 includes a JSON response like this:
{
"Id" : 1,
"Name" : "John",
"Age" : 25
}
A good practice when interacting with a server within your game is to create service classes to encapsulate common backend functionality, and wrap the RestClient inside it. This will help you have your code better structured and will also make writing your unit tests easier.
In this example, we're creating a very simple backend service class for operations related to player data:
public class PlayerDataService : IPlayerDataService {
private IRestClient restClient;
public PlayerDataService(IRestClient restClient) {
this.restClient = restClient;
}
public void Create(PlayerData playerData, Action<int> onResponse) {
restClient.Post<PlayerData>($"players", playerData, response => {
if(response.IsSuccess) onResponse.Invoke(response.Data.Id);
else onResponse.Invoke(-1);
});
}
public void Get(int playerId, Action<PlayerData> onResponse) {
restClient.Get<PlayerData>($"players/{playerId}", response => {
if(response.IsSuccess) onResponse.Invoke(response.Data);
else onResponse.Invoke(null);
});
}
public void Update(int playerId, PlayerData playerData, Action<bool> onResponse) {
restClient.Put($"players", playerId, response => {
onResponse.Invoke(response.IsSuccess);
});
}
}
Please note this implementation is only for demonstration purposes.
The first method (Create
) sends a POST
request to the action relative path "players"
of the API to create a player in the backend. Assuming that endpoint returns a PlayerData object as response, the service returns the player Id of the newly created player, or a negative number if unsuccessful.
The second method (Get
) retrieves player data by making a GET
call to the $"players/{playerId}"
relative path of the API, and returns the resulting data in a callback.
Finally, the Update
method sends a PlayerData object to be updated in the server with a PUT
request to the "players"
endpoint.
So let's say, for demonstration purposes only, that you want to create a player, then retreive its data and update its name, you could do something like the following:
IPlayerDataService service = new PlayerDataService(new RestClient("http://www.my-sample-api.com/"));
var playerData = new PlayerData { Name = "John", Age = 25 };
service.Create(playerData, newPlayerId => {
if(newPlayerId > 0) {
service.Get(newPlayerId, playerDataFromBackend => {
if(playerDataFromBackend != null) {
playerDataFromBackend.Name = "John Smith";
service.Update(newPlayerId, playerDataFromBackend, dataWasUpdated => {
if(dataWasUpdated) Debug.Log("Everything's ok");
else Debug.LogError("Couldn't update player's data");
});
}
});
}
});
In the code above, we first created a player data instance locally, then called our service to create it in the backend. We'll receive the ID of this new player in the callback, and we can use it to retrieve the data from the API calling the Get method of the service; this will return a PlayerData object which contains the data stored in the backend, and in the example we use it to modify the name and send the update to the backend through the service. Finally, we get a boolean indicating whether the update was successful in the last callback.
Again, please remember this code is only for demonstration purposes. You would normally not have these calls and callbacks all nested but scattered through different events in your game.
The RestClient
class provided with this library is a generic client which sends request with only one predefined header: "Content-Type": "application/json".
In case you want to replace this header, or add additional headers to each call, you can create a class derived from RestClient
and override the SetRequestHeaders
method.
Let's say you want to add a Bearer token to all your API calls; you can extend the RestClient
like this:
public class MyAuthorizedRestClient: RestClient {
private readonly string authToken;
public MyAuthenticatedRestClient(string apiUrl, string authToken): base(apiUrl) => this.authToken = authToken;
protected override void SetRequestHeaders(UnityWebRequest www) {
www.SetRequestHeader("Authorization", $"Bearer {authToken}");
}
You only care about adding the bearer token to the headers, and the RestClient will take care of everything else.
When you do this, you don't need to modify your code other than the initialization of the rest client. You would replace this:
IRestClient restClient = new RestClient("yourApiUrl");
with this:
IRestClient restClient = new MyAuthorizedRestClient("yourApiUrl", "yourToken");
and the rest of the code can remain the same.