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 (streamListener != null) {
180                 streamListener.onRequestHead(conn, request);
181             }
182             conn.receiveRequestEntity(request);
183             final ProtocolVersion transportVersion = request.getVersion();
184             context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1);
185             context.setAttribute(HttpCoreContext.SSL_SESSION, conn.getSSLSession());
186             context.setAttribute(HttpCoreContext.CONNECTION_ENDPOINT, conn.getEndpointDetails());
187             context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
188             this.processor.process(request, request.getEntity(), context);
189 
190             this.requestHandler.handle(request, new HttpServerRequestHandler.ResponseTrigger() {
191 
192                 @Override
193                 public void sendInformation(final ClassicHttpResponse response) throws HttpException, IOException {
194                     if (responseSubmitted.get()) {
195                         throw new HttpException("Response already submitted");
196                     }
197                     if (response.getCode() >= HttpStatus.SC_SUCCESS) {
198                         throw new HttpException("Invalid intermediate response");
199                     }
200                     if (streamListener != null) {
201                         streamListener.onResponseHead(conn, response);
202                     }
203                     conn.sendResponseHeader(response);
204                     conn.flush();
205                 }
206 
207                 @Override
208                 public void submitResponse(final ClassicHttpResponse response) throws HttpException, IOException {
209                     try {
210                         final ProtocolVersion transportVersion = response.getVersion();
211                         if (transportVersion != null && transportVersion.greaterEquals(HttpVersion.HTTP_2)) {
212                             throw new UnsupportedHttpVersionException(transportVersion);
213                         }
214                         ServerSupport.validateResponse(response, response.getEntity());
215                         context.setProtocolVersion(transportVersion != null ? transportVersion : HttpVersion.HTTP_1_1);
216                         context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
217                         processor.process(response, response.getEntity(), context);
218 
219                         responseSubmitted.set(true);
220                         conn.sendResponseHeader(response);
221                         if (streamListener != null) {
222                             streamListener.onResponseHead(conn, response);
223                         }
224                         if (MessageSupport.canResponseHaveBody(request.getMethod(), response)) {
225                             conn.sendResponseEntity(response);
226                         }
227                         // Make sure the request content is fully consumed
228                         EntityUtils.consume(request.getEntity());
229                         final boolean keepAlive = connReuseStrategy.keepAlive(request, response, context);
230                         if (streamListener != null) {
231                             streamListener.onExchangeComplete(conn, keepAlive);
232                         }
233                         if (!keepAlive) {
234                             conn.close();
235                         }
236                         conn.flush();
237                     } finally {
238                         response.close();
239                     }
240                 }
241 
242             }, context);
243 
244         } catch (final HttpException ex) {
245             if (responseSubmitted.get()) {
246                 throw ex;
247             }
248             try (final ClassicHttpResponse errorResponse = new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR)) {
249                 handleException(ex, errorResponse);
250                 errorResponse.setHeader(HttpHeaders.CONNECTION, HeaderElements.CLOSE);
251                 context.setAttribute(HttpCoreContext.HTTP_RESPONSE, errorResponse);
252                 this.processor.process(errorResponse, errorResponse.getEntity(), context);
253 
254                 conn.sendResponseHeader(errorResponse);
255                 if (streamListener != null) {
256                     streamListener.onResponseHead(conn, errorResponse);
257                 }
258                 conn.sendResponseEntity(errorResponse);
259                 conn.close();
260             }
261         }
262     }
263 
264     /**
265      * Handles the given exception and generates an HTTP response to be sent
266      * back to the client to inform about the exceptional condition encountered
267      * in the course of the request processing.
268      *
269      * @param ex the exception.
270      * @param response the HTTP response.
271      */
272     protected void handleException(final HttpException ex, final ClassicHttpResponse response) {
273         response.setCode(toStatusCode(ex));
274         response.setEntity(new StringEntity(ServerSupport.toErrorMessage(ex), ContentType.TEXT_PLAIN));
275     }
276 
277     protected int toStatusCode(final Exception ex) {
278         return ServerSupport.toStatusCode(ex);
279     }
280 
281 }