Home > Delphi, Object Pascal, Smart Mobile Studio > In REST we trust

In REST we trust

Delphi is a bit odd these days. For instance, Embarcadero ships some fairly good REST client components. Bloated beyond belief, but fairly good. Yet where exactly is the REST server? I could hardly believe my ears when I was told that DataSnap was the only option supported by Embarcadero. It should be considered a crime against humanity to push possibly the most sluggish DB solution in existence as the basis for REST based networking protocols.

It completely undermines the purpose of REST, namely to mine the power of fast HTTP servers to create quick, small and responsive services. REST is essentially a technology which piggyback’s on the established HTTP standard. So the explanation Embarcadero comes up with to justify mixing this with Datasnap should be absolutely spectacular.

It’s like “fixed” order in XML, it utterly undermines the point of a flexible, text based format.

Sweet Jesus no! Arrrg...

Sweet Jesus no! Arrrg… Datasnap!

Well, guess I better build a REST server myself then..

Indy still rocks

The basis for my server is good old Indy. Some might say that you get 0.2 microseconds better speed using Synapse or whatever other library out there, but for me Indy is king when it comes to server solutions. Indy is rock solid, stable as a mountain, not to mention tried, tested and developed over 10+ years, it has been used successfully in thousands of products, its platform independent and the architecture is one of the best you’ll ever see; regardless of programming language or platform.

Adapting an Indy HTTP server (TIdCustomHTTPServer) so it supports the REST paradigm is really not that hard. In essence you override 3 protected methods and with a few lines of code you can support both REST and normal HTTP behavior.

  TIdRestServer = Class(TIdCustomHTTPServer,
                        IIdRestServer,
                        IIdRestElementSchema)
strict protected
    procedure DoCommandError(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
              AResponseInfo: TIdHTTPResponseInfo; AException: Exception); override;

    procedure DoCommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
              AResponseInfo: TIdHTTPResponseInfo); override;

    procedure DoCommandOther(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo;
              AResponseInfo: TIdHTTPResponseInfo); override;

    procedure DoRestCommand(Uri: TIdURI; AContext: TIdContext;
              ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
end;

REST Services

In my system I have decided to “emulate” what I normally use for tasks like this, namely Remobjects SDK. Remobjects SDK makes it a snap to create RPC (remote procedure call) servers. It even ships with a service designer that helps you define X number of services, which in turn has X number of invokable methods. Each method has X number of parameters and once defined they behave just like local procedures and functions.

The power of products like Remobjects SDK is that they are (like I mentioned above) RPC systems. This means that you can isolate classes and tasks on a server half way around the world, and call them in your programs just like they were linked and compiled as usual.

Logical and straight forward namespacing

Logical and straight forward namespacing

When you call a RPC service there is quite a lot taking place: Your parameters are serialized, a connection is made to the server, data is transferred; on the other end the data is de-serialized into an object, validated against a schema – and finally the implementation of your code fires. The result of the operation is again serialized and sent back to your program.

With REST as the basis, accessing services becomes even easier than with Remobjects SDK (or at least en-par). Why? Because the service-with-method architecture lends itself easily to REST methodology. The URL scheme is so simple and easy to use – and comes with a pleasant benefit; namely using your browser to access schemas! Heck, you can even call functions directly from the browser (if you omit any form of security and authentication that is).

Schema generation

The server generates JSON based schemas for the services it exposes

The server generates JSON based schemas for the services it exposes

Making heads or tails of an alien REST service can be hard. Trust me, there are some seriously disturbed services out there. And if you think there is a fixed REST standard that everyone follows, think again.

Errors for instance is a hot topic. Should you re-label some HTTP errors and then respond to that on the client? Well, some do that and others don’t. I firmly believe your REST based server should not interfere with the ordinary HTTP behavior, but rather kick in only if the referenced URI points to the /REST/ namespace. And since the API will be used by a client who reads and understands JSON, there is no point in throwing an error back at all.

Now viewing schemas is easy: In essence, when you visit a valid REST URL on the server – but you don’t provide any parameters, the server generates the schema for that element. There is no such thing as a REST method without a parameter (POST attachment also counts as a parameter).

So if you are wondering about, say, the syntax for a particular method, you can visit the method URL directly with your browser and view the schema, This works on all levels (root, service and method) so you can get the full API for the whole server by visiting the root /rest/ URL.

Writing server-side methods

Having to dabble in tons of events or (perhaps even worse) nested TCollection items is something we want to avoid. First of all because synchronization with the main VCL thread is easily abused (with catastrophic performance results), and secondly we want to de-couple as much as we can to make the best of a multi-threaded environment. So for our API I decided to make full use of anonymous methods and weak references. It’s now so much easier to write services, even easier than the Remobjects SDK service designer even.

In the example below we first create the server, then we create a service – and implement a single method for it (GetServerTime). The service registers with the server on creation (as does the method with the service) so we don’t have to think about that. Also, on invocation the server automatically validates all parameters with the auto-generated schema, so most of the tedious work is taken care of.

Implementing a REST server should be fast, fun and easy!

  FServer := TIdRestServer.Create(self);
  FServer.Defaultport := 8090;

  FService := TidRESTService.Create(FServer);
  FService.ServiceName.Value:='sharebike';

  FMethod := TidRESTMethodGET.Create(FService);
  FMethod.RestMethodName.Value := 'getservertime';
  FMethod.Parameters.Add('id',TIdRestSchemaParameterType.rpText,'');
  FMethod.HandleWith( procedure (const Info:TidRestMethodCallData)
    var
      mData:  String;
      mError: TIdRestErrorMessage;
    begin
      // Make sure we can read the ID parameter
      if Info.Params.TryGetValue('id',mData) then
      begin
        with info.SocketInfo do
        begin
          Response.ContentText :='{ "result": ' + DQuoteStr(mData) + ' }';
          Response.WriteHeader;
          Response.WriteContent;
        end;
      end else
      begin
        mError := TIdRestErrorMessage.Create('syntax-error','Parameter ID could not be read error');
        try
          with info.SocketInfo do
          response.contentText:=mError.Serialize('Tag-value-here');
        finally
          mError.free;
        end;
      end;
    end);

That’s essentially all it takes to implement a fully multi-threaded REST server.

Internal project

If you are expecting source-code for this, then sadly I have to decline. This is an in-house project and naturally belongs to my employer. But writing your own REST server is not hard if you know you way around Delphi and Indy.

But my final verdict is that I’m getting sold on REST. I have been very WebSocket biased, but having spent a couple of weeks working on REST I see that it indeed is very useful and powerful. And our system in place we at least get to enjoy writing services 🙂

Pretty cool to work with

Pretty cool to work with

As a bonus, Smart Mobile Studio now supports REST calls, so make sure you check back soon for a demonstration!

Advertisements
  1. October 9, 2015 at 7:49 am

    Note that you could have REST+XML, which is perfectly RESTful.
    For a general presentation of REST, see http://synopse.info/files/html/Synopse%20mORMot%20Framework%20SAD%201.18.html#TITL_9
    Marshalling of REST by hand, as you propose in your article, is indeed very simple (don’t even try to do the same with SOAP!). But it is also very error prone. Even with REST + JSON.
    If you want to easily create fast REST servers, try our little http://mormot.net
    We support Smart Mobile Studio as client: all the SMS client code is generated on the fly by the server, so that you can consume REST servers, with all its complex marshalling of methods and parameters, with interfaces and high-level types (classes or records).
    You could even mix REST and WebSockets, using asynchronous callbacks via interface parameters – see http://blog.synopse.info/post/2015/04/06/Asynchronous-Service-WebSockets,-Callbacks-and-Publish-Subscribe
    And it is Open Source, and compiles with FPC, so you could host under Linux… Some users reported to have a farm of mORMot servers on Rasberry PI, thanks to FPC!

  2. October 15, 2015 at 4:40 pm

    I guess your DQuoteStr() use is wrong: JSON does not escape double double quotes, but escaped double quotes. Or the DQuoteStr() function name is misleading. You should also escape other characters in the string, like { or }, by the way.
    This is what happens when you create some JSON by hand, using a naive concatenation.

    • Jon Lennart Aasenden
      October 19, 2015 at 5:08 am

      DQuote adds “..”, and since perhaps the word “naive” should be corrected

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: