Example #1
0
 /**
  * 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);
    }
  }
Example #3
0
  @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, "&", "&amp;");
          message = StringUtil.replace(message, "<", "&lt;");
          message = StringUtil.replace(message, ">", "&gt;");
        }
        String uri = request.getRequestURI();
        if (uri != null) {
          uri = StringUtil.replace(uri, "&", "&amp;");
          uri = StringUtil.replace(uri, "<", "&lt;");
          uri = StringUtil.replace(uri, ">", "&gt;");
        }

        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();
  }
Example #4
0
  /** @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();
    }
  }