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.HttpVersion;
46  import org.apache.hc.core5.http.ProtocolVersion;
47  import org.apache.hc.core5.http.UnsupportedHttpVersionException;
48  import org.apache.hc.core5.http.config.Http1Config;
49  import org.apache.hc.core5.http.impl.DefaultConnectionReuseStrategy;
50  import org.apache.hc.core5.http.impl.Http1StreamListener;
51  import org.apache.hc.core5.http.impl.ServerSupport;
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 Http1Config http1Config;
87      private final HttpServerRequestHandler requestHandler;
88      private final ConnectionReuseStrategy connReuseStrategy;
89      private final Http1StreamListener streamListener;
90  
91      /**
92       * Create a new HTTP service.
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                 Http1Config.DEFAULT,
110                 connReuseStrategy,
111                 streamListener);
112     }
113 
114     /**
115      * Create a new HTTP service.
116      *
117      * @param processor the processor to use on requests and responses
118      * @param handlerMapper  the handler mapper
119      * @param connReuseStrategy the connection reuse strategy. If {@code null}
120      *   {@link DefaultConnectionReuseStrategy#INSTANCE} will be used.
121      * @param responseFactory  the response factory. If {@code null}
122      *   {@link DefaultClassicHttpResponseFactory#INSTANCE} will be used.
123      */
124     public HttpService(
125             final HttpProcessor processor,
126             final HttpRequestMapper<HttpRequestHandler> handlerMapper,
127             final ConnectionReuseStrategy connReuseStrategy,
128             final HttpResponseFactory<ClassicHttpResponse> responseFactory) {
129         this(processor, handlerMapper, connReuseStrategy, responseFactory, null);
130     }
131 
132     /**
133      * Create a new HTTP service.
134      *
135      * @param processor the processor to use on requests and responses
136      * @param requestHandler  the request handler.
137      * @param connReuseStrategy the connection reuse strategy. If {@code null}
138      *   {@link DefaultConnectionReuseStrategy#INSTANCE} will be used.
139      * @param streamListener message stream listener.
140      */
141     public HttpService(
142             final HttpProcessor processor,
143             final HttpServerRequestHandler requestHandler,
144             final ConnectionReuseStrategy connReuseStrategy,
145             final Http1StreamListener streamListener) {
146         this(processor, requestHandler, Http1Config.DEFAULT, connReuseStrategy, streamListener);
147     }
148 
149     /**
150      * Create a new HTTP service.
151      *
152      * @param processor the processor to use on requests and responses
153      * @param requestHandler  the request handler.
154      * @param http1Config HTTP/1 protocol configuration.
155      * @param connReuseStrategy the connection reuse strategy. If {@code null}
156      *   {@link DefaultConnectionReuseStrategy#INSTANCE} will be used.
157      * @param streamListener message stream listener.
158      *
159      * @since 5.3
160      */
161     public HttpService(
162             final HttpProcessor processor,
163             final HttpServerRequestHandler requestHandler,
164             final Http1Config http1Config,
165             final ConnectionReuseStrategy connReuseStrategy,
166             final Http1StreamListener streamListener) {
167         super();
168         this.processor =  Args.notNull(processor, "HTTP processor");
169         this.requestHandler =  Args.notNull(requestHandler, "Request handler");
170         this.http1Config = http1Config != null ? http1Config : Http1Config.DEFAULT;
171         this.connReuseStrategy = connReuseStrategy != null ? connReuseStrategy : DefaultConnectionReuseStrategy.INSTANCE;
172         this.streamListener = streamListener;
173     }
174 
175     /**
176      * Create a new HTTP service.
177      *
178      * @param processor the processor to use on requests and responses
179      * @param requestHandler  the request handler.
180      */
181     public HttpService(
182             final HttpProcessor processor, final HttpServerRequestHandler requestHandler) {
183         this(processor, requestHandler, Http1Config.DEFAULT, null, null);
184     }
185 
186     /**
187      * Handles receives one HTTP request over the given connection within the
188      * given execution context and sends a response back to the client.
189      *
190      * @param conn the active connection to the client
191      * @param context the actual execution context.
192      * @throws IOException in case of an I/O error.
193      * @throws HttpException in case of HTTP protocol violation or a processing
194      *   problem.
195      */
196     public void handleRequest(
197             final HttpServerConnection conn,
198             final HttpContext context) throws IOException, HttpException {
199 
200         final AtomicBoolean responseSubmitted = new AtomicBoolean(false);
201         try {
202             final ClassicHttpRequest request = conn.receiveRequestHeader();
203             if (request == null) {
204                 conn.close();
205                 return;
206             }
207             if (streamListener != null) {
208                 streamListener.onRequestHead(conn, request);
209             }
210             conn.receiveRequestEntity(request);
211             final ProtocolVersion transportVersion = request.getVersion();
212             if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
213                 throw new UnsupportedHttpVersionException(transportVersion);
214             }
215             context.setProtocolVersion(transportVersion != null ? transportVersion : http1Config.getVersion());
216             context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession());
217             context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails());
218             context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
219             this.processor.process(request, request.getEntity(), context);
220 
221             this.requestHandler.handle(request, new HttpServerRequestHandler.ResponseTrigger() {
222 
223                 @Override
224                 public void sendInformation(final ClassicHttpResponse response) throws HttpException, IOException {
225                     if (responseSubmitted.get()) {
226                         throw new HttpException("Response already submitted");
227                     }
228                     if (response.getCode() >= HttpStatus.SC_SUCCESS) {
229                         throw new HttpException("Invalid intermediate response");
230                     }
231                     if (streamListener != null) {
232                         streamListener.onResponseHead(conn, response);
233                     }
234                     conn.sendResponseHeader(response);
235                     conn.flush();
236                 }
237 
238                 @Override
239                 public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException {
240                     try {
241                         final ProtocolVersion transportVersion = response.getVersion();
242                         if (transportVersion != null) {
243                             if (!transportVersion.lessEquals(http1Config.getVersion())) {
244                                 throw new UnsupportedHttpVersionException(transportVersion);
245                             }
246                             context.setProtocolVersion(transportVersion);
247                         }
248                         context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
249                         processor.process(response, response.getEntity(), context);
250 
251                         responseSubmitted.set(true);
252                         conn.sendResponseHeader(response);
253                         if (streamListener != null) {
254                             streamListener.onResponseHead(conn, response);
255                         }
256                         if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) {
257                             conn.sendResponseEntity(response);
258                         }
259                         // Make sure the request content is fully consumed
260                         EntityUtils.consume(request.getEntity());
261                         final boolean keepAlive = connReuseStrategy.keepAlive(request, response, context);
262                         if (streamListener != null) {
263                             streamListener.onExchangeComplete(conn, keepAlive);
264                         }
265                         if (!keepAlive) {
266                             conn.close();
267                         }
268                         conn.flush();
269                     } finally {
270                         response.close();
271                     }
272                 }
273 
274             }, context);
275 
276         } catch (final HttpException ex) {
277             if (responseSubmitted.get()) {
278                 throw ex;
279             }
280             try (final ClassicHttpResponse errorResponse = new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR)) {
281                 handleException(ex, errorResponse);
282                 errorResponse.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
283                 context.setAttribute(HttpCoreContext.HTTP_RESPONSE, errorResponse);
284                 this.processor.process(errorResponse, errorResponse.getEntity(), context);
285 
286                 conn.sendResponseHeader(errorResponse);
287                 if (streamListener != null) {
288                     streamListener.onResponseHead(conn, errorResponse);
289                 }
290                 conn.sendResponseEntity(errorResponse);
291                 conn.close();
292             }
293         }
294     }
295 
296     /**
297      * Handles the given exception and generates an HTTP response to be sent
298      * back to the client to inform about the exceptional condition encountered
299      * in the course of the request processing.
300      *
301      * @param ex the exception.
302      * @param response the HTTP response.
303      */
304     protected void handleException(final HttpException ex, final ClassicHttpResponse response) {
305         response.setCode(toStatusCode(ex));
306         response.setEntity(new StringEntity(ServerSupport.toErrorMessage(ex), ContentType.TEXT_PLAIN));
307     }
308 
309     protected int toStatusCode(final Exception ex) {
310         return ServerSupport.toStatusCode(ex);
311     }
312 
313 
314     /**
315      * Create a new {@link Builder}.
316      *
317      * @since 5.2
318      */
319     public static Builder builder() {
320         return new Builder();
321     }
322 
323     /**
324      * Builder for {@link HttpService}.
325      *
326      * @since 5.2
327      */
328     public static final class Builder {
329 
330         private HttpProcessor processor;
331         private HttpServerRequestHandler requestHandler;
332         private Http1Config http1Config;
333         private ConnectionReuseStrategy connReuseStrategy;
334         private Http1StreamListener streamListener;
335 
336         private Builder() {}
337 
338         public Builder withHttpProcessor(final HttpProcessor processor) {
339             this.processor = processor;
340             return this;
341         }
342 
343         public Builder withHttpServerRequestHandler(final HttpServerRequestHandler requestHandler) {
344             this.requestHandler = requestHandler;
345             return this;
346         }
347 
348         public Builder withHttp1Config(final Http1Config http1Config) {
349             this.http1Config = http1Config;
350             return this;
351         }
352 
353         public Builder withConnectionReuseStrategy(final ConnectionReuseStrategy connReuseStrategy) {
354             this.connReuseStrategy = connReuseStrategy;
355             return this;
356         }
357 
358         public Builder withHttp1StreamListener(final Http1StreamListener streamListener) {
359             this.streamListener = streamListener;
360             return this;
361         }
362 
363         /**
364          * Create a new HTTP service.
365          *
366          * @since 5.2
367          */
368         public HttpService build() {
369             return new HttpService(
370                     processor,
371                     requestHandler,
372                     http1Config,
373                     connReuseStrategy,
374                     streamListener);
375         }
376     }
377 
378 }