Creating a connection handler
This topic describes how to write a connection handler that can be configured to process requests and responses for a connection destination.
Overview
A connection handler is a small piece of logic that can be configured to examine and process the requests sent to, and the responses received from, a connection destination. For more information about destination configuration and an overview of destination processing, see Configuring destinations. For an overview of connection handlers and their configuration, see Configuring connection handlers.
There are two basic approaches that can be used to write a connection handler:
- Implement the connection handler by writing two event handlers; one to process the connection request and one to process the connection response. An event-based connection handler can be implemented by a Groovy or PHP script, or a Java™ class.
- Implement the connection handler by writing a Java class that implements the
zero.core.connection.handlers.Handlerinterface.
If you are writing a new connection handler you might choose the simplicity of the event-based approach
but older connection handler implementations might still be implementing the
zero.core.connection.handlers.Handler interface directly.
This topic focuses on the use of the simpler, event-based approach but the techniques and
global contexts keys discussed also apply to the class-based approach.
Writing an event-based connection handler
An event-based connection handler is implemented by handling two events; one to process the connection
request and one to process the connection response. The event names are constructed from the connection handler name
followed by Request or Response respectively. For example, if the connection
handler name is myConnectionHandler then the request event is
myConnectionHandlerRequest and the response event is myConnectionHandlerResponse.
The following Groovy script contains an example implementation of a connection handler
called myConnectionHandler:
def onMyConnectionHandlerRequest() {
// Request processing logic
}
def onMyConnectionHandlerResponse() {
// Response processing logic
}
When you write an event-based connection handler, you must also configure
the request and response event handler implementations
to handle the corresponding events.
For example, if the myConnectionHandler implementation is saved as myConnectionHandler.groovy
in the application app/scripts directory, then the following configuration makes the connection
handler available for use:
/config/handlers += [{
"events" : ["myConnectionHandlerRequest", "myConnectionHandlerResponse"],
"handler" : "myConnectionHandler.groovy"
}]
After the connection handler had been implemented and registered with the corresponding events, it
can be configured for one or more connection destinations.
For example, the following configuration associates the myConnectionHandler connection
handler with a connection destination:
/config/connection/destinations += {
"http://www.projectzero.org/*" : {
"handlers" : [{
"name" : "myConnectionHandler"
}]
}
}
When a connection request is sent to this destination, the myConnectionHandlerRequest
event will be fired and the connection handler can inspect or modify the request before it is sent to the
target service. When the response is retrieved, the myConnectionHandlerResponse event will
be fired and the connection handler can inspect of modify the response before it is returned to the
application.
For more information about destination configuration and an overview of destination processing, see Configuring destinations. For an overview of connection handlers and their configuration, see Configuring connection handlers.
The connection context
The request and response handler implementations interact directly with the /connection zone of the global context.
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/cookies -
ListofCookievalues for request, if applicable. -
/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
ConnectionAPI.
The contents of the response can be accessed using the following keys:
-
/connection/response/headers/header_name -
Listof values for response header. -
/connection/response/cookies -
ListofCookievalues for response, if applicable. -
/connection/response/status - Response status
String. -
/connection/response/body - Response body.
The response body can be any object type, including
InputStreamandReader. Exception conditions are indicated by the response body being an instance ofException.
The value of a header stored under /connection/request/headers/
header_name and
/connection/response/headers/
header_name is always a list.
If a connection handler implementation attempts to set a header value that is not an instance of List,
it is automatically placed in a list before it is stored in the /connection zone.
Reading connection 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 request and response handler implementations are invoked, the
configuration is made available in the global context as a Map,
under the key /connection/configuration.
In the following example configuration, the connection handler, myConnectionHandler, is configured for the named destination
and supplies some handler configuration:
/config/connection/destinations += {
"http://www.projectzero.org/*" : {
"handlers" : [{
"name" : "myConnectionHandler",
"config" : {
"myConfig" : "myValue"
}
}]
}
}
When the myConnectionHandler request and response handler implementations are invoked for this destination,
the value of /connection/configuration#myConfig will be myValue.
In the following example Groovy script, the value of the myConfig configuration parameter is retrieved in
both the request and response handler implementations:
def onMyConnectionHandlerRequest() {
def value = zget("/connection/configuration#myConfig");
// value will contain "myValue"
}
def onMyConnectionHandlerResponse() {
def value = zget("/connection/configuration#myConfig");
// value will contain "myValue"
}
- A connection handler must not replace or modify the configuration map at
/connection/configuration. Attempting to do this results in a runtime exception. - For more information about destination configuration, see Configuring destinations.
Carrying state between request and response event handlers
An event-based connection handler can carry state between the request and response event handlers
using the /event zone.
The request event handler can place any state it needs to pass to the response handler in the
/event zone.
When the response event is fired, this state is restored to the /event zone,
from where the response event handler can retrieve it.
In the following example Groovy script, the request event handler saves a value in the /event
zone, from where the response event handler retrieves it:
def onMyConnectionHandlerRequest() {
zput("/event/someState", "someValue");
}
def onMyConnectionHandlerResponse() {
def value = zget("/event/someState");
// value will contain "someValue"
}
A class-based connection handler should not update the /event zone
because the onRequest() and onResponse() methods do not have an
event associated with them.
See Writing a class-based connection handler for more information.
Suppressing request processing
A connection handler request implementation can choose to prevent further connection request processing. If a connection request is suppressed in this way, no further connection handlers are executed and the request is not sent to the target resource. Connection response processing begins, starting with the preceding connection handler.
To suppress request processing, the connection handler request implementation
places a value in the global context using any
key starting with /connection/response/
If the connection handler request implementation sets any part of the response in the global context,
it is deemed to have determined the complete response.
It might be necessary for the connection handler to set several values, such as
/connection/response/body, /connection/response/status and
values under /connection/response/headers,
to simulate a full response for the applicable protocol.
Exception handling
A connection handler can signal an exception situation by placing an instance of
Exception in /connection/response/body,
during either request or response processing.
Connection handler implementations should be designed to use this mechanism to signal
exceptions rather than throwing a runtime exceptions.
Setting /connection/response/body suppresses any remaining connection
request processing and response processing begins, starting with the preceding
connection handler.
If an unexpected RuntimeException is thrown during
connection handler processing processing, it is caught by the connection infrastructure and placed in
/connection/response/body on behalf of the connection handler implementation.
The Exception contained in /connection/response/body can be examined
or replaced by a connection handler response implementation.
If /connection/response/body contains an Exception after connection handler
response processing is complete, the exception is
thrown to the application that is using the Connection API. The Exception is
wrapped in an ConnectionException if it is not an instance of ConnectionException or IOException.
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 instance of InputStream or
Reader,
the implementation of the new body can reference the /connection global context zone. The connection
infrastructure ensures that the correct /connection context is made available when the application reads
the stream.
However the implementation of any other object used to represent a response body must
not reference the /connection global context zone.
Connection handlers do not support direct OutputStream processing. If the application requests an output stream using
Connection.getRequestBodyOutputStream() and a handler is configured, the request body is automatically
collected in a buffer, and /connection/request/body contains an InputStream from which the request body
can be read.
Writing a class-based connection handler
Class-based 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()
A class-based connection handler should not update the /event zone
because the onRequest() and onResponse() methods do not have an
event associated with them.
The contents of the /event zone when a class-based connection handler is invoked
belongs to the event handler that called the Connection API.
However, a new instance of the Handler implementation is created for each connection request and the same instance
is used for the corresponding response processing, so state can be carried between them using instance variables.
A class-based connection handler works with the contents /connection zone of the global context
in the same way that an event-based connection handler does.
For example, the following class-based connection handler sets the Accept header
for the connection request:
package examples.handler;
import zero.core.connection.handlers.Handler;
import zero.core.context.GlobalContext;
public class AcceptTypeHandler implements Handler {
public void onRequest() {
GlobalContext.zput("/connection/request/headers/Accept", "text/*");
}
public void onResponse() {
// Do nothing
}
}
- A class-based connection handler does not require any event handler registrations. To use a class-based connection handler, the full Java class name must be specified in the connection destination configuration. For more information, see Configuring connection handlers.
- See the API reference for
zero.corefor a description of thezero.core.connection.handlers.Handlerinterface.
Example connection handler implementations
The following examples contain simple, event-based connection handler implementations.
Setting a request header
In the following example Groovy implementation of a connection handler, acceptText,
the request handler sets the Accept header on each connection request before it is sent to the
target service:
def onAcceptTextRequest() {
zput("/connection/request/headers/Accept", "text/*");
}
def onAcceptTextResponse() {
// Do nothing
}
If the acceptText implementation is saved as acceptText.groovy
in the application app/scripts directory, then the following configuration makes the connection
handler available for use in a connection destination configuration:
/config/handlers += [{
"events" : ["acceptTextRequest", "acceptTextResponse"],
"handler" : "acceptText.groovy"
}]
The following configuration example shows how the acceptText connection handler could be
associated with a connection destination:
/config/connection/destinations += {
"http://www.projectzero.org/service.groovy" : {
"handlers" : [{
"name" : "acceptText"
}]
}
}
Selecting user ID and password for a request
As an example of how connection configuration might be used, consider a scenario in which an application
makes requests to a service which must be authenticated using a dynamically determined user ID and password.
The selectUserCredentials connection handler must select a user ID and password from a map supplied
in the destination configuration and supply it to the http protocol configuration.
The following example configuration shows how this handler might be configured for a connection destination:
/config/connection/destinations += {
"http://www.projectzero.org/service.groovy*" : {
"handlers" : [{
"name" : "selectUserCredentials",
"config" : {
"credentials" : {
"gold" : {
"userid" : "user1",
"password" : "password1"
},
"silver" : {
"userid" : "user2",
"password" : "password2"
},
"bronze" : {
"userid" : "user3",
"password" : "password3"
}
}
}
}]
}
}
The selectUserCredentials connection handler implementation must:
- Identify the service level required for the current connection request;
gold,silverorbronze. For this example, we will assume that the application has determined this when the user signed on and stored the result in the global context, as/user/level. - Collect the corresponding user ID and password from the destination configuration.
- Set the
useridandpasswordprotocol configuration parameters for thehttpprotocol.
The following example Groovy script contains an implementation of the
selectUserCredentials connection handler:
def onSelectUserCredentialsRequest() {
// Read user level from /user zone
def level = zget("/user/level");
// Read user ID from connection handler configuration
def userid = zget("/connection/configuration#credentials/" + level + "/userid");
if (userid == null) {
// No appropriate user ID specified so return a 403 status code to application.
// By setting any part of /connection/response this handler prevents the
// request from getting any further.
zput("/connection/response/status" , "403")
} else {
// Set configured user ID and password on connection request
zput("/connection/request/protocol/userid", userid);
zput("/connection/request/protocol/password",
zget("/connection/configuration#credentials/" + level + "/password")
);
}
}
def onSelectUserCredentialsResponse() {
// Do nothing
}
The connection handler request implementation does the following:
- The user's service level,
level, is read from where the application had previously recorded it in the global context. - The
levelvalue is used to form a global context key, which used to read the associateduseridvalue from the connection handler configuration. - If no
useridis configured to match thelevelvalue, the request handler sets the response status to simulate the HTTP 403 status code. By setting any part of the connection response, the request handler suppresses further request processing and the request will not be sent to the target service. - If a
useridis configured, it is used as the protocol configuration property,userid, and the correspondingpasswordis copied from the connection handler configuration.
If the selectUserCredentials implementation is saved as selectUserCredentials.groovy
in the application app/scripts directory, then the following configuration makes the connection
handler available for use in a connection destination configuration:
/config/handlers += [{
"events" : ["selectUserCredentialsRequest", "selectUserCredentialsResponse"],
"handler" : "selectUserCredentials.groovy"
}]