Tomcat本质是一个servlet容器,servlet是java用来规范网络请求处理一套接口规范,其中,在javax.servlet
中定义了所有的servlet类都必须实现的接口,在javax.servlet.http
中则定义了采用HTTP协议的HttpServlet
。而Tomcat主要就是用来作为处理Http请求的servlet服务的容器,因此,在系统梳理Tomcat之前,有必要先了解下Http与servlet规范。
示例 Http
Http也是建立在tcp/ip协议之上的应用层协议,而应用层协议无非就是用来约定通信双方收发数据的组织形式,这里并不打算展开说明Http协议的内容,可以在浏览器中通过F12看下Network中的请求,比如可以先通过springMvc开放一个Http服务接口:
1 2 3 4 5
| @RequestMapping("/hello") @ResponseBody public String hello(String name){ return "hello " + name; }
|
然后在浏览器中进行访问,便可以看到发送的请求内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| GET /hello?name=shanhm HTTP/1.1 Host: 127.0.0.1:8080 Connection: keep-alive Cache-Control: max-age=0 sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="90", "Google Chrome";v="90" sec-ch-ua-mobile: ?0 DNT: 1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9
|
了解了Http协议中请求的格式规范之后,便可以自己来组织Http请求通过Socket
通信了,这里省去一些非必需的参数,比如:
GET1 2 3 4 5 6 7 8 9 10 11 12 13 14
| try(Socket socket = new Socket("127.0.0.1", 8080); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))){ writer.println("GET /hello?name=shanhm HTTP/1.1"); writer.println("Host: 127.0.0.1:8080"); writer.println("Connection: keep-alive"); writer.println();
String line = null; while((line = reader.readLine()) != null){ System.out.println(line); } }
|
1 2 3 4 5 6
| HTTP/1.1 200 Content-Type: text/plain;charset=UTF-8 Content-Length: 12 Date: Wed, 09 Jun 2021 08:09:49 GMT
hello shanhm
|
也可以用POST请求,区别在于GET请求的参数拼在url中,而POST请求则放在body中,因此多了两个必需参数:Content-Type
和Content-Length
,其中Content-Type
有以下几种取值,其目的是用来告诉服务应该采用何种方式进行解析
- application/x-www-form-urlencoded:即form表单方式,也是默认的方式;
- multipart/form-data:表单上传文件
- application/json:表明消息是序列化后的JSON字符串
- application/xml或者text/xml:使用Xml作为编码方式
POST1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| try(Socket socket = new Socket("127.0.0.1", 8080); PrintWriter writer = new PrintWriter(socket.getOutputStream(), true); BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()))){
writer.println("POST /hello HTTP/1.1"); writer.println("Host: 127.0.0.1:8080"); writer.println("Connection: keep-alive"); writer.println("Content-Type: application/x-www-form-urlencoded;charset=utf-8"); writer.println("Content-Length: " + "name=shanhm".length()); writer.println(); writer.println("name=shanhm");
String line = null; while((line = reader.readLine()) != null){ System.out.println(line); } }
|
示例 Servlet
上面示例中演示了如何组织发送Http请求,并没有说明如何接收和解析处理的,当然它是交给了springMvc,而springMvc也是委托给Tomcat接收并基于Servlet来解析处理的。下面尝试下自己来监听请求并基于servlet进行处理,首先看下Servlet
接口:
javax.servlet.Servlet1 2 3 4 5 6 7
| public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res)throws ServletException, IOException; public String getServletInfo(); public void destroy(); }
|
从接口定义我们可以看出,如果要完整的基于servlet进行请求处理,至少还需要做几件事:
- 初始化servlet实例,并调用init方法
- 监听请求,并根据接收到的请求创建对应的
ServletRequest
和ServletResponse
实例
- 将
ServletRequest
和ServletResponse
交给servlet实例处理,即调用service方法
这里需要先引入servlet依赖
pom.xml1 2 3 4 5
| <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency>
|
然后首先启动ServerSocket
进行监听,并根据接收到的请求创建ServletRequest
和ServletResponse
,然后交给servlet处理
HttpServer.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class HttpServer {
private final Map<String, Servlet> servletMap = new HashMap<>(); private volatile boolean isShutDown = false; public HttpServer(){ servletMap.put("/hello", new HelloServlet()); }
public void await() throws Exception{ ServerSocket serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1")); while(!isShutDown){ try(Socket socket = serverSocket.accept(); InputStream input = socket.getInputStream(); OutputStream output = socket.getOutputStream()){ HttpRequest httpRequest = new HttpRequest(input); HttpResponse httpResponse = new HttpResponse(output); String contextPath = httpRequest.getContextPath(); Servlet Servlet = servletMap.get(contextPath); if(Servlet != null){ Servlet.service(httpRequest, httpResponse); }else{ throw new OperationNotSupportedException("404..."); } } } serverSocket.close(); } }
|
定义ServletRequest
和ServletResponse
的实现
HttpRequest.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class HttpRequest implements HttpServletRequest { private final String contextPath; private final Map<String, String> params = new HashMap<>(); public HttpRequest(InputStream input) throws IOException{ byte[] buffer = new byte[2048]; input.read(buffer); String head = new String(buffer); int urlBegin = head.indexOf(' '); int urlEnd = head.indexOf(' ', urlBegin + 1); String url = head.substring(urlBegin + 1, urlEnd); int paramIndex = url.indexOf('?'); if(paramIndex == -1){ contextPath = url; }else{ contextPath = url.substring(0, paramIndex); String param = url.substring(paramIndex + 1); String[] arr = param.split("&"); for(String p : arr){ String[] kv = p.split("="); params.put(kv[0], kv[1]); } } } @Override public String getContextPath() { return contextPath; } }
|
HttpResponse.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class HttpResponse implements HttpServletResponse { private final OutputStream output; public HttpResponse(OutputStream output){ this.output = output; } @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(output, true); } }
|
再定义Servlet的实现
HelloServlet.java1 2 3 4 5 6 7 8 9 10 11 12 13
| public class HelloServlet implements Servlet { @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { HttpRequest httpRequest = (HttpRequest)request; String name = request.getParameter("name"); System.out.println("receive request: url=" + httpRequest.getContextPath() + ", param=" + name); response.getWriter().println("hello " + name); } }
|
最后启动Http接口服务便可
1 2
| HttpServer httpServer = new HttpServer(); httpServer.await();
|
Tomcat
以上只是基于servlet简易地实现了一个Http服务,但实际上要实现一个在生产环境中健壮高可用的Http服务还需要考虑想当多的问题,而Tomcat就是帮我们做了这样的事,比如管理Servlet,负责监听请求并分配给对应的Servlet处理等。
参考:
- 《深入剖析Tomcat》