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 }