Adding HttpAPI to a Server (.NET)

This topic will run you through the steps of adding HttpAPI support to a Remoting SDK server. If you don't have a server project yet, create a new CodeFirst Remoting SDK application using the corresponding template.

Then add the HttpApiDispatcher to it:

static class Program
{
    public static int Main(string[] args)
    {
        ApplicationServer server = new ApplicationServer("HttpAPI Sample");

        HttpApiDispatcher dispatcher = new HttpApiDispatcher();
        dispatcher.ApiHost = "localhost:8099";
        dispatcher.Server = server.NetworkServer.ServerChannel as IHttpServer;

        server.Run(args);
        return 0;
    }
}
NotInheritable Class Program
    Public Shared Function Main(args As String()) As Int32
        Dim server As ApplicationServer = New ApplicationServer("HttpAPI Sample")
        Dim dispatcher As HttpApiDispatcher = New HttpApiDispatcher

        dispatcher.ApiHost = "localhost:8099"
        dispatcher.Server = CType(server.NetworkServer.ServerChannel, IHttpServer)

        server.Run(args)

        Return 0
    End Function
End Class
Program = class
public
  class method Main(args: array of String): Integer;
  begin
    var server: ApplicationServer := new ApplicationServer('HttpAPI Sample');

    var dispatcher: HttpApiDispatcher := new HttpApiDispatcher();
    dispatcher.ApiHost := 'localhost:8099';
    dispatcher.Server := IHttpServer(server.NetworkServer.ServerChannel);
    server.Run(args);

    exit 0;
  end;
end;
import RemObjects.SDK.Server

let server = ApplicationServer("HttpAPI Sample")

let dispatcher = HttpApiDispatcher()
dispatcher.ApiHost = "localhost:8099"
dispatcher.Server = (server.NetworkServer.ServerChannel as? IHttpServer)

server.Run(C_ARGV.nativeArray)
package HttpAPISample;

import RemObjects.SDK.Server.*;

public class Program
{
    public static int Main(String []args)
    {
        ApplicationServer server = new ApplicationServer("HttpAPI Sample");

        HttpApiDispatcher dispatcher = new HttpApiDispatcher();
        dispatcher.ApiHost = "localhost:8099";
        dispatcher.Server = (IHttpServer)server.NetworkServer.ServerChannel;

        server.Run(args);
        return 0;
    }
}

You can now run the server and go to http://localhost:8099/api and have a look. The server will expose its meta-information there as JSON, e.g.:

{
  "swagger": "2.0",
  "info": {
    "version": 1.0.0,
    "title": "HttpAPI_Sample"
  },
  "basePath": /api/,
  "consumes": [
    "application/json"
  ],
  "produces": [
    "application/json"
  ],
  "paths": {
  }
}

As an exersise modify the dispatcher's properties to expose base hostname, custom server name and version.

Now let's expose some server methods to play with. Add a new service to the server defined as such:

using System.Collections.Generic;
using System.Net;
using RemObjects.SDK.Server;
using RemObjects.SDK.Server.HttpApi;

namespace HttpAPISample
{
    [Service]
    public class SampleService : Service
    {
        private static IDictionary<string, string> _dataCache = new Dictionary<string, string>();

        [ApiMethod(HttpApiPath = "convert/{value}")]
        public string ConvertToString(int value)
        {
            return "The value is " + value.ToString();
        }

        [ApiMethod(HttpApiPath = "calculate", HttpApiMethod = ApiRequestMethod.Get)]
        public int[] Calculate([ApiQueryParameter] int a, [ApiQueryParameter] int b)
        {
            int[] result = new int[4];

            result[0] = a * b;
            // This code line will result in the DivideByZero exception
            // if the b parameter value is not provided or its value is 0
            // Client will receive a generic 500 Internal Server Error response code
            result[1] = a / b;
            result[2] = a + b;
            result[3] = a - b;

            return result;
        }

        [ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Post, HttpApiResult = HttpStatusCode.Created)]
        public void AddToCache(string key, [ApiQueryParameter]string value)
        {
            if (_dataCache.ContainsKey(key))
            {
                throw new ApiMethodException(HttpStatusCode.Conflict);
            }

            _dataCache.Add(key, value);
        }

        [ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Get)]
        public string ReadFromCache(string key)
        {
            if (!_dataCache.ContainsKey(key))
            {
                throw new ApiMethodException(HttpStatusCode.NotFound);
            }

            return _dataCache[key];
        }

        [ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Put)]
        public void UpdateCache(string key, [ApiQueryParameter]string value)
        {
            if (!_dataCache.ContainsKey(key))
            {
                throw new ApiMethodException(HttpStatusCode.NotFound);
            }

            _dataCache[key] = value;
        }

        [ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Delete)]
        public void DeleteFromCache(string key)
        {
            if (!_dataCache.Remove(key))
            {
                throw new ApiMethodException(HttpStatusCode.NotFound);
            }
        }
    }
}
Imports System.Collections.Generic
Imports System.Net
Imports RemObjects.SDK.Server
Imports RemObjects.SDK.Server.HttpApi

<Service()>
Public Class SampleService
    Inherits Service

    Private Shared _dataCache As IDictionary(Of String, String) = New Dictionary(Of String, String)

    <ApiMethod(HttpApiPath:="convert/{value}")>
    Public Function ConvertToString(ByVal value As Integer) As String
        Return ("The value is " + value.ToString)
    End Function

    <ApiMethod(HttpApiPath:="calculate", HttpApiMethod:=ApiRequestMethod.Get)>
    Public Function Calculate(ByVal a As Integer, ByVal b As Integer) As Integer()
        Dim result() As Integer = New Integer((4) - 1) {}
        result(0) = (a * b)
        ' This code line will result in the DivideByZero exception
        ' if the b parameter value is not provided or its value is 0
        ' Client will receive a generic 500 Internal Server Error response code
        result(1) = (a / b)
        result(2) = (a + b)
        result(3) = (a - b)
        Return result
    End Function

    <ApiMethod(HttpApiPath:="cache/{key}", HttpApiMethod:=ApiRequestMethod.Post, HttpApiResult:=HttpStatusCode.Created)>
    Public Sub AddToCache(ByVal key As String, ByVal value As String)
        If _dataCache.ContainsKey(key) Then
            Throw New ApiMethodException(HttpStatusCode.Conflict)
        End If

        _dataCache.Add(key, value)
    End Sub

    <ApiMethod(HttpApiPath:="cache/{key}", HttpApiMethod:=ApiRequestMethod.Get)>
    Public Function ReadFromCache(ByVal key As String) As String
        If Not _dataCache.ContainsKey(key) Then
            Throw New ApiMethodException(HttpStatusCode.NotFound)
        End If

        Return _dataCache(key)
    End Function

    <ApiMethod(HttpApiPath:="cache/{key}", HttpApiMethod:=ApiRequestMethod.Put)>
    Public Sub UpdateCache(ByVal key As String, ByVal value As String)
        If Not _dataCache.ContainsKey(key) Then
            Throw New ApiMethodException(HttpStatusCode.NotFound)
        End If

        _dataCache(key) = value
    End Sub

    <ApiMethod(HttpApiPath:="cache/{key}", HttpApiMethod:=ApiRequestMethod.Delete)>
    Public Sub DeleteFromCache(ByVal key As String)
        If Not _dataCache.Remove(key) Then
            Throw New ApiMethodException(HttpStatusCode.NotFound)
        End If

    End Sub
End Class
namespace HttpAPISample;

uses
  System.Collections.Generic,
  System.Net,
  RemObjects.SDK.Server,
  RemObjects.SDK.Server.HttpApi;

type
  [Service]
  SampleService = public class(Service)
  private
    class var _dataCache: IDictionary<String,String> := new Dictionary<String,String>();
  public
    [ApiMethod(HttpApiPath := 'convert/{value}')]
    method ConvertToString(value: Integer): String;
    begin
      exit 'The value is ' + value.ToString();
    end;

    [ApiMethod(HttpApiPath := 'calculate', HttpApiMethod := ApiRequestMethod.Get)]
    method Calculate(a: Integer; b: Integer): array of Integer;
    begin
      var &result: array of Integer := new Integer[4];
      &result[0] := a * b;
      //  This code line will result in the DivideByZero exception
      //  if the b parameter value is not provided or its value is 0
      //  Client will receive a generic 500 Internal Server Error response code
      &result[1] := a / b;
      &result[2] := a + b;
      &result[3] := a - b;

      exit &result;
    end;

    [ApiMethod(HttpApiPath := 'cache/{key}', HttpApiMethod := ApiRequestMethod.Post, HttpApiResult := HttpStatusCode.Created)]
    method AddToCache(key: String; value: String);
    begin
      if _dataCache.ContainsKey(key) then begin
        raise new ApiMethodException(HttpStatusCode.Conflict);
      end;

      _dataCache.Add(key, value);
    end;

    [ApiMethod(HttpApiPath := 'cache/{key}', HttpApiMethod := ApiRequestMethod.Get)]
    method ReadFromCache(key: String): String;
    begin
      if not _dataCache.ContainsKey(key) then begin
        raise new ApiMethodException(HttpStatusCode.NotFound);
      end;
      exit _dataCache[key];
    end;

    [ApiMethod(HttpApiPath := 'cache/{key}', HttpApiMethod := ApiRequestMethod.Put)]
    method UpdateCache(key: String; value: String);
    begin
      if not _dataCache.ContainsKey(key) then begin
        raise new ApiMethodException(HttpStatusCode.NotFound);
      end;
      _dataCache[key] := value;
    end;

    [ApiMethod(HttpApiPath := 'cache/{key}', HttpApiMethod := ApiRequestMethod.Delete)]
    method DeleteFromCache(key: String);
    begin
      if not _dataCache.Remove(key) then begin
        raise new ApiMethodException(HttpStatusCode.NotFound);
      end;
    end;
  end;


end.
import System.Collections.Generic
import System.Net
import RemObjects.SDK.Server
import RemObjects.SDK.Server.HttpApi

@Service
open class SampleService : Service { 
    private static var _dataCache: IDictionary<String,String>! = Dictionary<String,String>()

    @ApiMethod(HttpApiPath = "convert/{value}")
    public func ConvertToString(_ value: Int32) -> String! {
        return "The value is " + value.ToString()
    }

    @ApiMethod(HttpApiPath = "calculate", HttpApiMethod = ApiRequestMethod.Get)
    public func Calculate(_ a: Int32, _ b: Int32) -> Int32[] {
        var result: Int32[] = Int32[](count: 4)
        result[0] = a * b
        //  This code line will result in the DivideByZero exception
        //  if the b parameter value is not provided or its value is 0
        //  Client will receive a generic 500 Internal Server Error response code
        result[1] = a / b
        result[2] = a + b
        result[3] = a - b
        return result
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Post, HttpApiResult = HttpStatusCode.Created)
    public func AddToCache(_ key: String!, _ value: String!) {
        if _dataCache.ContainsKey(key) {
            throw ApiMethodException(HttpStatusCode.Conflict)
        }
        _dataCache.Add(key, value)
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Get)
    public func ReadFromCache(_ key: String!) -> String! {
        if !_dataCache.ContainsKey(key) {
            throw ApiMethodException(HttpStatusCode.NotFound)
        }
        return _dataCache[key]
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Put)
    public func UpdateCache(_ key: String!, _ value: String!) {
        if !_dataCache.ContainsKey(key) {
            throw ApiMethodException(HttpStatusCode.NotFound)
        }
        _dataCache[key] = value
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Delete)
    public func DeleteFromCache(_ key: String!) {
        if !_dataCache.Remove(key) {
            throw ApiMethodException(HttpStatusCode.NotFound)
        }
    }
}
package HttpAPISample;

import System.Collections.Generic.*;
import System.Net.*;
import RemObjects.SDK.Server.*;
import RemObjects.SDK.Server.HttpApi.*;

@Service
public class SampleService extends Service
{
    private static IDictionary<String, String> _dataCache = new Dictionary<String, String>();

    @ApiMethod(HttpApiPath = "convert/{value}")
    public String ConvertToString(int value)
    {
        return "The value is " + value.ToString();
    }

    @ApiMethod(HttpApiPath = "calculate", HttpApiMethod = ApiRequestMethod.Get)
    public int[] Calculate(@ApiQueryParameter int a, @ApiQueryParameter int b)
    {
        int[] result = new int[4];

        result[0] = a * b;
        // This code line will result in the DivideByZero exception
        // if the b parameter value is not provided or its value is 0
        // Client will receive a generic 500 Internal Server Error response code
        result[1] = a / b;
        result[2] = a + b;
        result[3] = a - b;

        return result;
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Post, HttpApiResult = HttpStatusCode.Created)
    public void AddToCache(String key, @ApiQueryParameter String value)
    {
        if (_dataCache.ContainsKey(key))
        {
            throw new ApiMethodException(HttpStatusCode.Conflict);
        }

        _dataCache.Add(key, value);
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Get)
    public String ReadFromCache(String key)
    {
        if (!_dataCache.ContainsKey(key))
        {
            throw new ApiMethodException(HttpStatusCode.NotFound);
        }

        return _dataCache[key];
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Put)
    public void UpdateCache(String key, @ApiQueryParameter String value)
    {
        if (!_dataCache.ContainsKey(key))
        {
            throw new ApiMethodException(HttpStatusCode.NotFound);
        }

        _dataCache[key] = value;
    }

    @ApiMethod(HttpApiPath = "cache/{key}", HttpApiMethod = ApiRequestMethod.Delete)
    public void DeleteFromCache(String key)
    {
        if (!_dataCache.Remove(key))
        {
            throw new ApiMethodException(HttpStatusCode.NotFound);
        }
    }
}

Note how several service methods are defined on the same HTTP path. Also take a look how custom error codes are returned to the client.

Restart the server and go to http://localhost:8099/api again. If everything was done correct you'll see much longer service description JSON than in the first time.

You can open the online Swagger Editor and copy-paste the API description JSON from http://localhost:8099/api. This tool not only allows to generate the client-side code to access the server, it also allows to try out the server methods:

Play with different methods and key values to see how the *cache method will return different results and Http result codes.