View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  
20  package org.eclipse.jetty.server;
21  
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import org.eclipse.jetty.http.BadMessageException;
28  import org.eclipse.jetty.http.HostPortHttpField;
29  import org.eclipse.jetty.http.HttpCompliance;
30  import org.eclipse.jetty.http.HttpField;
31  import org.eclipse.jetty.http.HttpFields;
32  import org.eclipse.jetty.http.HttpGenerator;
33  import org.eclipse.jetty.http.HttpHeader;
34  import org.eclipse.jetty.http.HttpHeaderValue;
35  import org.eclipse.jetty.http.HttpMethod;
36  import org.eclipse.jetty.http.HttpParser;
37  import org.eclipse.jetty.http.HttpStatus;
38  import org.eclipse.jetty.http.HttpURI;
39  import org.eclipse.jetty.http.HttpVersion;
40  import org.eclipse.jetty.http.MetaData;
41  import org.eclipse.jetty.io.Connection;
42  import org.eclipse.jetty.io.EndPoint;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  
46  /**
47   * A HttpChannel customized to be transported over the HTTP/1 protocol
48   */
49  public class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler, HttpParser.ComplianceHandler
50  {
51      private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class);
52      private final static HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c");
53      private static final String ATTR_COMPLIANCE_VIOLATIONS = "org.eclipse.jetty.http.compliance.violations";
54  
55      private final HttpFields _fields = new HttpFields();
56      private final MetaData.Request _metadata = new MetaData.Request(_fields);
57      private final HttpConnection _httpConnection;
58      private HttpField _connection;
59      private HttpField _upgrade = null;
60      private boolean _delayedForContent;
61      private boolean _unknownExpectation = false;
62      private boolean _expect100Continue = false;
63      private boolean _expect102Processing = false;
64      private List<String> _complianceViolations;
65  
66      public HttpChannelOverHttp(HttpConnection httpConnection, Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport)
67      {
68          super(connector, config, endPoint, transport);
69          _httpConnection = httpConnection;
70          _metadata.setURI(new HttpURI());
71      }
72  
73      @Override
74      protected HttpInput newHttpInput(HttpChannelState state)
75      {
76          return new HttpInputOverHTTP(state);
77      }
78  
79      @Override
80      public void recycle()
81      {
82          super.recycle();
83          _unknownExpectation = false;
84          _expect100Continue = false;
85          _expect102Processing = false;
86          _metadata.recycle();
87          _connection = null;
88          _fields.clear();
89          _upgrade = null;
90      }
91  
92      @Override
93      public boolean isExpecting100Continue()
94      {
95          return _expect100Continue;
96      }
97  
98      @Override
99      public boolean isExpecting102Processing()
100     {
101         return _expect102Processing;
102     }
103 
104     @Override
105     public boolean startRequest(String method, String uri, HttpVersion version)
106     {
107         _metadata.setMethod(method);
108         _metadata.getURI().parseRequestTarget(method, uri);
109         _metadata.setHttpVersion(version);
110         _unknownExpectation = false;
111         _expect100Continue = false;
112         _expect102Processing = false;
113         return false;
114     }
115 
116     @Override
117     public void parsedHeader(HttpField field)
118     {
119         HttpHeader header = field.getHeader();
120         String value = field.getValue();
121         if (header != null)
122         {
123             switch (header)
124             {
125                 case CONNECTION:
126                     _connection = field;
127                     break;
128 
129                 case HOST:
130                     if (!_metadata.getURI().isAbsolute() && field instanceof HostPortHttpField)
131                     {
132                         HostPortHttpField hp = (HostPortHttpField)field;
133                         _metadata.getURI().setAuthority(hp.getHost(), hp.getPort());
134                     }
135                     break;
136 
137                 case EXPECT:
138                 {
139                     if (_metadata.getVersion() == HttpVersion.HTTP_1_1)
140                     {
141                         HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
142                         switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
143                         {
144                             case CONTINUE:
145                                 _expect100Continue = true;
146                                 break;
147 
148                             case PROCESSING:
149                                 _expect102Processing = true;
150                                 break;
151 
152                             default:
153                                 String[] values = field.getValues();
154                                 for (int i = 0; values != null && i < values.length; i++)
155                                 {
156                                     expect = HttpHeaderValue.CACHE.get(values[i].trim());
157                                     if (expect == null)
158                                         _unknownExpectation = true;
159                                     else
160                                     {
161                                         switch (expect)
162                                         {
163                                             case CONTINUE:
164                                                 _expect100Continue = true;
165                                                 break;
166                                             case PROCESSING:
167                                                 _expect102Processing = true;
168                                                 break;
169                                             default:
170                                                 _unknownExpectation = true;
171                                         }
172                                     }
173                                 }
174                         }
175                     }
176                     break;
177                 }
178 
179                 case UPGRADE:
180                     _upgrade = field;
181                     break;
182 
183                 default:
184                     break;
185             }
186         }
187         _fields.add(field);
188     }
189 
190     /**
191      * If the associated response has the Expect header set to 100 Continue,
192      * then accessing the input stream indicates that the handler/servlet
193      * is ready for the request body and thus a 100 Continue response is sent.
194      *
195      * @throws IOException if the InputStream cannot be created
196      */
197     @Override
198     public void continue100(int available) throws IOException
199     {
200         // If the client is expecting 100 CONTINUE, then send it now.
201         // TODO: consider using an AtomicBoolean ?
202         if (isExpecting100Continue())
203         {
204             _expect100Continue = false;
205 
206             // is content missing?
207             if (available == 0)
208             {
209                 if (getResponse().isCommitted())
210                     throw new IOException("Committed before 100 Continues");
211 
212                 boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
213                 if (!committed)
214                     throw new IOException("Concurrent commit while trying to send 100-Continue");
215             }
216         }
217     }
218 
219     @Override
220     public void earlyEOF()
221     {
222         // If we have no request yet, just close
223         if (_metadata.getMethod() == null)
224             _httpConnection.close();
225         else
226             onEarlyEOF();
227     }
228 
229     @Override
230     public boolean content(ByteBuffer content)
231     {
232         HttpInput.Content c = _httpConnection.newContent(content);
233         boolean handle = onContent(c) || _delayedForContent;
234         _delayedForContent = false;
235         return handle;
236     }
237 
238     public void asyncReadFillInterested()
239     {
240         _httpConnection.asyncReadFillInterested();
241     }
242 
243     @Override
244     public void badMessage(int status, String reason)
245     {
246         _httpConnection.getGenerator().setPersistent(false);
247         try
248         {
249             // Need to call onRequest, so RequestLog can reports as much as possible
250             onRequest(_metadata);
251             getRequest().getHttpInput().earlyEOF();
252         }
253         catch (Exception e)
254         {
255             LOG.ignore(e);
256         }
257 
258         onBadMessage(status, reason);
259     }
260 
261     @Override
262     public boolean headerComplete()
263     {
264         if (_complianceViolations != null)
265             this.getRequest().setAttribute(ATTR_COMPLIANCE_VIOLATIONS, _complianceViolations);
266 
267         boolean persistent;
268 
269         switch (_metadata.getVersion())
270         {
271             case HTTP_0_9:
272             {
273                 persistent = false;
274                 break;
275             }
276             case HTTP_1_0:
277             {
278                 if (getHttpConfiguration().isPersistentConnectionsEnabled())
279                 {
280                     if (_connection != null)
281                     {
282                         if (_connection.contains(HttpHeaderValue.KEEP_ALIVE.asString()))
283                             persistent = true;
284                         else
285                             persistent = _fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString());
286                     }
287                     else
288                         persistent = false;
289                 }
290                 else
291                     persistent = false;
292 
293                 if (!persistent)
294                     persistent = HttpMethod.CONNECT.is(_metadata.getMethod());
295                 if (persistent)
296                     getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE);
297 
298                 break;
299             }
300 
301             case HTTP_1_1:
302             {
303                 if (_unknownExpectation)
304                 {
305                     badMessage(HttpStatus.EXPECTATION_FAILED_417, null);
306                     return false;
307                 }
308 
309                 if (getHttpConfiguration().isPersistentConnectionsEnabled())
310                 {
311                     if (_connection != null)
312                     {
313                         if (_connection.contains(HttpHeaderValue.CLOSE.asString()))
314                             persistent = false;
315                         else
316                             persistent = !_fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); // handle multiple connection fields
317                     }
318                     else
319                         persistent = true;
320                 }
321                 else
322                     persistent = false;
323 
324                 if (!persistent)
325                     persistent = HttpMethod.CONNECT.is(_metadata.getMethod());
326                 if (!persistent)
327                     getResponse().getHttpFields().add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE);
328 
329                 if (_upgrade != null && upgrade())
330                     return true;
331 
332                 break;
333             }
334 
335             case HTTP_2:
336             {
337                 // Allow direct "upgrade" to HTTP_2_0 only if the connector supports h2c.
338                 _upgrade = PREAMBLE_UPGRADE_H2C;
339 
340                 if (HttpMethod.PRI.is(_metadata.getMethod()) &&
341                         "*".equals(_metadata.getURI().toString()) &&
342                         _fields.size() == 0 &&
343                         upgrade())
344                     return true;
345 
346                 badMessage(HttpStatus.UPGRADE_REQUIRED_426, null);
347                 _httpConnection.getParser().close();
348                 return false;
349             }
350 
351             default:
352             {
353                 throw new IllegalStateException("unsupported version " + _metadata.getVersion());
354             }
355         }
356 
357         if (!persistent)
358             _httpConnection.getGenerator().setPersistent(false);
359 
360         onRequest(_metadata);
361 
362         // Should we delay dispatch until we have some content?
363         // We should not delay if there is no content expect or client is expecting 100 or the response is already committed or the request buffer already has something in it to parse
364         _delayedForContent = (getHttpConfiguration().isDelayDispatchUntilContent()
365                 && (_httpConnection.getParser().getContentLength() > 0 || _httpConnection.getParser().isChunking())
366                 && !isExpecting100Continue()
367                 && !isCommitted()
368                 && _httpConnection.isRequestBufferEmpty());
369 
370         return !_delayedForContent;
371     }
372 
373 
374     /**
375      * <p>Attempts to perform a HTTP/1.1 upgrade.</p>
376      * <p>The upgrade looks up a {@link ConnectionFactory.Upgrading} from the connector
377      * matching the protocol specified in the {@code Upgrade} header.</p>
378      * <p>The upgrade may succeed, be ignored (which can allow a later handler to implement)
379      * or fail with a {@link BadMessageException}.</p>
380      *
381      * @return true if the upgrade was performed, false if it was ignored
382      * @throws BadMessageException if the upgrade failed
383      */
384     private boolean upgrade() throws BadMessageException
385     {
386         if (LOG.isDebugEnabled())
387             LOG.debug("upgrade {} {}", this, _upgrade);
388 
389         if (_upgrade != PREAMBLE_UPGRADE_H2C && (_connection == null || !_connection.contains("upgrade")))
390             throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
391 
392         // Find the upgrade factory
393         ConnectionFactory.Upgrading factory = null;
394         for (ConnectionFactory f : getConnector().getConnectionFactories())
395         {
396             if (f instanceof ConnectionFactory.Upgrading)
397             {
398                 if (f.getProtocols().contains(_upgrade.getValue()))
399                 {
400                     factory = (ConnectionFactory.Upgrading)f;
401                     break;
402                 }
403             }
404         }
405 
406         if (factory == null)
407         {
408             if (LOG.isDebugEnabled())
409                 LOG.debug("No factory for {} in {}", _upgrade, getConnector());
410             return false;
411         }
412 
413         // Create new connection
414         HttpFields response101 = new HttpFields();
415         Connection upgrade_connection = factory.upgradeConnection(getConnector(), getEndPoint(), _metadata, response101);
416         if (upgrade_connection == null)
417         {
418             if (LOG.isDebugEnabled())
419                 LOG.debug("Upgrade ignored for {} by {}", _upgrade, factory);
420             return false;
421         }
422 
423         // Send 101 if needed
424         try
425         {
426             if (_upgrade != PREAMBLE_UPGRADE_H2C)
427                 sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1, HttpStatus.SWITCHING_PROTOCOLS_101, response101, 0), null, true);
428         }
429         catch (IOException e)
430         {
431             throw new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, null, e);
432         }
433 
434         if (LOG.isDebugEnabled())
435             LOG.debug("Upgrade from {} to {}", getEndPoint().getConnection(), upgrade_connection);
436         getRequest().setAttribute(HttpConnection.UPGRADE_CONNECTION_ATTRIBUTE, upgrade_connection);
437         getResponse().setStatus(101);
438         getHttpTransport().onCompleted();
439         return true;
440     }
441 
442     @Override
443     protected void handleException(Throwable x)
444     {
445         _httpConnection.getGenerator().setPersistent(false);
446         super.handleException(x);
447     }
448 
449     @Override
450     public void abort(Throwable failure)
451     {
452         super.abort(failure);
453         _httpConnection.getGenerator().setPersistent(false);
454     }
455 
456     @Override
457     public boolean messageComplete()
458     {
459         boolean handle = onRequestComplete() || _delayedForContent;
460         _delayedForContent = false;
461         return handle;
462     }
463 
464     @Override
465     public int getHeaderCacheSize()
466     {
467         return getHttpConfiguration().getHeaderCacheSize();
468     }
469 
470     @Override
471     public void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, String reason)
472     {
473         if (_httpConnection.isRecordHttpComplianceViolations())
474         {
475             if (_complianceViolations == null)
476             {
477                 _complianceViolations = new ArrayList<>();
478             }
479             String violation = String.format("%s<%s: %s for %s", compliance, required, reason, getHttpTransport());
480             _complianceViolations.add(violation);
481             if (LOG.isDebugEnabled())
482                 LOG.debug(violation);
483         }
484     }
485 }