| | |
|
|
|
Creating a connection handler
This topic outlines how to create a new connection handler using the Handler SPI. For an overview of connection handlers and their configuration, see Configuring connection handlers.
For more information about destination configuration and an overview of destination processing, see Configuring destinations.
Also review the Javadoc SPI reference for more information.
Overview
The Handler interface
Connection handler implementations must implement the zero.core.connection.handlers.Handler interface. This interface has two methods which must be implemented.
| Handler |
| 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.
The connection context
The onRequest() and onResponse() methods interact directly with the /connection zone of the GlobalContext. Details of the outbound request can be found using the following keys:
| GlobalContext keys |
/connection/request/target | Destination resource name. |
/connection/request/operation | Operation name. |
/connection/request/headers/ header_name | List of 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 will depend on the body supplied by the caller of the Connection API.
Details of the response can be found using the following keys:
| GlobalContext keys |
/connection/response/headers/ header_name | List of values for response header. |
/connection/response/status | Response status String. |
/connection/response/body | Response body. |
The response body may be any object type, including InputStream and Reader.
Considerations when streaming
Care 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. It may be necessary to 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 GlobalContext zone.
Note connection handlers do not support OutputStream processing. If the caller requested a Connection.getRequestBodyOutputStream() and a handler is configured, the request body will be automatically be collected in a buffer and /connection/request/body will reference an InputStream from which the request body it can be read.
Reading handler configuration
Handler-specific configuration 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.
For example, consider the following destination configuration:
/config/connection/destinations += [{
"name" : "http://localhost:8080/target.groovy",
"handlers" : [{
"class" : "examples.handler.SimpleMap2JSONHandler",
"config" : {
"contentType" : "text/json; charset=UTF-8"
}
}]
}]
This 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 will be able to find the configuration in the GlobalContext as follows:
/connection/configuration#contentType
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 will be 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 will not be invoked and the request will not be 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 have completed their processing, it will eventually be thrown to the application when it uses the Connection API. The Exception will be wrapped in an ConnectionException if it is not an instance of ConnectionException or IOException.
Example handler implementation
To demonstrate the principles involved in writing a connection handler, we will use an example called SimpleMap2JSONHandler.
In this scenario 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 will convert 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.
Here is the example implementation of 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 is a summary of what this onRequest() method is doing:
- The request body (if any)
/connection/request/body key is read from the GlobalContext.
- If the request body is an instance of
Map:
-
/connection/request/body is replaced with a String representation of the Map using JSON syntax.
-
/connection/request/headers/Content-Type is set, using the contentType configuration property supplied to the Handler.
- If any exceptional situation arises, an
Exception is placed in /connection/response/body.
Here is the example implementation of 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 is a summary of what this onResponse() method is doing:
-
/connection/response/headers/Content-Type is examined and compared to the value of the contentType configuration property supplied to the Handler.
- If the content type matches,
/connection/response/body is replaced with a decoded form of the JSON representation (using a utility method in zero.core.connection.utils.DataUtilities to obtain a String value).
- If any exceptional situation arises, an
Exception is places in /connection/response/body.
Here 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 this simple connection handler, it needs to be configured for a destination as in the following example configuration:
/config/connection/destinations += [{
"name" : "http://localhost:8080/target.groovy",
"handlers" : [{
"class" : "examples.handler.SimpleMap2JSONHandler",
"config" : {
"contentType" : "text/json; charset=UTF-8"
}
}]
}]
It should be be remembered that the SimpleMap2JSONHandler example is intended to illustrate the principles of writing a Handler implementation and may not be suitable for deployment in this form. In particular, it is worth noting that it relies on the "Content-Type" header having the exact value specified by the "contentType" configuration property and is case sensitive; that is it is not able to cope with responses containing "CONTENT-TYPE" or "content-type" headers.
|
|
r24 - 07 Feb 2008 - 20:06:43 - todkap
|
|
|
| | |