View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.protocol;
29  
30  import java.io.IOException;
31  
32  import org.apache.http.ConnectionReuseStrategy;
33  import org.apache.http.HttpEntity;
34  import org.apache.http.HttpEntityEnclosingRequest;
35  import org.apache.http.HttpException;
36  import org.apache.http.HttpRequest;
37  import org.apache.http.HttpResponse;
38  import org.apache.http.HttpResponseFactory;
39  import org.apache.http.HttpServerConnection;
40  import org.apache.http.HttpStatus;
41  import org.apache.http.HttpVersion;
42  import org.apache.http.MethodNotSupportedException;
43  import org.apache.http.ProtocolException;
44  import org.apache.http.UnsupportedHttpVersionException;
45  import org.apache.http.annotation.Immutable;
46  import org.apache.http.entity.ByteArrayEntity;
47  import org.apache.http.params.HttpParams;
48  import org.apache.http.params.DefaultedHttpParams;
49  import org.apache.http.util.EncodingUtils;
50  import org.apache.http.util.EntityUtils;
51  
52  /**
53   * <tt>HttpService</tt> is a server side HTTP protocol handler based on
54   * the classic (blocking) I/O model.
55   * <p/>
56   * <tt>HttpService</tt> relies on {@link HttpProcessor} to generate mandatory
57   * protocol headers for all outgoing messages and apply common, cross-cutting
58   * message transformations to all incoming and outgoing messages, whereas
59   * individual {@link HttpRequestHandler}s are expected to implement
60   * application specific content generation and processing.
61   * <p/>
62   * <tt>HttpService</tt> uses {@link HttpRequestHandlerResolver} to resolve
63   * matching request handler for a particular request URI of an incoming HTTP
64   * request.
65   * <p/>
66   * <tt>HttpService</tt> can use optional {@link HttpExpectationVerifier}
67   * to ensure that incoming requests meet server's expectations.
68   *
69   * @since 4.0
70   */
71  @Immutable // provided injected dependencies are immutable and deprecated methods are not used
72  public class HttpService {
73  
74      /**
75       * TODO: make all variables final in the next major version
76       */
77      private volatile HttpParams params = null;
78      private volatile HttpProcessor processor = null;
79      private volatile HttpRequestHandlerResolver handlerResolver = null;
80      private volatile ConnectionReuseStrategy connStrategy = null;
81      private volatile HttpResponseFactory responseFactory = null;
82      private volatile HttpExpectationVerifier expectationVerifier = null;
83  
84      /**
85       * Create a new HTTP service.
86       *
87       * @param processor            the processor to use on requests and responses
88       * @param connStrategy         the connection reuse strategy
89       * @param responseFactory      the response factory
90       * @param handlerResolver      the handler resolver. May be null.
91       * @param expectationVerifier  the expectation verifier. May be null.
92       * @param params               the HTTP parameters
93       *
94       * @since 4.1
95       */
96      public HttpService(
97              final HttpProcessor processor,
98              final ConnectionReuseStrategy connStrategy,
99              final HttpResponseFactory responseFactory,
100             final HttpRequestHandlerResolver handlerResolver,
101             final HttpExpectationVerifier expectationVerifier,
102             final HttpParams params) {
103         super();
104         if (processor == null) {
105             throw new IllegalArgumentException("HTTP processor may not be null");
106         }
107         if (connStrategy == null) {
108             throw new IllegalArgumentException("Connection reuse strategy may not be null");
109         }
110         if (responseFactory == null) {
111             throw new IllegalArgumentException("Response factory may not be null");
112         }
113         if (params == null) {
114             throw new IllegalArgumentException("HTTP parameters may not be null");
115         }
116         this.processor = processor;
117         this.connStrategy = connStrategy;
118         this.responseFactory = responseFactory;
119         this.handlerResolver = handlerResolver;
120         this.expectationVerifier = expectationVerifier;
121         this.params = params;
122     }
123 
124     /**
125      * Create a new HTTP service.
126      *
127      * @param processor            the processor to use on requests and responses
128      * @param connStrategy         the connection reuse strategy
129      * @param responseFactory      the response factory
130      * @param handlerResolver      the handler resolver. May be null.
131      * @param params               the HTTP parameters
132      *
133      * @since 4.1
134      */
135     public HttpService(
136             final HttpProcessor processor,
137             final ConnectionReuseStrategy connStrategy,
138             final HttpResponseFactory responseFactory,
139             final HttpRequestHandlerResolver handlerResolver,
140             final HttpParams params) {
141         this(processor, connStrategy, responseFactory, handlerResolver, null, params);
142     }
143 
144     /**
145      * Create a new HTTP service.
146      *
147      * @param proc             the processor to use on requests and responses
148      * @param connStrategy     the connection reuse strategy
149      * @param responseFactory  the response factory
150      *
151      * @deprecated (4.1) use {@link HttpService#HttpService(HttpProcessor,
152      *  ConnectionReuseStrategy, HttpResponseFactory, HttpRequestHandlerResolver, HttpParams)}
153      */
154     @Deprecated
155     public HttpService(
156             final HttpProcessor proc,
157             final ConnectionReuseStrategy connStrategy,
158             final HttpResponseFactory responseFactory) {
159         super();
160         setHttpProcessor(proc);
161         setConnReuseStrategy(connStrategy);
162         setResponseFactory(responseFactory);
163     }
164 
165     /**
166      * @deprecated (4.1) set {@link HttpProcessor} using constructor
167      */
168     @Deprecated
169     public void setHttpProcessor(final HttpProcessor processor) {
170         if (processor == null) {
171             throw new IllegalArgumentException("HTTP processor may not be null");
172         }
173         this.processor = processor;
174     }
175 
176     /**
177      * @deprecated (4.1) set {@link ConnectionReuseStrategy} using constructor
178      */
179     @Deprecated
180     public void setConnReuseStrategy(final ConnectionReuseStrategy connStrategy) {
181         if (connStrategy == null) {
182             throw new IllegalArgumentException("Connection reuse strategy may not be null");
183         }
184         this.connStrategy = connStrategy;
185     }
186 
187     /**
188      * @deprecated (4.1) set {@link HttpResponseFactory} using constructor
189      */
190     @Deprecated
191     public void setResponseFactory(final HttpResponseFactory responseFactory) {
192         if (responseFactory == null) {
193             throw new IllegalArgumentException("Response factory may not be null");
194         }
195         this.responseFactory = responseFactory;
196     }
197 
198     /**
199      * @deprecated (4.1) set {@link HttpResponseFactory} using constructor
200      */
201     @Deprecated
202     public void setParams(final HttpParams params) {
203         this.params = params;
204     }
205 
206     /**
207      * @deprecated (4.1) set {@link HttpRequestHandlerResolver} using constructor
208      */
209     @Deprecated
210     public void setHandlerResolver(final HttpRequestHandlerResolver handlerResolver) {
211         this.handlerResolver = handlerResolver;
212     }
213 
214     /**
215      * @deprecated (4.1) set {@link HttpExpectationVerifier} using constructor
216      */
217     @Deprecated
218     public void setExpectationVerifier(final HttpExpectationVerifier expectationVerifier) {
219         this.expectationVerifier = expectationVerifier;
220     }
221 
222     public HttpParams getParams() {
223         return this.params;
224     }
225 
226     /**
227      * Handles receives one HTTP request over the given connection within the
228      * given execution context and sends a response back to the client.
229      *
230      * @param conn the active connection to the client
231      * @param context the actual execution context.
232      * @throws IOException in case of an I/O error.
233      * @throws HttpException in case of HTTP protocol violation or a processing
234      *   problem.
235      */
236     public void handleRequest(
237             final HttpServerConnection conn,
238             final HttpContext context) throws IOException, HttpException {
239 
240         context.setAttribute(ExecutionContext.HTTP_CONNECTION, conn);
241 
242         HttpResponse response = null;
243 
244         try {
245 
246             HttpRequest request = conn.receiveRequestHeader();
247             request.setParams(
248                     new DefaultedHttpParams(request.getParams(), this.params));
249 
250             if (request instanceof HttpEntityEnclosingRequest) {
251 
252                 if (((HttpEntityEnclosingRequest) request).expectContinue()) {
253                     response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_1,
254                             HttpStatus.SC_CONTINUE, context);
255                     response.setParams(
256                             new DefaultedHttpParams(response.getParams(), this.params));
257 
258                     if (this.expectationVerifier != null) {
259                         try {
260                             this.expectationVerifier.verify(request, response, context);
261                         } catch (HttpException ex) {
262                             response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_0,
263                                     HttpStatus.SC_INTERNAL_SERVER_ERROR, context);
264                             response.setParams(
265                                     new DefaultedHttpParams(response.getParams(), this.params));
266                             handleException(ex, response);
267                         }
268                     }
269                     if (response.getStatusLine().getStatusCode() < 200) {
270                         // Send 1xx response indicating the server expections
271                         // have been met
272                         conn.sendResponseHeader(response);
273                         conn.flush();
274                         response = null;
275                         conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);
276                     }
277                 } else {
278                     conn.receiveRequestEntity((HttpEntityEnclosingRequest) request);
279                 }
280             }
281 
282             context.setAttribute(ExecutionContext.HTTP_REQUEST, request);
283 
284             if (response == null) {
285                 response = this.responseFactory.newHttpResponse(HttpVersion.HTTP_1_1,
286                         HttpStatus.SC_OK, context);
287                 response.setParams(
288                         new DefaultedHttpParams(response.getParams(), this.params));
289                 this.processor.process(request, context);
290                 doService(request, response, context);
291             }
292 
293             // Make sure the request content is fully consumed
294             if (request instanceof HttpEntityEnclosingRequest) {
295                 HttpEntity entity = ((HttpEntityEnclosingRequest)request).getEntity();
296                 EntityUtils.consume(entity);
297             }
298 
299         } catch (HttpException ex) {
300             response = this.responseFactory.newHttpResponse
301                 (HttpVersion.HTTP_1_0, HttpStatus.SC_INTERNAL_SERVER_ERROR,
302                  context);
303             response.setParams(
304                     new DefaultedHttpParams(response.getParams(), this.params));
305             handleException(ex, response);
306         }
307 
308         context.setAttribute(ExecutionContext.HTTP_RESPONSE, response);
309 
310         this.processor.process(response, context);
311         conn.sendResponseHeader(response);
312         conn.sendResponseEntity(response);
313         conn.flush();
314 
315         if (!this.connStrategy.keepAlive(response, context)) {
316             conn.close();
317         }
318     }
319 
320     /**
321      * Handles the given exception and generates an HTTP response to be sent
322      * back to the client to inform about the exceptional condition encountered
323      * in the course of the request processing.
324      *
325      * @param ex the exception.
326      * @param response the HTTP response.
327      */
328     protected void handleException(final HttpException ex, final HttpResponse response) {
329         if (ex instanceof MethodNotSupportedException) {
330             response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED);
331         } else if (ex instanceof UnsupportedHttpVersionException) {
332             response.setStatusCode(HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED);
333         } else if (ex instanceof ProtocolException) {
334             response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
335         } else {
336             response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
337         }
338         String message = ex.getMessage();
339         if (message == null) {
340             message = ex.toString();
341         }
342         byte[] msg = EncodingUtils.getAsciiBytes(message);
343         ByteArrayEntity entity = new ByteArrayEntity(msg);
344         entity.setContentType("text/plain; charset=US-ASCII");
345         response.setEntity(entity);
346     }
347 
348     /**
349      * The default implementation of this method attempts to resolve an
350      * {@link HttpRequestHandler} for the request URI of the given request
351      * and, if found, executes its
352      * {@link HttpRequestHandler#handle(HttpRequest, HttpResponse, HttpContext)}
353      * method.
354      * <p>
355      * Super-classes can override this method in order to provide a custom
356      * implementation of the request processing logic.
357      *
358      * @param request the HTTP request.
359      * @param response the HTTP response.
360      * @param context the execution context.
361      * @throws IOException in case of an I/O error.
362      * @throws HttpException in case of HTTP protocol violation or a processing
363      *   problem.
364      */
365     protected void doService(
366             final HttpRequest request,
367             final HttpResponse response,
368             final HttpContext context) throws HttpException, IOException {
369         HttpRequestHandler handler = null;
370         if (this.handlerResolver != null) {
371             String requestURI = request.getRequestLine().getUri();
372             handler = this.handlerResolver.lookup(requestURI);
373         }
374         if (handler != null) {
375             handler.handle(request, response, context);
376         } else {
377             response.setStatusCode(HttpStatus.SC_NOT_IMPLEMENTED);
378         }
379     }
380 
381 }