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    public class BufferingHttpServiceHandler extends NHttpServiceHandlerBase
065                                             implements NHttpServiceHandler {
066    
067        public BufferingHttpServiceHandler(
068                final HttpProcessor httpProcessor,
069                final HttpResponseFactory responseFactory,
070                final ConnectionReuseStrategy connStrategy,
071                final ByteBufferAllocator allocator,
072                final HttpParams params) {
073            super(httpProcessor, responseFactory, connStrategy, allocator, params);
074        }
075    
076        public BufferingHttpServiceHandler(
077                final HttpProcessor httpProcessor,
078                final HttpResponseFactory responseFactory,
079                final ConnectionReuseStrategy connStrategy,
080                final HttpParams params) {
081            this(httpProcessor, responseFactory, connStrategy,
082                    new HeapByteBufferAllocator(), params);
083        }
084    
085        public void connected(final NHttpServerConnection conn) {
086            HttpContext context = conn.getContext();
087    
088            ServerConnState connState = new ServerConnState(allocator);
089            context.setAttribute(CONN_STATE, connState);
090    
091            if (this.eventListener != null) {
092                this.eventListener.connectionOpen(conn);
093            }
094        }
095    
096        public void requestReceived(final NHttpServerConnection conn) {
097            HttpContext context = conn.getContext();
098    
099            HttpRequest request = conn.getHttpRequest();
100            request.setParams(this.params);
101    
102            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
103    
104            // Update connection state
105            connState.resetInput();
106            connState.setRequest(request);
107            connState.setInputState(ServerConnState.REQUEST_RECEIVED);
108    
109            ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
110            if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
111                // Downgrade protocol version if greater than HTTP/1.1
112                ver = HttpVersion.HTTP_1_1;
113            }
114    
115            HttpResponse response;
116    
117            try {
118    
119                if (request instanceof HttpEntityEnclosingRequest) {
120                    if (((HttpEntityEnclosingRequest) request).expectContinue()) {
121                        response = this.responseFactory.newHttpResponse(
122                            ver, HttpStatus.SC_CONTINUE, context);
123                        request.setParams(this.params);
124    
125                        if (this.expectationVerifier != null) {
126                            try {
127                                this.expectationVerifier.verify(request, response, context);
128                            } catch (HttpException ex) {
129                                response = this.responseFactory.newHttpResponse(
130                                        HttpVersion.HTTP_1_0,
131                                        HttpStatus.SC_INTERNAL_SERVER_ERROR,
132                                        context);
133                                request.setParams(this.params);
134                                handleException(ex, response);
135                            }
136                        }
137    
138                        if (response.getStatusLine().getStatusCode() < 200) {
139                            // Send 1xx response indicating the server expections
140                            // have been met
141                            conn.submitResponse(response);
142                        } else {
143                            // The request does not meet the server expections
144                            conn.resetInput();
145                            connState.resetInput();
146                            sendResponse(conn, response);
147                        }
148                    }
149                    // Request content is expected.
150                    // Wait until the request content is fully received
151                } else {
152                    // No request content is expected.
153                    // Process request right away
154                    conn.suspendInput();
155                    processRequest(conn, request);
156                }
157    
158            } catch (IOException ex) {
159                shutdownConnection(conn, ex);
160                if (this.eventListener != null) {
161                    this.eventListener.fatalIOException(ex, conn);
162                }
163            } catch (HttpException ex) {
164                closeConnection(conn, ex);
165                if (this.eventListener != null) {
166                    this.eventListener.fatalProtocolException(ex, conn);
167                }
168            }
169        }
170    
171        public void closed(final NHttpServerConnection conn) {
172            if (this.eventListener != null) {
173                this.eventListener.connectionClosed(conn);
174            }
175        }
176    
177        public void exception(final NHttpServerConnection conn, final HttpException httpex) {
178            HttpContext context = conn.getContext();
179            try {
180                HttpResponse response = this.responseFactory.newHttpResponse(
181                    HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
182                response.setParams(this.params);
183                handleException(httpex, response);
184                response.setEntity(null);
185                sendResponse(conn, response);
186    
187            } catch (IOException ex) {
188                shutdownConnection(conn, ex);
189                if (this.eventListener != null) {
190                    this.eventListener.fatalIOException(ex, conn);
191                }
192            } catch (HttpException ex) {
193                closeConnection(conn, ex);
194                if (this.eventListener != null) {
195                    this.eventListener.fatalProtocolException(ex, conn);
196                }
197            }
198        }
199    
200        public void inputReady(final NHttpServerConnection conn, final ContentDecoder decoder) {
201            HttpContext context = conn.getContext();
202            HttpRequest request = conn.getHttpRequest();
203    
204            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
205            ContentInputBuffer buffer = connState.getInbuffer();
206    
207            // Update connection state
208            connState.setInputState(ServerConnState.REQUEST_BODY_STREAM);
209    
210            try {
211                buffer.consumeContent(decoder);
212                if (decoder.isCompleted()) {
213                    // Request entity has been fully received
214                    connState.setInputState(ServerConnState.REQUEST_BODY_DONE);
215    
216                    // Create a wrapper entity instead of the original one
217                    HttpEntityEnclosingRequest entityReq = (HttpEntityEnclosingRequest) request;
218                    if (entityReq.getEntity() != null) {
219                        entityReq.setEntity(new ContentBufferEntity(
220                                entityReq.getEntity(),
221                                connState.getInbuffer()));
222                    }
223                    conn.suspendInput();
224                    processRequest(conn, request);
225                }
226    
227            } catch (IOException ex) {
228                shutdownConnection(conn, ex);
229                if (this.eventListener != null) {
230                    this.eventListener.fatalIOException(ex, conn);
231                }
232            } catch (HttpException ex) {
233                closeConnection(conn, ex);
234                if (this.eventListener != null) {
235                    this.eventListener.fatalProtocolException(ex, conn);
236                }
237            }
238        }
239    
240        public void responseReady(final NHttpServerConnection conn) {
241        }
242    
243        public void outputReady(final NHttpServerConnection conn, final ContentEncoder encoder) {
244    
245            HttpContext context = conn.getContext();
246            HttpResponse response = conn.getHttpResponse();
247    
248            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
249            ContentOutputBuffer buffer = connState.getOutbuffer();
250    
251            // Update connection state
252            connState.setOutputState(ServerConnState.RESPONSE_BODY_STREAM);
253    
254            try {
255    
256                buffer.produceContent(encoder);
257                if (encoder.isCompleted()) {
258                    connState.setOutputState(ServerConnState.RESPONSE_BODY_DONE);
259                    connState.resetOutput();
260                    if (!this.connStrategy.keepAlive(response, context)) {
261                        conn.close();
262                    } else {
263                        conn.requestInput();
264                    }
265                }
266    
267            } catch (IOException ex) {
268                shutdownConnection(conn, ex);
269                if (this.eventListener != null) {
270                    this.eventListener.fatalIOException(ex, conn);
271                }
272            }
273        }
274    
275        protected void handleException(final HttpException ex, final HttpResponse response) {
276            int code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
277            if (ex instanceof MethodNotSupportedException) {
278                code = HttpStatus.SC_NOT_IMPLEMENTED;
279            } else if (ex instanceof UnsupportedHttpVersionException) {
280                code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED;
281            } else if (ex instanceof ProtocolException) {
282                code = HttpStatus.SC_BAD_REQUEST;
283            }
284            response.setStatusCode(code);
285    
286            byte[] msg = EncodingUtils.getAsciiBytes(ex.getMessage());
287            ByteArrayEntity entity = new ByteArrayEntity(msg);
288            entity.setContentType("text/plain; charset=US-ASCII");
289            response.setEntity(entity);
290        }
291    
292        protected void processRequest(
293                final NHttpServerConnection conn,
294                final HttpRequest request) throws IOException, HttpException {
295    
296            HttpContext context = conn.getContext();
297            ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
298    
299            if (!ver.lessEquals(HttpVersion.HTTP_1_1)) {
300                // Downgrade protocol version if greater than HTTP/1.1
301                ver = HttpVersion.HTTP_1_1;
302            }
303    
304            HttpResponse response = this.responseFactory.newHttpResponse(
305                ver, HttpStatus.SC_OK, conn.getContext());
306            request.setParams(this.params);
307    
308            context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
309            context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
310            context.setAttribute(ExecutionContext.HTTP_RESPONSE, response);
311    
312            try {
313    
314                this.httpProcessor.process(request, context);
315    
316                HttpRequestHandler handler = null;
317                if (this.handlerResolver != null) {
318                    String requestURI = request.getRequestLine().getUri();
319                    handler = this.handlerResolver.lookup(requestURI);
320                }
321                if (handler != null) {
322                    handler.handle(request, response, context);
323                } else {
324                    response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED);
325                }
326    
327            } catch (HttpException ex) {
328                response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0,
329                    HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
330                request.setParams(this.params);
331                handleException(ex, response);
332            }
333    
334            sendResponse(conn, response);
335        }
336    
337        protected void sendResponse(
338                final NHttpServerConnection conn,
339                final HttpResponse response) throws IOException, HttpException {
340    
341            HttpContext context = conn.getContext();
342    
343            ServerConnState connState = (ServerConnState) context.getAttribute(CONN_STATE);
344            ContentOutputBuffer buffer = connState.getOutbuffer();
345    
346            this.httpProcessor.process(response, context);
347    
348            if (!canResponseHaveBody(connState.getRequest(), response)) {
349                response.setEntity(null);
350            }
351    
352            conn.submitResponse(response);
353    
354            // Update connection state
355            connState.setOutputState(ServerConnState.RESPONSE_SENT);
356    
357            HttpEntity entity = response.getEntity();
358            if (entity != null) {
359                OutputStream outstream = new ContentOutputStream(buffer);
360                entity.writeTo(outstream);
361                outstream.flush();
362                outstream.close();
363            } else {
364                connState.resetOutput();
365                if (!this.connStrategy.keepAlive(response, context)) {
366                    conn.close();
367                } else {
368                    conn.requestInput();
369                }
370            }
371        }
372    
373        static class ServerConnState {
374    
375            public static final int READY                      = 0;
376            public static final int REQUEST_RECEIVED           = 1;
377            public static final int REQUEST_BODY_STREAM        = 2;
378            public static final int REQUEST_BODY_DONE          = 4;
379            public static final int RESPONSE_SENT              = 8;
380            public static final int RESPONSE_BODY_STREAM       = 16;
381            public static final int RESPONSE_BODY_DONE         = 32;
382    
383            private SimpleInputBuffer inbuffer;
384            private ContentOutputBuffer outbuffer;
385    
386            private int inputState;
387            private int outputState;
388    
389            private HttpRequest request;
390            private final ByteBufferAllocator allocator;
391    
392            public ServerConnState(final ByteBufferAllocator allocator) {
393                super();
394                this.inputState = READY;
395                this.outputState = READY;
396                this.allocator = allocator;
397            }
398    
399            public ContentInputBuffer getInbuffer() {
400                if (this.inbuffer == null) {
401                    this.inbuffer = new SimpleInputBuffer(2048, allocator);
402                }
403                return this.inbuffer;
404            }
405    
406            public ContentOutputBuffer getOutbuffer() {
407                if (this.outbuffer == null) {
408                    this.outbuffer = new SimpleOutputBuffer(2048, allocator);
409                }
410                return this.outbuffer;
411            }
412    
413            public int getInputState() {
414                return this.inputState;
415            }
416    
417            public void setInputState(int inputState) {
418                this.inputState = inputState;
419            }
420    
421            public int getOutputState() {
422                return this.outputState;
423            }
424    
425            public void setOutputState(int outputState) {
426                this.outputState = outputState;
427            }
428    
429            public HttpRequest getRequest() {
430                return this.request;
431            }
432    
433            public void setRequest(final HttpRequest request) {
434                this.request = request;
435            }
436    
437            public void resetInput() {
438                this.inbuffer = null;
439                this.request = null;
440                this.inputState = READY;
441            }
442    
443            public void resetOutput() {
444                this.outbuffer = null;
445                this.outputState = READY;
446            }
447    
448        }
449    
450    }