Message Interceptors/Plugins
The architecture of Membrane API Gateway is open for extension. To add functionality or to change the behavior, an interceptor can be plugged into the flow of messages. When a message flows through Membrane, each interceptor gets a chance to ...
- read the content of a message and the protocol information like HTTP header fields.
- modify a message. It can for instance change the destination of a message.
- stop the message processing and reverse the direction of flow for the exchange.
- Outcome.CONTINUE to indicate that request processing should continue,
- Outcome.RETURN to indicate that the direction of flow should be reversed and response processing should start, (perhaps a request object should have been set on the exchange by the interceptor) ,
- Outcome.ABORT to indicate that an error occurred during processing (this immediately throws an exception effectively).
On the way back, the Exchange takes either the router through an interceptor's handleResponse or its handleAbort, depending on whether an exception has been caught from further down the chain or not.
If handleResponse returns Outcome.ABORT, an exception is immediately thrown.
Figure1:
In the standard flow, the HTTPServerHandler accepts an incoming HTTP request and creates an Exchange object for the request. The Exchange object passes through interceptors 1, 2 and 3. Each of these interceptors might read and/or modify the HTTP request. They all return Outcome.CONTINUE and processing continues down the chain. Within the HttpClientInterceptor's handleRequest method, the request is then forwarded to its destination (probably some other HTTP server) and the HTTP response is associated with the Exchange. The HttpClientInterceptor's handleRequest method then returns Outcome.RETURN, which reverses the direction of flow.
The exchange object is then passed back through the interceptor 3, 2 and 1's handleResponse methods, which might read and/or modify the HTTP response. Finally, the HTTPServerHandler sends the HTTP response back to the caller.
Figure2:
In case one of the handleRequest or handleResponse methods returns Outcome.ABORT, an exception is immediately thrown. This (or any other) exception is catched at the next upper level. In this case, processing continues back up by using the handleAbort methods.
This allows for special error handling to take place. Note that the Exchange object might not have a response object associated with it when handleAbort is called. (For example when a ConnectException was caught within the HttpClientInterceptor, because the destination HTTP server could not be reached.)
Lifecycle
An interceptor is usually instanciated once per chain. This means that the handle* methods have to be thread-safe with respect to the interceptor's instance. They do not have to be thread-safe with respect to the Exchange, as the Exchange is handled by one thread only (and as long as it has not yet entered an ExchangeStore).
The instanciation can be configured in Membrane's proxies.xml Spring configuration file or can be done by the Java API. A per-exchange lifecycle can be achieved by attaching properties to the Exchange object:
public Outcome handleRequest(Exchange exc) throws Exception { exchange.setProperty("myobject", new MyObject()); return Outcome.CONTINUE; } public Outcome handleResponse(Exchange exc) throws Exception { MyObject o = (MyObject) exchange.getProperty("myobject"); return Outcome.CONTINUE; }
In the handleResponse method you get back the object that was put there for the corresponding request.
Interceptor Interface
Listing 1 shows the interceptor Interface.
public interface Interceptor extends XMLElement { public Outcome handleRequest(Exchange exc) throws Exception; public Outcome handleResponse(Exchange exc) throws Exception; public void handleAbort(Exchange exchange); public String getDisplayName(); public void setDisplayName(String name); public String getId(); public void setId(String id); public void setFlow(EnumSet<Flow> flow); public EnumSet<Flow> getFlow(); }
Invocation of Interceptor Methods
Each request
1. acquires, as handleRequest() calls of interceptors return Outcome.CONTINUE, a stack of named interceptors.
2. When the request reaches the end of the chain (in most cases this is the HttpClientInterceptor, which forwarded the request to some other HTTP server), each interceptor from the stack gets popped and handleResponse() is called.
The "end of the chain" is simply the first interceptor whose handleRequest() returned Outcome.RETURN instead of Outcome.CONTINUE. This last interceptor is not pushed onto the stack.
(In case interceptor.getFlow() returns RESPONSE (in which case handleRequest() should not be called for this interceptor), the call to handleRequest() is not executed during step 1, but treated as returning Outcome.CONTINUE.)
If an interceptor's handleRequest() or handleResponse() returns Outcome.ABORT, an AbortException is thrown.
If an exception is caught (during steps 1 or 2), each interceptor remaining on the stack is popped and its handleAbort() method is called. After any handleAbort()s have been called, the original exception is rethrown. If handleAbort() throws an exception, it is logged and ignored (processing continues with the next interceptor on the stack).
When the exception is then re-caught in the handler (AbstractHttpHandler) and the exchange does not yet have a response set, a generic error response is used. I/O-related exceptions (even when not related to the incoming connection) cause the connection to be closed. Any other exceptions are being logged and processing continues as usual: the response is sent to the client; then the next request on this connection is handled.