Netty是一个基于协议运作的框架,那么今天先来了解一下我们使用最多的Http协议,再使用该协议使用Java手写一个Tomcat。
1.什么是HTTP
1.1 介绍
超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求-响应协议。它运行在TCP之上,指定了客户端可能发送什么的消息以及得到什么样的响应。
工作原理:HTTP是基于客户端与服务端且面向连接的,典型的HTTP处理过程如下:
- 客户端与服务端建立连接
- 客户端向服务端提出请求
- 服务端接受请求,并返回相应的文件作为响应
- 客服端与服务端关闭连接
1.2 协议报文
使用抓包访问一个HTTP接口并返回,得到内容如下:
GET http://127.0.0.1:9012/api/app/online?type=1 HTTP/1.1
Host: 127.0.0.1:9012
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 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
POST http://127.0.0.1:9012/api/app/online HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Host: 127.0.0.1:9090
Connection: keep-alive
Content-Length: 11
{"a":"bbb"}
请求报文分析: 第一行中包含请求方法 请求路径 协议版本 其他行为请求头参数 如果是POST,那就是请求结果 请求头 空行 返回的body参数
HTTP/1.1 200
Content-Type: application/json
Date: Fri, 29 Apr 2022 06:56:06 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 46
{"code":0,"message":"ok","content":{"data":1}}
可以看出和Post请求一样,只有第一行返回的是响应结果,版本号 状态码
2.什么是Tomcat
2.1 介绍
轻量级的web应用服务器,在一台服务器配置好Tomcat后,可以利用它响应HTML页面的访问请求。Java中的Servlet 和JSP 规范总是能在Tomcat 中得到体现。
2.2 工作原理
Tomcat是一个中间件,也可以被运行在JVM中。Web项目的本质是一大堆的资源文件和方法,Web项目部署在Tomcat容器中就是希望Tomcat去调用写好的方法去返回客户端所需要的数据。 接下来看这样的一个web.xml文件,来源于Tomcat/conf中的web.xml
//sevlet拦截处理类
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
//监听的mapping
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
使用spring MVC开发过的人应该不在少数吧,这个配置应该很熟悉了吧。 MVC工作原理:通过拦截浏览器匹配的路径,返回Servlet中的doGet/doPost的逻辑处理结果。 如HTTP请求就是通过TCP建立连接,将请求头信息封装成HttpRequest对象调用到Servlet中的doGet/doPost,然后处理完逻辑后,再将返回结果封装成HttpResponse对象给客户端响应,接下来通过代码来深入的了解一下。
3.Java代码实现
3.1 结构
public class TestServlet extends Servlet {
@Override
public void doGet(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception {
doPost(httpRequest, httpResponse);
}
@Override
public void doPost(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception {
System.out.println("参数:" + httpRequest.getParamMap());
httpResponse.write("hello, my name is TestServlet,requestParam:" + httpRequest.getParamMap());
}
}
暂时不用引入其他jar包,底层均为自己实现
3.2 Servlet顶层设计
- 在原始的servlet包中,是通过doService方法调用到doGet/doPost,这里直接抽象了
public abstract class Servlet {
public void service(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception {
try {
if ("GET".equals(httpRequest.getMethodType())) {
doGet(httpRequest, httpResponse);
} else if ("POST".equals(httpRequest.getMethodType())) {
doPost(httpRequest, httpResponse);
}
} catch (Exception e) {
httpResponse.write("500 SERVICE ERROR");
}
}
public abstract void doGet(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception;
public abstract void doPost(HttpRequest httpRequest, HttpResponse httpResponse) throws Exception;
}
3.3 Http输入输出体
在MVC中获取参数都是在Request中获取,输出到浏览器是通过Response,这里简单实现一下 HttpRequest
@Data
public class HttpRequest {
String methodType;
String methodName;
Map<String, Object> paramMap;
public HttpRequest(InputStream is) throws IOException {
String content = null;
int len;
byte[] bytes = new byte[1024];
if ((len = is.read(bytes)) > 0) {
content = new String(bytes, 0, len);
}
System.out.println(content);
handlerRequest(content);
}
private void handlerRequest(String content) {
String[] split = content.split("\n")[0].split(" ");
this.methodType = split[0];
String[] split1 = split[1].split("\\?");
this.methodName = split1[0];
if (split1.length > 1) {
if(this.paramMap == null) this.paramMap = new HashMap<>();
String[] split2 = split1[1].split("&");
for (String str : split2) {
String[] split3 = str.split("=");
this.paramMap.put(split3[0], split3[1]);
}
}
}
}
public class HttpResponse {
OutputStream os;
public HttpResponse(OutputStream os) {
this.os = os;
}
public void write(String str) throws IOException {
StringBuilder sb = new StringBuilder().append("HTTP/1.0 200 OK\n")
.append("Content-Type: text/html\n")
.append("\r\n")
.append(str);
os.write(sb.toString().getBytes());
}
}
3.4 核心连接类
public class BioTomcat {
static int port = 9090;
static ServerSocket serverSocket;
static Map<String, String> mapping = new ConcurrentHashMap<>();
public static void main(String[] args) throws IOException {
mapping.put("/test.do", "com.example.demo.tomcat.servlet.TestServlet");
serverSocket = new ServerSocket(port);
System.out.println("socket 已启动,端口:" + port);
while (true) {
Socket socket = serverSocket.accept();
InputStream is = null;
OutputStream os = null;
try {
is = socket.getInputStream();
os = socket.getOutputStream();
HttpRequest httpRequest = new HttpRequest(is);
HttpResponse httpResponse = new HttpResponse(os);
if (!mapping.containsKey(httpRequest.getMethodName())) {
httpResponse.write("404 NOT FOUND");
continue;
}
String className = mapping.get(httpRequest.getMethodName());
Servlet servlet = (Servlet) Class.forName(className).newInstance();
servlet.service(httpRequest, httpResponse);
} catch (Exception e) {
} finally {
if (is != null) is.close();
if (os != null) {
os.flush();
os.close();
}
socket.close();
}
}
}
}
4.tomcat测试
启动服务,调用http://127.0.0.1:9090/test.do?type=22&name=123456 网页输出: hello, my name is TestServlet,requestParam:{name=123456, type=22} 日志输出:
GET /test.do?type=22&name=123456 HTTP/1.1
Host: 127.0.0.1:9090
Connection: keep-alive
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 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-HK,zh-CN;q=0.9,zh;q=0.8
参数:{name=123456, type=22}
源码全贴了,就不上云了,原理应该了解了吧,下篇文章会改造成Netty版本的。 以上就是本章的全部内容了。
上一篇:通信框架之Netty第一话 - NIO的超神发展之路 下一篇:通信框架之Netty第三话 - Netty的使用以及使用Netty改造Tomcat
不见只今汾水上,唯有年年秋雁飞
|