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