`
臻是二哥
  • 浏览: 189188 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
博客专栏
Group-logo
Java技术分享
浏览量:0
社区版块
存档分类
最新评论

tomct处理请求的流程

阅读更多
在讲项目之前,先讲解一些基础知识。
1. HTTP请求格式,一个HTTP请求包括以下三个部分:

请求方法(POST,GET等) URI 协议版本
请求头部
请求实体

举例如下:
POST /examples/default.jsp HTTP/1.1

Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate

lastName=Franks&firstName=Michael

就是说,当在浏览器输入 http://localhost/examples/default.jsp?lastName=Franks&firstName=Michael 之后,客户端浏览器会把请求变成如上面的格式发送给服务器端。

2. HTTP响应格式,一个HTTP响应包括以下三个部分:

协议版本 状态码 状态描述
响应头部
响应实体

举例如下:
HTTP/1.1 200 OK

Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
Content-Length: 112

<html>
<head><title>HTTP Response Example</title></head>
<body> Welcome to Brainy Software </body>
</html>

就是说,服务器端在接收到客户端请求并进行处理之后,会向客户端返回一个响应信息。

3. Socket

网络编程一定会涉及到客户端和服务器端,通信的过程实际上就是客户端和服务器端互相发送消息(消息就是满足一定格式的字符串)的过程。读者可以理解为在客户端有一个Socket,客户端仅仅需要把消息发送给Socket,这个Socket就会在服务器创建一个“分身”,在服务端创建一个Socket“分身”的过程对于用户是透明的。服务端和这个Socket“分身”通信就相当于和客户端通信了。

因此,可以理解为,客户端和服务器端通信的过程,就是客户端和服务器端与所在机器上的Socket通信的过程,通信的本质就是字符串传输,只不过这个字符串是满足一定格式的。

4. Servlet容器

在http请求中,有大量的对静态资源的请求,比如请求一个文本文件:这样的请求处理起来很容易:首先,服务端解析http请求并找出要请求的静态资源,然后将存储在服务端本地的静态资源通过Socket发送给客户端,这个请求就结束了。这个过程仅仅需要处理各种字符串和流操作即可。

但是,还有一些http请求,它们并非返回静态资源这么简单。它们需要有各种类型的后台程序来处理它们。每一类的后台程序被称作一个Servlet。但过程还是一样:客户端发送请求,服务端从自己的Socket中读取到请求的信息(满足一定格式的字符串),然后选择合适的ervlet来进行处理,并将结果返回给自己的Socket。从这个过程中我们发现:不同的Servlet它们对请求的处理过程都是相同的,不同的仅仅是Servlet本身。

那么从软件复用的角度来说,我们希望仅仅关心Servlet的实现即可,其他的诸如请求的解析过程可以交给一个固定的组件,这个组件称之为Servlet容器,常见的Servlet容器是Tomcat.

有了上面的基础知识,咱们开始讲项目是怎么搞得吧!

当我们在浏览器输入一个url之后,首先浏览器(即客户端)会将我们的请求格式化为满足ttp标准的字符串,然后发送到浏览器所在机器的Socket。之后浏览器会在服务器端建立一个当前socket的“分身”。假设此时服务端的tomcat已经在运行了。Tomcat中有个叫做connector的组件,connector组件的一生很纯粹,只做一件事:一个劲的检查是否有新的Socket“分身”
到来,如果有就将该Socket交给processor。

processor也是tomcat的组件,它隶属于connector,是connector的部下。一个connector有一群一模一样的processor部下(processor池)。因此,connector检查到有新的Socket"分身"到来之后,就随便找一个processor部下,让他负责接待。尽管connector有很多processor部下,无奈connector太纯粹,有时接收到超多的socket,这个时候自己的processor部下不够用了,只能抛弃客户,把接收到的socket扔掉了。做人要厚道,大家不要学习connector哈。

connector将socket的接待任务交给processor之后,就又去做自己那个纯粹的工作了。Processor负责接待socket,从socket中读取http请求的信息,从这些信息中提取有价值的内容构造出request对象和response对象,然后将request对象和response对象交给领导connector的合作伙伴container。container不像connector那样纯粹,但他很友好,将剩下的工作包了。
processor很开心,因为container接替了自己未完成的工作,这样自己就可以回到领导connector身边效劳了。

container是一个大老板。它手下光伙计就分四个级别,分别为Engine,Host,Context,Wrapper。对于一般的“活儿”,container不会去麻烦Engine和Host。毕竟好钢要用在刀刃上。对于从processor接来的活,container通常会让伙计Context来接手。这样就变成了,Context来处理processor传递来的request对象和response对象。

Context是个聪明人,它想把任务交给自己的Wrapper部下。但责任感告诉他必须完成自己应该完成的事情。因此,Context想了一个办法,它将任务分成若干阶段(每个阶段叫一个Value),能够交给Wrapper部下做的称之为Basic阶段(BasicValue)。Context完成了需要自己负责的阶段后,就执行了BasicValue阶段。在BasicValue阶段中,Context把最合适的Wrapper部下叫来去完成余下的任务。

其实Context有好多Wrapper部下,但每个Wrapper部下能干的活不一样,实际上每个Wrapper部下专门对应一个Servlet程序,你说对了,Servlet就是我们在J2EE中需要完成编写的那部分。

Wrapper部下也是聪明人,他向自己的领导Context学习,也将任务分成若干阶段(每个阶段叫一个Value),能够交给部下做的称之为Basic阶段(BasicValue)。但Wrapper没部下啊,没关系Wrapper有Servlet,可以将任务外包给Servlet啊。有了这个想法,Wrapper开始干活了:他向完成自己需要负责的Value,然后外包给Servlet。

Servlet从request对象中获得请求的信息,然后将结果写入到response中,然后通知甲方Wrapper,Wrapper通知领导Context,Context通知老板Container。Container通知processor,processor将response返回给socket即可。

奇怪,为啥processor没有通知老板connector,因为connector是个纯粹的人。哈哈,自此,tomcat就完成了一个来自浏览器的项目。你懂了吗?

上面是tomcat的核心工作流程,如果对实现细节感兴趣的话,就继续看吧!

HttpConnector的run()方法:接收socket并交给processor
    public void run() {
        // Loop until we receive a shutdown command
	while (!stopped) {
	    // Accept the next incoming connection from the server socket
	    Socket socket = null;
	    try {
			socket = serverSocket.accept();
            socket.setSoTimeout(connectionTimeout);
	    } catch (IOException e) {
		if (started && !stopped)
		    log("accept: ", e);
		break;
	    }

	    // Hand this socket off to an appropriate processor
	    HttpProcessor processor = createProcessor();//从processor池中获得对象
	    if (processor == null) {
		try {
		    log(sm.getString("httpConnector.noProcessor"));
		    socket.close();
		} catch (IOException e) {
		    ;
		}
		continue;
	    }
	    processor.assign(socket);//交给processor

	  
	}
        ......
    }



HttpProcessor的run()方法:接收connector传来的socket,并处理
 public void run() {
        // Process requests until we receive a shutdown signal
	while (!stopped) {
	    Socket socket = await();//获取socket
	    if (socket == null)
		continue;
	    // Process the request from this socket
	    process(socket);//处理socket
            ......
	}
	......
    }


HttpProcessor的process()方法:创建request,response对象并传递给container
private void process(Socket socket) {
  private void process(Socket socket) {

	boolean ok = true;
	InputStream input = null;
	OutputStream output = null;

	// Construct and initialize the objects we will need
	try {
	    input = new BufferedInputStream(socket.getInputStream(),
	    				    connector.getBufferSize());
	    request.setStream(input);
	    request.setResponse(response);
	    output = socket.getOutputStream();
	    response.setStream(output);
	    response.setRequest(request);
	    ((HttpServletResponse) response.getResponse()).setHeader
		("Server", Constants.ServerInfo);
	} catch (Exception e) {
	    log("process.create", e);
	    ok = false;
	}

	// Parse the incoming request
	try {
	    if (ok) {
		parseConnection(socket);
		parseRequest(input);
		if (!request.getRequest().getProtocol().startsWith("HTTP/0"))
		    parseHeaders(input);
	    }
	} catch (Exception e) {
	    try {
		log("process.parse", e);
		((HttpServletResponse) response.getResponse()).sendError
		    (HttpServletResponse.SC_BAD_REQUEST);
	    } catch (Exception f) {
		;
	    }
	}

	// Ask our Container to process this request
	try {
	    if (ok) {
		connector.getContainer().invoke(request, response);
	    }
	} catch (ServletException e) {
	    log("process.invoke", e);
	    try {
		((HttpServletResponse) response.getResponse()).sendError
		    (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	    } catch (Exception f) {
		;
	    }
	    ok = false;
	} catch (Throwable e) {
	    log("process.invoke", e);
	    try {
		((HttpServletResponse) response.getResponse()).sendError
		    (HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	    } catch (Exception f) {
		;
	    }
	    ok = false;
	}

	// Finish up the handling of the response
	try {
	    if (ok)
		response.finishResponse();
	} catch (IOException e) {
	    log("FIXME-Exception from finishResponse", e);
	}
	try {
	    if (output != null)
		output.flush();
	} catch (IOException e) {
	    log("FIXME-Exception flushing output", e);
	}
	try {
	    if (output != null)
		output.close();
	} catch (IOException e) {
	    log("FIXME-Exception closing output", e);
	}

	// Finish up the handling of the request
	try {
	    if (ok)
		request.finishRequest();
	} catch (IOException e) {
	    log("FIXME-Exception from finishRequest", e);
	}
	try {
	    if (input != null)
	        input.close();
	} catch (IOException e) {
	    log("FIXME-Exception closing input", e);
	}

	// Finish up the handling of the socket connection itself
	try {
	    socket.close();
	} catch (IOException e) {
	    log("FIXME-Exception closing socket", e);
	}
	socket = null;

    }


ContainerBase的invok()方法:先调用其他阀门,在调用基础阀门
    public void invoke(Request request, Response response)
	throws IOException, ServletException {

	if (first != null)
	    first.invoke(request, response);
	else if (basic != null)
	    basic.invoke(request, response);
	else
	    throw new IllegalStateException
		(sm.getString("containerBase.notConfigured"));

    }

基础阀门的invoke()方法:
 public void invoke(Request request, Response response)
	throws IOException, ServletException {

	// Validate the request and response object types
	if (!(request.getRequest() instanceof HttpServletRequest) ||
	    !(response.getResponse() instanceof HttpServletResponse)) {
	    return;	// NOTE - Not much else we can do generically
	}

        // Disallow any direct access to resources under WEB-INF or META-INF
        String contextPath =
            ((HttpServletRequest) request.getRequest()).getContextPath();
        String requestURI =
            ((HttpServletRequest) request.getRequest()).getRequestURI();
        String relativeURI =
            requestURI.substring(contextPath.length()).toUpperCase();
        if (relativeURI.equals("/META-INF") ||
            relativeURI.equals("/WEB-INF") ||
            relativeURI.startsWith("/META-INF/") ||
            relativeURI.startsWith("/WEB-INF/")) {
            notFound(requestURI, (HttpServletResponse) response.getResponse());
            try {
                response.finishResponse();
            } catch (IOException e) {
                ;
            }
            return;
        }

	// Select the Wrapper to be used for this Request
	StandardContext context = (StandardContext) getContainer();
	Wrapper wrapper = (Wrapper) context.map(request, true);
	if (wrapper == null) {
            notFound(requestURI, (HttpServletResponse) response.getResponse());
            try {
                response.finishResponse();
            } catch (IOException e) {
                ;
            }
	    return;
	}

	// Ask this Wrapper to process this Request
	response.setContext(context);

        if (context.isUseNaming()) {
            try {
                // Bind the thread to the context
                ContextBindings.bindThread(context.getName(), context);
            } catch (NamingException e) {
                e.printStackTrace();
            }
        }

	wrapper.invoke(request, response);

        if (context.isUseNaming()) {
            // Unbind the thread to the context
            ContextBindings.unbindThread(context.getName(), context);
        }

    }


总结下Context和Wrapper容器的内部流程:
1. 一个容器内部有一个流水线,容器的invoke方法调用该流水线的invoke方法。
2. 流水线的invoke方法先调用添加到该流水线中非基础阀门的invoke方法,再调用该流水线中的基础阀门的invoke方法。
3. 如果该容器是Context,那么他的流水线的基础阀门的invoke方法:先根据request中存储的内容确定选择哪个wrapper容器,然后调用该wrapper容器的invoke方法。转到步骤1。
4. 如果该容器是Wrapper,那么他的流水线的基础阀门的invoke方法:加载wrapper对应的servlet,然后调用servlet的service方法。

附件是tomcat4.0的源码。
4
2
分享到:
评论
1 楼 masuweng 2016-08-09  

相关推荐

    tomct常见信息

    - **415 Unsupported Media Type**:服务器拒绝以请求的内容格式处理请求。 #### 六、5xx(服务器错误) 5xx 系列的状态码表示服务器在处理请求的过程中发生了错误。 - **500 Internal Server Error**:服务器...

    tomct6.0.36

    Servlet是Java平台上的服务器端组件,它可以扩展Web服务器的功能,处理来自客户端的请求,并返回相应的响应。JSP则是一种视图技术,允许开发者将静态HTML与动态Java代码结合在一起,使网页具有更强的交互性。在...

    apache-tomct-6.0.30.rar

    7. **连接器与协议**:Tomcat支持多种连接器(Connector),如HTTP/1.1,AJP等,用于处理客户端请求。在“conf/server.xml”的Engine、Host和Context元素中配置连接器,可以设定端口、SSL支持、连接超时等参数。 8....

    Linux 下安装JDK,TOMCT,MYSQL,DB2 ,Eclipse文档

    在Linux环境下,安装Java Development Kit (JDK), Tomcat服务器, MySQL数据库, DB2数据库以及Eclipse集成开发环境是常见的IT操作。以下是对这些组件详细安装步骤的概述: 1. **JDK安装**: JDK是Java编程的基础,...

    jsp tomct 管理包

    **JSP(JavaServer Pages)** 是一种动态网页技术,由Sun Microsystems开发,现在由Oracle公司维护。它允许程序员使用Java代码和标记语言来创建交互式网页。... **Tomcat** 是Apache软件基金会的Jakarta项目下的一个...

    Tomcat_Tomcat

    同时,过滤器机制允许在请求处理过程中添加预处理和后处理逻辑。 8. **JSP改进**:JSP 2.2规范引入了一些新的特性,如EL表达式改进,自定义标签库的简化,以及更灵活的脚本元素。 9. **WebSocket支持**:Tomcat 7...

    tomcat源代码

    1. **Catalina**:这是Tomcat的核心模块,负责处理Servlet容器的主要任务,如管理Web应用程序、处理请求和响应等。 2. ** Coyote**:处理HTTP协议的引擎,负责接收和发送网络数据,将HTTP请求转换为内部表示,再将...

    apache-tomcat-9.0.45-windows-x64.zip

    1. **Tomcat的架构**:Tomcat由Catalina(Servlet容器)、Jasper(JSP引擎)、 Coyote(HTTP/HTTPS连接器)等主要组件构成,它们协同工作,使得Tomcat能够处理HTTP请求,解析并执行Java Servlets,以及将JSP文件编译...

    tomcat环境变量配置

    在IT领域,特别是Java开发环境中,Tomcat作为一款开源的Servlet容器...这种做法不仅简化了部署流程,还提高了系统的健壮性和适应性。对于任何希望独立控制Tomcat及其运行环境的开发者来说,这都是一个值得掌握的技巧。

    WEB开发面试宝典中的宝典    

    Servlet容器(如Tomcat)加载Servlet类,调用其`init()`方法进行初始化,接着处理客户端请求,执行`service()`方法,最后当Servlet不再使用时,调用`destroy()`方法进行清理。 【ServletConfig与ServletContext的...

    IIS+TOMCAT 端口整合 系统整合

    通过这样的整合,可以实现在IIS上发布静态网页或ASP.NET应用的同时,将JSP/Servlet等动态请求转发给Tomcat处理。这种方式不仅解决了不同技术栈之间的兼容性问题,还提高了资源利用率。 #### 三、具体步骤详解 ####...

    Docker构建tomcat镜像jdk1.8+tomcat9.zip

    docker制作自定义化的tomcat镜像,满足项目自定义需求,相关文章指导可参考https://blog.csdn.net/Ber_Bai/article/details/119960730?spm=1001.2014.3001.5501

    myeclipse配置

    2、 JDK、Tomct必须使用卖家给版本,且安装在D盘根目录下。 3、 一定要先配置myeclipse再导入项目源码。 4、 如果在导入源码之前,已经将部署程序解压放入了tomcat,一定要提前删除。 5、 如果导入前未进行以上操作...

    jenkins2.235.war

    Jenkins 是一个开源的持续集成(Continuous Integration, CI)和持续交付(Continuous Deployment, CD)工具,它在软件开发流程中扮演着自动化构建、测试和部署的重要角色。Jenkins 2.235.war 文件是 Jenkins 的一个...

    win2000/2003下整合IIS+Tomcat5支持jsp

    ### Win2000/2003下整合IIS+Tomcat5支持JSP的详细配置步骤 本文将详细介绍如何在Windows 2000/2003操作...通过这种方式,可以充分利用IIS的高效静态资源处理能力,同时又能利用Tomcat处理JSP等动态资源的强大功能。

    基于javaweb的物业管理系统

    物业管理系统是一个基于B/S架构的,主要使用JSP,JDBC,Servlet,js等技术,在MyEclipse下进行开发,使用的视SQL Server数据库,部署在Tomcat服务器下。代码包里面包含了数据库文件。

    MyEclipse运行问题

    myeclipse运行jsp出现问题

    Window下安装Tomcat服务器的教程

    Tomcat广泛用于开发和生产环境,特别是在处理动态内容的Web应用程序中。 首先,需要明确的是,安装Tomcat服务器的第一步是要从其官方网站下载相应版本的Tomcat压缩包。解压完成后,便可以开始进行安装。安装过程...

Global site tag (gtag.js) - Google Analytics