Creating a connection handler
Use the Handler SPI to create a new connection handler.
For an overview of connection handlers and their configuration, see Configuring connection handlers.
Overview
The handler interface
Connection handler implementations must implement the zero.core.connection.handlers.Handler interface. This
interface has two methods that must be implemented.
-
void onRequest() -
void onResponse()
When a connection handler is configured for a destination, the onRequest() method is invoked during request processing
and the onResponse() method is invoked during response processing.
For more information about destination configuration and an overview of destination processing, see Configuring destinations. Also review the Javadoc SPI reference for more information.
The connection context
The onRequest() and onResponse() methods interact directly with the /connection zone of the GlobalContext.
The contents of the outbound request can be accessed using the following keys:
-
/connection/request/target - Destination resource name.
-
/connection/request/operation - Operation name.
-
/connection/request/headers/header_name -
Listof values for request header. -
/connection/request/protocol/_name - Name of the protocol requested by the application.
-
/connection/request/protocol/parameter_name - Protocol configuration value.
-
/connection/request/body - Request body.
The object type of the request body depends on the body supplied by the caller of the Connection API.
The contents of the response can be accessed using the following keys:
-
/connection/response/headers/header_name -
Listof values for response header. -
/connection/response/status - Response status
String. -
/connection/response/body - Response body.
The response body can be any object type, including InputStream and Reader.
Considerations when streaming
Caution should be taken if a connection handler needs to examine a request or response body in streaming scenarios. If the connection
handler begins to read an InputStream or Reader body, then the read data is effectively removed from the body.
If necessary, replace the request body with a buffer containing the contents of the consumed the InputStream or
Reader, or provide a wrapper stream.
If a connection handler replaces a request or response body with an InputStream, Reader, or similar
streaming construct, the implementation of the new body should not reference the /connection global context zone.
Connection handlers do not support OutputStream processing. If the caller requested a
Connection.getRequestBodyOutputStream() and a handler is configured, the request body is automatically
collected in a buffer, and /connection/request/body references an InputStream from which the request body it
can be read.
Reading handler configuration
If a handler requires configuration, it can be specified
when a handler is registered with a connection destination, as described in
Configuring connection handlers. When the Handler is invoked, the
configuration is made available in the GlobalContext as a Map under the key /connection/configuration.
In the following example, the configuration associates the handler implementation class examples.handler.Map2JSONHandler with the destination,
http://localhost:8080/target.groovy, and supplies some handler configuration. When the Handler is invoked,
it locates the configuration in the global context as /connection/configuration#contentType:
/config/connection/destinations += {
"http://localhost:8080/target.groovy" : {
"handlers" : [{
"class" : "examples.handler.SimpleMap2JSONHandler",
"config" : {
"contentType" : "text/json; charset=UTF-8"
}
}]
}
}
For more information about destination configuration, see Configuring destinations.
Exception handling
Either onRequest() or onRespond() can signal an exception situation by placing an instance of
Exception in /connection/response/body.
Implementations of onRequest() and onRespond() should be designed to use this mechanism to signal
exceptions rather than throwing a RuntimeException. If an unexpected RuntimeException is thrown during
onRequest() or onRespond() processing, it is caught by the connection infrastructure and placed in
/connection/response/body on behalf of the Handler implementation.
If /connection/response/body contains an Exception when onRequest() or
onResponse() returns, response processing is carried out starting with the preceding connection handler. If
onRequest() sets and an Exception response, then any following connection handlers are not invoked and the
request is not sent to the resource. The Exception contained in /connection/response/body can be examined
or replaced by the onReponse() method of any remaining configured connection handlers.
If /connection/response/body contains an Exception after the connection handlers complete the
processing, it is eventually thrown to the application when it uses the Connection API. The Exception is
wrapped in an ConnectionException if it is not an instance of ConnectionException or IOException.
Example handler implementation
The following example demonstrates the principles involved in writing a connection handler, called SimpleMap2JSONHandler.
An application using the Connection API works with request and response bodies as instances of
Map, but the target resources is a REST service using JSON syntax. During onRequest() the connection handler
converts the request Map sent by the application to a JSON request for the target service. In onResponse(), the
JSON response from the service is decoded into a Map to be delivered to the application.
The following example code contains the implementation of SimpleMap2JSONHandler.onRequest():
public void onRequest() {
try {
// Examine request body
Object body = GlobalContext.zget(GlobalContextURIs.Connection.Request.body, null);
if (body instanceof Map) {
// Replace Map body with JSON encoded string
String jsonString = Json.encode(body);
GlobalContext.zput(GlobalContextURIs.Connection.Request.body, jsonString);
// Set Content-Type header
String contentType = GlobalContext.zget(GlobalContextURIs.Connection.configuration + "#contentType");
GlobalContext.zput(GlobalContextURIs.Connection.Request.headers + "/Content-Type",
ListFactory.create(contentType));
}
} catch (Exception e) {
GlobalContext.zput(GlobalContextURIs.Connection.Response.body, e);
}
}
The following list summarizes the function of the onRequest() method:
- The request body, if any,
/connection/request/bodykey is read from the GlobalContext. - If the request body is an instance of
Map:-
/connection/request/bodyis replaced with aStringrepresentation of theMapusing JSON syntax. -
/connection/request/headers/Content-Typeis set, using thecontentTypeconfiguration property supplied to theHandler.
-
- If an exception occurs, an
Exceptionis placed in/connection/response/body.
The following example code contains the implementation of SimpleMap2JSONHandler.onResponse():
public void onResponse() {
try {
// Look for content type header
List<Object> contentTypeHdr = (List<Object>)
GlobalContext.zget(GlobalContextURIs.Connection.Response.headers + "/Content-Type", null);
String contentType = GlobalContext.zget(GlobalContextURIs.Connection.configuration + "#contentType");
if (contentTypeHdr != null && contentTypeHdr.contains(contentType)) {
Object body = GlobalContext.zget(GlobalContextURIs.Connection.Response.body, null);
String jsonString = DataUtilities.convertToString(body, "UTF8");
Object decoded = Json.decode(jsonString);
GlobalContext.zput(GlobalContextURIs.Connection.Response.body, decoded);
}
} catch (Exception e) {
GlobalContext.zput(GlobalContextURIs.Connection.Response.body, e);
}
}
The following list summarizes the function of the onResponse() method:
-
/connection/response/headers/Content-Typeis examined and compared to the value of thecontentTypeconfiguration property supplied to theHandler. - If the content type matches,
/connection/response/bodyis replaced with a decoded form of the JSON representation using a utility method inzero.core.connection.utils.DataUtilitiesto obtain aStringvalue. - If an exception occurs, an
Exceptionis placed in/connection/response/body.
The following example is the remainder of the SimpleMap2JSONHandler:
package examples.handler;
import java.util.List;
import java.util.Map;
import zero.core.context.GlobalContext;
import zero.core.context.GlobalContextURIs;
import zero.core.connection.handlers.Handler;
import zero.core.connection.utils.DataUtilities;
import zero.json.Json;
import zero.util.ListFactory;
public class SimpleMap2JSONHandler implements Handler {
// onRequest() and onResponse() go here
}
To use the simple connection handler, configure the handler for a destination as shown in following example:
/config/connection/destinations += {
"http://localhost:8080/target.groovy" : {
"handlers" : [{
"class" : "examples.handler.SimpleMap2JSONHandler",
"config" : {
"contentType" : "text/json; charset=UTF-8"
}
}]
}
}
The SimpleMap2JSONHandler example is intended to illustrate the principles of writing a Handler implementation,
and might not be suitable for deployment in this form. In particular, it relies on the "Content-Type" header having the
exact value specified by the "contentType" configuration property and is case sensitive. It cannot cope with responses
containing "CONTENT-TYPE" or "content-type" headers.