Services
A Service hooks up a web service to an URI. Whenever there’s a request that matches with the URI, its handle(req, res)
method will be executed.
Important
|
A Service consists of a class implementing one of the Service interfaces such as JsonService and annotated with @RegisterPlugin .
|
The req
and res
arguments allow to retrieve request content, query parameters and headers and set the response status code, content and headers respectively. For instance, res.setStatusCode(200)
sets the response status code.
Note
|
No need to worry about the actual HTTP data transmission - RESTHeart will handle that for you automatically using the data set in the res object.
|
The Service class
A Service
consists of a class implementing one of the Service
interfaces such as JsonService
and annotated with @RegisterPlugin
.
The following Service interfaces are provided and differ from the data type used to hold the request and response content:
-
ByteArrayService
-
StringService
-
JsonService
-
BsonService
For instance, BsonService
uses BsonValue
to hold request and response content: this is useful when you cope with MongoDb that represents data using this class.
The code of example greeter-service implementing JsonService
follows:
RegisterPlugin(name = "greetings", description = "just another Hello World")
public class GreeterService implements JsonService {
@Override
public void handle(JsonRequest req, JsonResponse res) {
switch(req.getMethod()) {
case GET -> res.setContent(object().put("message", "Hello World!"));
case OPTIONS -> handleOptions(req);
default -> res.setStatusCode(HttpStatus.SC_METHOD_NOT_ALLOWED);
}
}
}
The key method is handle(req, res)
that is executed when a request matching the service’s URI hits RESTHeart.
Note
|
the verb OPTIONS is handled by the ready-to-use handleOptions(req) method. It copes with CORS headers making sure the Web Service can be safely invoked by browsers.
|
@RegisterPlugin annotation
The class implementing the Service
interfaces must be a annotated with @RegisterPlugin
to:
-
allow RESTHeart to find plugins' implementation classes in deployed jars (see How to Deploy Plugins)
-
specify parameters such us the URI of a Service or the intercept point of an Interceptor.
The following table describes the arguments of the annotation for Services:
param | description | mandatory | default value |
---|---|---|---|
|
the name of the Service |
yes |
none |
|
description of the Service |
yes |
none |
|
|
no |
|
|
the default URI of the Service; can be overridden by the service configuration option |
no |
/<srv-name> |
|
|
no |
|
|
|
no |
|
|
list of interceptPoints to be executed on requests handled by the service, e.g. |
no |
|
Service with custom generic types
To implement a Service
that handles different types of Request
and Response
, it must implement the base Service
interface.
The base Service
interface requires to implement methods to initialize and retrieve the Request
and Response
objects.
The following example shows how to handle XML content:
@RegisterPlugin(name = "myXmlService",
description = "example service consuming XML requests",
enabledByDefault = true,
defaultURI = "/xml")
public class MyXmlService implements Service<XmlRequest, XmlResponse> {
public void handle(XmlRequest req, XmlResponse res) {
// handling logic omitted
}
@Override
default Consumer<HttpServerExchange> requestInitializer() {
return e -> XmlRequest.init(e);
}
@Override
default Consumer<HttpServerExchange> responseInitializer() {
return e -> XmlResponse.init(e);
}
@Override
default Function<HttpServerExchange, JsonRequest> request() {
return e -> XmlRequest.of(e);
}
@Override
default Function<HttpServerExchange, JsonResponse> response() {
return e -> XmlResponse.of(e);
}
}
The example follows a pattern that delegates the actual initialization (in requestInitializer()
and responseInitializer()
) and retrieval of the object from the exchange (in request()
and response()
) to the concrete class, as follows:
public class XmlRequest extends ServiceRequest<Document> {
private XmlRequest(HttpServerExchange exchange) {
super(exchange);
}
public static XmlRequest init(HttpServerExchange exchange) {
var ret = new XmlRequest(exchange);
try {
ret.injectContent();
} catch (Throwable ieo) {
ret.setInError(true);
}
return ret;
}
public static XmlRequest of(HttpServerExchange exchange) {
return of(exchange, XmlRequest.class);
}
public void injectContent() throws SAXException, IOException {
var dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
var rawContent = ChannelReader.read(wrapped.getRequestChannel());
setContent(dBuilder.parse(rawContent)ml);
}
}
In the constructor a call to super(exchange)
attaches the object to the HttpServerExchange
. The object is retrieved using the inherited of()
method that gets the instance attachment from the HttpServerExchange
. This is fundamental for two reasons: first the same request and response objects must be shared by the all handlers of the processing chain. Second, this avoid the need to parse the content several times for performance reasons.
Tip
|
Watch Services |