View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 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  package org.eclipse.jetty.server;
20  
21  import java.io.IOException;
22  import java.net.InetSocketAddress;
23  import java.nio.ByteBuffer;
24  import java.util.concurrent.atomic.AtomicBoolean;
25  import java.util.concurrent.atomic.AtomicInteger;
26  
27  import javax.servlet.DispatcherType;
28  import javax.servlet.RequestDispatcher;
29  
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.HttpGenerator.ResponseInfo;
34  import org.eclipse.jetty.http.HttpHeader;
35  import org.eclipse.jetty.http.HttpHeaderValue;
36  import org.eclipse.jetty.http.HttpMethod;
37  import org.eclipse.jetty.http.HttpParser;
38  import org.eclipse.jetty.http.HttpStatus;
39  import org.eclipse.jetty.http.HttpURI;
40  import org.eclipse.jetty.http.HttpVersion;
41  import org.eclipse.jetty.http.MimeTypes;
42  import org.eclipse.jetty.io.ByteBufferPool;
43  import org.eclipse.jetty.io.EndPoint;
44  import org.eclipse.jetty.io.EofException;
45  import org.eclipse.jetty.server.handler.ErrorHandler;
46  import org.eclipse.jetty.util.StringUtil;
47  import org.eclipse.jetty.util.URIUtil;
48  import org.eclipse.jetty.util.log.Log;
49  import org.eclipse.jetty.util.log.Logger;
50  import org.eclipse.jetty.util.thread.Scheduler;
51  
52  
53  /* ------------------------------------------------------------ */
54  /** HttpChannel.
55   * Represents a single endpoint for HTTP semantic processing.
56   * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
57   * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
58   * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
59   * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
60   * HttpParser.RequestHandler callbacks.   The completion of the active phase is signalled by a call to
61   * HttpTransport.completed().
62   *
63   */
64  public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable
65  {
66      private static final Logger LOG = Log.getLogger(HttpChannel.class);
67      private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
68  
69      public static HttpChannel<?> getCurrentHttpChannel()
70      {
71          return __currentChannel.get();
72      }
73  
74      protected static void setCurrentHttpChannel(HttpChannel<?> channel)
75      {
76          __currentChannel.set(channel);
77      }
78  
79      private final AtomicBoolean _committed = new AtomicBoolean();
80      private final AtomicInteger _requests = new AtomicInteger();
81      private final Connector _connector;
82      private final HttpConfiguration _configuration;
83      private final EndPoint _endPoint;
84      private final HttpTransport _transport;
85      private final HttpURI _uri;
86      private final HttpChannelState _state;
87      private final Request _request;
88      private final Response _response;
89      private HttpVersion _version = HttpVersion.HTTP_1_1;
90      private boolean _expect = false;
91      private boolean _expect100Continue = false;
92      private boolean _expect102Processing = false;
93  
94      public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
95      {
96          _connector = connector;
97          _configuration = configuration;
98          _endPoint = endPoint;
99          _transport = transport;
100 
101         _uri = new HttpURI(URIUtil.__CHARSET);
102         _state = new HttpChannelState(this);
103         _request = new Request(this, input);
104         _response = new Response(this, new HttpOutput(this));
105     }
106 
107     public HttpChannelState getState()
108     {
109         return _state;
110     }
111 
112     public HttpVersion getHttpVersion()
113     {
114         return _version;
115     }
116 
117     /**
118      * @return the number of requests handled by this connection
119      */
120     public int getRequests()
121     {
122         return _requests.get();
123     }
124 
125     public Connector getConnector()
126     {
127         return _connector;
128     }
129 
130     public ByteBufferPool getByteBufferPool()
131     {
132         return _connector.getByteBufferPool();
133     }
134 
135     public HttpConfiguration getHttpConfiguration()
136     {
137         return _configuration;
138     }
139 
140     public Server getServer()
141     {
142         return _connector.getServer();
143     }
144 
145     public Request getRequest()
146     {
147         return _request;
148     }
149 
150     public Response getResponse()
151     {
152         return _response;
153     }
154 
155     public EndPoint getEndPoint()
156     {
157         return _endPoint;
158     }
159 
160     public InetSocketAddress getLocalAddress()
161     {
162         return _endPoint.getLocalAddress();
163     }
164 
165     public InetSocketAddress getRemoteAddress()
166     {
167         return _endPoint.getRemoteAddress();
168     }
169 
170     /**
171      * If the associated response has the Expect header set to 100 Continue,
172      * then accessing the input stream indicates that the handler/servlet
173      * is ready for the request body and thus a 100 Continue response is sent.
174      *
175      * @throws IOException if the InputStream cannot be created
176      */
177     public void continue100(int available) throws IOException
178     {
179         // If the client is expecting 100 CONTINUE, then send it now.
180         // TODO: consider using an AtomicBoolean ?
181         if (isExpecting100Continue())
182         {
183             _expect100Continue = false;
184 
185             // is content missing?
186             if (available == 0)
187             {
188                 if (_response.isCommitted())
189                     throw new IOException("Committed before 100 Continues");
190 
191                 // TODO: break this dependency with HttpGenerator
192                 boolean committed = commitResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
193                 if (!committed)
194                     throw new IOException("Concurrent commit while trying to send 100-Continue");
195             }
196         }
197     }
198 
199     public void reset()
200     {
201         _committed.set(false);
202         _expect = false;
203         _expect100Continue = false;
204         _expect102Processing = false;
205         _request.recycle();
206         _response.recycle();
207         _uri.clear();
208     }
209 
210     @Override
211     public void run()
212     {
213         LOG.debug("{} handle enter", this);
214 
215         setCurrentHttpChannel(this);
216 
217         String threadName = null;
218         if (LOG.isDebugEnabled())
219         {
220             threadName = Thread.currentThread().getName();
221             Thread.currentThread().setName(threadName + " - " + _uri);
222         }
223 
224         try
225         {
226             // Loop here to handle async request redispatches.
227             // The loop is controlled by the call to async.unhandle in the
228             // finally block below.  Unhandle will return false only if an async dispatch has
229             // already happened when unhandle is called.
230             boolean handling = _state.handling();
231 
232             while (handling && getServer().isRunning())
233             {
234                 try
235                 {
236                     _request.setHandled(false);
237                     _response.getHttpOutput().reopen();
238 
239                     if (_state.isInitial())
240                     {
241                         _request.setTimeStamp(System.currentTimeMillis());
242                         _request.setDispatcherType(DispatcherType.REQUEST);
243                         
244                         for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers())
245                             customizer.customize(getConnector(),_configuration,_request);
246                         getServer().handle(this);
247                     }
248                     else
249                     {
250                         _request.setDispatcherType(DispatcherType.ASYNC);
251                         getServer().handleAsync(this);
252                     }
253                 }
254                 catch (Error e)
255                 {
256                     if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
257                         LOG.ignore(e);
258                     else 
259                         throw e;
260                 }
261                 catch (Exception e)
262                 {
263                     if (e instanceof EofException)
264                         LOG.debug(e);
265                     else
266                         LOG.warn(String.valueOf(_uri), e);
267                     _state.error(e);
268                     _request.setHandled(true);
269                     handleException(e);
270                 }
271                 finally
272                 {
273                     handling = !_state.unhandle();
274                 }
275             }
276         }
277         finally
278         {
279             if (threadName != null && LOG.isDebugEnabled())
280                 Thread.currentThread().setName(threadName);
281             setCurrentHttpChannel(null);
282 
283             if (_state.isCompleting())
284             {
285                 try
286                 {
287                     _state.completed();
288 
289                     if (!_response.isCommitted() && !_request.isHandled())
290                         _response.sendError(404);
291 
292                     // Complete generating the response
293                     _response.complete();
294 
295                 }
296                 catch(EofException e)
297                 {
298                     LOG.debug(e);
299                 }
300                 catch(Exception e)
301                 {
302                     LOG.warn(e);
303                 }
304                 finally
305                 {
306                     _request.setHandled(true);
307                     _transport.completed();
308                 }
309             }
310 
311             LOG.debug("{} handle exit", this);
312         }
313     }
314 
315     /**
316      * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
317      * to avoid concurrent writes from the application.</p>
318      * <p>It may happen that the application suspends, and then throws an exception, while an application
319      * spawned thread writes the response content; in such case, we attempt to commit the error directly
320      * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
321      *
322      * @param x the Throwable that caused the problem
323      */
324     protected void handleException(Throwable x)
325     {
326         try
327         {
328             if (_state.isSuspended())
329             {
330                 HttpFields fields = new HttpFields();
331                 ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
332                 boolean committed = commitResponse(info, null, true);
333                 if (!committed)
334                     LOG.warn("Could not send response error 500: "+x);
335             }
336             else if (isCommitted())
337             {
338                 if (!(x instanceof EofException))
339                     LOG.warn("Could not send response error 500: "+x);
340             }
341             else
342             {
343                 _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
344                 _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
345                 _response.sendError(500, x.getMessage());
346             }
347         }
348         catch (IOException e)
349         {
350             // We tried our best, just log
351             LOG.debug("Could not commit response error 500", e);
352         }
353     }
354 
355     public boolean isExpecting100Continue()
356     {
357         return _expect100Continue;
358     }
359 
360     public boolean isExpecting102Processing()
361     {
362         return _expect102Processing;
363     }
364 
365     @Override
366     public String toString()
367     {
368         return String.format("%s@%x{r=%s,a=%s}",
369                 getClass().getSimpleName(),
370                 hashCode(),
371                 _requests,
372                 _state.getState());
373     }
374 
375     @Override
376     public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
377     {
378         _expect = false;
379         _expect100Continue = false;
380         _expect102Processing = false;
381 
382         if (_request.getTimeStamp() == 0)
383             _request.setTimeStamp(System.currentTimeMillis());
384         _request.setMethod(httpMethod, method);
385 
386         if (httpMethod == HttpMethod.CONNECT)
387             _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
388         else
389             _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
390         _request.setUri(_uri);
391 
392         String path;
393         try
394         {
395             path = _uri.getDecodedPath();
396         }
397         catch (Exception e)
398         {
399             LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
400             LOG.ignore(e);
401             path = _uri.getDecodedPath(StringUtil.__ISO_8859_1);
402         }
403         String info = URIUtil.canonicalPath(path);
404 
405         if (info == null)
406         {
407             info = "/";
408             _request.setRequestURI("");
409         }
410         _request.setPathInfo(info);
411         _version = version == null ? HttpVersion.HTTP_0_9 : version;
412         _request.setHttpVersion(_version);
413 
414         return false;
415     }
416 
417     @Override
418     public boolean parsedHeader(HttpField field)
419     {
420         HttpHeader header=field.getHeader();
421         String value=field.getValue();
422         if (value == null)
423             value = "";
424         if (header != null)
425         {
426             switch (header)
427             {
428                 case EXPECT:
429                     if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
430                     {
431                         HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
432                         switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
433                         {
434                             case CONTINUE:
435                                 _expect100Continue = true;
436                                 break;
437 
438                             case PROCESSING:
439                                 _expect102Processing = true;
440                                 break;
441 
442                             default:
443                                 String[] values = value.split(",");
444                                 for (int i = 0; values != null && i < values.length; i++)
445                                 {
446                                     expect = HttpHeaderValue.CACHE.get(values[i].trim());
447                                     if (expect == null)
448                                         _expect = true;
449                                     else
450                                     {
451                                         switch (expect)
452                                         {
453                                             case CONTINUE:
454                                                 _expect100Continue = true;
455                                                 break;
456                                             case PROCESSING:
457                                                 _expect102Processing = true;
458                                                 break;
459                                             default:
460                                                 _expect = true;
461                                         }
462                                     }
463                                 }
464                         }
465                     }
466                     break;
467 
468                 case CONTENT_TYPE:
469                     MimeTypes.Type mime = MimeTypes.CACHE.get(value);
470                     String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
471                     if (charset != null)
472                         _request.setCharacterEncodingUnchecked(charset);
473                     break;
474             }
475         }
476         
477         if (field.getName()!=null)
478             _request.getHttpFields().add(field);
479         return false;
480     }
481 
482     @Override
483     public boolean parsedHostHeader(String host, int port)
484     {
485         _request.setServerName(host);
486         _request.setServerPort(port);
487         return false;
488     }
489 
490     @Override
491     public boolean headerComplete()
492     {
493         _requests.incrementAndGet();
494         switch (_version)
495         {
496             case HTTP_0_9:
497                 break;
498 
499             case HTTP_1_0:
500                 if (_configuration.getSendDateHeader())
501                     _response.getHttpFields().put(_connector.getServer().getDateField());
502                 break;
503 
504             case HTTP_1_1:
505                 if (_configuration.getSendDateHeader())
506                     _response.getHttpFields().put(_connector.getServer().getDateField());
507 
508                 if (_expect)
509                 {
510                     badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
511                     return true;
512                 }
513 
514                 break;
515 
516             default:
517                 throw new IllegalStateException();
518         }
519 
520         // Either handle now or wait for first content/message complete
521         return _expect100Continue;
522     }
523 
524     @Override
525     public boolean content(T item)
526     {
527         if (LOG.isDebugEnabled())
528             LOG.debug("{} content {}", this, item);
529         @SuppressWarnings("unchecked")
530         HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
531         input.content(item);
532         return true;
533     }
534 
535     @Override
536     public boolean messageComplete()
537     {
538         _request.getHttpInput().shutdown();
539         return true;
540     }
541 
542     @Override
543     public boolean earlyEOF()
544     {
545         _request.getHttpInput().earlyEOF();
546         return false;
547     }
548 
549     @Override
550     public void badMessage(int status, String reason)
551     {
552         if (status < 400 || status > 599)
553             status = HttpStatus.BAD_REQUEST_400;
554 
555         try
556         {
557             if (_state.handling())
558             {
559                 commitResponse(new ResponseInfo(HttpVersion.HTTP_1_1,new HttpFields(),0,status,reason,false),null,true);
560                 _state.unhandle();
561             }
562         }
563         catch (IOException e)
564         {
565             LOG.warn(e);
566         }
567         finally
568         {
569             _state.completed();
570         }
571     }
572 
573     protected boolean commitResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
574     {
575         boolean committed = _committed.compareAndSet(false, true);
576         if (committed)
577         {
578             try
579             {
580                 // Try to commit with the passed info
581                 _transport.send(info, content, complete);
582 
583                 // If we are committing a 1xx response, we need to reset the commit
584                 // status so that the "real" response can be committed again.
585                 if (info.getStatus() < 200)
586                     _committed.set(false);
587             }
588             catch (EofException e)
589             {
590                 LOG.debug(e);
591                 // TODO is it worthwhile sending if we are at EoF?
592                 // "application" info failed to commit, commit with a failsafe 500 info
593                 _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true);
594                 complete=true;
595                 throw e;
596             }
597             catch (Exception e)
598             {
599                 LOG.warn(e);
600                 // "application" info failed to commit, commit with a failsafe 500 info
601                 _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true);
602                 complete=true;
603                 throw e;
604             }
605             finally
606             {
607                 // TODO this indicates the relationship with HttpOutput is not exactly correct
608                 if (complete)
609                     _response.getHttpOutput().closed();
610             }
611         }
612         return committed;
613     }
614 
615     protected boolean isCommitted()
616     {
617         return _committed.get();
618     }
619 
620     /**
621      * <p>Requests to write (in a blocking way) the given response content buffer,
622      * committing the response if needed.</p>
623      *
624      * @param content  the content buffer to write
625      * @param complete whether the content is complete for the response
626      * @throws IOException if the write fails
627      */
628     protected void write(ByteBuffer content, boolean complete) throws IOException
629     {
630         if (isCommitted())
631         {
632             _transport.send(null, content, complete);
633         }
634         else
635         {
636             ResponseInfo info = _response.newResponseInfo();
637             boolean committed = commitResponse(info, content, complete);
638             if (!committed)
639                 throw new IOException("Concurrent commit");
640         }
641     }
642 
643     protected void execute(Runnable task)
644     {
645         _connector.getExecutor().execute(task);
646     }
647 
648     public Scheduler getScheduler()
649     {
650         return _connector.getScheduler();
651     }
652 }