Ein herzliches Hallo an alle alten und neuen Leser, wir freuen uns, dass ihr auch heute wieder den Weg zu unserem DevBlog gefunden habt. Nach langer Pause ist heute Chris, unser Programmier-Gott, wieder einmal am Start und hat uns ein paar schicke Codezeilen zum Thema Networking bei Eosis: Raiders of Dawn mitgebracht. Denn damit ein Großteil der Features funktioniert, ist die App auf einen regen Datenaustausch mit dem Server angewiesen.

 

Datenübertragung mit RestSharp und JSON

Bei Eosis wird dies von der RESTful API übernommen. REST steht für Representational State Transfer, ein Programmierparadigma, das gerade für die von Eosis genutzte Client-Server-Architektur geeignet ist und für die zuverlässige Kommunikation zwischen zwei Maschinen sorgt. Bei White Pony verwenden wir RestSharp als Framework für Requests. Die Daten werden dazu in das JSON Format gepackt und verschickt.

Die REST API ist dafür zuständig, den zuverlässigen Austausch von Datenpaketen zu gewährleisten. Das heißt, dass z. B. die Level und Attribute einer Illusion oder auch Informationen über die Routen (Name der Stadt, Standortdaten der Sehenswürdigkeiten, etc.) abgespeichert und anschließend mit der REST API übertragen werden.

 

No-Go Nr. 2: Die Dinge sich selbst überlassen

Ihr seht also: RestSharp ist ein wunderbares Framework, aber es hat eben auch seine kleinen Fehler. So neigt RestSharp u. a. dazu, sich aufzuhängen und dabei Requests nicht zu beenden. Für diesen Fall musste Chris vorsorgen und manuell einen Timeout implementieren – sonst kommt es zu größeren Komplikationen beim Networking und dann ist es um eure Speicherstände trotz aller Sicherheitsvorkehrungen geschehen.

Manuell nachzuarbeiten und immer aufmerksam zu bleiben ist aber nicht nur beim Programmieren wichtig. Auch für die Organisation rund um euer Projekt oder Startup ist es wichtig, niemals den Dingen einfach ihren Lauf zu lassen. Vermeidet, das Projekt einfach „vor sich hin plätschern“ zu lassen. Gerade für Förderung und Feedback gilt: Von nichts kommt nichts! #indiedev #programming #nogo002

 

Alles klar – und wie genau funktioniert das jetzt?

So, nun aber genug vom erhobenen Zeigefinger, zurück zur EosisAPI. Für alle anderen Programmier-Götter da draußen hat Chris nämlich ein paar Ausschnitte aus dem Code mitgebracht. RestSharp kann nicht nur Requests an den Server senden und die Datenpakete direkt in das richtige Objekt parsen (also quasi übersetzen), sondern unterscheidet zusätzlich noch zwischen synchronen und asynchronen Requests.

Bei synchronen Requests wird das Programm so lange angehalten, bis alle Ressourcen heruntergeladen und geparsed wurden, bei asynchronen Requests hingegen läuft das Programm weiter. Ihr ahnt es schon: Durch seine dynamischen Features ist Eosis darauf angewiesen, asynchrone Requests zu stellen. Also los, schauen wir uns das doch mal an.

 

Gestatten: API – EosisAPI

Zu Beginn findet ihr, wie es so üblich ist, einige Definitionen und kleine Helferlein. Alle Antworten von unserem Server sind in einem Objekt zusammengefasst, das zwei weitere Objekte enthält: Metadaten (MetaDataObject MetaData) (wie z. B. den Status und eine eventuelle Fehlernachricht) und die Nutzdaten eines bestimmen Typs (T Payload). Dann folgt auch schon der Kern des ganzen, die EosisAPI.

public enum ResponseStatus
{
     None = 0,
     OK = 1,
     NotFound = 2,
     Error = 3
}
public class APIResponse<T>
{
     public struct MetaDataObject
     {
          public ResponseStatus Status { get; set; }
          public string Message { get; set; }
     }
     public MetaDataObject MetaData { get; set; }
     public T Payload { get; set; }
     public override string ToString()
     {
          return Newtonsoft.Json.JsonConvert.SerializeObject(this, Formatting.Indented);
     }
}

Und das ist sie: Die Klasse EosisAPI. Zuerst müssen die Variablen initialisiert werden. Falls ihr außerdem nicht wisst, was da für komisches Zeug nach dem „:“ steht, das könnt ihr hier nachlesen.

public partial class EosisAPI : Singleton<EosisAPI>
{
     const string BaseUrl = "http://123.123.123.123/path/to/api/";
#if UNITY_EDITOR
     private const float timeout = 5f;
#else
     private const float timeout = 10f;
#endif
     private float timeoutTimer = timeout;
     private const int retryCountMax = 3;
     private int retryCount = retryCountMax;
     private EosisAPI()
     {
     }

 

Alles reine Coroutine

Nachdem die ganzen Variablen nun gesetzt sind, kommen wir zum interessanten Teil der EosisAPI. Execute<T> ist die Hauptfunktion der API. Hier werden alle Requests initialisiert, vorausgesetzt, man ist im Besitz einer Internetverbindung. Falls ja, wird eine Coroutine gestartet, die den Request bearbeitet.

private void Execute<T>(RestRequest request, Action<IRestResponse<APIResponse<T>>> callback) where T : new()
{
     if (Application.internetReachability == NetworkReachability.NotReachable)
     {
          RestResponse<APIResponse<T>> response = new RestResponse<APIResponse<T>>();
          response.ResponseStatus = RestSharp.ResponseStatus.Error;
          response.Data = new APIResponse<T>();
          response.Data.MetaData = new APIResponse<T>.MetaDataObject()
          {
               Message = "No Internet",
               Status = ResponseStatus.Error
          };
          response.Data.Payload = default(T);
          callback(response);
     }
     else
     {
          StartCoroutine(DoExecute<T>(request, callback));
     }
}

Und das hier ist der Enumerator für die Coroutine. Diese Funktion läuft so lange, bis der Request und die Response komplett verarbeitet wurden.

private IEnumerator DoExecute<T>(RestRequest request, Action<IRestResponse<APIResponse<T>>> callback) where T : new()
{
     var client = new RestClient();
     client.BaseUrl = new System.Uri(BaseUrl);
     client.AddHandler("application/json", new JsonDeserializer());
     bool isDone = false;
     IRestResponse<APIResponse<T>> response = new RestResponse<APIResponse<T>>();

Zuerst muss der RestClient mit den richtigen Variablen initialisiert werden. Die BaseUrl ist hierbei das Wichtigste, denn dort hin werden alle Requests geschickt. Einen Handler für application/json zu definieren ist eigentlich nicht nötig, da RestSharp einen eigenen JSON-Deserializer besitzt, doch wir verwenden für all unsere JSON Operationen (auch abseits der API) den .NET JSON Deserializer von Newtonsoft.

 

Ich hätte da eine kurze Anfrage...

Jetzt folgt der eigentliche Request:

var asyncRequest = client.ExecuteAsync<APIResponse<T>>(request, resp =>
{
     isDone = true;
     response = resp;
     timeoutTimer = timeout;
});

Den eben initialisierten Client verwenden wir nun, um den Request, der an die EosisAPI übergeben wurde, asynchron zu bearbeiten. Dazu wird ExecuteAsync<T> des Clients aufgerufen. Sobald RestSharp mit der Bearbeitung fertig ist, wird die Action aus dem zweiten Parameter aufgerufen. Diese Action setzt isDone auf true und setzt den timeoutTimer auf den Ursprungswert zurück.

Debug.Log("EosisAPI: executing... (" + request.Method.ToString() + " on " + request.Resource + ")");
bool aborted = false;
while (!isDone && !aborted)
{
     timeoutTimer -= Time.deltaTime;
     if (timeoutTimer <= 0 && !aborted)
     {
          Debug.Log("Aborting request!");
          aborted = true;
          asyncRequest.Abort();
          timeoutTimer = timeout;
     }
     yield return null;
}

Solange isDone noch auf false steht und der Request auch noch nicht manuell abgebrochen wurde, läuft diese Endlosschleife. Dank der Macht des IEnumerators wird diese ganze Schleife aber nur einmal pro Frame aufgerufen (deshalb das yield return null). Hier wird der timeoutTimer heruntergezählt und der Request evtl. abgebrochen, falls der timeoutTimer auf einen Wert kleiner 0 fällt. Im Normalfall passiert das aber nicht, und isDone wird von der obigen Action, die von RestSharp nach dem Erhalt der Response aufgerufen wird, auf true gesetzt. Damit ist die Schleife beendet und es geht hier weiter:

if (response.StatusCode != HttpStatusCode.OK)
{
     Debug.LogError(response.StatusDescription + "\n" + response.ResponseStatus + "\n" + response.Content);
     TrackingManager.TrackError(response.Content);
}
else if (response.ErrorMessage != null)
{
     Debug.LogError(response.ErrorMessage);
     Debug.Log(response.Content);
     throw response.ErrorException;
     TrackingManager.TrackError(response.ErrorMessage);
}
else if(response.ErrorException != null)
{
     Debug.LogError(response.ErrorException);
     TrackingManager.TrackError(response.ErrorException.ToString());
}
Debug.Log("EosisAPI: Done! (" + request.Method.ToString() + " on " + request.Resource + ")" + (aborted ? " (was aborted)" : ""));

Dieser Abschnitt ist hauptsächlich dafür da, Fehler zu erkennen und diese auszugeben. Das erste if testet, ob der Server mit einem anderen Status Code als OK (also 200) geantwortet hat. In den anderen zwei if's wird überprüft, ob beim Parsen der Antwort ein Fehler aufgetreten ist. Die EosisAPI ist im Grunde jetzt aber fertig mit der Anfrage. Nun muss die Antwort nur noch zurück an den Teil des Codes, der die Anfrage initiiert hat.

if (aborted && retryCount > 0)
{
     retryCount--;
     Execute<T>(request, callback);
}
else
{
     if (aborted)
          Debug.Log("Stop retrying!");
     retryCount = retryCountMax;
     if (callback != null) callback(response);
}

Hier wird nochmal geprüft, ob die Anfrage abgebrochen (aborted) wurde und falls ja, ob man die Anfrage nochmal stellen soll (retryCount > 0). Falls ja, wird von retryCount eins abgezogen und die ganze Anfrage noch einmal gestellt. Falls der Request jedoch erfolgreich war bzw. retryCount 0 erreicht hat, wird die Action, die beim Funktionsaufruf angegeben wurde, aufgerufen und der Request so komplett abgeschlossen.

 

Abgeschlossen – ein gutes Stichwort

Erinnert ihr euch noch daran, dass in der Überschrift „Teil 1“ stand? Sehr gut, dann wisst ihr ja, was jetzt kommt: Wir machen nämlich erst mal Pause und lüften unsere grauen Programmierer-Zellen, aber unser Spaziergang durch den Eosis-Code geht nächste Woche weiter. Wenn ihr das auf keinen Fall verpassen wollt, dann folgt uns am besten auf Facebook und Twitter. Wir freuen uns schon darauf, wenn es nächste Woche wieder heißt: „Die rote oder die blaue Pille?“

Sollen wir dich auf dem Laufenden halten?


Keine Sorge, wir schreiben keinen täglichen, wöchentlichen oder monatlichen Newsletter. Aber wir geben dir gerne per E-Mail Bescheid, sobald bei uns oder unserer App EOSIS etwas Großartiges passiert, wenn du uns deine Mailadresse mitteilst.