@Override
  public void invoke(Request request, Response response) throws IOException, ServletException {

    /*
     * mod_header converts the '\n' into ' ' so we have to rebuild the client
     * certificate
     */
    String strcert0 = mygetHeader(request, "ssl_client_cert");
    if (strcert0 != null && strcert0.length() > 28) {
      String strcert1 = strcert0.replace(' ', '\n');
      String strcert2 = strcert1.substring(28, strcert1.length() - 26);
      String strcert3 = "-----BEGIN CERTIFICATE-----\n";
      String strcert4 = strcert3.concat(strcert2);
      String strcerts = strcert4.concat("\n-----END CERTIFICATE-----\n");
      // ByteArrayInputStream bais = new
      // ByteArrayInputStream(strcerts.getBytes("UTF-8"));
      ByteArrayInputStream bais =
          new ByteArrayInputStream(strcerts.getBytes(Charset.defaultCharset()));
      X509Certificate jsseCerts[] = null;
      String providerName = (String) request.getConnector().getProperty("clientCertProvider");
      try {
        CertificateFactory cf;
        if (providerName == null) {
          cf = CertificateFactory.getInstance("X.509");
        } else {
          cf = CertificateFactory.getInstance("X.509", providerName);
        }
        X509Certificate cert = (X509Certificate) cf.generateCertificate(bais);
        jsseCerts = new X509Certificate[1];
        jsseCerts[0] = cert;
      } catch (java.security.cert.CertificateException e) {
        log.warn(sm.getString("sslValve.certError", strcerts), e);
      } catch (NoSuchProviderException e) {
        log.error(sm.getString("sslValve.invalidProvider", providerName), e);
      }
      request.setAttribute(Globals.CERTIFICATES_ATTR, jsseCerts);
    }
    strcert0 = mygetHeader(request, "ssl_cipher");
    if (strcert0 != null) {
      request.setAttribute(Globals.CIPHER_SUITE_ATTR, strcert0);
    }
    strcert0 = mygetHeader(request, "ssl_session_id");
    if (strcert0 != null) {
      request.setAttribute(Globals.SSL_SESSION_ID_ATTR, strcert0);
      request.setAttribute(Globals.SSL_SESSION_ID_TOMCAT_ATTR, strcert0);
    }
    strcert0 = mygetHeader(request, "ssl_cipher_usekeysize");
    if (strcert0 != null) {
      request.setAttribute(Globals.KEY_SIZE_ATTR, Integer.valueOf(strcert0));
    }
    getNext().invoke(request, response);
  }
 /**
  * Mark Request that processed at primary node with attribute primaryIndicatorName
  *
  * @param request
  * @throws IOException
  */
 protected void createPrimaryIndicator(Request request) throws IOException {
   String id = request.getRequestedSessionId();
   if ((id != null) && (id.length() > 0)) {
     Manager manager = request.getContext().getManager();
     Session session = manager.findSession(id);
     if (session instanceof ClusterSession) {
       ClusterSession cses = (ClusterSession) session;
       if (log.isDebugEnabled())
         log.debug(
             sm.getString(
                 "ReplicationValve.session.indicator",
                 request.getContext().getName(),
                 id,
                 primaryIndicatorName,
                 Boolean.valueOf(cses.isPrimarySession())));
       request.setAttribute(
           primaryIndicatorName, cses.isPrimarySession() ? Boolean.TRUE : Boolean.FALSE);
     } else {
       if (log.isDebugEnabled()) {
         if (session != null) {
           log.debug(
               sm.getString("ReplicationValve.session.found", request.getContext().getName(), id));
         } else {
           log.debug(
               sm.getString(
                   "ReplicationValve.session.invalid", request.getContext().getName(), id));
         }
       }
     }
   }
 }
  /**
   * Handle the HTTP status code (and corresponding message) generated while processing the
   * specified Request to produce the specified Response. Any exceptions that occur during
   * generation of the error report are logged and swallowed.
   *
   * @param request The request being processed
   * @param response The response being generated
   */
  private void status(Request request, Response response) {

    int statusCode = response.getStatus();

    // Handle a custom error page for this status code
    Context context = request.getContext();
    if (context == null) return;

    /* Only look for error pages when isError() is set.
     * isError() is set when response.sendError() is invoked. This
     * allows custom error pages without relying on default from
     * web.xml.
     */
    if (!response.isError()) return;

    ErrorPage errorPage = context.findErrorPage(statusCode);
    if (errorPage == null) {
      // Look for a default error page
      errorPage = context.findErrorPage(0);
    }
    if (errorPage != null && response.setErrorReported()) {
      response.setAppCommitted(false);
      request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, Integer.valueOf(statusCode));

      String message = response.getMessage();
      if (message == null) message = "";
      request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message);
      request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, errorPage.getLocation());
      request.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.ERROR);

      Wrapper wrapper = request.getWrapper();
      if (wrapper != null)
        request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, wrapper.getName());
      request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
      if (custom(request, response, errorPage)) {
        try {
          response.finishResponse();
        } catch (ClientAbortException e) {
          // Ignore
        } catch (IOException e) {
          container.getLogger().warn("Exception Processing " + errorPage, e);
        }
      }
    }
  }
  /**
   * Handle the specified Throwable encountered while processing the specified Request to produce
   * the specified Response. Any exceptions that occur during generation of the exception report are
   * logged and swallowed.
   *
   * @param request The request being processed
   * @param response The response being generated
   * @param throwable The exception that occurred (which possibly wraps a root cause exception
   */
  protected void throwable(Request request, Response response, Throwable throwable) {
    Context context = request.getContext();
    if (context == null) return;

    Throwable realError = throwable;

    if (realError instanceof ServletException) {
      realError = ((ServletException) realError).getRootCause();
      if (realError == null) {
        realError = throwable;
      }
    }

    // If this is an aborted request from a client just log it and return
    if (realError instanceof ClientAbortException) {
      if (log.isDebugEnabled()) {
        log.debug(sm.getString("standardHost.clientAbort", realError.getCause().getMessage()));
      }
      return;
    }

    ErrorPage errorPage = findErrorPage(context, throwable);
    if ((errorPage == null) && (realError != throwable)) {
      errorPage = findErrorPage(context, realError);
    }

    if (errorPage != null) {
      if (response.setErrorReported()) {
        response.setAppCommitted(false);
        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, errorPage.getLocation());
        request.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.ERROR);
        request.setAttribute(
            RequestDispatcher.ERROR_STATUS_CODE,
            new Integer(HttpServletResponse.SC_INTERNAL_SERVER_ERROR));
        request.setAttribute(RequestDispatcher.ERROR_MESSAGE, throwable.getMessage());
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, realError);
        Wrapper wrapper = request.getWrapper();
        if (wrapper != null)
          request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, wrapper.getName());
        request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI());
        request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, realError.getClass());
        if (custom(request, response, errorPage)) {
          try {
            response.finishResponse();
          } catch (IOException e) {
            container.getLogger().warn("Exception Processing " + errorPage, e);
          }
        }
      }
    } else {
      // A custom error-page has not been defined for the exception
      // that was thrown during request processing. Check if an
      // error-page for error code 500 was specified and if so,
      // send that page back as the response.
      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      // The response is an error
      response.setError();

      status(request, response);
    }
  }
  /**
   * Select the appropriate child Context to process this request, based on the specified request
   * URI. If no matching Context can be found, return an appropriate HTTP error.
   *
   * @param request Request to be processed
   * @param response Response to be produced
   * @exception IOException if an input/output error occurred
   * @exception ServletException if a servlet error occurred
   */
  @Override
  public final void invoke(Request request, Response response)
      throws IOException, ServletException {

    /*
     * xujb:
     * 和Engine一样,调用下层的context,让它来做一些处理
     * 自身也处理了一些的逻辑
     *
     * */

    // Select the Context to be used for this Request
    Context context = request.getContext();
    if (context == null) {
      response.sendError(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR, sm.getString("standardHost.noContext"));
      return;
    }

    // Bind the context CL to the current thread
    if (context.getLoader() != null) {
      // Not started - it should check for availability first
      // This should eventually move to Engine, it's generic.
      if (Globals.IS_SECURITY_ENABLED) {
        PrivilegedAction<Void> pa = new PrivilegedSetTccl(context.getLoader().getClassLoader());
        AccessController.doPrivileged(pa);
      } else {
        Thread.currentThread().setContextClassLoader(context.getLoader().getClassLoader());
      }
    }
    if (request.isAsyncSupported()) {
      request.setAsyncSupported(context.getPipeline().isAsyncSupported());
    }

    boolean asyncAtStart = request.isAsync();
    boolean asyncDispatching = request.isAsyncDispatching();
    if (asyncAtStart || context.fireRequestInitEvent(request)) {

      // Ask this Context to process this request. Requests that are in
      // async mode and are not being dispatched to this resource must be
      // in error and have been routed here to check for application
      // defined error pages.
      try {
        if (!asyncAtStart || asyncDispatching) {
          context.getPipeline().getFirst().invoke(request, response);
        } else {
          // Make sure this request/response is here because an error
          // report is required.
          if (!response.isErrorReportRequired()) {
            throw new IllegalStateException(sm.getString("standardHost.asyncStateError"));
          }
        }
      } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        // If a new error occurred while trying to report a previous
        // error simply log the new error and allow the original error
        // to be reported.
        if (response.isErrorReportRequired()) {
          container.getLogger().error("Exception Processing " + request.getRequestURI(), t);
        } else {
          request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
          throwable(request, response, t);
        }
      }

      // Now that the request/response pair is back under container
      // control lift the suspension so that the error handling can
      // complete and/or the container can flush any remaining data
      response.setSuspended(false);

      Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);

      // Protect against NPEs if the context was destroyed during a
      // long running request.
      if (!context.getState().isAvailable()) {
        return;
      }

      // Look for (and render if found) an application level error page
      if (response.isErrorReportRequired()) {
        if (t != null) {
          throwable(request, response, t);
        } else {
          status(request, response);
        }
      }

      if (!request.isAsync() && (!asyncAtStart || !response.isErrorReportRequired())) {
        context.fireRequestDestroyEvent(request);
      }
    }

    // Access a session (if present) to update last accessed time, based on a
    // strict interpretation of the specification
    if (ACCESS_SESSION) {
      request.getSession(false);
    }

    // Restore the context classloader
    if (Globals.IS_SECURITY_ENABLED) {
      PrivilegedAction<Void> pa = new PrivilegedSetTccl(StandardHostValve.class.getClassLoader());
      AccessController.doPrivileged(pa);
    } else {
      Thread.currentThread().setContextClassLoader(StandardHostValve.class.getClassLoader());
    }
  }