/** * Sends an error 500, performing a special logic to detect whether the request is suspended, to * avoid concurrent writes from the application. * * <p>It may happen that the application suspends, and then throws an exception, while an * application spawned thread writes the response content; in such case, we attempt to commit the * error directly bypassing the {@link ErrorHandler} mechanisms and the response OutputStream. * * @param x the Throwable that caused the problem */ protected void handleException(Throwable x) { try { _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, x); _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, x.getClass()); if (_state.isSuspended()) { HttpFields fields = new HttpFields(); fields.add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); ResponseInfo info = new ResponseInfo( _request.getHttpVersion(), fields, 0, HttpStatus.INTERNAL_SERVER_ERROR_500, null, _request.isHead()); boolean committed = sendResponse(info, null, true); if (!committed) LOG.warn("Could not send response error 500: " + x); _request.getAsyncContext().complete(); } else if (isCommitted()) { if (!(x instanceof EofException)) LOG.warn("Could not send response error 500: " + x); } else { _response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); _response.sendError(500, x.getMessage()); } } catch (IOException e) { // We tried our best, just log LOG.debug("Could not commit response error 500", e); } }
@Override public boolean headerComplete() { _requests.incrementAndGet(); switch (_version) { case HTTP_0_9: break; case HTTP_1_0: if (_configuration.getSendDateHeader()) _response.getHttpFields().put(_connector.getServer().getDateField()); break; case HTTP_1_1: if (_configuration.getSendDateHeader()) _response.getHttpFields().put(_connector.getServer().getDateField()); if (_expect) { badMessage(HttpStatus.EXPECTATION_FAILED_417, null); return true; } break; default: throw new IllegalStateException(); } return true; }
@Override public void log(Request request, Response response) { if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null) return; int status = response.getStatus(); long written = response.getContentLength(); transmitter.queue( new AccessLogJettyAdapter(request, status, written, _preferProxiedForAddress)); }
protected boolean sendResponse( ResponseInfo info, ByteBuffer content, boolean complete, final Callback callback) { // TODO check that complete only set true once by changing _committed to AtomicRef<Enum> boolean committing = _committed.compareAndSet(false, true); if (committing) { // We need an info to commit if (info == null) info = _response.newResponseInfo(); // wrap callback to process 100 or 500 responses final int status = info.getStatus(); final Callback committed = (status < 200 && status >= 100) ? new Commit100Callback(callback) : new CommitCallback(callback); // committing write _transport.send(info, content, complete, committed); } else if (info == null) { // This is a normal write _transport.send(content, complete, callback); } else { callback.failed(new IllegalStateException("committed")); } return committing; }
public void reset() { _committed.set(false); _expect = false; _expect100Continue = false; _expect102Processing = false; _request.recycle(); _response.recycle(); _uri.clear(); }
/* ------------------------------------------------------------ */ public void reset(boolean returnBuffers) { _parser.reset(); if (returnBuffers) _parser.returnBuffers(); _requestFields.clear(); _request.recycle(); _generator.reset(returnBuffers); // TODO maybe only release when low on resources _responseFields.clear(); _response.recycle(); _uri.clear(); }
/* ------------------------------------------------------------ */ public void completeResponse() throws IOException { if (!_generator.isCommitted()) { _generator.setResponse(_response.getStatus(), _response.getReason()); try { _generator.completeHeader(_responseFields, Generator.LAST); } catch (IOException io) { throw io; } catch (RuntimeException e) { LOG.warn("header full: " + e); LOG.debug(e); _response.reset(); _generator.reset(true); _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, null); _generator.completeHeader(_responseFields, Generator.LAST); _generator.complete(); throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); } } _generator.complete(); }
/* ------------------------------------------------------------ */ public void commitResponse(boolean last) throws IOException { if (!_generator.isCommitted()) { _generator.setResponse(_response.getStatus(), _response.getReason()); try { // If the client was expecting 100 continues, but we sent something // else, then we need to close the connection if (_expect100Continue && _response.getStatus() != 100) _generator.setPersistent(false); _generator.completeHeader(_responseFields, last); } catch (IOException io) { throw io; } catch (RuntimeException e) { LOG.warn("header full: " + e); _response.reset(); _generator.reset(true); _generator.setResponse(HttpStatus.INTERNAL_SERVER_ERROR_500, null); _generator.completeHeader(_responseFields, Generator.LAST); _generator.complete(); throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR_500); } } if (last) _generator.complete(); }
/** * If the associated response has the Expect header set to 100 Continue, then accessing the input * stream indicates that the handler/servlet is ready for the request body and thus a 100 Continue * response is sent. * * @throws IOException if the InputStream cannot be created */ public void continue100(int available) throws IOException { // If the client is expecting 100 CONTINUE, then send it now. // TODO: consider using an AtomicBoolean ? if (isExpecting100Continue()) { _expect100Continue = false; // is content missing? if (available == 0) { if (_response.isCommitted()) throw new IOException("Committed before 100 Continues"); // TODO: break this dependency with HttpGenerator boolean committed = sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false); if (!committed) throw new IOException("Concurrent commit while trying to send 100-Continue"); } } }
/* * @see javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse) */ protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException { Request baseRequest = (request instanceof Request) ? ((Request) request) : HttpChannel.getCurrentHttpChannel().getRequest(); Response base_response = baseRequest.getResponse(); base_response.resetForForward(); if (!(request instanceof HttpServletRequest)) request = new ServletRequestHttpWrapper(request); if (!(response instanceof HttpServletResponse)) response = new ServletResponseHttpWrapper(response); final boolean old_handled = baseRequest.isHandled(); final String old_uri = baseRequest.getRequestURI(); final String old_context_path = baseRequest.getContextPath(); final String old_servlet_path = baseRequest.getServletPath(); final String old_path_info = baseRequest.getPathInfo(); final String old_query = baseRequest.getQueryString(); final Attributes old_attr = baseRequest.getAttributes(); final DispatcherType old_type = baseRequest.getDispatcherType(); MultiMap<String> old_params = baseRequest.getParameters(); try { baseRequest.setHandled(false); baseRequest.setDispatcherType(dispatch); if (_named != null) _contextHandler.handle( _named, baseRequest, (HttpServletRequest) request, (HttpServletResponse) response); else { // process any query string from the dispatch URL String query = _dQuery; if (query != null) { // force parameter extraction if (old_params == null) { baseRequest.extractParameters(); old_params = baseRequest.getParameters(); } baseRequest.mergeQueryString(query); } ForwardAttributes attr = new ForwardAttributes(old_attr); // If we have already been forwarded previously, then keep using the established // original value. Otherwise, this is the first forward and we need to establish the values. // Note: the established value on the original request for pathInfo and // for queryString is allowed to be null, but cannot be null for the other values. if (old_attr.getAttribute(FORWARD_REQUEST_URI) != null) { attr._pathInfo = (String) old_attr.getAttribute(FORWARD_PATH_INFO); attr._query = (String) old_attr.getAttribute(FORWARD_QUERY_STRING); attr._requestURI = (String) old_attr.getAttribute(FORWARD_REQUEST_URI); attr._contextPath = (String) old_attr.getAttribute(FORWARD_CONTEXT_PATH); attr._servletPath = (String) old_attr.getAttribute(FORWARD_SERVLET_PATH); } else { attr._pathInfo = old_path_info; attr._query = old_query; attr._requestURI = old_uri; attr._contextPath = old_context_path; attr._servletPath = old_servlet_path; } baseRequest.setRequestURI(_uri); baseRequest.setContextPath(_contextHandler.getContextPath()); baseRequest.setServletPath(null); baseRequest.setPathInfo(_uri); baseRequest.setAttributes(attr); _contextHandler.handle( _path, baseRequest, (HttpServletRequest) request, (HttpServletResponse) response); if (!baseRequest.getHttpChannelState().isAsync()) commitResponse(response, baseRequest); } } finally { baseRequest.setHandled(old_handled); baseRequest.setRequestURI(old_uri); baseRequest.setContextPath(old_context_path); baseRequest.setServletPath(old_servlet_path); baseRequest.setPathInfo(old_path_info); baseRequest.setAttributes(old_attr); baseRequest.setParameters(old_params); baseRequest.setQueryString(old_query); baseRequest.setDispatcherType(old_type); } }
/* ------------------------------------------------------------ */ protected void handleRequest() throws IOException { boolean error = false; String threadName = null; try { if (LOG.isDebugEnabled()) { threadName = Thread.currentThread().getName(); Thread.currentThread().setName(threadName + " - " + _uri); } // Loop here to handle async request redispatches. // The loop is controlled by the call to async.unhandle in the // finally block below. If call is from a non-blocking connector, // then the unhandle will return false only if an async dispatch has // already happened when unhandle is called. For a blocking connector, // the wait for the asynchronous dispatch or timeout actually happens // within the call to unhandle(). final Server server = _server; boolean handling = _request._async.handling() && server != null && server.isRunning(); while (handling) { _request.setHandled(false); String info = null; try { _uri.getPort(); info = URIUtil.canonicalPath(_uri.getDecodedPath()); if (info == null && !_request.getMethod().equals(HttpMethods.CONNECT)) throw new HttpException(400); _request.setPathInfo(info); if (_out != null) _out.reopen(); if (_request._async.isInitial()) { _request.setDispatcherType(DispatcherType.REQUEST); _connector.customize(_endp, _request); server.handle(this); } else { _request.setDispatcherType(DispatcherType.ASYNC); server.handleAsync(this); } } catch (ContinuationThrowable e) { LOG.ignore(e); } catch (EofException e) { LOG.debug(e); error = true; _request.setHandled(true); } catch (RuntimeIOException e) { LOG.debug(e); error = true; _request.setHandled(true); } catch (HttpException e) { LOG.debug(e); error = true; _request.setHandled(true); _response.sendError(e.getStatus(), e.getReason()); } catch (Throwable e) { if (e instanceof ThreadDeath) throw (ThreadDeath) e; LOG.warn(String.valueOf(_uri), e); error = true; _request.setHandled(true); _generator.sendError(info == null ? 400 : 500, null, null, true); } finally { handling = !_request._async.unhandle() && server.isRunning() && _server != null; } } } finally { if (threadName != null) Thread.currentThread().setName(threadName); if (_request._async.isUncompleted()) { _request._async.doComplete(); if (_expect100Continue) { LOG.debug("100 continues not sent"); // We didn't send 100 continues, but the latest interpretation // of the spec (see httpbis) is that the client will either // send the body anyway, or close. So we no longer need to // do anything special here other than make the connection not persistent _expect100Continue = false; if (!_response.isCommitted()) _generator.setPersistent(false); } if (_endp.isOpen()) { if (error) { _endp.shutdownOutput(); _generator.setPersistent(false); if (!_generator.isComplete()) _response.complete(); } else { if (!_response.isCommitted() && !_request.isHandled()) _response.sendError(HttpServletResponse.SC_NOT_FOUND); _response.complete(); if (_generator.isPersistent()) _connector.persist(_endp); } } else { _response.complete(); } _request.setHandled(true); } } }
/** @return True if the channel is ready to continue handling (ie it is not suspended) */ public boolean handle() { LOG.debug("{} handle enter", this); setCurrentHttpChannel(this); String threadName = null; if (LOG.isDebugEnabled()) { threadName = Thread.currentThread().getName(); Thread.currentThread().setName(threadName + " - " + _uri); } // Loop here to handle async request redispatches. // The loop is controlled by the call to async.unhandle in the // finally block below. Unhandle will return false only if an async dispatch has // already happened when unhandle is called. HttpChannelState.Next next = _state.handling(); while (next == Next.CONTINUE && getServer().isRunning()) { boolean error = false; try { _request.setHandled(false); _response.getHttpOutput().reopen(); if (_state.isInitial()) { _request.setTimeStamp(System.currentTimeMillis()); _request.setDispatcherType(DispatcherType.REQUEST); for (HttpConfiguration.Customizer customizer : _configuration.getCustomizers()) customizer.customize(getConnector(), _configuration, _request); getServer().handle(this); } else { if (_request.getHttpChannelState().isExpired()) { _request.setDispatcherType(DispatcherType.ERROR); Throwable ex = _state.getAsyncContextEvent().getThrowable(); String reason = "Async Timeout"; if (ex != null) { reason = "Async Exception"; _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ex); } _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, new Integer(500)); _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, reason); _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, _request.getRequestURI()); _response.setStatusWithReason(500, reason); ErrorHandler eh = _state.getContextHandler().getErrorHandler(); if (eh instanceof ErrorHandler.ErrorPageMapper) { String error_page = ((ErrorHandler.ErrorPageMapper) eh) .getErrorPage( (HttpServletRequest) _state.getAsyncContextEvent().getSuppliedRequest()); if (error_page != null) _state.getAsyncContextEvent().setDispatchPath(error_page); } } else _request.setDispatcherType(DispatcherType.ASYNC); getServer().handleAsync(this); } } catch (Error e) { if ("ContinuationThrowable".equals(e.getClass().getSimpleName())) LOG.ignore(e); else { error = true; throw e; } } catch (Exception e) { error = true; if (e instanceof EofException) LOG.debug(e); else LOG.warn(String.valueOf(_uri), e); _state.error(e); _request.setHandled(true); handleException(e); } finally { if (error && _state.isAsyncStarted()) _state.errorComplete(); next = _state.unhandle(); } } if (threadName != null && LOG.isDebugEnabled()) Thread.currentThread().setName(threadName); setCurrentHttpChannel(null); if (next == Next.COMPLETE) { try { _state.completed(); if (!_response.isCommitted() && !_request.isHandled()) _response.sendError(404); else // Complete generating the response _response.closeOutput(); } catch (EofException | ClosedChannelException e) { LOG.debug(e); } catch (Exception e) { LOG.warn("complete failed", e); } finally { _request.setHandled(true); _transport.completed(); } } LOG.debug("{} handle exit, result {}", this, next); return next != Next.WAIT; }
protected void commit(ByteBuffer content, boolean complete, Callback callback) { // Are we excluding because of status? Response response = _channel.getResponse(); int sc = response.getStatus(); if (sc > 0 && (sc < 200 || sc == 204 || sc == 205 || sc >= 300)) { LOG.debug("{} exclude by status {}", this, sc); noCompression(); if (sc == 304) { String request_etags = (String) _channel.getRequest().getAttribute("o.e.j.s.h.gzip.GzipHandler.etag"); String response_etag = response.getHttpFields().get(HttpHeader.ETAG); if (request_etags != null && response_etag != null) { String response_etag_gzip = etagGzip(response_etag); if (request_etags.contains(response_etag_gzip)) response.getHttpFields().put(HttpHeader.ETAG, response_etag_gzip); } } _interceptor.write(content, complete, callback); return; } // Are we excluding because of mime-type? String ct = response.getContentType(); if (ct != null) { ct = MimeTypes.getContentTypeWithoutCharset(ct); if (!_factory.isMimeTypeGzipable(StringUtil.asciiToLowerCase(ct))) { LOG.debug("{} exclude by mimeType {}", this, ct); noCompression(); _interceptor.write(content, complete, callback); return; } } // Has the Content-Encoding header already been set? HttpFields fields = response.getHttpFields(); String ce = fields.get(HttpHeader.CONTENT_ENCODING); if (ce != null) { LOG.debug("{} exclude by content-encoding {}", this, ce); noCompression(); _interceptor.write(content, complete, callback); return; } // Are we the thread that commits? if (_state.compareAndSet(GZState.MIGHT_COMPRESS, GZState.COMMITTING)) { // We are varying the response due to accept encoding header. if (_vary != null) { if (fields.contains(HttpHeader.VARY)) fields.addCSV(HttpHeader.VARY, _vary.getValues()); else fields.add(_vary); } long content_length = response.getContentLength(); if (content_length < 0 && complete) content_length = content.remaining(); _deflater = _factory.getDeflater(_channel.getRequest(), content_length); if (_deflater == null) { LOG.debug("{} exclude no deflater", this); _state.set(GZState.NOT_COMPRESSING); _interceptor.write(content, complete, callback); return; } fields.put(GZIP._contentEncoding); _crc.reset(); _buffer = _channel.getByteBufferPool().acquire(_bufferSize, false); BufferUtil.fill(_buffer, GZIP_HEADER, 0, GZIP_HEADER.length); // Adjust headers response.setContentLength(-1); String etag = fields.get(HttpHeader.ETAG); if (etag != null) fields.put(HttpHeader.ETAG, etagGzip(etag)); LOG.debug("{} compressing {}", this, _deflater); _state.set(GZState.COMPRESSING); gzip(content, complete, callback); } else callback.failed(new WritePendingException()); }
@Override public void log(Request request, Response response) { // copied almost entirely from NCSARequestLog final StringBuilder buf = new StringBuilder(256); String address = request.getHeader(HttpHeaders.X_FORWARDED_FOR); if (address == null) { address = request.getRemoteAddr(); } buf.append(address); buf.append(" - "); final Authentication authentication = request.getAuthentication(); if (authentication instanceof Authentication.User) { buf.append( ((Authentication.User) authentication).getUserIdentity().getUserPrincipal().getName()); } else { buf.append('-'); } buf.append(" ["); buf.append(dateCache.format(request.getTimeStamp())); buf.append("] \""); buf.append(request.getMethod()); buf.append(' '); buf.append(request.getUri().toString()); buf.append(' '); buf.append(request.getProtocol()); buf.append("\" "); // TODO: Handle async requests? // e.g. if (request.getAsyncContinuation().isInitial()) assert !request.isAsyncStarted(); int status = response.getStatus(); if (status <= 0) { if (request.isHandled()) { status = 200; } else { status = 404; } } buf.append((char) ('0' + ((status / 100) % 10))); buf.append((char) ('0' + ((status / 10) % 10))); buf.append((char) ('0' + (status % 10))); final long responseLength = response.getContentCount(); if (responseLength >= 0) { buf.append(' '); if (responseLength > 99999) { buf.append(responseLength); } else { if (responseLength > 9999) { buf.append((char) ('0' + ((responseLength / 10000) % 10))); } if (responseLength > 999) { buf.append((char) ('0' + ((responseLength / 1000) % 10))); } if (responseLength > 99) { buf.append((char) ('0' + ((responseLength / 100) % 10))); } if (responseLength > 9) { buf.append((char) ('0' + ((responseLength / 10) % 10))); } buf.append((char) ('0' + (responseLength % 10))); } } else { buf.append(" -"); } final long now = System.currentTimeMillis(); final long dispatchTime = request.getDispatchTime(); buf.append(' '); buf.append(now - ((dispatchTime == 0) ? request.getTimeStamp() : dispatchTime)); buf.append(' '); buf.append(now - request.getTimeStamp()); System.out.println(buf.toString()); }