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.hc.core5.http.impl.io;
29  
30  import java.io.IOException;
31  import java.util.concurrent.atomic.AtomicBoolean;
32  
33  import org.apache.hc.core5.annotation.Contract;
34  import org.apache.hc.core5.annotation.ThreadingBehavior;
35  import org.apache.hc.core5.http.ClassicHttpRequest;
36  import org.apache.hc.core5.http.ClassicHttpResponse;
37  import org.apache.hc.core5.http.ConnectionReuseStrategy;
38  import org.apache.hc.core5.http.ContentType;
39  import org.apache.hc.core5.http.HeaderElements;
40  import org.apache.hc.core5.http.HttpException;
41  import org.apache.hc.core5.http.HttpHeaders;
42  import org.apache.hc.core5.http.HttpRequestMapper;
43  import org.apache.hc.core5.http.HttpResponseFactory;
44  import org.apache.hc.core5.http.HttpStatus;
45  import org.apache.hc.core5.http.MethodNotSupportedException;
46  import org.apache.hc.core5.http.NotImplementedException;
47  import org.apache.hc.core5.http.ProtocolException;
48  import org.apache.hc.core5.http.ProtocolVersion;
49  import org.apache.hc.core5.http.UnsupportedHttpVersionException;
50  import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
51  import org.apache.hc.core5.http.impl.Http1StreamListener;
52  import org.apache.hc.core5.http.io.HttpRequestHandler;
53  import org.apache.hc.core5.http.io.HttpServerConnection;
54  import org.apache.hc.core5.http.io.HttpServerRequestHandler;
55  import org.apache.hc.core5.http.io.entity.EntityUtils;
56  import org.apache.hc.core5.http.io.entity.StringEntity;
57  import org.apache.hc.core5.http.io.support.BasicHttpServerExpectationDecorator;
58  import org.apache.hc.core5.http.io.support.BasicHttpServerRequestHandler;
59  import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
60  import org.apache.hc.core5.http.message.MessageSupport;
61  import org.apache.hc.core5.http.protocol.HttpContext;
62  import org.apache.hc.core5.http.protocol.HttpCoreContext;
63  import org.apache.hc.core5.http.protocol.HttpProcessor;
64  import org.apache.hc.core5.util.Args;
65  
66  /**
67   * {@code HttpService} is a server side HTTP protocol handler based on
68   * the classic (blocking) I/O model.
69   * <p>
70   * {@code HttpService} relies on {@link HttpProcessor} to generate mandatory
71   * protocol headers for all outgoing messages and apply common, cross-cutting
72   * message transformations to all incoming and outgoing messages, whereas
73   * individual {@link HttpRequestHandler}s are expected to implement
74   * application specific content generation and processing.
75   * <p>
76   * {@code HttpService} uses {@link HttpRequestMapper} to map
77   * matching request handler for a particular request URI of an incoming HTTP
78   * request.
79   *
80   * @since 4.0
81   */
82  @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
83  public class HttpService {
84  
85      private final HttpProcessor processor;
86      private final HttpServerRequestHandler requestHandler;
87      private final ConnectionReuseStrategy connReuseStrategy;
88      private final Http1StreamListener streamListener;
89  
90      /**
91       * Create a new HTTP service.
92       *
93       * @param processor the processor to use on requests and responses
94       * @param handlerMapper  the handler mapper
95       * @param responseFactory  the response factory. If {@code null}
96       *   {@link DefaultClassicHttpResponseFactory#INSTANCE} will be used.
97       * @param connReuseStrategy the connection reuse strategy. If {@code null}
98       *   {@link DefaultConnectionReuseStrategy#INSTANCE} will be used.
99       * @param streamListener message stream listener.
100      */
101     public HttpService(
102             final HttpProcessor processor,
103             final HttpRequestMapper<HttpRequestHandler> handlerMapper,
104             final ConnectionReuseStrategy connReuseStrategy,
105             final HttpResponseFactory<ClassicHttpResponse> responseFactory,
106             final Http1StreamListener streamListener) {
107         this(processor,
108                 new BasicHttpServerExpectationDecorator(new BasicHttpServerRequestHandler(handlerMapper, responseFactory)),
109                 connReuseStrategy,
110                 streamListener);
111     }
112 
113     /**
114      * Create a new HTTP service.
115      *
116      * @param processor the processor to use on requests and responses
117      * @param handlerMapper  the handler mapper
118      * @param connReuseStrategy the connection reuse strategy. If {@code null}
119      *   {@link DefaultConnectionReuseStrategy#INSTANCE} will be used.
120      * @param responseFactory  the response factory. If {@code null}
121      *   {@link DefaultClassicHttpResponseFactory#INSTANCE} will be used.
122      */
123     public HttpService(
124             final HttpProcessor processor,
125             final HttpRequestMapper<HttpRequestHandler> handlerMapper,
126             final ConnectionReuseStrategy connReuseStrategy,
127             final HttpResponseFactory<ClassicHttpResponse> responseFactory) {
128         this(processor, handlerMapper, connReuseStrategy, responseFactory, null);
129     }
130 
131     /**
132      * Create a new HTTP service.
133      *
134      * @param processor the processor to use on requests and responses
135      * @param requestHandler  the request handler.
136      * @param connReuseStrategy the connection reuse strategy. If {@code null}
137      *   {@link DefaultConnectionReuseStrategy#INSTANCE} will be used.
138      * @param streamListener message stream listener.
139      */
140     public HttpService(
141             final HttpProcessor processor,
142             final HttpServerRequestHandler requestHandler,
143             final ConnectionReuseStrategy connReuseStrategy,
144             final Http1StreamListener streamListener) {
145         super();
146         this.processor =  Args.notNull(processor, "HTTP processor");
147         this.requestHandler =  Args.notNull(requestHandler, "Request handler");
148         this.connReuseStrategy = connReuseStrategy != null ? connReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE;
149         this.streamListener = streamListener;
150     }
151 
152     /**
153      * Create a new HTTP service.
154      *
155      * @param processor the processor to use on requests and responses
156      * @param requestHandler  the request handler.
157      */
158     public HttpService(
159             final HttpProcessor processor, final HttpServerRequestHandler requestHandler) {
160         this(processor, requestHandler, null, null);
161     }
162 
163     /**
164      * Handles receives one HTTP request over the given connection within the
165      * given execution context and sends a response back to the client.
166      *
167      * @param conn the active connection to the client
168      * @param context the actual execution context.
169      * @throws IOException in case of an I/O error.
170      * @throws HttpException in case of HTTP protocol violation or a processing
171      *   problem.
172      */
173     public void handleRequest(
174             final HttpServerConnection conn,
175             final HttpContext context) throws IOException, HttpException {
176 
177         final AtomicBoolean responseSubmitted = new AtomicBoolean(false);
178         try {
179             final ClassicHttpRequest request = conn.receiveRequestHeader();
180             if (streamListener != null) {
181                 streamListener.onRequestHead(conn, request);
182             }
183             conn.receiveRequestEntity(request);
184             final ProtocolVersion transportVersion = request.getVersion();
185             if (transportVersion != null) {
186                 context.setProtocolVersion(transportVersion);
187             }
188             context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession());
189             context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails());
190             context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
191             this.processor.process(request, request.getEntity(), context);
192 
193             this.requestHandler.handle(request, new HttpServerRequestHandler.ResponseTrigger() {
194 
195                 @Override
196                 public void sendInformation(final ClassicHttpResponse response) throws HttpException, IOException {
197                     if (responseSubmitted.get()) {
198                         throw new HttpException("Response already submitted");
199                     }
200                     if (response.getCode() >= HttpStatus.SC_SUCCESS) {
201                         throw new HttpException("Invalid intermediate response");
202                     }
203                     if (streamListener != null) {
204                         streamListener.onResponseHead(conn, response);
205                     }
206                     conn.sendResponseHeader(response);
207                     conn.flush();
208                 }
209 
210                 @Override
211                 public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException {
212                     try {
213                         context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
214                         processor.process(response, response.getEntity(), context);
215 
216                         responseSubmitted.set(true);
217                         conn.sendResponseHeader(response);
218                         if (streamListener != null) {
219                             streamListener.onResponseHead(conn, response);
220                         }
221                         if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) {
222                             conn.sendResponseEntity(response);
223                         }
224                         // Make sure the request content is fully consumed
225                         EntityUtils.consume(request.getEntity());
226                         final boolean keepAlive = connReuseStrategy.keepAlive(request, response, context);
227                         if (streamListener != null) {
228                             streamListener.onExchangeComplete(conn, keepAlive);
229                         }
230                         if (!keepAlive) {
231                             conn.close();
232                         }
233                         conn.flush();
234                     } finally {
235                         response.close();
236                     }
237                 }
238 
239             }, context);
240 
241         } catch (final HttpException ex) {
242             if (responseSubmitted.get()) {
243                 throw ex;
244             } else {
245                 try (final ClassicHttpResponse errorResponse = new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR)) {
246                     handleException(ex, errorResponse);
247                     errorResponse.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
248                     context.setAttribute(HttpCoreContext.HTTP_RESPONSE, errorResponse);
249                     this.processor.process(errorResponse, errorResponse.getEntity(), context);
250 
251                     conn.sendResponseHeader(errorResponse);
252                     if (streamListener != null) {
253                         streamListener.onResponseHead(conn, errorResponse);
254                     }
255                     conn.sendResponseEntity(errorResponse);
256                     conn.close();
257                 }
258             }
259         }
260     }
261 
262     /**
263      * Handles the given exception and generates an HTTP response to be sent
264      * back to the client to inform about the exceptional condition encountered
265      * in the course of the request processing.
266      *
267      * @param ex the exception.
268      * @param response the HTTP response.
269      */
270     protected void handleException(final HttpException ex, final ClassicHttpResponse response) {
271         response.setCode(toStatusCode(ex, response));
272         String message = ex.getMessage();
273         if (message == null) {
274             message = ex.toString();
275         }
276         response.setEntity(new StringEntity(message, ContentType.TEXT_PLAIN));
277     }
278 
279     protected int toStatusCode(final Exception ex, final ClassicHttpResponse response) {
280         final int code;
281         if (ex instanceof MethodNotSupportedException) {
282             code = HttpStatus.SC_NOT_IMPLEMENTED;
283         } else if (ex instanceof UnsupportedHttpVersionException) {
284             code = HttpStatus.SC_HTTP_VERSION_NOT_SUPPORTED;
285         } else if (ex instanceof NotImplementedException) {
286             code = HttpStatus.SC_NOT_IMPLEMENTED;
287         } else if (ex instanceof ProtocolException) {
288             code = HttpStatus.SC_BAD_REQUEST;
289         } else {
290             code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
291         }
292         return code;
293     }
294 
295 }