`
Tyrion
  • 浏览: 260925 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Tomcat7中一次请求处理的前世今生(四)Tomcat7阀机制原理

阅读更多

通过前面的三篇文章看到了一次客户端连接在Tomcat内部被转换成了请求对象(org.apache.catalina.connector.Request类的实例),并在该请求对象内部将与本次请求相关的Host、Context、Wrapper对象的引用。本文主要分析该请求对象在容器内部流转的经过。

再来看一下Tomcat7内部的组件结构图:


其实这张图已经给出了答案,在Connector接收到一次连接并转化成请求(Request)后,会将请求传递到Engine的管道(Pipeline)的阀(ValveA)中。请求在Engine的管道中最终会传递到Engine Valve这个阀中。接着请求会从Engine Valve传递到一个Host的管道中,在该管道中最后传递到Host Valve这个阀里。接着从Host Valve传递到一个Context的管道中,在该管道中最后传递到Context Valve中。接下来请求会传递到Wrapper C内的管道所包含的阀Wrapper Valve中,在这里会经过一个过滤器链(Filter Chain),最终送到一个Servlet中。

 

如果你不了解上面这段文字描述中所谓的管道(Pipeline)和阀(Valve)的概念,别急,下面会讲到这个。先从源码层面看下这段文字描述的经过。上一篇文里提到的org.apache.catalina.connector.CoyoteAdapter类的service方法:

    public void service(org.apache.coyote.Request req,
                        org.apache.coyote.Response res)
        throws Exception {

        Request request = (Request) req.getNote(ADAPTER_NOTES);
        Response response = (Response) res.getNote(ADAPTER_NOTES);

        if (request == null) {

            // Create objects
            request = connector.createRequest();
            request.setCoyoteRequest(req);
            response = connector.createResponse();
            response.setCoyoteResponse(res);

            // Link objects
            request.setResponse(response);
            response.setRequest(request);

            // Set as notes
            req.setNote(ADAPTER_NOTES, request);
            res.setNote(ADAPTER_NOTES, response);

            // Set query string encoding
            req.getParameters().setQueryStringEncoding
                (connector.getURIEncoding());

        }

        if (connector.getXpoweredBy()) {
            response.addHeader("X-Powered-By", POWERED_BY);
        }

        boolean comet = false;
        boolean async = false;

        try {

            // Parse and set Catalina and configuration specific
            // request parameters
            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
            boolean postParseSuccess = postParseRequest(req, request, res, response);
            if (postParseSuccess) {
                //check valves if we support async
                request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                // Calling the container
                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

                if (request.isComet()) {
                    if (!response.isClosed() && !response.isError()) {
                        if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
                            // Invoke a read event right away if there are available bytes
                            if (event(req, res, SocketStatus.OPEN)) {
                                comet = true;
                                res.action(ActionCode.COMET_BEGIN, null);
                            }
                        } else {
                            comet = true;
                            res.action(ActionCode.COMET_BEGIN, null);
                        }
                    } else {
                        // Clear the filter chain, as otherwise it will not be reset elsewhere
                        // since this is a Comet request
                        request.setFilterChain(null);
                    }
                }

            }
            AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
            if (asyncConImpl != null) {
                async = true;
            } else if (!comet) {
                request.finishRequest();
                response.finishResponse();
                if (postParseSuccess &&
                        request.getMappingData().context != null) {
                    // Log only if processing was invoked.
                    // If postParseRequest() failed, it has already logged it.
                    // If context is null this was the start of a comet request
                    // that failed and has already been logged.
                    ((Context) request.getMappingData().context).logAccess(
                            request, response,
                            System.currentTimeMillis() - req.getStartTime(),
                            false);
                }
                req.action(ActionCode.POST_REQUEST , null);
            }

        } catch (IOException e) {
            // Ignore
        } finally {
            req.getRequestProcessor().setWorkerThreadName(null);
            // Recycle the wrapper request and response
            if (!comet && !async) {
                request.recycle();
                response.recycle();
            } else {
                // Clear converters so that the minimum amount of memory
                // is used by this processor
                request.clearEncoders();
                response.clearEncoders();
            }
        }

    }

前一篇文章主要分析了第42行的代码,通过postParseRequest方法的调用请求对象内保存了关于本次请求的具体要执行的Host、Context、Wrapper组件的引用。

看下第47行:

                connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

虽然只有一行,但调用了一堆方法,这里对这些方法逐个分析一下:

connector.getService()获取的是当前connector关联的Service组件,默认情况下获得的就是org.apache.catalina.core.StandardService的对象。其getContainer方法获得的是org.apache.catalina.core.StandardEngine的对象,这段的由来在前面讲Digester的解析文章时,createStartDigester方法中的这段代码:

        digester.addRuleSet(new EngineRuleSet("Server/Service/"));

在EngineRuleSet类的addRuleInstances方法中的这一段代码:

    public void addRuleInstances(Digester digester) {
        
        digester.addObjectCreate(prefix + "Engine",
                                 "org.apache.catalina.core.StandardEngine",
                                 "className");
        digester.addSetProperties(prefix + "Engine");
        digester.addRule(prefix + "Engine",
                         new LifecycleListenerRule
                         ("org.apache.catalina.startup.EngineConfig",
                          "engineConfigClass"));
        digester.addSetNext(prefix + "Engine",
                            "setContainer",
                            "org.apache.catalina.Container");

结合上一段代码可以看出Tomcat启动时,如果碰到server.xml里的Server/Service/Engine节点,先实例化一个org.apache.catalina.core.StandardEngine对象,在第11到13行,会以StandardEngine对象为入参调用org.apache.catalina.core.StandardService的setContainer方法。

 

所以上面connector.getService().getContainer()方法得到的实际上是StandardEngine对象。紧接着的getPipeline方法返回的是StandardEngine类的父类org.apache.catalina.core.ContainerBase类的成员变量pipeline,看下该类中这个变量的声明代码:

    /**
     * The Pipeline object with which this Container is associated.
     */
    protected Pipeline pipeline = new StandardPipeline(this);

所以connector.getService().getContainer().getPipeline()方法返回的是org.apache.catalina.core.StandardPipeline类的对象,该对象就是本文开头部分提到的管道(Pipeline)。

 

下面讲一下Tomcat7中的管道和阀的概念和实现:

所有的管道类都会实现org.apache.catalina.Pipeline这个接口,看下这个接口中定义的方法:


Tomat7中一个管道包含多个阀(Valve),这些阀共分为两类,一类叫基础阀(通过getBasic、setBasic方法调用),一类是普通阀(通过addValve、removeValve调用)。管道都是包含在一个容器当中,所以API里还有getContainer和setContainer方法。一个管道一般有一个基础阀(通过setBasic添加),可以有0到多个普通阀(通过addValve添加)。

所有的阀类都会实现org.apache.catalina.Valve这个接口,看下这个接口中定义的方法:


重点关注setNext、getNext、invoke这三个方法,通过setNext设置该阀的下一阀,通过getNext返回该阀的下一个阀的引用,invoke方法则执行该阀内部自定义的请求处理代码。

Tomcat7里Pipeline的默认实现类是org.apache.catalina.core.StandardPipeline,其内部有三个成员变量:basic、first、container。

    /**
     * The basic Valve (if any) associated with this Pipeline.
     */
    protected Valve basic = null;

    /**
     * The Container with which this Pipeline is associated.
     */
    protected Container container = null;

    /**
     * The first valve associated with this Pipeline.
     */
    protected Valve first = null;

看下该类的addValve方法:

    public void addValve(Valve valve) {
    
        // Validate that we can add this Valve
        if (valve instanceof Contained)
            ((Contained) valve).setContainer(this.container);

        // Start the new component if necessary
        if (getState().isAvailable()) {
            if (valve instanceof Lifecycle) {
                try {
                    ((Lifecycle) valve).start();
                } catch (LifecycleException e) {
                    log.error("StandardPipeline.addValve: start: ", e);
                }
            }
        }

        // Add this Valve to the set associated with this Pipeline
        if (first == null) {
            first = valve;
            valve.setNext(basic);
        } else {
            Valve current = first;
            while (current != null) {
                if (current.getNext() == basic) {
                    current.setNext(valve);
                    valve.setNext(basic);
                    break;
                }
                current = current.getNext();
            }
        }
        
        container.fireContainerEvent(Container.ADD_VALVE_EVENT, valve);
    }

在第18到32行,每次给管道添加一个普通阀的时候如果管道内原来没有普通阀则将新添加的阀作为该管道的成员变量first的引用,如果管道内已有普通阀,则把新加的阀加到所有普通阀链条末端,并且将该阀的下一个阀的引用设置为管道的基础阀。这样管道内的阀结构如下图所示:

 

即Pipeline内部维护first和basic两个阀,其它相关阀通过getNext来获取。

看下getFirst方法的实现:

    public Valve getFirst() {
        if (first != null) {
            return first;
        }
        
        return basic;
    }

如果管道中有普通阀则返回普通阀链条最开始的那个,否则就返回基础阀。

 

在Tomcat7中所有作为普通阀的类的invoke方法实现中都会有这段代码: 

getNext().invoke(request, response);

通过这种机制来保证调用管道最开头一端的阀的invoke方法,最终会执行完该管道相关的所有阀的invoke方法,并且最后执行的必定是该管道基础阀的invoke方法。

 

再回到connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)这段代码的解释,这里将会执行StandardEngine类的管道中的所有阀(包括普通阀和基础阀)的invoke方法,并且最后会执行基础阀的invoke方法

Tomcat7在默认情况下Engine节点没有普通阀,如果想要添加普通阀的话,可以通过在server.xml文件的engine节点下添加Valve节点,参加该文件中的普通阀配置的示例:

<Valve className="org.apache.catalina.authenticator.SingleSignOn" />

那么就来看看StandardEngine类的管道中的基础阀的代码实现。先看下该基础阀设置的代码,在org.apache.catalina.core.StandardEngine对象的构造函数中:

    public StandardEngine() {

        super();
        pipeline.setBasic(new StandardEngineValve());
        /* Set the jmvRoute using the system property jvmRoute */
        try {
            setJvmRoute(System.getProperty("jvmRoute"));
        } catch(Exception ex) {
            log.warn(sm.getString("standardEngine.jvmRouteFail"));
        }
        // By default, the engine will hold the reloading thread
        backgroundProcessorDelay = 10;

    }

第4行即设置基础阀。所以connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)会执行到org.apache.catalina.core.StandardEngineValve类的invoke方法:

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

        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost", 
                              request.getServerName()));
            return;
        }
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(host.getPipeline().isAsyncSupported());
        }

        // Ask this Host to process this request
        host.getPipeline().getFirst().invoke(request, response);

    }

第5行,从请求对象中取出该请求关联的Host(默认情况下是org.apache.catalina.core.StandardHost对象),请求是如何找到关联的Host的请看前一篇文章。经过上述代码分析应该可以看出第18行会执行StandardHost对象的管道内所有的阀的invoke方法。

 

看下StandardHost的构造方法的实现:

    public StandardHost() {

        super();
        pipeline.setBasic(new StandardHostValve());

    }

所以看下org.apache.catalina.core.StandardHostValve类的invoke方法:

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

        // 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());
        }

        // Don't fire listeners during async processing
        // If a request init listener throws an exception, the request is
        // aborted
        boolean asyncAtStart = request.isAsync(); 
        // An async error page may dispatch to another resource. This flag helps
        // ensure an infinite error handling loop is not entered
        boolean errorAtStart = response.isError();
        if (asyncAtStart || context.fireRequestInitEvent(request)) {

            // Ask this Context to process this request
            try {
                context.getPipeline().getFirst().invoke(request, response);
            } catch (Throwable t) {
                ExceptionUtils.handleThrowable(t);
                if (errorAtStart) {
                    container.getLogger().error("Exception Processing " +
                            request.getRequestURI(), t);
                } else {
                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
                    throwable(request, response, t);
                }
            }
    
            // If the request was async at the start and an error occurred then
            // the async error handling will kick-in and that will fire the
            // request destroyed event *after* the error handling has taken
            // place
            if (!(request.isAsync() || (asyncAtStart &&
                    request.getAttribute(
                            RequestDispatcher.ERROR_EXCEPTION) != null))) {
                // Protect against NPEs if context was destroyed during a
                // long running request.
                if (context.getState().isAvailable()) {
                    if (!errorAtStart) {
                        // Error page processing
                        response.setSuspended(false);
    
                        Throwable t = (Throwable) request.getAttribute(
                                RequestDispatcher.ERROR_EXCEPTION);
    
                        if (t != null) {
                            throwable(request, response, t);
                        } else {
                            status(request, response);
                        }
                    }
    
                    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());
        }
    }

第41行,会调用该请求相关的Context的管道内所有的阀的invoke方法,默认情况下Context是org.apache.catalina.core.StandardContext类的对象,其构造方法中设置了管道的基础阀:

    public StandardContext() {

        super();
        pipeline.setBasic(new StandardContextValve());
        broadcaster = new NotificationBroadcasterSupport();
        // Set defaults
        if (!Globals.STRICT_SERVLET_COMPLIANCE) {
            // Strict servlet compliance requires all extension mapped servlets
            // to be checked against welcome files
            resourceOnlyServlets.add("jsp");
        }
    }

看下其基础阀的invoke方法代码:

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

        // Disallow any direct access to resources under WEB-INF or META-INF
        MessageBytes requestPathMB = request.getRequestPathMB();
        if ((requestPathMB.startsWithIgnoreCase("/META-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/META-INF"))
                || (requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0))
                || (requestPathMB.equalsIgnoreCase("/WEB-INF"))) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Select the Wrapper to be used for this Request
        Wrapper wrapper = request.getWrapper();
        if (wrapper == null || wrapper.isUnavailable()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        // Acknowledge the request
        try {
            response.sendAcknowledgement();
        } catch (IOException ioe) {
            container.getLogger().error(sm.getString(
                    "standardContextValve.acknowledgeException"), ioe);
            request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ioe);
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }
        
        if (request.isAsyncSupported()) {
            request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
        }
        wrapper.getPipeline().getFirst().invoke(request, response);
    }

最后的第35行,从请求中取出关联的wrapper对象后调用其管道内所有阀的invoke方法。wrapper对象默认是org.apache.catalina.core.StandardWrapper类的实例,同样是在该类的构造方法中设置的基础阀:

    public StandardWrapper() {

        super();
        swValve=new StandardWrapperValve();
        pipeline.setBasic(swValve);
        broadcaster = new NotificationBroadcasterSupport();

    }

有兴趣可以看下基础阀org.apache.catalina.core.StandardWrapperValve的invoke方法,在这里最终会调用请求的url所匹配的Servlet相关过滤器(filter)的doFilter方法及该Servlet的service方法(这段实现都是在过滤器链ApplicationFilterChain类的doFilter方法中),这里不再贴出代码分析。

 

这里可以看出容器内的Engine、Host、Context、Wrapper容器组件的实现的共通点:

1.这些组件内部都有一个成员变量pipeline,因为它们都是从org.apache.catalina.core.ContainerBase类继承来的,pipeline就定义在这个类中。所以每一个容器内部都关联了一个管道。

2.都是在类的构造方法中设置管道内的基础阀。

3.所有的基础阀的实现最后都会调用其下一级容器(直接从请求中获取下一级容器对象的引用,在前一篇文章的分析中已经设置了与该请求相关的各级具体组件的引用)的getPipeline().getFirst().invoke()方法,直到Wrapper组件。因为Wrapper是对一个Servlet的包装,所以它的基础阀内部调用的过滤器链的doFilter方法和Servlet的service方法。

 

正是通过这种管道和阀的机制及上述的3点前提,使得请求可以从连接器内一步一步流转到具体Servlet的service方法中。这样,关于本系列文章《Tomcat7中一次请求处理的前世今生》介绍完毕,从中可以看出在浏览器发出一次Socket连接请求之后Tomcat容器内运转处理的大致流程。

  • 大小: 76.4 KB
  • 大小: 20 KB
  • 大小: 18.7 KB
  • 大小: 11.3 KB
  • 大小: 44.6 KB
3
1
分享到:
评论
3 楼 wgsddfe 2015-02-09  
顶一个,楼主写的真好
2 楼 Tyrion 2013-11-03  
flywangfei 写道
StandardPipeline 怎么初始化的

在所有容器组件的父类org.apache.catalina.core.ContainerBase类里面声明即初始化了啊:

    /**
     * The Pipeline object with which this Container is associated.
     */
    protected Pipeline pipeline = new StandardPipeline(this);
1 楼 flywangfei 2013-11-03  
StandardPipeline 怎么初始化的

相关推荐

    tomcat处理一个http请求的详细过程

    tomcat中server配置文件的结构,以及处理一个http请求的全过程

    Tomcat怎样防止跨站请求伪造(CSRF) 1

    Tomcat 防止跨站请求伪造(CSRF)机制浅析 在 Web 应用开发中,跨站请求伪造(CSRF)是一种常见的安全威胁。跨站请求伪造攻击是指攻击者诱骗受信任用户访问恶意网站,从而使得恶意网站能以用户身份对受信任网站执行...

    tomcat 7 和 tomcat 8

    3. NIO(非阻塞I/O)改进:Tomcat 7增强了NIO连接器,提高了服务器处理并发请求的能力,提升了性能。 4. 配置简化:引入了web.xml的注解配置,使得部署描述符的编写更为简洁。 5. 新的安全特性:增加了Secure ...

    tomcat7,tomcat8,tomcat9

    Tomcat是Apache软件基金会的Jakarta项目中的一个核心项目,是一个开源的Servlet容器,它实现了Java Servlet和JavaServer Pages(JSP)规范。Tomcat7、Tomcat8和Tomcat9是不同版本的Tomcat,每个版本都有其特性和改进...

    tomcat7源码下载

    Tomcat7是一款广泛使用的开源Java Servlet容器,它实现了Java EE中的Web应用服务器标准,尤其是Servlet和JSP规范。源码下载是开发者深入理解其内部工作原理的重要途径,本篇文章将围绕Tomcat7源码进行详细探讨。 一...

    tomcat的工作原理

    《深入解析Tomcat的工作原理》 一、简介与概览 Apache Tomcat是基于Java的开源Web服务器,它不仅能够运行Java Servlet,还能部署和执行Java Server Pages (JSP)技术,以及支持Java Web应用程序所需的其他标准协议...

    tomcat7tomcat8tomcat9

    Tomcat 8是在2013年推出的,它是Tomcat历史上最大的一次更新。这个版本引入了Servlet 3.1、JSP 2.3和EL 3.0等新规范,增加了对WebSocket的支持,使得实时通信变得更加便捷。此外,Tomcat 8在性能和安全性上都有所...

    TOMCAT原理详解及请求过程

    ### TOMCAT原理详解及请求过程 #### 一、TOMCAT概述 TOMCAT是一个开源的免费的轻量级Web应用服务器,它主要作为一个JSP/Servlet容器而被广泛使用。Tomcat支持Java Servlet和JavaServer Pages (JSP)技术,并且提供...

    tomcat 7 最新版本 apache-tomcat-7.0.109

    Apache Tomcat 7是Apache软件基金会的一个开源项目,专门用于实现Java Servlet和JavaServer Pages(JSP)技术的标准,以及Java EE的Web应用程序部署。这个最新版本,即7.0.109,包含了对先前版本的改进和修复,旨在...

    Tomcat请求处理UML序列图

    通过对Tomcat请求处理流程中各个组件的解析,我们可以更深入地理解Tomcat的工作原理。这种基于UML序列图的描述方式不仅能够清晰地展现各个组件之间的交互关系,还能够帮助开发者更好地理解Tomcat的架构设计思想。这...

    linux64_tomcat7

    标题中的"linux64_tomcat7"暗示了这是一个适用于64位Linux操作系统的Tomcat 7版本。Linux操作系统以其稳定性和安全性而受到广大服务器管理员的青睐,64位系统则能够更好地支持内存资源的使用,这对于处理大型Java...

    Tomcat深入剖析pdf+源码(Tomcat运行原理)

    3. **Servlet生命周期**:Servlet在Tomcat中的生命周期包括加载、初始化、服务、销毁四个阶段。Tomcat通过Servlet容器管理Servlet实例,确保其正确地创建、初始化和销毁。 4. **请求处理**:当一个HTTP请求到达时,...

    tomcat 7到9 版本

    这里提到的资源包含了Tomcat 7、8、8.5和9四个主要版本,这些版本都是为Java Web开发者准备的,特别是针对Eclipse集成开发环境的使用者。 Tomcat 7是Apache Tomcat的一个重要版本,它支持Java EE 6规范,包括...

    tomcat7安装使用及jvm连接数参数调优

    指定了当所有可以使用的处理请求的线程数都被使用时,可以放到处理队列中的请求数。 三、Tomcat7 的 JVM 内存使用情况 Tomcat7 的 JVM 内存使用情况可以通过访问 http://localhost:8080/manager/status 来查看。...

    tomcat 分配请求之——socket获取请求

    对于每个接收到的请求,Tomcat会从线程池中取出一个空闲线程来处理。线程会读取Socket中的剩余数据,解析请求,执行相应的Servlet或JSP,然后将结果写回Socket,最后关闭Socket连接。在这个过程中,线程池管理着线程...

    tomcat7源码

    Tomcat7是一款广泛使用的开源Java Servlet容器,它实现了Java EE中的Servlet和JSP规范。源码分析是提升开发者对服务器内部运作机制理解的重要途径,尤其对于Tomcat这样的核心组件,源码的学习能够帮助我们更深入地...

    tomcat7-Kylin-ARM64.rar

    标题中的"tomcat7-Kylin-ARM64.rar"表明这是一个针对银河麒麟操作系统的Tomcat7服务器的ARM64架构版本的压缩包。银河麒麟是国产化操作系统,旨在为国内用户提供安全、自主可控的软件环境。而ARM64则是处理器架构,常...

    tomcat GET请求与POST请求

    4. **错误处理**:无论哪种请求,都需要适当的错误处理机制,以提供清晰的错误消息并防止信息泄露。 5. **性能**:GET请求通常更快,因为它们不携带请求体,而POST请求可能需要更多时间来处理较大的数据量。 总之,...

    ngnix tomcat7 redis session manager jar包

    标题 "ngnix tomcat7 redis session manager jar包" 提供了我们正在处理的是一个用于在Tomcat7服务器上利用Redis实现session共享的解决方案。这个解决方案通常涉及到将Web应用程序的会话数据存储在Redis这样的分布式...

Global site tag (gtag.js) - Google Analytics