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
You must be logged in to post a comment.