转载地址:https://www.jianshu.com/p/dce1ee01fb90
Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打交道。这是一只神奇的猫,今天让我来抽象你,实现你!
Write MyTomcat
Tomcat是非常流行的Web Server,它还是一个满足Servlet规范的容器。那么想一想,Tomcat和我们的Web应用是什么关系?
从感性上来说,我们一般需要把Web应用打成WAR包部署到Tomcat中,在我们的Web应用中,我们要指明URL被哪个类的哪个方法所处理(不论是原始的Servlet开发,还是现在流行的Spring MVC都必须指明)。
由于我们的Web应用是运行在Tomcat中,那么显然,请求必定是先到达Tomcat的。Tomcat对于请求实际上会进行下面的处理:
第一:提供Socket服务
Tomcat的启动,必然是Socket服务,只不过它支持HTTP协议而已!
这里其实可以扩展思考下,Tomcat既然是基于Socket,那么是基于BIO or NIO or AIO呢?
第二:进行请求的分发
要知道一个Tomcat可以为多个Web应用提供服务,那么很显然,Tomcat可以把URL下发到不同的Web应用。
第三:需要把请求和响应封装成request/response
我们在Web应用这一层,可从来没有封装过request/response的,我们都是直接使用的,这就是因为Tomcat已经为你做好了!
话不多说,先来看一眼工程截图:
MyRequest
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);
}
String httpHead = httpRequest.split("\n")[0];
url = httpHead.split("\\s")[1];
method = httpHead.split("\\s")[0];
System.out.println(this);
}
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;
}
}
http请求协议
请求头 (599 字节)
Accept */*
Accept-Encoding gzip, deflate
Accept-Language zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection keep-alive
Cookie ...
Host localhost:8080
Referer http://localhost:8080/...
User-Agent Mozilla/5.0 (Windows NT 6.1; W…) Gecko/20100101 Firefox/65.0
X-Requested-With XMLHttpRequest
这里,你可以清楚的看到,我们通过输入流,对HTTP协议进行解析,拿到了HTTP请求头的方法以及URL。
MyResponse
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")
.append("Content-Type: text/html\n")
.append("\r\n")
.append("<html><body>")
.append(content)
.append("</body></html>");
outputStream.write(httpResponse.toString().getBytes());
outputStream.close();
}
}
基于HTTP协议的格式进行输出写入。
MyServlet
public abstract class MyServlet {
public abstract void doGet(MyRequest myRequest,MyResponse myResponse);
public abstract void doPost(MyRequest myRequest,MyResponse myResponse);
public void service(MyRequest myRequest,MyResponse myResponse){
if(myRequest.getMethod().equalsIgnoreCase("POST")){
doPost(myRequest,myResponse);
}else if(myRequest.getMethod().equalsIgnoreCase("GET")){
doGet(myRequest,myResponse);
}
}
}
前文说Tomcat是满足Servlet规范的容器,那么自然Tomcat需要提供API。这里你看到了Servlet常见的doGet/doPost/service方法。
FindGirlServlet和HelloWorldServlet
public class FindGirlServlet extends MyServlet{
@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("get girl ...");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("post girl ...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class HelloWorldServlet extends MyServlet{
@Override
public void doGet(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("get world...");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(MyRequest myRequest, MyResponse myResponse) {
try {
myResponse.write("post world...");
} catch (IOException e) {
e.printStackTrace();
}
}
}
提供这2个具体的Servlet实现,只是为了后续的测试!
ServletMapping和ServletMappingConfig
servlet配置
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 void setServletName(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;
}
}
servletConfig配置
public class ServletMappingConfig {
public static List<ServletMapping> servletMappingList = new ArrayList<>();
static{
servletMappingList.add(new ServletMapping("findGirl","/girl","mytomcat.FindGirlServlet"));
servletMappingList.add(new ServletMapping("helloWorld","/world","mytomcat.HelloWorldServlet"));
}
}
你应该有些感觉了吧?
我们在servlet开发中,会在web.xml中通过<servlet></servlet>和<servlet-mapping></servlet-mapping>来进行指定哪个URL交给哪个servlet进行处理。
MyTomcat
public class MyTomcat {
private int port = 8088;
private Map<String,String> urlServletMap = new HashMap<>();
public MyTomcat(int port){
this.port = port;
}
public void start(){
//初始化 url与对应处理的servlet的关系
initServletMapping();
ServerSocket serverSocket = null;
try{
serverSocket = new ServerSocket(port);
System.out.println("MyTomcat is start...");
while (true){
Socket socket = 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 (IOException e){
e.printStackTrace();
}finally {
if(serverSocket != null){
try{
serverSocket.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
private void dispatch(MyRequest myRequest, MyResponse myResponse) {
String clazz = urlServletMap.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();
}
}
private void initServletMapping() {
for(ServletMapping servletMapping : ServletMappingConfig.servletMappingList){
urlServletMap.put(servletMapping.getUrl(),servletMapping.getClazz());
}
}
public static void main(String[] args) {
new MyTomcat(8088).start();
}
}
这里,你能够看到Tomcat的处理流程:把URL对应处理的Servlet关系形成,解析HTTP协议,封装请求/响应对象,利用反射实例化具体的Servlet进行处理即可。
Test MyTomcat(请自行测试)
真正的Tomcat还是很复杂的,需要花心思研究下。