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 exercise 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 paste the API description JSON from http://localhost:8099/api. It renders interactive documentation for every method and can also generate client-side code for many languages and platforms:

The editor's Try it out button sends the call from the editor's site (HTTPS) to your local server (HTTP). Browsers block such cross-origin (CORS) requests, so the call won't complete from there. To actually invoke the methods, use the generated client, or - for GET methods - call them straight from your browser.
For example, after storing a value with POST /cache/{key}, you can read it back by opening the matching GET endpoint in your browser:
http://localhost:8099/api/cache/SampleKey

Try the other methods and key values to see how the cache service returns different results and HTTP status codes (200, 201, 404, 409).