Tomcat Http与Servlet

words: 1.5k    views:    time: 7min

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通信了,这里省去一些非必需的参数,比如:

GET
1
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);// autoFlush = 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-TypeContent-Length,其中Content-Type有以下几种取值,其目的是用来告诉服务应该采用何种方式进行解析

  • application/x-www-form-urlencoded:即form表单方式,也是默认的方式;
  • multipart/form-data:表单上传文件
  • application/json:表明消息是序列化后的JSON字符串
  • application/xml或者text/xml:使用Xml作为编码方式
POST
1
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);// autoFlush = 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.Servlet
1
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进行请求处理,至少还需要做几件事:

  1. 初始化servlet实例,并调用init方法
  2. 监听请求,并根据接收到的请求创建对应的ServletRequestServletResponse实例
  3. ServletRequestServletResponse交给servlet实例处理,即调用service方法

这里需要先引入servlet依赖

pom.xml
1
2
3
4
5
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>

然后首先启动ServerSocket进行监听,并根据接收到的请求创建ServletRequestServletResponse,然后交给servlet处理

HttpServer.java
1
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);

// 根据contextPath匹配对应的serverlet进行处理
String contextPath = httpRequest.getContextPath();
Servlet Servlet = servletMap.get(contextPath);
if(Servlet != null){
Servlet.service(httpRequest, httpResponse);
}else{
throw new OperationNotSupportedException("404...");
}
}
}
serverSocket.close();
}
}

定义ServletRequestServletResponse的实现

HttpRequest.java
1
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{
// 请求肯定是按照Http协议来发送的,这里简单从head中解析出url
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.java
1
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.java
1
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处理等。


参考:

  1. 《深入剖析Tomcat》