手写tomcat+servlet,浅析原理


最近回头看了servlet,写了一个简易版的tomcat+servlet。代码都上传到了github,请大家下载点星,感谢!!
https://github.com/kangwenzhuang/mytomcat
个人理解,观点可能片面,欢迎在评论区喷我,这样我才能成长
适合新手,高手请绕开

概念

首先弄懂http,servlet,http

socket是使用TCP/IP或者UDP协议在服务器与客户端之间进行传输的技术,是网络编程的基础

servlet是使用http协议在服务器与客户端之间通信的技术。是Socket的一种应用

http协议:是在tcp/ip协议之上进一步封装的一层协议,关注的事数据传输的格式是否规范,底层的数据传输还是运用了socket和tcp/ip
也就是说我们可以借助socket,实现servlet

逻辑

  1. 开启服务,开socket服务端口(8888),接听…
  2. 初始化,读取自定义web.xml,获取自定义servlet(定义servletMap保存servletname和对应的servlet对象,需要用到反射实现)和servletMapping(定义mappingMap保存url和对应的servletname),这样就能通过url找到对应的servlet对象了,然后就能执行里面的东东了,是不是这样的!
  3. 客户端访问,可以是浏览器(最好不要用谷歌,竟然连着发送两次get请求),还可以是postman,客户端访问的同时是不是就意味着socket服务端接受了连接
  4. 输入url,回车,本是一个http请求,但因为请求中带有端口号(8888),8888端口就一个,那么socket正好得到此http请求报文(1.请求行 2.请求头 3.请求体)
  5. 接下来解析http请求报文,需要知道请求方法,请求参数,对了还有一个最重要的请求路径,等等。有了这个请求路径是不是就能在servletMapping找到是哪个对象了,调用里面的方法就能得到结果了
  6. 得到的结果在服务器上,怎么在让请求者得到数据,没错,就是让该socket捎回去
    大致的过程就是这样

手写实现

便于管理我就在资源文件夹新建property.properties文件,可以设置端口

port=8888

当然还要有web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Archetype Created Web Application</display-name>
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.kang.servlet.ext.Myservlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

新建servlet之前可定要先定义Request和Response

在这里就要把Request和resonse和socket绑定,通过socket告诉Request请求报文,告诉Response响应报文

package com.kang.http;

import com.kang.utils.GetParm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class Request {
    private Socket client;
    private String url; // 请求资源
    private String method; // 请求方式
    private String protocal; // 协议

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    private Map<String, String> map; // 参数列表
    //工具类解析参数
    private GetParm getParm;

    public Request() {
    }

    public Request(Socket client) {
        this.client = client;
        map = new HashMap<String, String>();
        getParm = new GetParm();
        try {
            InputStream is = client.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            // 先读取第一行
            String firstLine = br.readLine();
            System.out.println("通过socket查看http请求报文:");
            System.out.println("**************************************************************");
            System.out.println(firstLine);
            String[] split = firstLine.split(" ");

            String rl;
            while (true) {
                if ((rl = br.readLine()).equals("")) break;
                System.out.println(rl);
            }

            System.out.println("**************************************************************");
            // 把提交方式、请求资源、协议取出
            method = split[0];
            url = split[1];
            protocal = split[2];
            // 解析url,分析参数
            if (method.equalsIgnoreCase("get")) {
                if (url.contains("?")) {
                    String[] split2 = url.split("[?]");
                    url = split2[0];
                    // 参数行
                    String property = split2[1];
                    map = getParm.getParm(property);
                }
            } else {
                BufferedReader br1 = new BufferedReader(new InputStreamReader(is));
                int length = 0;
                while (br1.ready()) {
                    String line = br1.readLine();
                    if (line.contains("Content-Length")) {
                        String[] split2 = line.split(" ");
                        length = Integer.parseInt(split2[1]);
                    }
                    if (line.equals("")) {
                        break;
                    }
                }
                String info = null;
                char[] ch = new char[length];
                br.read(ch, 0, length);
                info = new String(ch, 0, length);
                map = getParm.getParm(info);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getProtocal() {
        return protocal;
    }

    public void setProtocal(String protocal) {
        this.protocal = protocal;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public Socket getClient() {
        return client;
    }

    // 获得get post 方法
    public String getMethod() {
        return method;
    }

    // 获得url
    public String getUrl() {
        return url;
    }

}

package com.kang.http;

import com.kang.utils.GetParm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class Request {
    private Socket client;
    private String url; // 请求资源
    private String method; // 请求方式
    private String protocal; // 协议

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    private Map<String, String> map; // 参数列表
    //工具类解析参数
    private GetParm getParm;

    public Request() {
    }

    public Request(Socket client) {
        this.client = client;
        map = new HashMap<String, String>();
        getParm = new GetParm();
        try {
            InputStream is = client.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));

            // 先读取第一行
            String firstLine = br.readLine();
            System.out.println("通过socket查看http请求报文:");
            System.out.println("**************************************************************");
            System.out.println(firstLine);
            String[] split = firstLine.split(" ");

            String rl;
            while (true) {
                if ((rl = br.readLine()).equals("")) break;
                System.out.println(rl);
            }

            System.out.println("**************************************************************");
            // 把提交方式、请求资源、协议取出
            method = split[0];
            url = split[1];
            protocal = split[2];
            // 解析url,分析参数
            if (method.equalsIgnoreCase("get")) {
                if (url.contains("?")) {
                    String[] split2 = url.split("[?]");
                    url = split2[0];
                    // 参数行
                    String property = split2[1];
                    map = getParm.getParm(property);
                }
            } else {
                BufferedReader br1 = new BufferedReader(new InputStreamReader(is));
                int length = 0;
                while (br1.ready()) {
                    String line = br1.readLine();
                    if (line.contains("Content-Length")) {
                        String[] split2 = line.split(" ");
                        length = Integer.parseInt(split2[1]);
                    }
                    if (line.equals("")) {
                        break;
                    }
                }
                String info = null;
                char[] ch = new char[length];
                br.read(ch, 0, length);
                info = new String(ch, 0, length);
                map = getParm.getParm(info);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public String getProtocal() {
        return protocal;
    }

    public void setProtocal(String protocal) {
        this.protocal = protocal;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public Socket getClient() {
        return client;
    }

    // 获得get post 方法
    public String getMethod() {
        return method;
    }

    // 获得url
    public String getUrl() {
        return url;
    }

}

新建Myservlet,在这之前写一个servlet吧,毕竟有些方法每次都要用

package com.kang.servlet;

import com.kang.http.Request;
import com.kang.http.Response;

public abstract class Servlet {
    public void service(Request request, Response response) {

        //判断是调用doget 还是 dopost
        if ("get".equalsIgnoreCase(request.getMethod())) {
            this.doGet(request, response);
        } else {
            this.doPost(request, response);
        }


    }

    public abstract void doGet(Request request, Response response);

    public abstract void doPost(Request request, Response response);
}

package com.kang.servlet.ext;

import com.kang.http.Request;
import com.kang.http.Response;
import com.kang.servlet.Servlet;

import java.util.Map;

public class Myservlet extends Servlet {

    @Override
    public void doGet(Request request, Response response) {
        System.out.println("进入了我的第一个servlet");
        Map<String, String> map=request.getMap();
        for(Map.Entry<String,String> m:map.entrySet()){
            System.out.println(m.getKey()+":"+m.getValue());
        }
        response.setContentType("text/html");
        response.setWrite("我的名字是:"+map.get("name")+"\n"+"我的年龄是:"+map.get("age"));
    }

    @Override
    public void doPost(Request request, Response response) {
    }
}

需要用到的工具类

解析参数

package com.kang.utils;

import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

//模拟浏览器发送请求给服务器
public class GetParm {
	private Map<String, String> map = new HashMap<String, String>() ;
	public GetParm() {}
	
	public Map<String,String> getParm(String property){
		String[] split3 = property.split("&");
		
		for (int i = 0; i < split3.length; i++) {
			String[] split4 = split3[i].split("=");
			map.put(split4[0], URLDecoder.decode(split4[1]));
		}
		System.out.println(map);
		return map;
	}
}

解析xml

package com.kang.utils;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.util.List;

public class UtilsXml {
    //定义解析器和文档对象
    public SAXReader saxReader;
    public Document document;

    public UtilsXml(String path) {
        //获取解析器
        saxReader = new SAXReader();
        try {
            //获取文档对象
            document = saxReader.read(path);
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 根据节点名称获取内容
     *
     * @param name 节点名称
     * @return 节点内容
     */
    public String getElementText(String name) {
        //定位根节点
        Element root = document.getRootElement();

        List<Element> mapp = root.elements("servlet-mapping");

        List<Element> servlet = root.elements("servlet");

        String serveltName = "";
        String classpath = "";

        for (Element e : mapp) {
            if (e.element("url-pattern").getText().equals(name)) {
                serveltName = e.element("servlet-name").getText();
                break;
            }
        }


        for (Element e : servlet) {
            if (e.element("servlet-name").getText().equals(serveltName)) {
                classpath = e.element("servlet-class").getText();
                break;
            }
        }

        return classpath;
    }

    /**
     * 获取节点下的所有节点
     *
     * @param root
     * @param name
     * @return
     */
    public List<Element> getNodes(String name) {
        Element root = document.getRootElement();
        return root.elements(name);
    }

    public static void main(String[] args) {
        UtilsXml xml = new UtilsXml(UtilsXml.class.getResource("/") + "web.xml");
        //System.out.println(xml.getElementText("/myhtml.html"));

        List<Element> list = xml.getNodes("servlet");
        for (Element element : list) {
            System.out.println(element.element("servlet-name").getText());
            System.out.println(element.element("servlet-class").getText());
        }

    }
}

重头戏

还原tomcat

package com.kang;

import com.kang.socket.SocketProcess;
import com.kang.utils.UtilsXml;
import org.dom4j.Element;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

public class MyTomcat {
    private static final int port;

    private static Properties prop;

    public static final HashMap<String, Object> servletMapping = new HashMap<String, Object>();

    public static final HashMap<String, Object> servlet = new HashMap<String, Object>();

    static {
        prop = new Properties();
        try {
            File file = new File(MyTomcat.class.getClassLoader().getResource("property.properties").getFile());
            prop.load(new FileInputStream(file));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        port = Integer.parseInt(prop.getProperty("port"));
    }

    private void init() {

        InputStream io = null;

        String basePath;

        try {
            System.out.println("加载配置文件开始");
            //读取web.xml
            UtilsXml xml = new UtilsXml(MyTomcat.class.getClassLoader().getResource("web.xml").getFile());
            //讲所有的类都存储到容器中 并且创造对象
            List<Element> list = xml.getNodes("servlet");
            for (Element element : list) {
                servlet.put(element.element("servlet-name").getText(), Class.forName(element.element("servlet-class").getText()).newInstance());
            }
            //映射关系创建
            List<Element> list2 = xml.getNodes("servlet-mapping");
            for (Element element : list2) {
                servletMapping.put(element.element("url-pattern").getText(), element.element("servlet-name").getText());
            }
            System.out.println("加载配置文件结束");

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (io != null) {
                try {
                    io.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private void start() {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            System.out.println("Tomcat 服务已启动,地址:localhost ,端口:" + port);
            this.init();
            //持续监听
            do {
                Socket socket = serverSocket.accept();
                //处理任务
                Thread thread = new SocketProcess(socket);

                thread.start();
            } while (true);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MyTomcat tomcat = new MyTomcat();
        tomcat.start();

    }

}

在这里需要一个线程运行Myservlet里的函数,同时将结果让socket输出

package com.kang.socket;

import com.kang.MyTomcat;
import com.kang.http.Request;
import com.kang.http.Response;
import com.kang.servlet.Servlet;

import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class SocketProcess extends Thread {

    protected Socket socket;

    public SocketProcess(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            Request request = new Request(socket);
            Response response = new Response(socket);


            //从映射中找
            System.out.println("url:" + request.getUrl());
            String servelName = (String) MyTomcat.servletMapping.get(request.getUrl());
            System.out.println("servelName:" + servelName);

            if (servelName != null && !servelName.isEmpty()) {

                HashMap<String, Object> servlet1 = MyTomcat.servlet;
                for (Map.Entry<String, Object> m : servlet1.entrySet()) {
                    System.out.println("key:" + m.getKey());
                }
                //映射有的话找到对应的对象
                Servlet servlet = (Servlet) MyTomcat.servlet.get(servelName);
                if (servlet != null) {
                    servlet.doGet(request, response);

                } else {

                    System.out.println("找不到对应的servlet");
                }

            } else {
                System.out.println("找不到对应的servletMapping");
            }
            String responseHeader = "HTTP/1.1 200 \r\n"
                    + "Content-Type: " + response.getContentType() + "\r\n"
                    + "\r\n";
            String res = responseHeader + response.getWrite();
            System.out.println("通过socket查看http响应报文:");
            System.out.println("**************************************************************");
            System.out.println(res);
            System.out.println("**************************************************************");
            OutputStream outputStream=response.getPs();
//            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(res.getBytes("UTF-8"));
            outputStream.flush();
            outputStream.close();

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

回顾

  1. 通过在Request打印出socket的接收,对http请求报文的结构有了一定的认识
    在这里插入图片描述

  2. 在获得http的请求行,请求头,请求体时,我只是写了一个大概,我的写法是

while (true) {
                if ((rl = br.readLine()).equals("")) break;
                System.out.println(rl);
            }

其实这样不是很好,第一我把空行给去掉了,第二似乎也没分析去请求体,不过后来我也知道怎么获得了

一开始的写法是,这样就可以把整个报文都能分析出来,太天真了

while (true) {
                if ((rl = br.readLine())==null) break;
                System.out.println(rl);
            }

这样写的问题是,服务器没有回应,debug也不报错,后来我就想了是br.readLine()发生了阻塞,一直在等待,想到了jdk自带的工具jconsole.exe,查看之后果然是这种原因

  1. 刚开始用servlet的时候有没有人纳闷,doGet中只是形参,并没有实参传递,怎么就会有效果呢
    真正的并不是直接运行servlet,而是SocketProcess获取servlet实例,SocketProcess中有Request和Response实参,然后传递参数给doGet(Request,Response),同时修改Request和Response,由于是引用传递,那就能解释了
  2. 不加"\r\n"可以吗?肯定不行,这样的话不符合http超文本传输协议,网络识别不出来这到底是什么东西
String responseHeader = "HTTP/1.1 200 \r\n"
                    + "Content-Type: " + response.getContentType() + "\r\n"
                    + "\r\n";

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值