C# Very simple REST server

How to build a simple REST server in C#.

Main challenge was to bend C# web application to comply with the request and the response in the structure I wanted it to be.

If you want to skip my explanations, all the code is here: https://github.com/tan-tan-kanarek/csharp-rest-server-example

  1. Application configuration – WebApiConfig
First I defined the mapping to the different controllers types.
Routing path to all standard controllers using service and action in the path:

config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/service/{controller}/action/{action}"
            );

Routing path to multi-request controller:

config.Routes.MapHttpRoute(
                name: "Multirequest",
                routeTemplate: "api/service/multirequest",
                defaults: new { Controller = "multirequest" }
            );

Routing path to root, where scheme XML should be returned:

config.Routes.MapHttpRoute(
                name: "Scheme",
                routeTemplate: "",
                defaults: new { Controller = "scheme" }
            );

Next I defined request parser to translate the JSON request into my objects types:

config.Filters.Add(new RequestParser());

And response handlers to wrap the results with my own structure:

config.Filters.Add(new ErrorHandler());
config.MessageHandlers.Add(new WrappingHandler());

Last thing I set the XML serializer settings:

List knownTypes = Assembly.GetExecutingAssembly().GetTypes().Where(type => typeof(IRestObject).IsAssignableFrom(type) && !type.IsGenericType).ToList();
            knownTypes.Add(typeof(RestResponseList));

XmlObjectSerializer xmlSerializer = new DataContractSerializer(typeof(RestResponse), knownTypes);
config.Formatters.XmlFormatter.SetSerializer(xmlSerializer);

config.Formatters.XmlFormatter.SetSerializer(new XmlSerializer(typeof(SchemeController.Scheme)));

config.Formatters.XmlFormatter.WriterSettings.NamespaceHandling = NamespaceHandling.OmitDuplicates;

  2. Example users controller – UserController
User controller, like any controller in C# must implement ApiController and for my own marking to know it’s a standard controller, I marked it also as IRestController.

public class UserController : ApiController, IRestController
{
}

In order to specify action name for each method, I just added ActionName attribute:

[ActionName("add"), HttpPost]
public User Add([ValueProvider] User user)
{
    return user.Add();
}

Note the [ValueProvider] attribute that means that I will parse the POST body and will provide the value for user object, I do that in the RequestParser.

  3. Request parser – RequestParser
By implementing ActionFilterAttribute.OnActionExecuting I can manipulate the methods inputs, in my case I parse the JSON that I extracted from actionContext.Request.Content, then I go through the method arguments using actionContext.ActionDescriptor.ActionBinding.ParameterBindings and deserialize each argument from JSON object into the expected argument type.

  4. Data model – User
Each object in the data model must extend RestObject and define DataContract attribute, while each property must define the DataMember attribute, both for XML and JSON serialization.
I also used the same object to map the properties to the database using RestTable and RestColumn attributes.

[DataContract(Namespace = "")]
[RestTable("users")]
public class User : RestPersistentObject
{
    public User()
        : base()
    {
    }

    public User(long id)
        : base(id)
    {
    }

    protected override void SetDefaults()
    {
        base.SetDefaults();
        Status = UserStatus.ACTIVE;
    }

    [DataMember(Name = "firstName")]
    [RestColumn("first_name")]
	public string FirstName { get; set; }

    [DataMember(Name = "lastName")]
    [RestColumn("last_name")]
	public string LastName { get; set; }

    [DataMember(Name = "status")]
	public UserStatus? Status { get; set; }
}

Note that extending RestPersistentObject gives me id, created-at and updated-at properties.

  5. Multi-request – MultirequestController
Multi-request controller enables series of requests, that can save the HTTP round-trip in case you want to execute several requests together.
Note that this controller force me to have flat list of services, it simplifies the routing URLs as well as the server code.

Node.js Very simple REST server

How to build a simple REST server in node.js.

For this example I tried to avoid any frameworks targeting at a simple node.js code.
I did use however ES6 and promises.

If you want to skip my explanations, all the code is here: https://github.com/tan-tan-kanarek/node-rest-server-example

  1. Main script – index.js
The main script defines a new RestServer with port to listen for HTTP requests and map of services (controllers), while the map keys will be used as service name in the REST requests and the map values are the classes that handle these requests.
Note that all services must extend RestController.

  2. Server – RestServer
The server starts a new HTTP server listening on the port that supplied in the RestServer constructor.
Any request will be handled using RestServer.handler that parses the request (RestServer.parse) in order to get a RestRequest object.
If a valid RestRequest object returned, the handler will execute it and will send the response to the selected RestResponseSerializer.
Any unsuccessful parsing will result with sending the returned error to the RestResponseSerializer.
The RestResponseSerializer (more details bellow) will serialize the response to JSON or XML.

  3. Request objects
I defined three types of requests.
RestSchemeRequest Parses the entire API and provides an XML descriptor that describe the entire API scheme, including controllers, actions, objects and enumerators. The scheme is auto-generated based on the code and configured services.
Note that the services, actions and data model objects are parsed using comments in the code, see example.

RestControllerRequest Accepts service (controller), action (method) and parameters (method arguments), as they parsed from the REST HTTP request.
The RestControllerRequest executes the method on a new instance of the service and returns the response or the error back to the server.

RestMultiRequest Handles a series of requests, that can save the HTTP round-trip in case you want to execute several requests together.
Note that this model force me to have flat list of services, it simplifies the routing URLs as well as the server code.

  4. Services (controllers)
I created a simple users-service example with CRUD action (add, update, delete, get and search). The users-service is performing DB actions on a simple sqlite example file (run install.js to create it).
Normally I would have a single service for each data object while each service has CRUD actions for its data object, trying as much as I can to avoid nested objects, for example, if user has devices, I won’t add devices array on user object, I will prefer to create a new service called user-device that manages each user-device separately.
All service classes must extend RestController that translates the REST request to structured objects according to comments in the code.

  5. Data model
An example user data model implemented in order to add, update, delete and search users.
All data-model objects must extend RestObject that translate plain JSON objects from the REST request to data-model objects, according to setters documentation in the code, see example.

  6. Response serializer
JSON
XML
Note that the response already contains errors and warnings in addition to the response content, so you don’t have to choose between response and error, you can return them both, that also means that the HTTP response code is always 200 OK, unless there is a true HTTP error, this way I distinguish between applicative errors and HTTP protocol errors.

Java Very simple REST server

How to build a simple REST server in J2EE.

For this example I tried to avoid any frameworks targeting at a simple Java code.

If you want to skip my explanations, all the code is here: https://github.com/tan-tan-kanarek/java-rest-server-example

  1. web.xml configuration

In order to direct all REST paths into a single entry point, I defined a single mapping to /*:

    <servlet-mapping>
        <servlet-name>REST Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

And defined my servlet with list of available services in Services init-param:

    <servlet>
        <servlet-name>REST Application</servlet-name>
        <jsp-file>/index.jsp</jsp-file>
        <!--  Controllers configuration -->
        <init-param>
        	<param-name>Services</param-name>
        	<param-value>com.example.services.UsersService</param-value>
        </init-param>
        <init-param>
        	<param-name>debug</param-name>
        	<param-value>true</param-value>
        </init-param>
    </servlet>

  2. Single entry point – index.jsp
The entry point reads the servlet configuration (from web.xml) and registers all the configured services:

String services = getInitParameter("Services");
RestServiceManager.setServices(services);

The list of services is cached:

servletContext.setAttribute("ServicesClasses", RestServiceManager.getClasses());

The entry point triggering request-deserializer that returns a request object, the request object must be set with context that defines the DB interface:

RestRequest restRequest = RestRequestDeserializer.deserialize(request);
restRequest.setContext(context);

The request is executed and returns response serializer:

IRestResponseSerializer restResponseSerializer = restRequest.execute();

And finally printing out the serialized response:

restResponseSerializer.serialize(response, out);

  3. Deserializing the request – RestRequestDeserializer
The request-deserializer is a static class, its most important method is parse that currently just deserialize the JSON, no matter if it arrived as application/json content-type or as json field inside multipart/form-data content-type (which could be useful if you want to upload files and send JSON in the same request).
The output of the serialization process is always a request-object.
Note that the request-deserializer is very simple thanks to the fact that all the routing points are always built of single service and single action, that enables me to implement controllers without defining a unique routing path for each action. I also don’t have to check the HTTP method because the action is part of the URL.

  4. Request objects
I defined three types of requests.
Invalid request accepts the exception object in its constructor and just sets the response error, it’s instantiated by the request-deserializer in case of invalid inputs.

Service request accepts controller, action (method) and arguments. The service-request executes the action and set the response object or the response error in case of exception.
The execute method returns the response-serializer that later will serialize the response object and response errors into a well formed JSON or XML.

Multi request handles a series of requests, that can save the HTTP round-trip in case you want to execute several requests together.
Note that this model force me to have flat list of services, it simplifies the routing URLs as well as the server code.

Scheme request Parses the entire API and provides an XML descriptor that describe the entire API scheme, including controllers, actions, objects and enumerators. The scheme is auto-generated based on the code and configured services.

  5. Services (controllers)
I created a simple users-service example with CRUD action (add, update, delete, get and search). The users-service is performing DB actions on a simple sqlite example file (run Install.java to create it).
Normally I would have a single service for each data object while each service has CRUD actions for its data object, trying as much as I can to avoid nested objects, for example, if user has devices, I won’t add devices array on user object, I will prefer to create a new service called user-device that manages each user-device separately.

  6. Data model
An example user data model implemented in order to add, update, delete and search users.

  7. Response serializer
JSON
XML
Note that the response already contains errors and warnings in addition to the response content, so you don’t have to choose between response and error, you can return them both, that also means that the HTTP response code is always 200 OK, unless there is a true HTTP error, this way I distinguish between applicative error and HTTP protocol errors.

  8. Tests
I used JUnit to test all my user-service CRUD action as well as pagination and multi-request in JSON and XML.
All tests are fully compatible with PHP server example as well.

PHP Very simple REST server

How to build a simple REST server in PHP.

For this example I tried to avoid any frameworks targeting at a simple PHP code.

If you want to skip my explanations, all the code is here: https://github.com/tan-tan-kanarek/php-rest-server-example

  1. Apache configuration

In order to direct all REST paths into a single entry point, I defined rewrite rule:

Listen 82
<VirtualHost *:82>
	DocumentRoot "C:/My/Projects/php-rest-server-example/web"
	ErrorLog "C:/My/Projects/php-rest-server-example/log/apache-error.log"
	CustomLog "C:/My/Projects/php-rest-server-example/log/apache-access.log" combined
	RewriteEngine on
	RewriteRule ^/(.+)$                /index.php/$1    [PT]
	
	<Directory "/">
		DirectoryIndex index.php
		Order allow,deny
		Allow from all
	</Directory "/">
</VirtualHost *:82>

  2. Single entry point – index.php
The entry point triggering request-deserializer that returns a request object:

$request = RestRequestDeserializer::deserialize();

Executing the request:

$response = $request->execute();

And finally echoing the response:

echo $response;

  3. Deserializing the request – RestRequestDeserializer.class.php
The request-deserializer is a static class, its most important method is parse that currently just deserialize the JSON, no matter if it arrived as application/json content-type or as json field inside multipart/form-data content-type (which could be useful if you want to upload files and send JSON in the same request).
The output of the serialization process is always a request-object.
Note that the request-deserializer is very simple thanks to the fact that all the routing points are always built of single service and single action, that enables me to implement controllers without defining a unique routing path for each action. I also don’t have to check the HTTP method because the action is part of the URL.

  4. Request objects
I defined three types of requests.
Invalid request accepts the exception object in its constructor and just sets the response error, it’s instantiated by the request-deserializer in case of invalid inputs.
Controller request accepts controller, action (method) and arguments. The controller-request executes the action and set the response object or the response error in case of exception.
The execute method returns the request serializer that later will serialize the response object and response errors into a well formed JSON or XML.
Scheme request Parses the entire API and provides an XML descriptor that describe the entire API scheme, including controllers, actions, objects and enumerators. The scheme is auto-generated based on the code.

  5. Services (controllers)
I created a simple users-service example with CRUD action (add, update, delete, get and search). The users-service is performing DB actions on a simple sqlite example file (run install.php to create it).
Normally I would have a single service for each data object while each service has CRUD actions for its data object, trying as much as I can to avoid nested objects, for example, if user has devices, I won’t add devices array on user object, I will prefer to create a new service called user-device that manages each user-device separately.

I also created a multi-request-controller that handles a series of requests, that can save the HTTP round-trip in case you want to execute several requests together.
Note that this model force me to have flat list of services, it simplifies the routing URLs as well as the server code.

  6. Data model
An example user data model implemented in order to add, update, delete and search users.

  7. Response serializer
JSON
XML
Note that the response already contains errors and warnings in addition to the response content, so you don’t have to choose between response and error, you can return them both, that also means that the HTTP response code is always 200 OK, unless there is a true HTTP error, this way I distinguish between applicative error and HTTP protocol errors.

  8. Tests
I used PHPUnit to test all my user-service CRUD action as well as pagination and multi-request.

Create new REST server

During the last few years while working at Kaltura I have written, with my team members, many REST client libraries in many languages (including Python, Java, PHP, Javascript, ActionScript, C#, Erlang, ObjectiveC and Ruby) against different servers. We also implemented REST servers in different languages (PHP, C#, NodeJS).
The REST standard is wide, and yet the common implementation of REST is very narrow and repeatedly follows the same mistakes that already done by others.
I want to share Kalturians’ criticism of the common implementation and to offer a few solutions and tips that we developed through the years at Kaltura.

  • The problem: Complicated nested paths complicate the data model and make it hard to understand.
    The solution: We do not believe in complex nested paths. Instead, have a flat list of services with a flat list of actions for each service.
  • The problem: Advanced HTTP methods are not supported by all firewalls and platforms.
    The solution: Avoid using custom HTTP methods such as PUT, HEAD and PATCH. Instead, we specify the action in the URL and support both GET and POST with all actions.
  • The problem (part 1): If the same error codes could be relevant both to the HTTP protocol and to the application, it might be difficult to understand which one of them raised the error.
    The problem (part 2): Single HTTP error prevents sending multiple applicative requests in a single HTTP request so that each one of them may have different response or different error code.
    The problem (part 3): Single error code prevent complex responses with partial success or warnings.
    The solution: HTTP error codes should be used for HTTP protocol, while applicative errors should be returned in the response content (JSON or XML).
  • The problem: Sending XML or JSON and upload files in the same HTTP request.
    The solution: Use multipart/form-data for the files and send XML or JSON field in the same request.
  • The problem: Serving content binary and textual content as part of REST API.
    The solution: You can use your API, just make sure to use the right content-type in the response.
  • The problem (part 1): Keeping the documentation up to date.
    The problem (part 2): Enabling easy development of client libraries for different languages.
    The problem (part 3): In micro-services architecture, different servers are written in different languages. A cross-platform solution requires many different client libraries in different coding languages to communicate with the same servers that the API is changing and developing consistently.
    The solution: The XML descriptor generated from the code describes the entire API. Based on that XML, it’s easy to generate documentation and client libraries.

Complex paths

REST implementers commonly try to reflect nested objects using hierarchic paths.
For example, if you want to get the devices of a user that belongs to a household that belongs to an account, you might need to use a URL that looks something like this::

/api/account/[account-id]/household/[household-id]/user/[user-id]/device/[device-id]

I believe that if each device has a unique id, it’s better to use a simple form such as:

/api/device/[device-id]

If your system supports more than one type of device (for example, users’ devices and household devices), I still recommend one simple form such as:

/api/userDevice/[device-id]

Flattening your data model to list of services, when each service represents a single data object type will force you to choose simple data model for your server side as well.
Of course, flat models will be much easier for your integrators and customers to use.

HTTP methods

Most implementations of REST are counting on different HTTP methods for different CRUD operations on the same object.
Examples include GET for list, PUT for update, POST for create, and DELETE for delete, I also saw implementations that use OPTIONS, HEAD, PATCH, INSERT, UPDATE and other non-standard methods.
I see few problems with this approach.

  1. Not all firewalls enable these custom methods, whereas POST and GET are always enabled.
  2. Associating API call with a single HTTP method will prevent any future wrapping of several API calls in a single HTTP round-trip.
  3. Because different implementations use different methods for the same actions, it’s not really self-explanatory and documentation on the right method for each operation is still needed.
  4. Assuming that you want to enable deleting a user by its id in one API action and deleting user by its e-mail in a different action, how would you differentiate between the two using HTTP methods?

The solution I prefer is to add the operation to the URL and the HTTP request. this way, you can support both GET and POST with plain form data, in XML or JSON.
Here are a few examples (the examples are self-explanatory):

Using path (GET):

  • /api/device/get/id/[device-id]
  • /api/device/delete/id/[device-id]
  • /api/device/list
  • /api/device/list/filter:householdIdEqual/[household-id]/filter:statusEqual/[status]
  • /api/device/add/name/[device-name]/description/[device-description]/tags/[device-tags]
  • /api/device/update/id/[device-id]/name/[device-name]/tags/[device-tags]

Using query string (GET):

  • /api?service=device&action=get&id=[device-id]
  • /api?service=device&action=delete&id=[device-id]
  • /api?service=device&action=list
  • /api?service=device&action=list&filter[householdIdEqual]=[household-id]&filter[statusEqual]=[status]
  • /api?service=device&action=add&name=[device-name]&description=[device-description]&tags=[device-tags]
  • /api?service=device&action=update&id=[device-id]&name=[device-name]&tags=[device-tags]

Using POST (form data combined with path):

  • /api/service/device/action/get
    id=[device-id]
  • /api/service/device/action/delete
    id=[device-id]
  • /api/service/device/action/list
  • /api/service/device/action/list
    filter[householdIdEqual]=[household-id]&filter[statusEqual]=[status]
  • /api/service/device/action/add
    name=[device-name]&description=[device-description]&tags=[device-tags]
  • /api/service/device/action/update
    id=[device-id]&name=[device-name]&tags=[device-tags]

Using POST (JSON combined with query string):

  • /api?service=device&action=get
    {id: [device-id]}
  • /api?service=device&action=delete
    {id: [device-id]}
  • /api?service=device&action=list
    {}
  • /api?service=device&action=list
    {
    filter: {
    householdIdEqual: [household-id],
    statusEqual: [status]
    }
    }
  • /api?service=device&action=add
    {
    device: {
    name: [device-name],
    description: [device-description],
    tags: [device-tags]
    }
    }
  • /api?service=device&action=update
    {
    id: [device-id],
    device: {
    name: [device-name],
    tags: [device-tags]
    }
    }

Using POST (XML):

  • /api
    <request service="device" action="get">
    <id>[device-id]</id>
    </request>
  • /api
    <request service="device" action="delete">
    <id>[device-id]</id>
    </request>
  • /api
    <request service="device" action="list" />
  • /api
    <request service="device" action="list">
    <filter>
    <householdIdEqual>[household-id]</householdIdEqual>
    <statusEqual>[status]</statusEqual>
    </filter>
    </request>
  • /api
    <request service="device" action="add">
    <device>
    <name>[device-name]</name>
    <description>[device-description]</description>
    <tags>[device-tags]</tags>
    </device>
    </request>
  • /api
    <request service="device" action="update">
    <id>[device-id]</id>
    <device>
    <name>[device-name]</name>
    <tags>[device-tags]</tags>
    </device>
    </request>

Looking at the examples, you can see how self-explained the actions are and how it’s simple to add a new action (deleteByEmail for example).

Assuming that you want to create a user and a device that associated with that user, you could perform both actions in a single HTTP request.
Note the token I entered into the example requests. {results:1:id} means use the ID attribute from result number one.
this empowers the multi-request by enabling the client to use the output of one action as the input of another action in the same HTTP request.
Here are a few examples:
Using POST (form data combined with path):

  • /api/service/multirequest
    1[service]=user&1[action]=add&1[user][firstName]=[user-first-name]&1[user][lastName]=[user-last-name]&1[user][email]=[user-email]&2[service]=device&2[action]=add&2[device][userId]={results:1:id}&2[device][name]=[device-name]&2[device][description]=[device-description]&2[device][tags]=[device-tags]

Using POST (JSON combined with query string):

  • /api?service=multirequest
    [{
    service: "user",
    action: "add",
    user: {
    firstName: [user-first-name],
    lastName: [user-last-name],
    email: [user-email]
    }
    },{
    service: "device",
    action: "add",
    device: {
    userId: "{results:1:id}",
    name: [device-name],
    description: [device-description],
    tags: [device-tags]
    }
    }]

Using POST (XML):

  • /api
    <multirequest>
    <request service="user" action="add">
    <user>
    <firstName>[user-first-name]</firstName>
    <lastName>[user-last-name]</lastName>
    <email>[user-email]</email>
    </user>
    </request>
    <request service="device" action="add">
    <device>
    <userId>{results:1:id}</userId>
    <name>[device-name]</name>
    <description>[device-description]</description>
    <tags>[device-tags]</tags>
    </device>
    </request>
    </multirequest>

HTTP Error codes

Usually, the HTTP protocol is used to return HTTP error code that reflects the status of the request.
For example, 400 for bad request, 422 for unprocessable entity, 404 for entity not found, 403 for forbidden service or action, 401 for unauthorized object or action.
Also, the success error codes could be different. For example, 200 for successful list, 201 for successful creation, 202 for successful update, 204 for no content, 205 for successful deletion.

These are the issues I see with this approach:

  1. Numbers are never self explained, you always need to read the textual description and the documentation to react correctly to each error code.
  2. HTTP protocol enables only one error code (preventing you from returning warnings) or a few codes in case of a few requests (such as in case of multi-request that suggested above).
  3. One textual description doesn’t offer detailed error message with additional attributes such as “Device filter is invalid, at least on of the properties userIdEqual or householdIdEqual is required”.
  4. Multilinguality and localization is impossible if the error message may contain different attribute names or real data, for example “Device id [1234] not found”.

The error codes that I want to reflect in the HTTP protocol are always HTTP errors, such as 200 OK, 404 Not Found, 500 Internal Server Error.
All other applicative errors will be returned with HTTP status 200 and the response content will reflect the error.

Few JSON request and response:

  • Request: /api/user/add
    {
    user: {
    firstName: [user-first-name],
    lastName: [user-last-name],
    email: [user-email]
    }
    }

    Good response:

    {
    user: {
    id: [user-id],
    createdAt: [user-create-time],
    updatedAt: [user-update-time],
    firstName: [user-first-name],
    lastName: [user-last-name],
    email: [user-email]
    },
    warnings: [{
    code: 123456,
    message: "User first name [user-first-name] is too long",
    attributes: {
    firstName: [user-first-name]
    }
    }],
    executionTime: 0.6555
    }

    Bad response:

    {
    error: {
    code: 123457,
    message: "User e-mail [user-email] is not valid",
    attributes: {
    email: [user-email]
    }
    },
    executionTime: 0.6555
    }
  • /api/multirequest
    [{
    service: "user",
    action: "add",
    user: {
    firstName: [user-first-name],
    lastName: [user-last-name],
    email: [user-email]
    }
    },{
    service: "device",
    action: "add",
    device: {
    userId: "{results:1:id}",
    name: [device-name],
    description: [device-description],
    tags: [device-tags]
    }
    }]

    Response:

    {
    [{
    user: {
    id: [user-id],
    createdAt: [user-create-time],
    updatedAt: [user-update-time],
    firstName: [user-first-name],
    lastName: [user-last-name],
    email: [user-email]
    },
    warnings: [{
    code: 123456,
    message: "User first name [user-first-name] is too long",
    attributes: {
    firstName: [user-first-name]
    }
    }
    },{
    error: {
    code: 123458,
    message: "Device name [device-name] may not contain special charecters [&]",
    attributes: {
    name: [device-name]
    charecters: "&"
    }
    },
    }],
    executionTime: 0.6555
    }

As you can see, using error code and attributes you can translate the error message to any language in any structure.
Also, returning the errors in the response body you can support complex responses with detailed explaination for each failure.

File uploads

Most REST API implementations do not support file uploads.
If you followed my recommendations above to always support both GET and POST, then when it comes to actions that include uploaded file, you’re required to use POST only.
In addition, the content-type of your request can’t be application/json, application/xml, text/xml, or even application/x-www-form-urlencoded. it must be always multipart/form-data.
If you still want to support JSON or XML content, you can always submit them as a form field, for example:

Content-Type: multipart/form-data; boundary=---------------------------90519140415448433659727646345634

-----------------------------90519140415448433659727646345634
Content-Disposition: form-data; name="json"

user: {
    firstName: [user-first-name],
    lastName: [user-last-name],
    email: [user-email]
}
-----------------------------90519140415448433659727646345634
Content-Disposition: form-data; name="homepageContent"; filename="myHomepage.html"
Content-Type: text/html

<!DOCTYPE html>
<title>Content of my homepage HTML.</title>

-----------------------------90519140415448433659727646345634--

Content

Most systems are required to also serve images, video files, textual content, and other binary content.
Many binary content entities are used in browsers such as images or used as URL to integrate with UI and external systems.
Therefore I believe it’s important to support also plain GET with path attributes or query string.
A few examples:

  • /api/user/serveImage/id/[user-id]
  • /api/user/generateThumbnail/id/[user-id]/width/400/height/300
  • /api?service=user&action=generateThumbnail&id=[user-id]&width=400&height=300

Note that I encountered few STB devices that accept feeds for video content that do not support query strings. That’s why supporting variables in path was important to me.

If your content should be protected, you can always add authorization token to the URL or tokenize the URL using a CDN token that expires after configurable time.

Documentation and client libraries generation

At Kaltura, we easily generate different client libraries using clients generator we wrote. It’s open source and generates client libraries in many coding languages: Python, Java, PHP, Javascript, ActionScript, C#, Erlang, ObjectiveC, and Ruby.

Talk to other developers about using Kaltura in the Kaltura Community.