Tomcat源码之旅--最简单的Servlet容器实现
2018-03-19 19:18
441 查看
学习Tomcat源码是因为我之前写的《Spring之我见》系列文章,当学习到spring是如何启动的时候涉及到了tomcat从web.xml读取到ContextLoaderListener,从而初始化spring容器。换句话说,spring启动靠的是tomcat的帮助,这让我想先研究tomcat开始。
tomcat我参考的是《深入剖析Tomcat》,虽然介绍的tomcat比较老,还是tomcat4,但是学习思想是最重要的。而且这本书是教你怎么从零实现一个servlet容器,这种学习方法能够让我们知其然,并知其所以然。
最简单的servlet容器需要几个比较重要的组件
HttpServlet 等待并接受请求
Request 封装Request请求
Response 封装Response请求
ServletProcessor 具体处理Servlet请求
StaticResourceProcessor 具体处理静态页面
Socket的介绍我直接摘百度百科吧
对于我肤浅的理解。socket就是操作TCP/IP请求的api,给编程人员带来便利,从这点我们也知道,Tomcat的基础是Socket。
Request实现了ServletRequest,大部分方法因为现在用不上所以没有实现,其中parse()方法从inputStream读取请求信息,读取内容范例如下
而作为最简单的servlet容器,我们先不管Cookie啥信息,只看第一行。嗯,我们读到了是一个GET请求,请求的url是 /servlet/TestServlet ,http协议版本是HTTP/1.1 ,前面说到我们要根据url要分发请求,这就用上了parseUri()方法,它通过字符串处理直接提取出了 “/servlet/TestServlet” 并保存到uri变量。
Response也是同理,多了一个sendStaticResource方法,这个方法是由静态页面处理器(StaticResourceProcessor)直接调用的,用来返回html页面。这个方法现在还没用上,后面会再提。
ServletProcessor只有一个process方法,接受Request和Response参数,这里面涉及类加载器的,大概流程是通过类加载器加载url中同名的类,然后调用service方法,比如请求url是“/servlet/TestServlet”,“/servlet”说明需要调用servlet处理器(ServletProcessor),然后 “TestServlet”说明调用同名的TestServlet类。
ServletProcessor:
TestServlet:继承标准的Servlet接口,复写service方法,service方法会由ServletProcessor调用,到这一块对于学习了Servlet的人就很熟悉了。
这里面有一点改进的地方在于:对于TestServlet 来说,是客户端程序员会编写的类,而ServletProcessor做了什么是不需要关心的,但是service会拿到ServletProcessor给的ServletRequest 和ServletResponse 变量,也就是Request和Response对象,而我们知道Request和Response有一些拓展方法比如 parseUri,这些是不允许 客户端程序员通过强转来调用的。 那如何保证安全性呢?
我们可以通过外观类来实现,我们设计一个外观类RequestWrapper ,它拥有一个Request对象,一般方法都直接调用Request的方法,而对于私密的方法不暴露在RequestWrapper 类中,达到调用安全的目的。
ResponseWrapper一样,不再贴出代码
而对于静态页面处理器就更简单了,只有一个方法,方法也只有一句话
sendStaticResource方法就是之前写在Response 的拓展方法。通过File读取servlet容器中预存的html页面,然后通过outputStream发出去。
对应的分支是v1.1
tomcat我参考的是《深入剖析Tomcat》,虽然介绍的tomcat比较老,还是tomcat4,但是学习思想是最重要的。而且这本书是教你怎么从零实现一个servlet容器,这种学习方法能够让我们知其然,并知其所以然。
最简单的servlet容器需要几个比较重要的组件
HttpServlet 等待并接受请求
Request 封装Request请求
Response 封装Response请求
ServletProcessor 具体处理Servlet请求
StaticResourceProcessor 具体处理静态页面
HttpServlet 容器的入口
我们先看 HttpServlet ,这段代码本质是ServerSocket 阻塞等待请求,如果拿到了就封装Request和Response,然后根据url分发给不同的处理器,处理完后继续轮询等待请求,直到遇到SHUTDOWN_COMMAND命令关闭程序。import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.time.LocalDateTime; import java.util.logging.Logger; public class HttpServlet { private static final Logger log = Logger.getLogger(HttpServlet.class.getName()); // 请求接口 private static final int port = 8088; private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; private boolean shutdown = false; private void await() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(HttpServlet.port, 1, InetAddress.getByName("127.0.0.1")); } catch (Exception e) { e.printStackTrace(); System.exit(1); } while (true) { Socket socket; InputStream input; OutputStream output; try { System.out.println("等待指令。。。。" + LocalDateTime.now().toString()); // 从 socket拿到InputStream 和 OutputStream 并封装成 Request 和 Response socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); Request request = new Request(input); request.parse(); Response response = new Response(output); if (SHUTDOWN_COMMAND.equals(request.getUri())) { break; } response.setRequest(request); // 根据url 分发任务给 servlet处理器(ServletProcessor) 或者 静态页面处理器 (StaticResourceProcessor) if (request.getUri().startsWith("/servlet/")) { ServletProcessor servletProcessor = new ServletProcessor(); servletProcessor.process(request, response); } else { StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor(); staticResourceProcessor.process(request, response); } socket.close(); } catch (Exception e) { } } } public static void main(String[] args) { HttpServlet httpServlet = new HttpServlet(); System.out.println("servlet容器启动成功"); httpServlet.await(); } }
Socket的介绍我直接摘百度百科吧
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。 建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
对于我肤浅的理解。socket就是操作TCP/IP请求的api,给编程人员带来便利,从这点我们也知道,Tomcat的基础是Socket。
Request 与 Response
HttpServlet代码首先提到了Request 和 Response,封装的理由是为了做一些额外的操作,我们看一下Request代码import javax.servlet.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; public class Request implements ServletRequest { private static final int BUFFER_SIZE = 2048; private InputStream inputStream; private String uri; private static final Logger log = Logger.getLogger(Request.class.getName()); /** * 解析请求url */ public String parseUri(String requestUri) { int index1, index2; index1 = requestUri.indexOf(" "); if (index1 != -1) { index2 = requestUri.indexOf(" ", index1 + 1); if (index2 > index1) { return requestUri.substring(index1 + 1, index2); } } return null; } /** * 读取inputStream */ public void parse() { StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[BUFFER_SIZE]; try { i = inputStream.read(buffer); } catch (Exception e) { log.severe(e.toString()); i = -1; } for (int j = 0; j < i; j++) { request.append((char) buffer[j]); } uri = parseUri(request.toString()); } public Request(InputStream inputStream) { this.inputStream = inputStream; } public String getUri() { return uri; } public void setUri(String uri) { this.uri = uri; } public Object getAttribute(String s) { return null; } public Enumeration<String> getAttributeNames() { return null; } public String getCharacterEncoding() { return null; } public void setCharacterEncoding(String s) throws UnsupportedEncodingException { } public int getContentLength() { return 0; } public long getContentLengthLong() { return 0; } public String getContentType() { return null; } public ServletInputStream getInputStream() throws IOException { return null; } public String getParameter(String s) { return null; } public Enumeration<String> getParameterNames() { return null; } public String[] getParameterValues(String s) { return new String[0]; } public Map<String, String[]> getParameterMap() { return null; } public String getProtocol() { return null; } public String getScheme() { return null; } public String getServerName() { return null; } public int getServerPort() { return 0; } public BufferedReader getReader() throws IOException { return null; } public String getRemoteAddr() { return null; } public String getRemoteHost() { return null; } public void setAttribute(String s, Object o) { } public void removeAttribute(String s) { } public Locale getLocale() { return null; } public Enumeration<Locale> getLocales() { return null; } public boolean isSecure() { return false; } public RequestDispatcher getRequestDispatcher(String s) { return null; } public String getRealPath(String s) { return null; } public int getRemotePort() { return 0; } public String getLocalName() { return null; } public String getLocalAddr() { return null; } public int getLocalPort() { return 0; } public ServletContext getServletContext() { return null; } public AsyncContext startAsync() throws IllegalStateException { return null; } public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { return null; } public boolean isAsyncStarted() { return false; } public boolean isAsyncSupported() { return false; } public AsyncContext getAsyncContext() { return null; } public DispatcherType getDispatcherType() { return null; } }
Request实现了ServletRequest,大部分方法因为现在用不上所以没有实现,其中parse()方法从inputStream读取请求信息,读取内容范例如下
GET /servlet/TestServlet HTTP/1.1 Host: localhost:8088 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: cookie_mobile=15921307683; cookie_user_info="{\"firstName\":\"%E8%B4%BE%E7%A7%8B%E7%94%9F\",\"mobile\":\"15921307683\",\"email\":\"\"}"; cookie_user_org="{\"orgName\":\"%E5%BC%80%E5%8F%91%E7%BB%84\",\"orgCode\":\"006\"}"; sessionid=cc8798be-08f6-4051-936f-988a5c846ad7
而作为最简单的servlet容器,我们先不管Cookie啥信息,只看第一行。嗯,我们读到了是一个GET请求,请求的url是 /servlet/TestServlet ,http协议版本是HTTP/1.1 ,前面说到我们要根据url要分发请求,这就用上了parseUri()方法,它通过字符串处理直接提取出了 “/servlet/TestServlet” 并保存到uri变量。
Response也是同理,多了一个sendStaticResource方法,这个方法是由静态页面处理器(StaticResourceProcessor)直接调用的,用来返回html页面。这个方法现在还没用上,后面会再提。
public void sendStaticResource() throws IOException { byte[] bytes = new byte[BUFFER_SIZE]; FileInputStream fis = null; try { File file = new File(WEB_ROOT + "webapp", request.getUri()); fis = new FileInputStream(file); outputStream.write(("HTTP/1.1 200 \r\n" + "Content-Type: text/html\r\n" + "\r\n" ).getBytes()); int ch = fis.read(bytes, 0, BUFFER_SIZE); while (ch != -1) { outputStream.write(bytes, 0, ch); ch = fis.read(bytes, 0, BUFFER_SIZE); } } catch (Exception e) { String errorMsg = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>"; outputStream.write(errorMsg.getBytes()); } finally { outputStream.flush(); outputStream.close(); if (fis != null) { fis.close(); } } }
ServletProcessor StaticResourceProcessor 处理的大脑
介绍完了Request和Response,开始进入核心处理类ServletProcessorServletProcessor只有一个process方法,接受Request和Response参数,这里面涉及类加载器的,大概流程是通过类加载器加载url中同名的类,然后调用service方法,比如请求url是“/servlet/TestServlet”,“/servlet”说明需要调用servlet处理器(ServletProcessor),然后 “TestServlet”说明调用同名的TestServlet类。
ServletProcessor:
import javax.servlet.Servlet; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import java.util.logging.Logger; public class ServletProcessor { private static final Logger log = Logger.getLogger(HttpServlet.class.getName()); public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); URLClassLoader loader = null; try { URL[] urls = new URL[1]; URLStreamHandler streamHandler = null; File classPath = new File(Response.WEB_ROOT + "servlet" + File.separator); String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString(); urls[0] = classPath.toURI().toURL(); loader = new URLClassLoader(urls); } catch (Exception e) { log.severe(e.toString()); } Class myClass = null; try { myClass = loader.loadClass("servlet." + servletName); } catch (Exception e) { log.severe(e.toString()); } Servlet servlet; try { servlet = (Servlet) myClass.newInstance(); // 使用包装类,可以让Servlet开发者接触不到parseUri等方法 RequestWrapper requestWrapper = new RequestWrapper(request); ResponseWrapper responseWrapper = new ResponseWrapper(response); servlet.service(requestWrapper, responseWrapper); } catch (Exception e) { log.severe(e.toString()); } } }
TestServlet:继承标准的Servlet接口,复写service方法,service方法会由ServletProcessor调用,到这一块对于学习了Servlet的人就很熟悉了。
package servlet; import javax.servlet.*; import java.io.IOException; import java.io.PrintWriter; public class TestServlet implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("TestServlet init"); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse res) throws IOException { System.out.println("TestServlet service "); PrintWriter writer = res.getWriter(); writer.println("HTTP/1.1 200 \r\n" + "Content-Type: text/html\r\n" + "\r\n" + "hello , it is TestServlet"); } @Override public String getServletInfo() { return null; } @Override public void destroy() { System.out.println("TestServlet destroy"); } }
这里面有一点改进的地方在于:对于TestServlet 来说,是客户端程序员会编写的类,而ServletProcessor做了什么是不需要关心的,但是service会拿到ServletProcessor给的ServletRequest 和ServletResponse 变量,也就是Request和Response对象,而我们知道Request和Response有一些拓展方法比如 parseUri,这些是不允许 客户端程序员通过强转来调用的。 那如何保证安全性呢?
我们可以通过外观类来实现,我们设计一个外观类RequestWrapper ,它拥有一个Request对象,一般方法都直接调用Request的方法,而对于私密的方法不暴露在RequestWrapper 类中,达到调用安全的目的。
ResponseWrapper一样,不再贴出代码
import javax.servlet.*; import java.io.BufferedReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Enumeration; import java.util.Locale; import java.util.Map; public class RequestWrapper implements ServletRequest { private Request request; public RequestWrapper(Request request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } @Override public Object getAttribute(String name) { return request.getAttribute(name); } @Override public Enumeration<String> getAttributeNames() { return request.getAttributeNames(); } @Override public String getCharacterEncoding() { return null; } @Override public void setCharacterEncoding(String env) throws UnsupportedEncodingException { } @Override public int getContentLength() { return 0; } @Override public long getContentLengthLong() { return 0; } @Override public String getContentType() { return null; } @Override public ServletInputStream getInputStream() throws IOException { return null; } @Override public String getParameter(String name) { return null; } @Override public Enumeration<String> getParameterNames() { return null; } @Override public String[] getParameterValues(String name) { return new String[0]; } @Override public Map<String, String[]> getParameterMap() { return null; } @Override public String getProtocol() { return null; } @Override public String getScheme() { return null; } @Override public String getServerName() { return null; } @Override public int getServerPort() { return 0; } @Override public BufferedReader getReader() throws IOException { return null; } @Override public String getRemoteAddr() { return null; } @Override public String getRemoteHost() { return null; } @Override public void setAttribute(String name, Object o) { } @Override public void removeAttribute(String name) { } @Override public Locale getLocale() { return null; } @Override public Enumeration<Locale> getLocales() { return null; } @Override public boolean isSecure() { return false; } @Override public RequestDispatcher getRequestDispatcher(String path) { return null; } @Override public String getRealPath(String path) { return null; } @Override public int getRemotePort() { return 0; } @Override public String getLocalName() { return null; } @Override public String getLocalAddr() { return null; } @Override public int getLocalPort() { return 0; } @Override public ServletContext getServletContext() { return null; } @Override public AsyncContext startAsync() throws IllegalStateException { return null; } @Override public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException { return null; } @Override public boolean isAsyncStarted() { return false; } @Override public boolean isAsyncSupported() { return false; } @Override public AsyncContext getAsyncContext() { return null; } @Override public DispatcherType getDispatcherType() { return null; } }
而对于静态页面处理器就更简单了,只有一个方法,方法也只有一句话
sendStaticResource方法就是之前写在Response 的拓展方法。通过File读取servlet容器中预存的html页面,然后通过outputStream发出去。
/** * http://localhost:8080/index.html */ public class StaticResourceProcessor { public void process(Request request, Response response) { try { response.sendStaticResource(); } catch (Exception e) { e.printStackTrace(); } } }
结语
大致的流程就是这些,这是最简单的servlet容器,本书会一层层深入,完整代码可以看我的项目SimpleServlet ,我会跟着看书的进度同时更新代码和文章对应的分支是v1.1
https://github.com/lovejj1994/SimpleServlet
相关文章推荐
- 学习tomcat源码(2) 实现servlet容器功能
- [深入剖析Tomcat]一个简单的servlet容器实现
- [深入剖析Tomcat]一个简单的servlet容器实现2
- how tomcat works 读书笔记(二)----------一个简单的servlet容器
- Tomcat源码深入——Servlet容器之外观模式
- 基于Java web服务器简单实现一个Servlet容器
- Tomcat实现:Servlet与web.xml介绍 以及 源码分析Tomcat实现细节
- 基于jsp+servlet实现的简单博客系统实例(附源码)
- 实现一个简单的Servlet容器
- Java+MyEclipse+Tomcat (二)配置Servlet及简单实现表单提交
- HowTomcatWorks学习笔记--一个简单的Servlet容器
- how tomcat works 读书笔记(二)----------一个简单的servlet容器
- Servlet容器Tomcat中web.xml中url-pattern的配置详解[附带源码分析]
- tomcat(2)一个简单的servlet容器
- 简单servlet容器的实现原理
- 基于HTML5和Tomcat WebSocketServlet的聊天室简单实现
- 用JSP+Servlet+JavaBean模式实现一个简单的登录网页设计(JSP+Tomcat+MySQL)
- Tomcat :一个简单的Servlet容器+Spring MVC响应流程
- servlet容器tomcat和jetty的简单使用
- Tomcat源码分析(五)--容器处理连接之servlet的映射