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
Welcome to Brainy Software
就是说,服务器端在接收到客户端请求并进行处理之后,会向客户端返回一个响应信息。
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的源码。