View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   */
16  package org.forgerock.http.grizzly;
17  
18  import static org.forgerock.http.handler.Handlers.asDescribableHandler;
19  import static org.forgerock.http.handler.Handlers.chainOf;
20  import static org.forgerock.http.handler.Handlers.internalServerErrorHandler;
21  import static org.forgerock.http.io.IO.newBranchingInputStream;
22  import static org.forgerock.http.io.IO.newTemporaryStorage;
23  import static org.forgerock.http.protocol.Responses.newInternalServerError;
24  import static org.forgerock.http.routing.UriRouterContext.uriRouterContext;
25  import static org.forgerock.util.Utils.closeSilently;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.net.URISyntaxException;
30  import java.security.cert.X509Certificate;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  
34  import org.forgerock.http.ApiProducer;
35  import org.forgerock.http.DescribedHttpApplication;
36  import org.forgerock.http.Handler;
37  import org.forgerock.http.HttpApplication;
38  import org.forgerock.http.HttpApplicationException;
39  import org.forgerock.http.filter.TransactionIdInboundFilter;
40  import org.forgerock.http.handler.DescribableHandler;
41  import org.forgerock.http.io.Buffer;
42  import org.forgerock.http.io.IO;
43  import org.forgerock.http.routing.UriRouterContext;
44  import org.forgerock.http.session.SessionContext;
45  import org.forgerock.http.util.CaseInsensitiveSet;
46  import org.forgerock.http.util.Uris;
47  import org.forgerock.services.context.AttributesContext;
48  import org.forgerock.services.context.ClientContext;
49  import org.forgerock.services.context.Context;
50  import org.forgerock.services.context.RequestAuditContext;
51  import org.forgerock.services.context.RootContext;
52  import org.forgerock.util.Factory;
53  import org.forgerock.util.promise.ResultHandler;
54  import org.forgerock.util.promise.RuntimeExceptionHandler;
55  import org.glassfish.grizzly.http.server.HttpHandler;
56  import org.glassfish.grizzly.http.server.Request;
57  import org.glassfish.grizzly.http.server.Response;
58  import org.glassfish.grizzly.http.server.util.Globals;
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  import io.swagger.models.Swagger;
63  
64  /**
65   * A Grizzly implementation which provides integration between the Grizzly API and the common HTTP Framework.
66   *
67   * @see HttpApplication
68   * @see Handler
69   */
70  final class HandlerAdapter extends HttpHandler {
71  
72      /** Methods that should not include an entity body. */
73      private static final CaseInsensitiveSet NON_ENTITY_METHODS = new CaseInsensitiveSet(
74              Arrays.asList("GET", "HEAD", "TRACE"));
75  
76      private static final Logger LOGGER = LoggerFactory.getLogger(HandlerAdapter.class);
77  
78      private final HttpApplication httpApplication;
79      private final Factory<Buffer> storage;
80      private DescribableHandler describedHandler;
81  
82      HandlerAdapter(HttpApplication httpApplication) {
83          this.httpApplication = httpApplication;
84          final Factory<Buffer> applicationStorage = httpApplication.getBufferFactory();
85          this.storage = applicationStorage != null
86                  ? applicationStorage
87                  : newTemporaryStorage(new File(System.getProperty("java.io.tmpdir")));
88      }
89  
90      @Override
91      @SuppressWarnings("unchecked")
92      public void start() {
93          super.start();
94          try {
95              describedHandler = chainOf(httpApplication.start(), new TransactionIdInboundFilter());
96              if (httpApplication instanceof DescribedHttpApplication) {
97                  ApiProducer<Swagger> apiProducer = ((DescribedHttpApplication) httpApplication).getApiProducer();
98                  describedHandler.api(apiProducer);
99              }
100         } catch (HttpApplicationException e) {
101             LOGGER.error("Error while starting the application.", e);
102             describedHandler = asDescribableHandler(internalServerErrorHandler(e));
103         }
104     }
105 
106     @Override
107     public void destroy() {
108         httpApplication.stop();
109         describedHandler = null;
110         super.destroy();
111     }
112 
113     @Override
114     public void service(final Request request, final Response response) throws Exception {
115         final org.forgerock.http.protocol.Request chfRequest = toChfRequest(request);
116         final RootContext rootContext = new RootContext();
117         final SessionContext sessionContext = new SessionContext(rootContext, new SessionAdapter(request.getSession()));
118         final UriRouterContext uriRouterContext = createRouterContext(sessionContext, request, chfRequest);
119         final AttributesContext attributesContext = new AttributesContext(new RequestAuditContext(uriRouterContext));
120         final ClientContext context = createClientContext(attributesContext, request);
121 
122         response.suspend();
123         describedHandler.handle(context, chfRequest)
124                 .thenOnResult(new ResultHandler<org.forgerock.http.protocol.Response>() {
125                     @Override
126                     public void handleResult(org.forgerock.http.protocol.Response chfResponse) {
127                         writeResponse(chfResponse, response, sessionContext);
128                     }
129                 })
130                 .thenOnRuntimeException(new RuntimeExceptionHandler() {
131                     @Override
132                     public void handleRuntimeException(RuntimeException e) {
133                         LOGGER.error("RuntimeException caught", e);
134                         writeResponse(
135                                 newInternalServerError(e),
136                                 response, sessionContext);
137                     }
138                 })
139                 .thenAlways(new Runnable() {
140                     @Override
141                     public void run() {
142                         response.resume();
143                     }
144                 });
145     }
146 
147     private void writeResponse(final org.forgerock.http.protocol.Response chfResponse, final Response grizzlyResponse,
148             final SessionContext sessionContext) {
149         try {
150             grizzlyResponse.setStatus(chfResponse.getStatus().getCode());
151             sessionContext.getSession().save(chfResponse);
152 
153             // response headers
154             for (String name : chfResponse.getHeaders().keySet()) {
155                 for (String value : chfResponse.getHeaders().get(name).getValues()) {
156                     if (value != null && !value.isEmpty()) {
157                         grizzlyResponse.addHeader(name, value);
158                     }
159                 }
160             }
161             IO.stream(chfResponse.getEntity().getRawContentInputStream(), grizzlyResponse.getOutputStream());
162         } catch (IOException e) {
163             LOGGER.trace("Failed to write response", e);
164         } finally {
165             closeSilently(chfResponse);
166         }
167     }
168 
169     private org.forgerock.http.protocol.Request toChfRequest(Request req) throws URISyntaxException {
170         // populate request
171         org.forgerock.http.protocol.Request request = new org.forgerock.http.protocol.Request();
172         request.setMethod(req.getMethod().toString());
173 
174         /*
175          * CHF-81: containers are generally quite tolerant of invalid query strings, so we'll try to be as well by
176          * decoding the query string and re-encoding it correctly before constructing the URI.
177          */
178         request.setUri(Uris.createNonStrict(req.getScheme(), null, req.getServerName(), req.getServerPort(),
179                 req.getRequestURI(), req.getQueryString(), null));
180 
181         // request headers
182         for (String e : req.getHeaderNames()) {
183             final ArrayList<String> values = new ArrayList<>(1);
184             for (String value : req.getHeaders(e)) {
185                 values.add(value);
186             }
187             request.getHeaders().add(e, values);
188         }
189 
190         // include request entity if appears to be provided with request
191         if ((req.getContentLength() > 0 || req.getHeader("Transfer-Encoding") != null)
192                 && !NON_ENTITY_METHODS.contains(request.getMethod())) {
193             request.setEntity(newBranchingInputStream(req.getInputStream(), storage));
194         }
195 
196         return request;
197     }
198 
199     private UriRouterContext createRouterContext(Context parent, Request req,
200             org.forgerock.http.protocol.Request request) {
201         return uriRouterContext(parent).matchedUri("").remainingUri(req.getRequestURI())
202                 .originalUri(request.getUri().asURI()).build();
203     }
204 
205     private ClientContext createClientContext(Context parent, Request req) {
206         return ClientContext.buildExternalClientContext(parent)
207                             .remoteUser(req.getRemoteUser())
208                             .remoteAddress(req.getRemoteAddr())
209                             .remotePort(req.getRemotePort())
210                             .secure("https".equalsIgnoreCase(req.getScheme()))
211                             .certificates((X509Certificate[]) req.getAttribute(Globals.CERTIFICATES_ATTR))
212                             .userAgent(req.getHeader("User-Agent"))
213                             .localAddress(req.getLocalAddr())
214                             .localPort(req.getLocalPort())
215                             .build();
216     }
217 
218 }