Update: this project is now available on Github

Thorntail (https://thorntail.io/) is an open source project which creates stand-alone Java EE application servers that can be launched with a simple java -jar command.

Thorntail can be a very handy tool for quick prototyping and integration testing of Delphi web service client code, as it requires no installation of a heavy-weight Java EE application server enviroment.

Example 1: HTTP GET Test

JAX-RS web service code / TIdHTTP client code


package com.example.rest;

import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;

@Path("/hello")
public class HelloWorldEndpoint {

  @GET
  @Produces("text/plain")
  public Response doGet() {
    return Response.ok("Hello from Thorntail!").build();
  }
}

That’s the standard JAX-RS web service code for a simple endpoint which processes GET requests. The boilerplate code which defines the root context is:

package com.example.rest;

import javax.ws.rs.core.Application;
import javax.ws.rs.ApplicationPath;

@ApplicationPath("/rest")
public class RestApplication extends Application {
}

The web service client code, written with Delphi and the Indy TIdHTTP client class:

program IndyClient;

{$APPTYPE CONSOLE}

uses
  IdHTTP,
  SysUtils;

procedure Main;
var
  IdHTTP: TIdHTTP;
  Response: string;
begin
  IdHTTP := TIdHTTP.Create;
  try
    Response := IdHTTP.Get('http://localhost:8080/rest/hello');
    WriteLn(Response);
  finally
    IdHTTP.Free;
  end;
end;

begin
  try
    Main;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  ReadLn;
end.

After building the server with Maven, we launch it from the command line:

>java -jar target/restful-endpoint-thorntail.jar

The server will log the startup and deployment of our web service application:

2019-01-23 15:45:58,037 INFO [org.jboss.as.server] (main) WFLYSRV0010: Deployed
"restful-endpoint.war" (runtime-name : "restful-endpoint.war")

And we are ready to test our Delphi client.
Typically for GET requests, we can also launch a web browser and navigate to https://localhost:8080/rest/hello to test it.
In the next example, we move one step further and add a POST handler to the web service, and change the Delphi code to send a HTTP POST request with an JSON payload.

Example 2: HTTP POST Test

JAX-RS web service code / TIdHTTP client code


package com.example.rest;

import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;

@Path("/hello")
public class HelloWorldEndpoint {

  @GET
  @Produces("text/plain")
  public Response doGet() {
    return Response.ok("Hello from Thorntail!").build();
  }

  @POST
  @Produces("text/plain")
  public Response doPost() {
    return Response.ok("Hello from Thorntail POST!").build();
  }
}

And the corresponding Delphi test code is:

program IndyClient;

{$APPTYPE CONSOLE}

uses
  IdHTTP,
  SysUtils, Classes;

procedure Main;
var
  IdHTTP: TIdHTTP;
  RequestBody: TStream;
  Response: string;
begin
  IdHTTP := TIdHTTP.Create;
  RequestBody := TStringStream.Create('{"answer":42}',
    TEncoding.UTF8);
  try
    Response := IdHTTP.Post('http://localhost:8080/rest/hello',
      RequestBody);
    WriteLn(Response);
  finally
    RequestBody.Free;
    IdHTTP.Free;
  end;
end;

begin
  try
    Main;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
  ReadLn;
end.

Example 3: HTTP POST with media type application/json

In this example, we remove the GET handler to the server code (so that only HTTP POST requests are allowed now), and add a restriction that the POST request must use the media type “application/json”:

JAX-RS web service code / TIdHTTP client code

@Path("/hello")
public class HelloWorldEndpoint {

  @POST
  @Produces("text/plain")
  @Consumes("application/json")
  public Response doPost(String body) {
    return Response.ok("Hello from Thorntail POST!\n" + body).build();
  }
}

The server code now also prints the body of the POST request so we can verify it has been received.

If we run the Delphi code unmodified, the server will refuse the POST request (because it is lacking the mediatype) and our client will show an exception:

EIdHTTPProtocolException: HTTP/1.1 415 Unsupported Media Type

The fix on the Delphi side is shown below, in line 6, the content type header is added to the request.

  IdHTTP := TIdHTTP.Create;
  try
    RequestBody := TStringStream.Create('{"answer":42}',
      TEncoding.UTF8);
    try
      IdHTTP.Request.ContentType := 'application/json';
      Response := IdHTTP.Post('http://localhost:8080/rest/hello',
        RequestBody);
      WriteLn(Response);
    finally
      RequestBody.Free;
    end;
  finally
    IdHTTP.Free;
  end;

And with this change, the client receives the expected response:

Hello from Thorntail POST!
{"answer":42}

Example 4: HTTP POST with media type and response type application/json

In this example, the Delphi client sends another request header. Now the media type “application/json” is not only specified for the request content, but also required for the server response, by setting the HTTP Accept header (in line 7).

TIdHTTP client code

  IdHTTP := TIdHTTP.Create;
  try
    RequestBody := TStringStream.Create('{"answer":42}',
      TEncoding.UTF8);
    try
      IdHTTP.Request.ContentType := 'application/json';
      IdHTTP.Request.Accept := 'application/json';
      Response := IdHTTP.Post('http://localhost:8080/rest/hello',
        RequestBody);
      WriteLn(Response);
    finally
      RequestBody.Free;
    end;
  finally
    IdHTTP.Free;
  end;

If we run this client code with the same server as in the previous example, there will be a new exception with HTTP code 406:

EIdHTTPProtocolException: HTTP/1.1 406 Not Acceptable

Since our Delphi client used the Accept header to ask for a response with media type “application/json”, the server rejects the request because the POST handler responds with media type “text/plain”, which does not match the client request.

So we update the server to match the change on the client side:

JAX-RS web service code / TIdHTTP client code

@Path("/hello")
public class HelloWorldEndpoint {

  @POST
  @Produces("application/json")
  @Consumes("application/json")
  public Response doPost(String body) {
    return Response.ok(body).build();
  }

}

Note that the server code also only returns the request body (which must be JSON).

Now the client and server code use matching media types both for request and for the accepted response, and the client shows the expected server response:

{"answer":42}

Conclusion

Thorntail, which is designed for usage in microservice architectures, saves much of the time which is usually needed for creating, deploying, and testing Java EE components (such as JAX-RS web services). Building a Java EE compliant web service server with Thorntail requires only a basic Java IDE. The resulting server is packaged as a single jar file and can be run instantly from the command line, or can be packaged as a war file ready for deployment in any Java EE compatible application server.

References

Example code based on Thorntail docs https://docs.thorntail.io/2.3.0.Final/#_basics

JAX-RS https://en.wikipedia.org/wiki/Java_API_for_RESTful_Web_Services

Related articles

Indy 10.6 HTTPS POST example with JSON body

Advertisements

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s