README
The
REFCODES.ORG
codes represent a group of artifacts consolidating parts of my work in the past years. Several topics are covered which I consider useful for you, programmers, developers and software engineers.
What is this repository for?
With this artifact you easily create your serverless RESTful services and REST driven Java clients. It lets you do it the Bare-Metal way or the client syntactic sugar alongside the server syntactic sugar way (being the use of statically imported methods). Lambda expressions bound to a REST driven Java endpoint equate to your RESTful service, as easy as that.
Quick start archetype
For a jump start into developing Java driven command line tools, I created some fully pre-configured Maven Archetypes available on Maven Central. Those Maven Archetypes already provide means to directly create native executables, bundles as well as launchers and support out of the box command line argument parsing as well as out of the box property file handling.
Use the refcodes-archetype-alt-rest
archetype to create a bare metal REST driven Java application within just one source code file:
Please adjust
my.corp
with your actualGroup-ID
andmyapp
with your actualArtifact-ID
:
Using the defaults, this will generate a RESTful server and client application by harnessing the refcodes-rest
toolkit.
How do I get set up?
To get up and running, include the following dependency (without the three dots “…”) in your pom.xml
:
1
2
3
4
5
6
7
8
9
<dependencies>
...
<dependency>
<artifactId>refcodes-rest</artifactId>
<groupId>org.refcodes</groupId>
<version>3.3.8</version>
</dependency>
...
</dependencies>
The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.
RESTful server
Within three simple steps you implement your lightweight Java based RESTful server. Below, you see the three steps with the help of a little syntactic sugar:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// STEP 1: We use a singleton with syntactic sugar instead of instantiating a HttpRestServer:
import static org.refcodes.rest.HttpRestServerSugar.*;
...
public static void main( String[] args ) {
// STEP 2: Using syntactic sugar, we register our lambda expression:
onGet( "/say/${name}=*", ( aRequest, aResponse ) -> {
String name = aRequest.getWildcardReplacement( "name" );
aResponse.getHeaderFields().withContentType( MediaType.APPLICATION_JSON ).withAddCookie( "greeting", "Hello " + name + "!" );
return "Hello " + name + "!" ;
} ).open();
// STEP 3: We open the HttpRestServer singleton on port 8080 using our syntactic sugar:
open( 8080 );
}
...
The TinyRestfulServer
demo application uses syntactic sugar for setting up a RESTful server including command line arguments parsing.
REST client
- Instantiate the REST client
- Register your
lambda
expression for the response - Fire the client’s REST request
Again, within three simple steps you implement your lightweight Java based REST client. Below you see the three steps with the help of a little syntactic sugar:
1
2
3
4
5
6
7
8
9
10
11
// STEP 1: We use a singleton with syntactic sugar instead of instantiating a HttpRestClient:
import static org.refcodes.rest.HttpRestClientSugar.*;
...
public static void main( String[] args ) {
// STEP 2: Using syntactic sugar, we define our caller, including the response listener:
doGet( "http://mydomain:8080/say", ( aResponse ) -> {
... = aResponse.getResponse( SomeType.class );
} ).withRequest( ... ).open();
// STEP 3: We opened the caller so it fires the request to port 8080 of domain "mydomain"
}
...
Snippets of interest
Below find some code snippets which demonstrate the various aspects of using the refcodes-rest
artifact (and , if applicable, its offsprings). See also the example source codes of this artifact for further information on the usage of this artifact.
How do I get started with the RESTful server?
Above you saw an example on how to setup your own RESTful service using syntactic sugar. One drawback of using syntactic sugar is that we can only make use of the one HttpRestServerSingleton
(as of this syntactic sugar being the statically imported methods), preventing us from running multiple HttpRestServer
instances on different ports in one Java application.
Lets do it the Bare-Metal way, which is not very complicated either, and which lets us instantiate as many HttpRestServer
instances as we want:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
public static void main( String[] args ) {
// STEP 1: We instantiate our HttpRestServer:
HttpRestServer theRestServer = new HttpRestServer();
// STEP 2: We register our lambda expression:
theRestServer.onGet( "/say/${name}=*", ( aRequest, aResponse ) -> {
String name = aRequest.getWildcardReplacement( "name" );
aResponse.getHeaderFields().withContentType( MediaType.APPLICATION_JSON ).withAddCookie( "greeting", "Hello " + name + "!" );
return "Hello " + name + "!" ;
} ).open();
// STEP 3: We open the HttpRestServer instance on port 8080:
theRestServer.open( 8080 );
}
...
The Locator-Pattern
Did you notice the Locator-Pattern "/say/${name}=*"
above when registering your lambda
? Subscribing your lambda
expressions for incoming REST requests on specific locators
, you may use a common wildcard
syntax to define the lambda
’s Locator-Pattern
:
- A single asterisk (
*
) matches zero or more characters within a locator name. - A double asterisk (
**
) matches zero or more characters across directory levels. - A question mark (
?
) matches exactly one character within a locator name.
The single asterisk (*
), the double asterisk (**
) and the question mark (?
) we refer to as wildcard
: You get an array with all the substitutes of the wildcards
using the RestRequestEvent
class’ getWildcardReplacements()
method.
You may name a wildcard
by prefixing it with “${someWildcardName}=
”. For example a named wildcard
may look as follows: “${arg1}=*
” or “${arg2}=**
” or “${arg3}=?
” or as of the example above "/say/${name}=*"
. When your lambda
is being invoked, you can retrieve the wildcard
substitution by the name of the wildcard
which has been substituted (by parts of the incoming locator). You can get the text substituting a named wildcard
using the RestRequestEvent
class’ getWildcardReplacement(String)
method.
As of refcodes-rest
version 1.1.3
a placeholder “${arg1}
” with no wildcard assignment “=
” is equivalent to “${arg1}=*
”. In the example above you could have used "/say/${name}"
instead of "/say/${name}=*"
!
Error handling
In case an Exception
gets thrown while your lambda
is processing an HTTP-Response due to an incoming HTTP-Request, you may either handle it manually in your lambda
expression or you may use a predefined behavior or you may even register a custom global Exception
handler. The predefined behavior is configured as follows:
1
2
3
...
HttpRestServer theRestServer = new HttpRestServer().withHttpExceptionHandling( HttpExceptionHandling.REPLACE );
...
The behavior is specified by the enumeration HttpExceptionHandling
. You currently have the following options on how an exceptional situation is handled:
HttpExceptionHandling.REPLACE
: Create a new HTTP-Response with the accordingHTTP-Status-Code
and the error description fields.HttpExceptionHandling.UPDATE
: Keep the so far processed HTTP-Response as is except theHTTP-Status-Code
and also update all error description fields. Useful when you want to preserve already set fields.HttpExceptionHandling.MERGE
: Keep the so far processed HTTP-Response as is except update the actualHTTP-Status-Code
, add only non-existing fields if possible with the error description fields. Useful when you manually prepared the HTTP-Response yourself.HttpExceptionHandling.KEEP
: Do not modify the so far processed HTTP-Response except update the actualHTTP-Status-Code
. Useful when you manually prepared the HTTP-Response yourself.HttpExceptionHandling.EMPTY
: Create a new HTTP-Response with the accordingHTTP-Status-Code
and and an empty body.
In case the Media-Type
used is Application/JSON
, then the error description fields generated by the predefined exception handler looks as follows:
1
2
3
4
5
6
7
8
9
{
"status": {
"exception": "org.refcodes.net.NotFoundException",
"code": "404",
"alias": "NOT FOUND",
"message": "There is none endpoint for handling resource locator </repl/sessionsx> with HTTP-Method <GET>.",
"timestamp": "1525436619219"
}
}
This custom global exception handler is represented by an instance of the functional interface HttpExceptionHandler
being registered to your HttpRestServer
. You may register it using the lambda
notation:
1
2
3
4
5
6
7
...
HttpRestServer theRestServer = new HttpRestServer().withOnHttpException( ( request, response, exception, statusCode ) -> {
// ...
response.setResponse( new HttpBodyMapImpl().withPut( "message", exception.getMessage() ) );
// ...
} );
...
Using HTTP-Request and HTTP-Response interceptors
The HttpServerInterceptor
and its parents PreHttpServerInterceptor
as well as PostHttpServerInterceptor
are explained by the example of the CorrelationServerInterceptor
:
1
2
3
4
...
RestfulHttpServer theRestServer = new HttpRestServer();
theRestServer.addHttpInterceptor( new CorrelationServerInterceptor() );
...
The
CorrelationServerInterceptor
is an implementation of theHttpServerInterceptor
interface and defines two methods, thepreIntercept
method as well as thepostIntercept
method: The former being invoked before the HTTP-Request is handed over to your business logic and the latter one being invoked after your business logic has finished with an HTTP-Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CorrelationServerInterceptor implements HttpServerInterceptor {
/**
* {@inheritDoc}
*/
@Override
public void preIntercept( HttpServerRequest aRequest, HttpServerResponse aResponse ) {
String theRequestId = aRequest.getHeaderFields().getRequestId();
if ( theRequestId != null && theRequestId.length() != 0 ) {
Correlation.REQUEST.setId( theRequestId );
}
else {
theRequestId = Correlation.REQUEST.pullId();
}
aResponse.getHeaderFields().putRequestId( theRequestId );
String theSessionId = aRequest.getHeaderFields().getSessionId();
if ( theSessionId != null && theSessionId.length() != 0 ) {
Correlation.SESSION.setId( theSessionId );
}
else {
theSessionId = Correlation.SESSION.pullId();
}
aResponse.getHeaderFields().putSessionId( theSessionId );
}
// ...
The above code intercepts incoming HTTP-Requests with means to, amongst others, prepare the according HTTP-Responses. Usually only the header fields of an HTTP-Response are prepared as any other portions of the HTTP-Response should solely be managed by your business logic. The below code intercepts with the outgoing HTTP-Responses, while still having access by the according HTTP-Requests:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ...
/**
* {@inheritDoc}
*/
@Override
public void postIntercept( HttpServerRequest aRequest, HttpServerResponse aResponse ) {
String theRequestId = aResponse.getHeaderFields().getRequestId();
if ( theRequestId != null && theRequestId.length() != 0 ) {
Correlation.REQUEST.setId( theRequestId );
}
else {
theRequestId = aRequest.getHeaderFields().getRequestId();
if ( theRequestId == null || theRequestId.length() == 0 ) {
theRequestId = Correlation.REQUEST.pullId();
}
aResponse.getHeaderFields().putRequestId( theRequestId );
}
String theSessionId = aResponse.getHeaderFields().getSessionId();
if ( theSessionId != null && theSessionId.length() != 0 ) {
Correlation.SESSION.setId( theSessionId );
}
else {
theSessionId = aRequest.getHeaderFields().getSessionId();
if ( theSessionId == null || theSessionId.length() == 0 ) {
theSessionId = Correlation.SESSION.pullId();
}
aResponse.getHeaderFields().putSessionId( theSessionId );
}
}
}
Correlation-IDs are used to identify HTTP-Requests and HTTP-Responses passed between your zoo of microservices and correlate them.
In the example above, the
Session-TID
as well as theRequest-TID
are set according to the header fields’s settings and passed on the HTTP-Response.
You may also just add the pre-processing or the post-processing part of an HttpServerInterceptor
by using its parents PreHttpServerInterceptor
as well as PostHttpServerInterceptor
:
1
2
3
4
5
...
RestfulHttpServer theRestServer = new HttpRestServer();
theRestServer.addPreHttpInterceptor( ( req, res ) -> { System.out.println( "HTTP-Request: Session-ID = " + req.getHeaderFields().getSessionId() + ", Request-ID = " + req.getHeaderFields().getRequestId() ); } );
theRestServer.addPostHttpInterceptor( ( req, res ) -> { System.out.println( "HTTP-Response: Session-ID = " + res.getHeaderFields().getSessionId() + ", Request-ID = " + res.getHeaderFields().getRequestId() ); } );
...
How do I get started with the REST client?
Above you saw an example on how to setup your own REST client using syntactic sugar. One drawback of using syntactic sugar is that we can only make use of the one HttpRestClientSingleton
(as of this syntactic sugar being the statically imported methods), preventing us from running multiple HttpRestClient
instances in one Java application (which is actually no real drawback, as the HttpRestClientSingleton
can fire at any HTTP or HTTPS targets you wish to connect to).
Lets do it the Bare-Metal way, which is not very complicated either, and which lets us instantiate as many HttpRestClient
instances as we want:
1
2
3
4
5
6
7
8
9
10
11
...
public static void main( String[] args ) {
// STEP 1: We instantiate our HttpRestClient:
HttpRestClient theRestClient = new HttpRestClient();
// STEP 2: We register our lambda expression:
theRestClient.doRequest( HttpMethod.POST, "http://mydomain:8080/say", ( aResponse ) -> {
String theResponse = aResponse.getResponse( String.class );
} ).withRequest( ... ).open();
// STEP 3: We opened the caller so it fires the request to port 8080 of domain "mydomain"
}
...
Using HTTP-Request and HTTP-Response interceptors
The HttpClientInterceptor
and its parents PreHttpClientInterceptor
as well as PostHttpClientInterceptor
are explained by the example of the CorrelationClientInterceptor
:
1
2
3
4
...
HttpRestClient theRestClient = new HttpRestClient();
theRestClient.addHttpInterceptor( new CorrelationClientInterceptor() );
...
The
CorrelationClientInterceptor
is an implementation of theHttpClientInterceptor
interface and defines two methods, thepreIntercept
method as well as thepostIntercept
method: The former being invoked before the HTTP-Request is handed over to your business logic and the latter one being invoked after your business logic has finished with an HTTP-Response:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CorrelationClientInterceptor implements HttpClientInterceptor {
/**
* {@inheritDoc}
*/
@Override
public void preIntercept( HttpClientRequest aRequest, HttpClientResponse aResponse ) {
String theRequestId = aRequest.getHeaderFields().getRequestId();
if ( theRequestId != null && theRequestId.length() != 0 ) {
Correlation.REQUEST.setId( theRequestId );
}
else {
theRequestId = Correlation.REQUEST.pullId();
}
aResponse.getHeaderFields().putRequestId( theRequestId );
String theSessionId = aRequest.getHeaderFields().getSessionId();
if ( theSessionId != null && theSessionId.length() != 0 ) {
Correlation.SESSION.setId( theSessionId );
}
else {
theSessionId = Correlation.SESSION.pullId();
}
aResponse.getHeaderFields().putSessionId( theSessionId );
}
// ...
The above code intercepts incoming HTTP-Requests with means to, amongst others, prepare the according HTTP-Responses. Usually only the header fields of an HTTP-Response are prepared as any other portions of the HTTP-Response should solely be managed by your business logic. The below code intercepts with the outgoing HTTP-Responses, while still having access by the according HTTP-Requests:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ...
/**
* {@inheritDoc}
*/
@Override
public void postIntercept( HttpClientRequest aRequest, HttpClientResponse aResponse ) {
String theRequestId = aResponse.getHeaderFields().getRequestId();
if ( theRequestId != null && theRequestId.length() != 0 ) {
Correlation.REQUEST.setId( theRequestId );
}
else {
theRequestId = aRequest.getHeaderFields().getRequestId();
if ( theRequestId == null || theRequestId.length() == 0 ) {
theRequestId = Correlation.REQUEST.pullId();
}
aResponse.getHeaderFields().putRequestId( theRequestId );
}
String theSessionId = aResponse.getHeaderFields().getSessionId();
if ( theSessionId != null && theSessionId.length() != 0 ) {
Correlation.SESSION.setId( theSessionId );
}
else {
theSessionId = aRequest.getHeaderFields().getSessionId();
if ( theSessionId == null || theSessionId.length() == 0 ) {
theSessionId = Correlation.SESSION.pullId();
}
aResponse.getHeaderFields().putSessionId( theSessionId );
}
}
}
Correlation-IDs are used to identify HTTP-Requests and HTTP-Responses passed between your zoo of microservices and correlate them.
In the example above, the
Session-TID
as well as theRequest-TID
are set according to the header fields’s settings and passed on the HTTP-Response.
You may also just add the pre-processing or the post-processing part of an HttpClientInterceptor
by using its parents PreHttpClientInterceptor
as well as PostHttpClientInterceptor
:
1
2
3
4
5
...
HttpRestClient theRestClient = new HttpRestClient();
theRestClient.addPreHttpInterceptor( ( req, res ) -> { System.out.println( "HTTP-Request: Session-ID = " + req.getHeaderFields().getSessionId() + ", Request-ID = " + req.getHeaderFields().getRequestId() ); } );
theRestClient.addPostHttpInterceptor( ( req, res ) -> { System.out.println( "HTTP-Response: Session-ID = " + res.getHeaderFields().getSessionId() + ", Request-ID = " + res.getHeaderFields().getRequestId() ); } );
...
Bits and pieces
The RESTful server’s bits and pieces
-
RestServer
: It acts as the target for clients issuing REST requests.RestEndpointBuilder
instances, most easily being created with theRestServer#subscribeObserver(HttpMethod, String, RestRequestObserver)
or the like methods, are registered as listeners to theRestServer
. TheRestServer
firesRestRequestEvent
events to theRestRequestObserver
s of aRestEndpoint
dedicated to an accordinglocator
(pattern) for a specificHttpMethod
. -
HttpRestServer
: It extends aRestServer
to be capable of opening a server socket on the local host with the provided port number via#open(Integer)
or with an additional maximum number of connections via#open(Integer, int)
. AHttpRestServer
can be shutdown via#close()
. -
HttpRestServer
: Implementation of theHttpRestServer
interface using theHttpServer
defined in thecom.sun.net.httpserver
artifact. TheHttpRestServer
can also be implemented with other HTTP servers under the hood, use theAbstractRestServer
as super class of your own implementation to easily do so. -
RestEndpoint
: ARestEndpoint
subscribes to aRestServer
(HttpRestServer
) and defines the target for a REST request. Therefore theRestEndpoint
describes theHttpMethod
, the locator (pattern) to which to respond as well as aRestRequestObserver
responsible for processing the request. TheRestRequestObserver
is invoked as soon as a request with the givenHttpMethod
for a locator matching the given Locator-Pattern is being processed by theRestServer
(HttpRestServer
). -
RestEndpointBuilder
: ARestEndpointBuilder
extends aRestEndpoint
with builder functionality and addslambda
support for handling the request addressed to thisRestEndpoint
. The lambda defined asRestRequestObserver
acts as the single listener to thisRestEndpoint
responsible for handling the request for which thisRestEndpoint
is responsible. -
HttpRestServerSugar
: The syntactic sugar for setting up your RESTful service as quickly as possible (import static org.refcodes.rest.HttpRestServerSugar.*;
).
The REST client’s bits and pieces
-
RestClient
: It acts as the origin for clients issuing REST requests.RestCallerBuilder
instances, most easily being created with theRestClient#doRequest( HttpMethod , aLocator, aResponseObserver )
or the like methods, are registered as listeners to theRestClient
’s request, waiting for the response. TheRestClient
firesRestResponseEvent
events to theRestResponseObserver
of theRestCaller
dedicated to the according request. -
HttpRestClient
: It extends aRestClient
to be capable of doing HTTP (HTTPS). AHttpRestClient
can be shutdown via#close()
. -
HttpRestClient
: Implementation of theHttpRestClient
interface using theHttpURLConnection
defined in thejava.net
package. TheHttpRestClient
can also be implemented with other HTTP connectors under the hood, use theAbstractRestClient
as super class of your own implementation to easily do so. -
HttpRestClientSugar
: The syntactic sugar for setting up your REST client as quickly as possible (import static org.refcodes.rest.HttpRestClientSugar.*;
).
Examples
Please refer to the example source code for more examples on the usage of this artifact.
See also the blog post Bare-Metal REST with just a few lines of codes!
Eureka support
The refcodes-rest-ext-eureka
artifact supports the Eureka microservice registry/discovery mechanism (providing a sidecar) to register (attach) your RESTful services to Eureka.
See the
refcodes-rest-ext-eureka
artifact’s example source code on how to enable your RESTful services to use Eureka service discovery.
Useful information may be puzzled together from resources such as Eureka REST operations, JSON format to register service with Eureka or the eureka-client.properties
file.
Contribution guidelines
- Report issues
- Finding bugs
- Helping fixing bugs
- Making code and documentation better
- Enhance the code
Who do I talk to?
- Siegfried Steiner (steiner@refcodes.org)
Terms and conditions
The REFCODES.ORG
group of artifacts is published under some open source licenses; covered by the refcodes-licensing
(org.refcodes
group) artifact - evident in each artifact in question as of the pom.xml
dependency included in such artifact.
Latest updates
2023-04-29
Added client-side interceptors as well as server-side interceptors support.