/** * 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); } }
/** * Customizes the request attributes to be set for SSL requests. * * <p>The requirements of the Servlet specs are: * * <ul> * <li>an attribute named "javax.servlet.request.ssl_session_id" of type String (since Servlet * Spec 3.0). * <li>an attribute named "javax.servlet.request.cipher_suite" of type String. * <li>an attribute named "javax.servlet.request.key_size" of type Integer. * <li>an attribute named "javax.servlet.request.X509Certificate" of type * java.security.cert.X509Certificate[]. This is an array of objects of type * X509Certificate, the order of this array is defined as being in ascending order of trust. * The first certificate in the chain is the one set by the client, the next is the one used * to authenticate the first, and so on. * </ul> * * @param sslEngine the sslEngine to be customized. * @param request HttpRequest to be customized. */ protected void customize(SSLEngine sslEngine, Request request) { SSLSession sslSession = sslEngine.getSession(); if (_sniHostCheck) { String name = request.getServerName(); X509 x509 = (X509) sslSession.getValue(SniX509ExtendedKeyManager.SNI_X509); if (x509 != null && !x509.matches(name)) { LOG.warn("Host {} does not match SNI {}", name, x509); throw new BadMessageException(400, "Host does not match SNI"); } if (LOG.isDebugEnabled()) LOG.debug("Host {} matched SNI {}", name, x509); } try { String cipherSuite = sslSession.getCipherSuite(); Integer keySize; X509Certificate[] certs; String idStr; CachedInfo cachedInfo = (CachedInfo) sslSession.getValue(CACHED_INFO_ATTR); if (cachedInfo != null) { keySize = cachedInfo.getKeySize(); certs = cachedInfo.getCerts(); idStr = cachedInfo.getIdStr(); } else { keySize = SslContextFactory.deduceKeyLength(cipherSuite); certs = SslContextFactory.getCertChain(sslSession); byte[] bytes = sslSession.getId(); idStr = TypeUtil.toHexString(bytes); cachedInfo = new CachedInfo(keySize, certs, idStr); sslSession.putValue(CACHED_INFO_ATTR, cachedInfo); } if (certs != null) request.setAttribute("javax.servlet.request.X509Certificate", certs); request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite); request.setAttribute("javax.servlet.request.key_size", keySize); request.setAttribute("javax.servlet.request.ssl_session_id", idStr); } catch (Exception e) { LOG.warn(Log.EXCEPTION, e); } }
@Override public void sendError(int code, String message) throws IOException { if (isIncluding()) return; if (isCommitted()) LOG.warn("Committed before " + code + " " + message); resetBuffer(); _characterEncoding = null; setHeader(HttpHeader.EXPIRES, null); setHeader(HttpHeader.LAST_MODIFIED, null); setHeader(HttpHeader.CACHE_CONTROL, null); setHeader(HttpHeader.CONTENT_TYPE, null); setHeader(HttpHeader.CONTENT_LENGTH, null); _outputType = OutputType.NONE; setStatus(code); _reason = message; Request request = _channel.getRequest(); Throwable cause = (Throwable) request.getAttribute(Dispatcher.ERROR_EXCEPTION); if (message == null) message = cause == null ? HttpStatus.getMessage(code) : cause.toString(); // If we are allowed to have a body if (code != SC_NO_CONTENT && code != SC_NOT_MODIFIED && code != SC_PARTIAL_CONTENT && code >= SC_OK) { ErrorHandler error_handler = null; ContextHandler.Context context = request.getContext(); if (context != null) error_handler = context.getContextHandler().getErrorHandler(); if (error_handler == null) error_handler = _channel.getServer().getBean(ErrorHandler.class); if (error_handler != null) { request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, new Integer(code)); request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message); request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, request.getServletName()); error_handler.handle(null, _channel.getRequest(), _channel.getRequest(), this); } else { setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString()); ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(2048); if (message != null) { message = StringUtil.replace(message, "&", "&"); message = StringUtil.replace(message, "<", "<"); message = StringUtil.replace(message, ">", ">"); } String uri = request.getRequestURI(); if (uri != null) { uri = StringUtil.replace(uri, "&", "&"); uri = StringUtil.replace(uri, "<", "<"); uri = StringUtil.replace(uri, ">", ">"); } writer.write( "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n"); writer.write("<title>Error "); writer.write(Integer.toString(code)); writer.write(' '); if (message == null) writer.write(message); writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: "); writer.write(Integer.toString(code)); writer.write("</h2>\n<p>Problem accessing "); writer.write(uri); writer.write(". Reason:\n<pre> "); writer.write(message); writer.write("</pre>"); writer.write("</p>\n<hr /><i><small>Powered by Jetty://</small></i>"); writer.write("\n</body>\n</html>\n"); writer.flush(); setContentLength(writer.size()); writer.writeTo(getOutputStream()); writer.destroy(); } } else if (code != SC_PARTIAL_CONTENT) { // TODO work out why this is required? _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE); _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH); _characterEncoding = null; _mimeType = null; } complete(); }
/** @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; }
/** * Normally sets the path and a few attributes that the JSPs are likely to need. Also verifies the * login information. If necessary, just redirects to the login page. * * @param target * @param request * @param httpServletResponse * @param secured * @return true if the request is already handled so the .jsp shouldn't get called * @throws Exception */ private boolean prepareForJspGet( String target, Request request, HttpServletResponse httpServletResponse, boolean secured) throws Exception { LoginInfo.SessionInfo sessionInfo = UserHelpers.getSessionInfo(request); LOG.info( String.format( "hndl - %s ; %s; %s ; %s", target, request.getPathInfo(), request.getMethod(), secured ? "secured" : "not secured")); String path = request.getUri().getDecodedPath(); boolean redirectToLogin = path.equals(PATH_LOGOUT); LoginInfo loginInfo = null; if (sessionInfo.isNull()) { redirectToLogin = true; LOG.info("Null session info. Logging in again."); } else { loginInfo = loginInfoDb.get( sessionInfo.browserId, sessionInfo.sessionId); // ttt2 use a cache, to avoid going to DB if (loginInfo == null || loginInfo.expiresOn < System.currentTimeMillis()) { LOG.info("Session has expired. Logging in again. Info: " + loginInfo); redirectToLogin = true; } } if (!path.equals(PATH_LOGIN) && !path.equals(PATH_SIGNUP) && !path.equals(PATH_ERROR)) { if (redirectToLogin) { // ttt2 perhaps store URI, to return to it after login logOut(sessionInfo.browserId); addLoginParams(request, loginInfo); httpServletResponse.sendRedirect(PATH_LOGIN); return true; } User user = userDb.get(loginInfo.userId); if (user == null) { WebUtils.redirectToError("Unknown user", request, httpServletResponse); return true; } if (!user.active) { WebUtils.redirectToError("Account is not active", request, httpServletResponse); return true; } request.setAttribute(VAR_FEED_DB, feedDb); request.setAttribute(VAR_USER_DB, userDb); request.setAttribute(VAR_ARTICLE_DB, articleDb); request.setAttribute(VAR_READ_ARTICLES_COLL_DB, readArticlesCollDb); request.setAttribute(VAR_USER, user); request.setAttribute(VAR_LOGIN_INFO, loginInfo); MultiMap<String> params = new MultiMap<>(); params.put(PARAM_PATH, path); request.setParameters(params); } if (path.equals(PATH_LOGIN)) { addLoginParams(request, loginInfo); } return false; }
protected void onError(Throwable failure) { final List<AsyncListener> listeners; final AsyncContextEvent event; final Request baseRequest = _channel.getRequest(); int code = HttpStatus.INTERNAL_SERVER_ERROR_500; String reason = null; if (failure instanceof BadMessageException) { BadMessageException bme = (BadMessageException) failure; code = bme.getCode(); reason = bme.getReason(); } else if (failure instanceof UnavailableException) { if (((UnavailableException) failure).isPermanent()) code = HttpStatus.NOT_FOUND_404; else code = HttpStatus.SERVICE_UNAVAILABLE_503; } try (Locker.Lock lock = _locker.lock()) { if (DEBUG) LOG.debug("onError {} {}", toStringLocked(), failure); // Set error on request. if (_event != null) { if (_event.getThrowable() != null) throw new IllegalStateException("Error already set", _event.getThrowable()); _event.addThrowable(failure); _event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE, code); _event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION, failure); _event .getSuppliedRequest() .setAttribute( RequestDispatcher.ERROR_EXCEPTION_TYPE, failure == null ? null : failure.getClass()); _event.getSuppliedRequest().setAttribute(ERROR_MESSAGE, reason != null ? reason : null); } else { Throwable error = (Throwable) baseRequest.getAttribute(ERROR_EXCEPTION); if (error != null) throw new IllegalStateException("Error already set", error); baseRequest.setAttribute(ERROR_STATUS_CODE, code); baseRequest.setAttribute(ERROR_EXCEPTION, failure); baseRequest.setAttribute( RequestDispatcher.ERROR_EXCEPTION_TYPE, failure == null ? null : failure.getClass()); baseRequest.setAttribute(ERROR_MESSAGE, reason != null ? reason : null); } // Are we blocking? if (_async == null) { // Only called from within HttpChannel Handling, so much be dispatched, let's stay // dispatched! if (_state == State.DISPATCHED) { _state = State.THROWN; return; } throw new IllegalStateException(this.getStatusStringLocked()); } // We are Async _async = Async.ERRORING; listeners = _asyncListeners; event = _event; } if (listeners != null) { Runnable task = new Runnable() { @Override public void run() { for (AsyncListener listener : listeners) { try { listener.onError(event); } catch (Throwable x) { LOG.warn(x + " while invoking onError listener " + listener); LOG.debug(x); } } } @Override public String toString() { return "onError"; } }; runInContext(event, task); } boolean dispatch = false; try (Locker.Lock lock = _locker.lock()) { switch (_async) { case ERRORING: { // Still in this state ? The listeners did not invoke API methods // and the container must provide a default error dispatch. _async = Async.ERRORED; break; } case DISPATCH: case COMPLETE: { // The listeners called dispatch() or complete(). break; } default: { throw new IllegalStateException(toString()); } } if (_state == State.ASYNC_WAIT) { _state = State.ASYNC_WOKEN; dispatch = true; } } if (dispatch) { if (LOG.isDebugEnabled()) LOG.debug("Dispatch after error {}", this); scheduleDispatch(); } }