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