001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.camel.component.jhc;
019    
020    import java.io.IOException;
021    import java.io.OutputStream;
022    
023    import org.apache.http.ConnectionReuseStrategy;
024    import org.apache.http.HttpEntity;
025    import org.apache.http.HttpEntityEnclosingRequest;
026    import org.apache.http.HttpException;
027    import org.apache.http.HttpRequest;
028    import org.apache.http.HttpResponse;
029    import org.apache.http.HttpResponseFactory;
030    import org.apache.http.HttpStatus;
031    import org.apache.http.HttpVersion;
032    import org.apache.http.MethodNotSupportedException;
033    import org.apache.http.ProtocolException;
034    import org.apache.http.ProtocolVersion;
035    import org.apache.http.UnsupportedHttpVersionException;
036    import org.apache.http.entity.ByteArrayEntity;
037    import org.apache.http.nio.ContentDecoder;
038    import org.apache.http.nio.ContentEncoder;
039    import org.apache.http.nio.NHttpServerConnection;
040    import org.apache.http.nio.NHttpServiceHandler;
041    import org.apache.http.nio.entity.ContentBufferEntity;
042    import org.apache.http.nio.entity.ContentOutputStream;
043    import org.apache.http.nio.protocol.NHttpServiceHandlerBase;
044    import org.apache.http.nio.util.ByteBufferAllocator;
045    import org.apache.http.nio.util.ContentInputBuffer;
046    import org.apache.http.nio.util.ContentOutputBuffer;
047    import org.apache.http.nio.util.HeapByteBufferAllocator;
048    import org.apache.http.nio.util.SimpleInputBuffer;
049    import org.apache.http.nio.util.SimpleOutputBuffer;
050    import org.apache.http.params.HttpParams;
051    import org.apache.http.protocol.ExecutionContext;
052    import org.apache.http.protocol.HttpContext;
053    import org.apache.http.protocol.HttpProcessor;
054    import org.apache.http.protocol.HttpRequestHandler;
055    import org.apache.http.util.EncodingUtils;
056    
057    /**
058     * HTTP service handler implementation that buffers the content of HTTP messages
059     * entirely in memory and processes HTTP requests on the main I/O thread.
060     *
061     * <p>This service handler should be used only when dealing with HTTP messages
062     * that are known to be limited in length</p>
063     *
064     * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
065     *
066     */
067    public class BufferingHttpServiceHandler extends NHttpServiceHandlerBase
068                                             implements NHttpServiceHandler {
069    
070        public BufferingHttpServiceHandler(
071                final HttpProcessor httpProcessor,
072                final HttpResponseFactory responseFactory,
073                final ConnectionReuseStrategy connStrategy,
074                final ByteBufferAllocator allocator,
075                final HttpParams params) {
076            super(httpProcessor, responseFactory, connStrategy, allocator, params);
077        }
078    
079        public BufferingHttpServiceHandler(
080                final HttpProcessor httpProcessor,
081                final HttpResponseFactory responseFactory,
082                final ConnectionReuseStrategy connStrategy,
083                final HttpParams params) {
084            this(httpProcessor, responseFactory, connStrategy,
085                    new HeapByteBufferAllocator(), params);
086        }
087    
088        public void connected(final NHttpServerConnection conn) {
089            HttpContext context = conn.getContext();
090    
091            ServerConnState connState = new ServerConnState(allocator);
092            context.setAttribute(CONN_STATE, connState);
093    
094            if (this.eventListener != null) {
095                this.eventListener.connectionOpen(conn);
096            }
097        }
098    
099        public void requestReceived(final NHttpServerConnection conn) {
100            HttpContext context = conn.getContext();
101    
102            HttpRequest request = conn.getHttpRequest();
103            request.setParams(this.params);
104    
105            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
106    
107            // Update connection state
108            connState.resetInput();
109            connState.setRequest(request);
110            connState.setInputState(ServerConnState.REQUEST_RECEIVED);
111    
112            ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
113            if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
114                // Downgrade protocol version if greater than HTTP/1.1
115                ver = HttpVersion.HTTP_1_1;
116            }
117    
118            HttpResponse response;
119    
120            try {
121    
122                if (request instanceof HttpEntityEnclosingRequest) {
123                    if (((HttpEntityEnclosingRequest) request).expectContinue()) {
124                        response = this.responseFactory.newHttpResponse(
125                            ver, HttpStatus.SC_CONTINUE, context);
126                        request.setParams(this.params);
127    
128                        if (this.expectationVerifier != null) {
129                            try {
130                                this.expectationVerifier.verify(request, response, context);
131                            } catch (HttpException ex) {
132                                response = this.responseFactory.newHttpResponse(
133                                        HttpVersion.HTTP_1_0,
134                                        HttpStatus.SC_INTERNAL_SERVER_ERROR,
135                                        context);
136                                request.setParams(this.params);
137                                handleException(ex, response);
138                            }
139                        }
140    
141                        if (response.getStatusLine().getStatusCode() < 200) {
142                            // Send 1xx response indicating the server expections
143                            // have been met
144                            conn.submitResponse(response);
145                        } else {
146                            // The request does not meet the server expections
147                            conn.resetInput();
148                            connState.resetInput();
149                            sendResponse(conn, response);
150                        }
151                    }
152                    // Request content is expected.
153                    // Wait until the request content is fully received
154                } else {
155                    // No request content is expected.
156                    // Process request right away
157                    conn.suspendInput();
158                    processRequest(conn, request);
159                }
160    
161            } catch (IOException ex) {
162                shutdownConnection(conn, ex);
163                if (this.eventListener != null) {
164                    this.eventListener.fatalIOException(ex, conn);
165                }
166            } catch (HttpException ex) {
167                closeConnection(conn, ex);
168                if (this.eventListener != null) {
169                    this.eventListener.fatalProtocolException(ex, conn);
170                }
171            }
172        }
173    
174        public void closed(final NHttpServerConnection conn) {
175            if (this.eventListener != null) {
176                this.eventListener.connectionClosed(conn);
177            }
178        }
179    
180        public void exception(final NHttpServerConnection conn, final HttpException httpex) {
181            HttpContext context = conn.getContext();
182            try {
183                HttpResponse response = this.responseFactory.newHttpResponse(
184                    HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
185                response.setParams(this.params);
186                handleException(httpex, response);
187                response.setEntity(null);
188                sendResponse(conn, response);
189    
190            } catch (IOException ex) {
191                shutdownConnection(conn, ex);
192                if (this.eventListener != null) {
193                    this.eventListener.fatalIOException(ex, conn);
194                }
195            } catch (HttpException ex) {
196                closeConnection(conn, ex);
197                if (this.eventListener != null) {
198                    this.eventListener.fatalProtocolException(ex, conn);
199                }
200            }
201        }
202    
203        public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) {
204            HttpContext context = conn.getContext();
205            HttpRequest request = conn.getHttpRequest();
206    
207            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
208            ContentInputBuffer buffer = connState.getInbuffer();
209    
210            // Update connection state
211            connState.setInputState(ServerConnState.REQUEST_BODY_STREAM);
212    
213            try {
214                buffer.consumeContent(decoder);
215                if (decoder.isCompleted()) {
216                    // Request entity has been fully received
217                    connState.setInputState(ServerConnState.REQUEST_BODY_DONE);
218    
219                    // Create a wrapper entity instead of the original one
220                    HttpEntityEnclosingRequest entityReq = (HttpEntityEnclosingRequest) request;
221                    if (entityReq.getEntity() != null) {
222                        entityReq.setEntity(new ContentBufferEntity(
223                                entityReq.getEntity(),
224                                connState.getInbuffer()));
225                    }
226                    conn.suspendInput();
227                    processRequest(conn, request);
228                }
229    
230            } catch (IOException ex) {
231                shutdownConnection(conn, ex);
232                if (this.eventListener != null) {
233                    this.eventListener.fatalIOException(ex, conn);
234                }
235            } catch (HttpException ex) {
236                closeConnection(conn, ex);
237                if (this.eventListener != null) {
238                    this.eventListener.fatalProtocolException(ex, conn);
239                }
240            }
241        }
242    
243        public void responseReady(final NHttpServerConnection conn) {
244        }
245    
246        public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) {
247    
248            HttpContext context = conn.getContext();
249            HttpResponse response = conn.getHttpResponse();
250    
251            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
252            ContentOutputBuffer buffer = connState.getOutbuffer();
253    
254            // Update connection state
255            connState.setOutputState(ServerConnState.RESPONSE_BODY_STREAM);
256    
257            try {
258    
259                buffer.produceContent(encoder);
260                if (encoder.isCompleted()) {
261                    connState.setOutputState(ServerConnState.RESPONSE_BODY_DONE);
262                    connState.resetOutput();
263                    if (!this.connStrategy.keepAlive(response, context)) {
264                        conn.close();
265                    } else {
266                        conn.requestInput();
267                    }
268                }
269    
270            } catch (IOException ex) {
271                shutdownConnection(conn, ex);
272                if (this.eventListener != null) {
273                    this.eventListener.fatalIOException(ex, conn);
274                }
275            }
276        }
277    
278        protected void handleException(final HttpException ex, final HttpResponse response) {
279            int code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
280            if (ex instanceof MethodNotSupportedException) {
281                code = HttpStatus.SC_NOT_IMPLEMENTED;
282            } else if (ex instanceof UnsupportedHttpVersionException) {
283                code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED;
284            } else if (ex instanceof ProtocolException) {
285                code = HttpStatus.SC_BAD_REQUEST;
286            }
287            response.setStatusCode(code);
288    
289            byte[] msg = EncodingUtils.getAsciiBytes(ex.getMessage());
290            ByteArrayEntity entity = new ByteArrayEntity(msg);
291            entity.setContentType("text/plain; charset=US-ASCII");
292            response.setEntity(entity);
293        }
294    
295        protected void processRequest(
296                final NHttpServerConnection conn,
297                final HttpRequest request) throws IOException, HttpException {
298    
299            HttpContext context = conn.getContext();
300            ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
301    
302            if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
303                // Downgrade protocol version if greater than HTTP/1.1
304                ver = HttpVersion.HTTP_1_1;
305            }
306    
307            HttpResponse response = this.responseFactory.newHttpResponse(
308                ver, HttpStatus.SC_OK, conn.getContext());
309            request.setParams(this.params);
310    
311            context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
312            context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
313            context.setAttribute(ExecutionContext.HTTP_RESPONSE, response);
314    
315            try {
316    
317                this.httpProcessor.process(request, context);
318    
319                HttpRequestHandler handler = null;
320                if (this.handlerResolver != null) {
321                    String requestURI = request.getRequestLine().getUri();
322                    handler = this.handlerResolver.lookup(requestURI);
323                }
324                if (handler != null) {
325                    handler.handle(request, response, context);
326                } else {
327                    response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED);
328                }
329    
330            } catch (HttpException ex) {
331                response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0,
332                    HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
333                request.setParams(this.params);
334                handleException(ex, response);
335            }
336    
337            sendResponse(conn, response);
338        }
339    
340        protected void sendResponse(
341                final NHttpServerConnection conn,
342                final HttpResponse response) throws IOException, HttpException {
343    
344            HttpContext context = conn.getContext();
345    
346            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
347            ContentOutputBuffer buffer = connState.getOutbuffer();
348    
349            this.httpProcessor.process(response, context);
350    
351            if (!canResponseHaveBody(connState.getRequest(), response)) {
352                response.setEntity(null);
353            }
354    
355            conn.submitResponse(response);
356    
357            // Update connection state
358            connState.setOutputState(ServerConnState.RESPONSE_SENT);
359    
360            HttpEntity entity = response.getEntity();
361            if (entity != null) {
362                OutputStream outstream = new ContentOutputStream(buffer);
363                entity.writeTo(outstream);
364                outstream.flush();
365                outstream.close();
366            } else {
367                connState.resetOutput();
368                if (!this.connStrategy.keepAlive(response, context)) {
369                    conn.close();
370                } else {
371                    conn.requestInput();
372                }
373            }
374        }
375    
376        static class ServerConnState {
377    
378            public static final int READY                      = 0;
379            public static final int REQUEST_RECEIVED           = 1;
380            public static final int REQUEST_BODY_STREAM        = 2;
381            public static final int REQUEST_BODY_DONE          = 4;
382            public static final int RESPONSE_SENT              = 8;
383            public static final int RESPONSE_BODY_STREAM       = 16;
384            public static final int RESPONSE_BODY_DONE         = 32;
385    
386            private SimpleInputBuffer inbuffer;
387            private ContentOutputBuffer outbuffer;
388    
389            private int inputState;
390            private int outputState;
391    
392            private HttpRequest request;
393            private final ByteBufferAllocator allocator;
394    
395            public ServerConnState(final ByteBufferAllocator allocator) {
396                super();
397                this.inputState = READY;
398                this.outputState = READY;
399                this.allocator = allocator;
400            }
401    
402            public ContentInputBuffer getInbuffer() {
403                if (this.inbuffer == null) {
404                    this.inbuffer = new SimpleInputBuffer(2048, allocator);
405                }
406                return this.inbuffer;
407            }
408    
409            public ContentOutputBuffer getOutbuffer() {
410                if (this.outbuffer == null) {
411                    this.outbuffer = new SimpleOutputBuffer(2048, allocator);
412                }
413                return this.outbuffer;
414            }
415    
416            public int getInputState() {
417                return this.inputState;
418            }
419    
420            public void setInputState(int inputState) {
421                this.inputState = inputState;
422            }
423    
424            public int getOutputState() {
425                return this.outputState;
426            }
427    
428            public void setOutputState(int outputState) {
429                this.outputState = outputState;
430            }
431    
432            public HttpRequest getRequest() {
433                return this.request;
434            }
435    
436            public void setRequest(final HttpRequest request) {
437                this.request = request;
438            }
439    
440            public void resetInput() {
441                this.inbuffer = null;
442                this.request = null;
443                this.inputState = READY;
444            }
445    
446            public void resetOutput() {
447                this.outbuffer = null;
448                this.outputState = READY;
449            }
450    
451        }
452    
453    }