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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.server;
21 import java.io.IOException;
22 import java.net.InetSocketAddress;
23 import java.nio.ByteBuffer;
24 import java.nio.channels.ClosedChannelException;
25 import java.nio.charset.StandardCharsets;
26 import java.util.List;
27 import java.util.concurrent.atomic.AtomicBoolean;
28 import java.util.concurrent.atomic.AtomicInteger;
30 import javax.servlet.DispatcherType;
31 import javax.servlet.RequestDispatcher;
32 import javax.servlet.http.HttpServletRequest;
34 import org.eclipse.jetty.http.HttpField;
35 import org.eclipse.jetty.http.HttpFields;
36 import org.eclipse.jetty.http.HttpGenerator;
37 import org.eclipse.jetty.http.HttpGenerator.ResponseInfo;
38 import org.eclipse.jetty.http.HttpHeader;
39 import org.eclipse.jetty.http.HttpHeaderValue;
40 import org.eclipse.jetty.http.HttpMethod;
41 import org.eclipse.jetty.http.HttpParser;
42 import org.eclipse.jetty.http.HttpStatus;
43 import org.eclipse.jetty.http.HttpURI;
44 import org.eclipse.jetty.http.HttpVersion;
45 import org.eclipse.jetty.http.MimeTypes;
46 import org.eclipse.jetty.io.ByteBufferPool;
47 import org.eclipse.jetty.io.ChannelEndPoint;
48 import org.eclipse.jetty.io.EndPoint;
49 import org.eclipse.jetty.io.EofException;
50 import org.eclipse.jetty.server.HttpChannelState.Action;
51 import org.eclipse.jetty.server.handler.ContextHandler;
52 import org.eclipse.jetty.server.handler.ErrorHandler;
53 import org.eclipse.jetty.util.Callback;
54 import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
55 import org.eclipse.jetty.util.URIUtil;
56 import org.eclipse.jetty.util.log.Log;
57 import org.eclipse.jetty.util.log.Logger;
58 import org.eclipse.jetty.util.thread.Scheduler;
61 /* ------------------------------------------------------------ */
63 * Represents a single endpoint for HTTP semantic processing.
64 * The HttpChannel is both a HttpParser.RequestHandler, where it passively receives events from
65 * an incoming HTTP request, and a Runnable, where it actively takes control of the request/response
66 * life cycle and calls the application (perhaps suspending and resuming with multiple calls to run).
67 * The HttpChannel signals the switch from passive mode to active mode by returning true to one of the
68 * HttpParser.RequestHandler callbacks. The completion of the active phase is signalled by a call to
69 * HttpTransport.completed().
72 public class HttpChannel<T> implements HttpParser.RequestHandler<T>, Runnable, HttpParser.ProxyHandler
74 private static final Logger LOG = Log.getLogger(HttpChannel.class);
75 private static final ThreadLocal<HttpChannel<?>> __currentChannel = new ThreadLocal<>();
77 /* ------------------------------------------------------------ */
78 /** Get the current channel that this thread is dispatched to.
79 * @see Request#getAttribute(String) for a more general way to access the HttpChannel
80 * @return the current HttpChannel or null
82 public static HttpChannel<?> getCurrentHttpChannel()
84 return __currentChannel.get();
87 protected static HttpChannel<?> setCurrentHttpChannel(HttpChannel<?> channel)
89 HttpChannel<?> last=__currentChannel.get();
90 __currentChannel.set(channel);
94 private final AtomicBoolean _committed = new AtomicBoolean();
95 private final AtomicInteger _requests = new AtomicInteger();
96 private final Connector _connector;
97 private final HttpConfiguration _configuration;
98 private final EndPoint _endPoint;
99 private final HttpTransport _transport;
100 private final HttpURI _uri;
101 private final HttpChannelState _state;
102 private final Request _request;
103 private final Response _response;
104 private HttpVersion _version = HttpVersion.HTTP_1_1;
105 private boolean _expect = false;
106 private boolean _expect100Continue = false;
107 private boolean _expect102Processing = false;
109 public HttpChannel(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<T> input)
111 _connector = connector;
112 _configuration = configuration;
113 _endPoint = endPoint;
114 _transport = transport;
116 _uri = new HttpURI(URIUtil.__CHARSET);
117 _state = new HttpChannelState(this);
119 _request = new Request(this, input);
120 _response = new Response(this, new HttpOutput(this));
122 if (LOG.isDebugEnabled())
123 LOG.debug("new {} -> {},{},{}",this,_endPoint,_endPoint.getConnection(),_state);
126 public HttpChannelState getState()
131 public HttpVersion getHttpVersion()
136 * @return the number of requests handled by this connection
138 public int getRequests()
140 return _requests.get();
143 public Connector getConnector()
148 public HttpTransport getHttpTransport()
154 * Get the idle timeout.
155 * <p>This is implemented as a call to {@link EndPoint#getIdleTimeout()}, but may be
156 * overridden by channels that have timeouts different from their connections.
158 public long getIdleTimeout()
160 return _endPoint.getIdleTimeout();
164 * Set the idle timeout.
165 * <p>This is implemented as a call to {@link EndPoint#setIdleTimeout(long)}, but may be
166 * overridden by channels that have timeouts different from their connections.
168 public void setIdleTimeout(long timeoutMs)
170 _endPoint.setIdleTimeout(timeoutMs);
173 public ByteBufferPool getByteBufferPool()
175 return _connector.getByteBufferPool();
178 public HttpConfiguration getHttpConfiguration()
180 return _configuration;
183 public Server getServer()
185 return _connector.getServer();
188 public Request getRequest()
193 public Response getResponse()
198 public EndPoint getEndPoint()
203 public InetSocketAddress getLocalAddress()
205 return _endPoint.getLocalAddress();
208 public InetSocketAddress getRemoteAddress()
210 return _endPoint.getRemoteAddress();
214 public int getHeaderCacheSize()
216 return _configuration.getHeaderCacheSize();
220 * If the associated response has the Expect header set to 100 Continue,
221 * then accessing the input stream indicates that the handler/servlet
222 * is ready for the request body and thus a 100 Continue response is sent.
224 * @throws IOException if the InputStream cannot be created
226 public void continue100(int available) throws IOException
228 // If the client is expecting 100 CONTINUE, then send it now.
229 // TODO: consider using an AtomicBoolean ?
230 if (isExpecting100Continue())
232 _expect100Continue = false;
234 // is content missing?
237 if (_response.isCommitted())
238 throw new IOException("Committed before 100 Continues");
240 // TODO: break this dependency with HttpGenerator
241 boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
243 throw new IOException("Concurrent commit while trying to send 100-Continue");
250 _committed.set(false);
252 _expect100Continue = false;
253 _expect102Processing = false;
265 /* ------------------------------------------------------------ */
267 * @return True if the channel is ready to continue handling (ie it is not suspended)
269 public boolean handle()
271 if (LOG.isDebugEnabled())
272 LOG.debug("{} handle enter", this);
274 final HttpChannel<?>last = setCurrentHttpChannel(this);
276 String threadName = null;
277 if (LOG.isDebugEnabled())
279 threadName = Thread.currentThread().getName();
280 Thread.currentThread().setName(threadName + " - " + _uri);
283 HttpChannelState.Action action = _state.handling();
286 // Loop here to handle async request redispatches.
287 // The loop is controlled by the call to async.unhandle in the
288 // finally block below. Unhandle will return false only if an async dispatch has
289 // already happened when unhandle is called.
290 loop: while (action.ordinal()<HttpChannelState.Action.WAIT.ordinal() && getServer().isRunning())
295 if (LOG.isDebugEnabled())
296 LOG.debug("{} action {}",this,action);
300 case REQUEST_DISPATCH:
301 _request.setHandled(false);
302 _response.getHttpOutput().reopen();
303 _request.setDispatcherType(DispatcherType.REQUEST);
305 List<HttpConfiguration.Customizer> customizers = _configuration.getCustomizers();
306 if (!customizers.isEmpty())
308 for (HttpConfiguration.Customizer customizer : customizers)
309 customizer.customize(getConnector(), _configuration, _request);
311 getServer().handle(this);
315 _request.setHandled(false);
316 _response.getHttpOutput().reopen();
317 _request.setDispatcherType(DispatcherType.ASYNC);
318 getServer().handleAsync(this);
322 _request.setHandled(false);
323 _response.getHttpOutput().reopen();
324 _request.setDispatcherType(DispatcherType.ERROR);
326 Throwable ex=_state.getAsyncContextEvent().getThrowable();
327 String reason="Async Timeout";
330 reason="Async Exception";
331 _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,ex);
333 _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(500));
334 _request.setAttribute(RequestDispatcher.ERROR_MESSAGE,reason);
335 _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI,_request.getRequestURI());
337 _response.setStatusWithReason(500,reason);
340 ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(),_state.getContextHandler());
341 if (eh instanceof ErrorHandler.ErrorPageMapper)
343 String error_page=((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest());
344 if (error_page!=null)
345 _state.getAsyncContextEvent().setDispatchPath(error_page);
348 getServer().handleAsync(this);
353 ContextHandler handler=_state.getContextHandler();
355 handler.handle(_request.getHttpInput());
357 _request.getHttpInput().run();
363 ContextHandler handler=_state.getContextHandler();
366 handler.handle(_response.getHttpOutput());
368 _response.getHttpOutput().run();
379 if ("ContinuationThrowable".equals(e.getClass().getSimpleName()))
384 LOG.warn(String.valueOf(_uri), e);
386 _request.setHandled(true);
393 if (e instanceof EofException)
396 LOG.warn(String.valueOf(_uri), e);
398 _request.setHandled(true);
403 if (error && _state.isAsyncStarted())
404 _state.errorComplete();
405 action = _state.unhandle();
409 if (action==Action.COMPLETE)
415 if (!_response.isCommitted() && !_request.isHandled())
417 _response.sendError(404);
421 // Complete generating the response
422 _response.closeOutput();
425 catch(EofException|ClosedChannelException e)
431 LOG.warn("complete failed",e);
435 _request.setHandled(true);
436 _transport.completed();
442 setCurrentHttpChannel(last);
443 if (threadName != null && LOG.isDebugEnabled())
444 Thread.currentThread().setName(threadName);
447 if (LOG.isDebugEnabled())
448 LOG.debug("{} handle exit, result {}", this, action);
450 return action!=Action.WAIT;
454 * <p>Sends an error 500, performing a special logic to detect whether the request is suspended,
455 * to avoid concurrent writes from the application.</p>
456 * <p>It may happen that the application suspends, and then throws an exception, while an application
457 * spawned thread writes the response content; in such case, we attempt to commit the error directly
458 * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p>
460 * @param x the Throwable that caused the problem
462 protected void handleException(Throwable x)
466 _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,x);
467 _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,x.getClass());
468 if (_state.isSuspended())
470 HttpFields fields = new HttpFields();
471 fields.add(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE);
472 ResponseInfo info = new ResponseInfo(_request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead());
473 boolean committed = sendResponse(info, null, true);
475 LOG.warn("Could not send response error 500: "+x);
476 _request.getAsyncContext().complete();
478 else if (isCommitted())
481 if (!(x instanceof EofException))
482 LOG.warn("Could not send response error 500: "+x);
486 _response.setHeader(HttpHeader.CONNECTION.asString(),HttpHeaderValue.CLOSE.asString());
487 _response.sendError(500, x.getMessage());
490 catch (IOException e)
492 // We tried our best, just log
493 LOG.debug("Could not commit response error 500", e);
497 public boolean isExpecting100Continue()
499 return _expect100Continue;
502 public boolean isExpecting102Processing()
504 return _expect102Processing;
508 public String toString()
510 return String.format("%s@%x{r=%s,c=%b,a=%s,uri=%s}",
511 getClass().getSimpleName(),
520 public void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort)
522 _request.setAttribute("PROXY", protocol);
523 _request.setServerName(sAddr);
524 _request.setServerPort(dPort);
525 _request.setRemoteAddr(InetSocketAddress.createUnresolved(sAddr,sPort));
529 public boolean startRequest(HttpMethod httpMethod, String method, ByteBuffer uri, HttpVersion version)
532 _expect100Continue = false;
533 _expect102Processing = false;
535 _request.setTimeStamp(System.currentTimeMillis());
536 _request.setMethod(httpMethod, method);
538 if (httpMethod == HttpMethod.CONNECT)
539 _uri.parseConnect(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
541 _uri.parse(uri.array(),uri.arrayOffset()+uri.position(),uri.remaining());
542 _request.setUri(_uri);
547 path = _uri.getDecodedPath();
551 LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1");
553 path = _uri.getDecodedPath(StandardCharsets.ISO_8859_1);
556 String info = URIUtil.canonicalPath(path);
560 if( path==null && _uri.getScheme()!=null &&_uri.getHost()!=null)
563 _request.setRequestURI("");
567 badMessage(400,null);
571 _request.setPathInfo(info);
572 _version = version == null ? HttpVersion.HTTP_0_9 : version;
573 _request.setHttpVersion(_version);
579 public boolean parsedHeader(HttpField field)
581 HttpHeader header=field.getHeader();
582 String value=field.getValue();
590 if (_version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
592 HttpHeaderValue expect = HttpHeaderValue.CACHE.get(value);
593 switch (expect == null ? HttpHeaderValue.UNKNOWN : expect)
596 _expect100Continue = true;
600 _expect102Processing = true;
604 String[] values = value.split(",");
605 for (int i = 0; i < values.length; i++)
607 expect = HttpHeaderValue.CACHE.get(values[i].trim());
615 _expect100Continue = true;
618 _expect102Processing = true;
630 MimeTypes.Type mime = MimeTypes.CACHE.get(value);
631 String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(value) : mime.getCharset().toString();
633 _request.setCharacterEncodingUnchecked(charset);
639 if (field.getName()!=null)
640 _request.getHttpFields().add(field);
645 public boolean parsedHostHeader(String host, int port)
647 if (_uri.getHost()==null)
649 _request.setServerName(host);
650 _request.setServerPort(port);
656 public boolean headerComplete()
658 _requests.incrementAndGet();
659 HttpFields fields = _response.getHttpFields();
666 if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE))
667 _response.getHttpFields().add(_connector.getServer().getDateField());
671 if (_configuration.getSendDateHeader() && !fields.contains(HttpHeader.DATE))
672 _response.getHttpFields().add(_connector.getServer().getDateField());
676 badMessage(HttpStatus.EXPECTATION_FAILED_417,null);
683 throw new IllegalStateException();
690 public boolean content(T item)
692 if (LOG.isDebugEnabled())
693 LOG.debug("{} content {}", this, item);
694 @SuppressWarnings("unchecked")
695 HttpInput<T> input = (HttpInput<T>)_request.getHttpInput();
702 public boolean messageComplete()
704 if (LOG.isDebugEnabled())
705 LOG.debug("{} messageComplete", this);
706 _request.getHttpInput().messageComplete();
711 public void earlyEOF()
713 _request.getHttpInput().earlyEOF();
717 public void badMessage(int status, String reason)
719 if (status < 400 || status > 599)
720 status = HttpStatus.BAD_REQUEST_400;
724 if (_state.handling()==Action.REQUEST_DISPATCH)
726 ByteBuffer content=null;
727 HttpFields fields=new HttpFields();
729 ErrorHandler handler=getServer().getBean(ErrorHandler.class);
731 content=handler.badMessageError(status,reason,fields);
733 sendResponse(new ResponseInfo(HttpVersion.HTTP_1_1,fields,0,status,reason,false),content ,true);
736 catch (IOException e)
742 if (_state.unhandle()==Action.COMPLETE)
745 throw new IllegalStateException();
749 protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback)
751 boolean committing = _committed.compareAndSet(false, true);
754 // We need an info to commit
756 info = _response.newResponseInfo();
758 // wrap callback to process 100 responses
759 final int status=info.getStatus();
760 final Callback committed = (status<200&&status>=100)?new Commit100Callback(callback):new CommitCallback(callback);
763 _transport.send(info, content, complete, committed);
767 // This is a normal write
768 _transport.send(content, complete, callback);
772 callback.failed(new IllegalStateException("committed"));
777 protected boolean sendResponse(ResponseInfo info, ByteBuffer content, boolean complete) throws IOException
779 try(Blocker blocker = _response.getHttpOutput().acquireWriteBlockingCallback())
781 boolean committing = sendResponse(info,content,complete,blocker);
787 public boolean isCommitted()
789 return _committed.get();
793 * <p>Non-Blocking write, committing the response if needed.</p>
795 * @param content the content buffer to write
796 * @param complete whether the content is complete for the response
797 * @param callback Callback when complete or failed
799 protected void write(ByteBuffer content, boolean complete, Callback callback)
801 sendResponse(null,content,complete,callback);
804 protected void execute(Runnable task)
806 _connector.getExecutor().execute(task);
809 public Scheduler getScheduler()
811 return _connector.getScheduler();
814 /* ------------------------------------------------------------ */
816 * @return true if the HttpChannel can efficiently use direct buffer (typically this means it is not over SSL or a multiplexed protocol)
818 public boolean useDirectBuffers()
820 return getEndPoint() instanceof ChannelEndPoint;
824 * If a write or similar to this channel fails this method should be called. The standard implementation
825 * is to call {@link HttpTransport#abort()}
832 private class CommitCallback implements Callback
834 private final Callback _callback;
836 private CommitCallback(Callback callback)
838 _callback = callback;
842 public void succeeded()
844 _callback.succeeded();
848 public void failed(final Throwable x)
850 if (x instanceof EofException || x instanceof ClosedChannelException)
854 _response.getHttpOutput().closed();
858 LOG.warn("Commit failed",x);
859 _transport.send(HttpGenerator.RESPONSE_500_INFO,null,true,new Callback()
862 public void succeeded()
865 _response.getHttpOutput().closed();
869 public void failed(Throwable th)
873 _response.getHttpOutput().closed();
880 private class Commit100Callback extends CommitCallback
882 private Commit100Callback(Callback callback)
888 public void succeeded()
890 if (_committed.compareAndSet(true, false))
893 super.failed(new IllegalStateException());