前述

  自己手写的简易的tomcat,实现了tomcat的基本响应功能,项目代码已经上传到我的Github,刚刚开始学习这里,当前还存在很多问题

项目简述及代码

  当我们的Web运行的时候,从浏览器发出的请求,必然首先到达tomcat中,之后由tomcat进行处理,由此要考虑tomcat要进行哪些处理,首先便是提供Socket服务,之后对于请求进行分发,把请求和产生的响应封装成request和response

  (1)提供Socket服务

  (2)封装请求/响应对象

  (3)将不同的请求映射到具体的Servlet处理

处理请求

  我们首先考虑的,是客户端发送来请求时,我们应该怎么去识别它,这里涉及到的就是HTTP请求协议的部分,我直接那Github页面的HTTP请求协议做例子来说,如下图

  我们可以看到,在Request头的首行,由 GET  /jyroy  HTTP/1.1 三部分构成,而这三部分分别的含义是 请求方法  请求路径  请求协议及其对应版本号

  我们在拿到Resquest请求之后根据上面的分析,拿到相应的信息就可以进行后续的处理了。

 package myTomcat;

 import java.io.IOException;
import java.io.InputStream; /**
* @author jyroy
*
*/
public class MyRequest { //请求路径
private String url;
//请求方法
private String method; //读取输入字节流,封装成字符串格式的请求内容
public MyRequest(InputStream inputStream) throws IOException{
String httpRequest = ""; byte[] httpRequestBytes = new byte[1024]; int length = 0; if((length = inputStream.read(httpRequestBytes)) > 0) {
httpRequest = new String(httpRequestBytes, 0, length);
}
//HTTP请求协议:首行的内容依次为:请求方法、请求路径以及请求协议及其对应版本号
// GET /index HTTP/1.1
String httpHead = httpRequest.split("\n")[0]; //取出HTTP请求协议的首行
System.out.println(httpHead);
method = httpHead.split("\\s")[0]; //按照空格进行分割,第一个是请求的方法
url = httpHead.split("\\s")[1]; //按照空格进行分割,第二个是请求的路径
System.out.println(this.toString());
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public String getMethod() {
return method;
} public void setMethod(String method) {
this.method = method;
} @Override
public String toString() {
return "MyRequest [url=" + url + ", method=" + method + "]";
} }

处理响应

  考虑完接受请求之后,我们再来考虑一下怎么来做出我们的响应,浏览器才能识别,这里要涉及到的就是HTTP响应报文的内容,我的思路是,利用字符串拼接出Response报文,再将String转换为字节流就可以了。

  我们也是来看一下Github的Response报文的格式,如下图

  这么多的响应头,其实不是全部需要的,我们只需要写入一些基本的必须响应头信息,例如 请求协议及其对应版本号  响应号 响应状态 和 Cotent-type 等,如下

  最后只要转化字节流就可以

  

 package myTomcat;

 import java.io.IOException;
import java.io.OutputStream; public class MyResponse {
private OutputStream outputStream; public MyResponse(OutputStream outputStream) {
this.outputStream = outputStream;
} //将文本转换为字节流
public void write(String content) throws IOException{
StringBuffer httpResponse = new StringBuffer();
httpResponse.append("HTTP/1.1 200 OK\n") //按照HTTP响应报文的格式写入
.append("Content-Type:text/html\n")
.append("\r\n")
.append("<html><head><link rel=\"icon\" href=\"data:;base64,=\"></head><body>")
.append(content) //将页面内容写入
.append("</body></html>");
outputStream.write(httpResponse.toString().getBytes()); //将文本转为字节流
outputStream.close();
} }

Servlet请求处理基类

  当我们的请求和响应都已经准备好之后,接下来考虑servlet请求处理的部分,tomcat本身是一种满足servlet规范的容器,我们需要识别接收到的请求之后并做出响应,就涉及到了 doGet  doPost  service 三个方法

 package myTomcat;

 /**
* @author jyroy
* 提供API:doGet doPost service 方法
*/
public abstract class MyServlet { public void service(MyRequest myRequest, MyResponse myResponse) {
if(myRequest.getMethod().equalsIgnoreCase("POST")) {
doPost(myRequest, myResponse);
}else if(myRequest.getMethod().equalsIgnoreCase("GET")) {
doGet(myRequest, myResponse);
}
} public void doGet(MyRequest myRequest, MyResponse myResponse) { } public void doPost(MyRequest myRequest, MyResponse myResponse) { } }

Servlet配置

  考虑完上述问题之后,下一步需要的是分配url给哪一个servlet来处理,首先需要的就是一个反应映射关系的类

  

 package myTomcat;

 public class ServletMapping {
private String servletName;
private String url;
private String clazz; public ServletMapping(String servletName, String url, String clazz) {
super();
this.servletName = servletName;
this.url = url;
this.clazz = clazz;
} public String getServletName() {
return servletName;
} public void setServeletName(String servletName) {
this.servletName = servletName;
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public String getClazz() {
return clazz;
} public void setClazz(String clazz) {
this.clazz = clazz;
}
}

  以及相关配置文件

 package myTomcat;

 import java.util.ArrayList;
import java.util.List; /**
* @author jyroy
*
*/
public class ServletMappingConfig {
public static List<ServletMapping> servletMappingList = new ArrayList<>(); static {
servletMappingList.add(new ServletMapping("index", "/index", "myTomcat.test.IndexServlet"));
servletMappingList.add(new ServletMapping("myblog", "/myblog", "myTomcat.test.MyBlog"));
}
}

核心类

  最终,我们准备好基类后,需要的就是实现开始提到的整个处理流程

  (1)提供Socket服务

  (2)封装请求/响应对象

  (3)将不同的请求映射到具体的Servlet处理

  这里重点说的是,要利用 ServerSocket 通过服务器上的端口通信 以及 accpt方法一直等待客户端的请求

  具体逻辑在代码中注释

 package myTomcat;

 import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.util.HashMap;
import java.util.Map;
import java.net.Socket; /**
* @author jyroy
* Tomcat的处理流程:把URL对应处理的Servlet关系形成,解析HTTP协议,封装请求/响应对象,
* 利用反射实例化具体的Servlet进行处理即可。
*/
public class MyTomcat {
private Integer port = 8080; //定义8080端口 private Map<String, String> urlServletMapping = new HashMap<>(); //存储url和对应的类 public MyTomcat(Integer port) {
super();
this.port = port;
} @SuppressWarnings("resource")
public void start() {
initServletMapping(); try {
ServerSocket serverSocket = null; //实例化一个 ServerSocket 对象,表示通过服务器上的端口通信
serverSocket = new ServerSocket(port);
System.out.println("MyTomcat is starting...");
while(true) {
Socket socket = serverSocket.accept(); //服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream(); MyRequest myRequest = new MyRequest(inputStream);
MyResponse myResponse = new MyResponse (outputStream); dispatch(myRequest, myResponse); socket.close();
}
}catch(Exception e) {
e.printStackTrace();
} // }finally {
// if(serverSocket != null) {
// try {
// serverSocket.close();
// }catch(Exception e){
// e.printStackTrace();
// }
// }
// }
} //初始化映射
public void initServletMapping() {
for(ServletMapping servletMapping : ServletMappingConfig.servletMappingList) {
urlServletMapping.put(servletMapping.getUrl(), servletMapping.getClazz());
}
} //分发请求
@SuppressWarnings("unchecked")
public void dispatch(MyRequest myRequest, MyResponse myResponse) {
String clazz = urlServletMapping.get(myRequest.getUrl()); try {
Class<MyServlet> myServletClass = (Class<MyServlet>)Class.forName(clazz);
MyServlet myservlet = myServletClass.newInstance();
myservlet.service(myRequest, myResponse);
}catch(ClassNotFoundException e) {
e.printStackTrace();
}catch(InstantiationException e) {
e.printStackTrace();
}catch(IllegalAccessException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
MyTomcat myTomcat = new MyTomcat(8080);
myTomcat.start();
} }

测试类

 package myTomcat.test;

 import java.io.IOException;

 import myTomcat.MyRequest;
import myTomcat.MyResponse;
import myTomcat.MyServlet; public class IndexServlet extends MyServlet {
@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("Hello, myTomcat");
} catch (IOException e) {
e.printStackTrace();
}
} @Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("Hello, myTomcat");
} catch (IOException e) {
e.printStackTrace();
}
}
}
 package myTomcat.test;

 import java.io.IOException;

 import myTomcat.MyRequest;
import myTomcat.MyResponse;
import myTomcat.MyServlet; public class MyBlog extends MyServlet {
@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("Hello, this is my blog");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("Hello, this is my blog");
} catch (IOException e) {
e.printStackTrace();
}
}
}

运行结果

我手写的简易tomcat的更多相关文章

  1. 来,我们手写一个简易版的mock.js吧(模拟fetch && Ajax请求)

    预期的mock的使用方式 首先我们从使用的角度出发,思考编码过程 M1. 通过配置文件配置url和response M2. 自动检测环境为开发环境时启动Mock.js M3. mock代码能直接覆盖g ...

  2. 手写Promise简易版

    话不多说,直接上代码 通过ES5的模块化封装,向外暴露一个属性 (function(window){ const PENDING = 'pending'; const RESOLVED = 'fulf ...

  3. 手写一个简易版Tomcat

    前言 Tomcat Write MyTomcat Tomcat是非常流行的Web Server,它还是一个满足Servlet规范的容器.那么想一想,Tomcat和我们的Web应用是什么关系? 从感性上 ...

  4. 用python 10min手写一个简易的实时内存监控系统

    简易的内存监控系统 本文需要有一定的python和前端基础,如果没基础的,请关注我后续的基础教程系列博客 文章github源地址,还可以看到具体的代码,喜欢请在原链接右上角加个star 腾讯视频链接 ...

  5. [转]用python 10min手写一个简易的实时内存监控系统

    简易的内存监控系统 本文需要有一定的python和前端基础,如果没基础的,请关注我后续的基础教程系列博客 文章github源地址,还可以看到具体的代码,喜欢请在原链接右上角加个star 腾讯视频链接 ...

  6. 手写一个简化版Tomcat

    一.Tomcat工作原理 我们启动Tomcat时双击的startup.bat文件的主要作用是找到catalina.bat,并且把参数传递给它,而catalina.bat中有这样一段话: Bootstr ...

  7. 手写一个简易的IOC

    这个小项目是我读过一点Spring的源码后,模仿Spring的IOC写的一个简易的IOC,当然Spring的在天上,我写的在马里亚纳海沟,哈哈 感兴趣的小伙伴可以去我的github拉取代码看着玩 地址 ...

  8. 如何手写实现简易的Dubbo[z]

    [z]https://juejin.im/post/5ccf8dec6fb9a0321c45ebb5 前言 结束了集群容错和服务发布原理这两个小专题之后,有朋友问我服务引用什么时候开始,本篇为服务引用 ...

  9. 手写一个简易的多周期 MIPS CPU

    一点前言 多周期 CPU 相比单周期 CPU 以及流水线 CPU 实现来说其实写起来要麻烦那么一些,但是相对于流水线 CPU 和单周期 CPU 而言,多周期 CPU 除了能提升主频之外似乎并没有什么卵 ...

随机推荐

  1. Django Push 的一些资料

    先来中文的: 主要讲Orbited: http://sunsetsunrising.com/2009/django_comet.html#gsc.tab=0 再来英文的: http://www.rkb ...

  2. Ocelot中文文档-认证

    为了验证ReRoutes并随后使用Ocelot的任何基于声明的功能,如授权或使用令牌中的值修改请求. 用户必须像往常一样在他们的Startup.cs中注册认证服务,但他们给每个注册提供了一个方案(认证 ...

  3. java线程之线程通信

    前面提到多线程操作会有一个资源共享问题. 日常生活中,对于一个超市,有供货商往超市运货,有消费者从超市取货,供货商和消费者都与超市 建立了某种连接,超市就相当于公共资源,而他们的这种行为放到线程上说就 ...

  4. Vue作者尤雨溪:以匠人的态度不断打磨完善Vue (图灵访谈)

    访谈对象: 尤雨溪,Vue.js 创作者,Vue Technology创始人,致力于Vue的研究开发. 访谈内容: 你为何选择从事前端方面的工作? 其实,我本科读的是艺术史,研究生阶段学习Design ...

  5. birt4.6部署到tomcat及启动服务报错解决方法

    一.下载birt-runtime-4.6.0-20160607.zip包 解压后birt-runtime-4.6.0-20160607\WebViewerExample将WebViewerExampl ...

  6. Code Review Checklist

    左按:当年需要一份详细的代码评审清单作参考,翻译了此文. 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] General Code Smoke Test 通用测试 Comm ...

  7. 输入流IS和输出流OS学习总结

    1.我们编写的程序,除了自身会定义一些数据信息外,经常还会引用外界的数据,或是将自身的数据发送到外界,比如我们编写的程序想读取一个文本文件, 又或者是我们想将程序的一些数据写到一个文件中,这时我们就要 ...

  8. linux的远程唤醒

    这里主要是针对ubuntu系统的说明 1.检查计算机硬件是否支持WOL(wake on lan)功能. 1.1.检查主板和电源是否支持WOL: 进入BIOS的Power Management Setu ...

  9. 二十五、Hadoop学记笔记————Hive复习与深入

    Hive主要为了简化MapReduce流程,使非编程人员也能进行数据的梳理,即直接使用sql语句代替MapReduce程序 Hive建表的时候元数据(表明,字段信息等)存于关系型数据库中,数据存于HD ...

  10. 聊聊Spring Cloud版本的那些事儿

    说说Spring Cloud版本的那些事儿. 版本命名 之前提到过,Spring Cloud是一个拥有诸多子项目的大型综合项目,原则上其子项目也都维护着自己的发布版本号.那么每一个Spring Clo ...