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 }