参考了"https://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247487938&idx=1&sn=6b195038397f261c401fce11c3e8f0de&chksm=eb5394f4dc241de287ece3aba5c5d8c866ed01bd5fba6d19874d7b15888154c98071f44fe63a&mpshare=1&scene=1&srcid=0218x1u8CCLgfl5TpTFZ9OWV#rd"这篇文章,修改了部分代码,添加了一些自己的理解
(1) 提供Socket服务
Tomcat的启动是Socket服务,只不过它支持HTTP协议而已
(Tomcat既然是基于Socket,那么是基于BIO or NIO or AIO 呢?)(Tomcat 8 以后默认NIO)
(2) 进行请求的分发
Tomcat可以为多个Web应用提供服务,所以Tomcat可以把URL下发到不同的Web应用
(3) 把请求和响应封装成 request / response
从头开始手写Tomcat都需要完成哪些步骤
(1) 一个类用于记录url、Servlet名称\Servlet的class名称三者的对应关系
真实的tomcat是靠web.xml中的配置做到的
CompressionFilterTestServlet
compressionFilters.CompressionFilterTestServlet
...
CompressionFilterTestServlet
/CompressionTest
示例
public class ServletMappingConfig {
public static List servletMappingList = new ArrayList<>();
static {
servletMappingList.add(new ServletMapping("haha", "/haha", "myTomcat.HahaServlet"));
}
}
public class ServletMapping {
private String servletName;
private String url;
private String clazz;
public ServletMapping(String servletName, String url, String clazz) {
this.servletName = servletName;
this.url = url;
this.clazz = clazz;
}
public String getServletName() {
return servletName;
}
public String getUrl() {
return url;
}
public String getClazz() {
return clazz;
}
}
(2) 一个类用于解析输入流,从中提取URL、GET/POST
示例
public class MyRequest {
private String url;
private String method;
public MyRequest(InputStream inputStream) throws IOException {
// Step 1: Read request from stream
String httpRequest = "";
byte[] httpRequestBytes = new byte[1024];
int requestLength = inputStream.read(httpRequestBytes);
if (requestLength > 0) {
httpRequest = new String(httpRequestBytes, 0, requestLength);
}
// Step 2: Parse the request
/* A typical Http request protocol
GET /favicon.ico HTTP/1.1
Accept: ./.
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT/6.1)
Host: localhost:8080
Connection: Keep-Alive
*/
String httpHead = httpRequest.split("\n")[0]; // GET /favicon.ico HTTP/1.1
this.url = httpHead.split("\\s")[1]; // /favicon.ico
this.method = httpHead.split("\\s")[0]; // GET
}
public String getMethod() {
return method;
}
public String getUrl() {
return url;
}
}
(3) 一个类用于包装输出流
示例
public class MyResponse {
private OutputStream outputStream;
public MyResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
public void write(String content) throws IOException {
/* A typical Http response protocol
HTTP/1.1 200 OK
Content-Type: text/html
...
*/
String httpResponse = "HTTP/1.1 200 OK\n" + "Content-Type: text/html\n" +
"\r\n" + "" +
content +
"";
outputStream.write(httpResponse.getBytes());
outputStream.close();
}
}
(4) 和Tomcat类似的, 实现一个具体的Servlet
public abstract class AbstractServlet {
public abstract void doGet(MyRequest myRequest, MyResponse myResponse) throws IOException;
public abstract void doPost(MyRequest myRequest, MyResponse myResponse) throws IOException;
public void service(MyRequest myRequest, MyResponse myResponse) throws IOException{
if (myRequest.getMethod().toUpperCase().equals("POST")) {
doPost(myRequest, myResponse);
} else if (myRequest.getMethod().toUpperCase().equals("GET")) {
doGet(myRequest, myResponse);
}
}
}
public class HahaServlet extends AbstractServlet {
@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) throws IOException {
myResponse.write("Get haha");
}
@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) throws IOException {
myResponse.write("Post haha");
}
}
(4) Tomcat的启动类
1° 读取URL和Servlet class的映射关系
2° 根据端口号(默认8080)创建一个ServerSocket
3° 主循环中这个ServerSocket不断accept, 等待请求到来
4° 创建新线程(或者用线程池),处理当前的请求
I. 从到来的Socket中取出输入流InputStream和输出流OutputStream
II. 包装输入流和输出流为Request和Response
III. 根据Request中的URL和映射关系找到对应Servlet的class位置
IV. 利用反射机制创建这个Servlet的实例对象
V. 用这个Servlet的实例对象进行操作
示例
public class MyTomcat {
private final int port;
private final Map urlServletMap = new HashMap<>();
public MyTomcat(int port) {
this.port = port;
initServletMapping();
}
private void initServletMapping() {
for (ServletMapping servletMapping: ServletMappingConfig.servletMappingList) {
urlServletMap.put(servletMapping.getUrl(), servletMapping.getClazz());
}
}
public void start() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(this.port);
while (true) {
Socket currentSocket = serverSocket.accept();
Thread thread = new Thread(new SingleRequestAndResponseRunnable(
currentSocket, urlServletMap));
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
MyTomcat tomcat = new MyTomcat(8080);
tomcat.start();
}
}
public class SingleRequestAndResponseRunnable implements Runnable {
private final Socket socket;
private final Map urlServletMapping;
public SingleRequestAndResponseRunnable(Socket socket, Map urlServletMap) {
this.socket = socket;
this.urlServletMapping = urlServletMap;
}
@Override
public void run() {
try {
InputStream inputStream = this.socket.getInputStream();
OutputStream outputStream = this.socket.getOutputStream();
MyRequest myRequest = new MyRequest(inputStream);
MyResponse myResponse = new MyResponse(outputStream);
dispatch(myRequest, myResponse);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (!this.socket.isClosed()) {
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void dispatch(MyRequest myRequest, MyResponse myResponse) {
String clazz = this.urlServletMapping.get(myRequest.getUrl());
// Reflection used
try {
Class abstractServletClass
= (Class) Class.forName(clazz);
AbstractServlet abstractServlet
= abstractServletClass.getDeclaredConstructor().newInstance();
abstractServlet.service(myRequest, myResponse);
} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException
| InstantiationException | IllegalAccessException | IOException e) {
e.printStackTrace();
}
}
}