HttpRequestResponseWrapper
1. Requirements
The servlet requests and responses should be wrapped to allow deegree specific extensions. The main reason behind this is to allow caching of the input and output streams.
1.1. Response Caching
All output that is written to the servlet response (via #getOutputStream() or #getPrintWriter()) should be cached internally. This allows to things:
- The header can be changed after the response is generated.
- The whole response and the header can be discarded.
With the first the service is able to set the Content-length. The second allows to discard the generated response and start the response from scratch. This is useful if an exception occurred and a ExceptionReport should be returned and not the partial original result.
1.2. Request Caching
A normal request stream can only be read once. For the dynamic dispatching of the requests within the OCGFrontController it is necessary to be able to peak into the stream and read XML namespaces or version parameters. Therefore the input stream of the request must implement the #reset() method.
1.3. (File)-Buffering
Since a request and response can be arbitrary long, the buffer should use a file if the buffer size reaches a given threshold.
1.4. Configuration
As a side effect the first byte of the response will only be send after the whole result is generated. This is undesired for very large stream-based responses. So the buffering should be configurable.
2. Implementation
At a first glance it seams easy but a first evaluation shows that the implementation might be tricky. The wrapper of the HttpServletResponse implements the #getOutputStream() or #getPrintWriter() methods. These methods return streams/writer that buffer all data. The HttpServletResponse doesn't define a close method or something else that indicates the end of the result and thus the time to flush the buffer. Instead the stream/writer will be flushed by the container. But, it seems that the flush method is only called on the PrintWriter and not on the OutputStream. That means that our HttpResponseWrapper doesn't know that the result is complete and the buffer should be flushed to the real, wrapped HttpResponse.
One solution is to flush the HttpResponseWrapper with flushBuffer() explicitly after the result is finally complete. To hide these details, it was decided to implement our own Request and Response objects.
2.1. Request Object
There will be an abstract request object that will wrap the HTTPServletRequest. The Request<T> offers the <T> getRequest() method that will return the preprocessed request data. Possible implementations are KVPRequest that returns a Map<String, String>, XMLRequest that returns XMLAdapter (or XMLStream?) and a SOAPRequest that returns SOAPEnvelopes.
The abstract Request will only implement a subset of the HTTPServletRequest methods. Good candidates are getHeader(), getContentType, etc.. But it will offer access to the wrapped servlet request with getWrappedRequest().
By contract, the results of getInputStream() and getReader() will implement the reset() method to support a rewind to the beginning of the stream.
2.2. Response Object
Similar to the Request object, the Response object will implement a subset of the HTTPServletResonse methods. Furthermore it will offer commit() and rollback() methods to control the caching of the output stream. Before the request is committed all headers can be changed (e.g. set content length). A rollback() will reset the output stream and the service can start with the response from scratch. This allows to discard requests and to send a complete new response. This is useful when an exception occurs in the middle of a request and the response should be changed to an exception report (with different headers and content).
Note: The Java Servlet-API describes buffer handling but it only allows a fixed buffer size which is to inflexible for our use cases.
The method [setBufferSize()] must be called before any content is written using a ServletOutputStream or Writer.
Since a response can be arbitrary large, it should be possible to cache the stream in a file. To reduce overhead, this should be done only if a certain threshold is exceeded.
2.3. Buffered{Request|Response}Wrapper
The OGCFrontController accesses the request stream in order to dispatch it to the right SubController with the right method (KVP, XML, SOAP). At this moment it is not known what kind of request it is and which Request<T> object should be used for wrapping. Therefore it is necessary to add buffering(caching) to the HTTPServletRequest before it is accessed the first time. The wrapper class BufferedRequestWrapper will do that. The BufferedRequestWrapper will return an InputStream or Reader that implements the reset() method. After it is decided which kind of request it is, an appropriate Request object will be instantiated with the wrapper.
Similar to the request, the response (HTTPServletResponse) will be wrapped in a BufferedResponseWrapper. It will implement the reset() and flushBuffer() methods from the HttpServletResponseWrapper, but unlike the parent class it will allow a reset() even if a large amount of data was written to the stream.