tomcat原理刨析之手写tomcat

admin 2022年7月22日09:29:49安全文章评论3 views19061字阅读63分32秒阅读模式

tomcat源码剖析

socket编程

tomcat其实就是一个网络请求处理程序,如果需要重写,我们需要通过socket编程进行接受数据与发送数据。

InetAddress类

getLocalHost  #获取本机InetAddress对象
getByName  #根据指定主机名/域名获取ip地址对象
getHostName  #获取inetAddress对象的主机名
getHostAddress  #获取InetAddress对象的地址

package com.socke.demo;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
        //获取本机的InetAddress对象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost);
        System.out.println("*******************************");

        //根据主机名/域名获取uo地址对象
        InetAddress host1 = InetAddress.getByName("DESKTOP-MSCGOJK");
        System.out.println(host1);
        System.out.println("*******************************");
        InetAddress host2 = InetAddress.getByName("www.baidu.com");
        System.out.println(host2);

        //获取InetAddress对象的主机名
        String hostName = host2.getHostName();
        System.out.println(hostName);
        System.out.println("*******************************");

        //获取InetAddress对象的地址
        String hostAddress = host2.getHostAddress();
        System.out.println(hostName);
        System.out.println("*******************************");
    }

}

结果

DESKTOP-MSCGOJK/192.168.0.108
*******************************
DESKTOP-MSCGOJK/192.168.0.108
*******************************
www.baidu.com/39.156.66.14
www.baidu.com
*******************************
www.baidu.com
*******************************

Process finished with exit code 0

ServerSocket类

构造方法
ServerSocket(int port)  #创建绑定到特定端口的服务器套接字
ServerSocket(int port, int backlog)  #用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
ServerSocket(int port, int backlog, InetAddress address)  #使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器。
ServerSocket()  #创建非绑定服务器套接字

常用方法
getLocalPort()  #返回此套接字在其上侦听的端口
accept()  #侦听并接受到此套接字的连接
setSoTimeout(int timeout)  #通过指定超时值启用/禁用 SO_TIMEOUT,以毫秒为单位
bind(SocketAddress host, int backlog)  #将 ServerSocket 绑定到特定地址(IP 地址和端口号)

package com.socke.demo;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServerTcpDemo {
    public static void main(String[] args) throws IOException {
        //1.在本机的9999端口监听,
        ServerSocket serverSocket = new ServerSocket(9999);
        //2.接受数据,如果没有数据接受会进行阻塞
        Socket socket = serverSocket.accept();
        //3.有链接,则返回Socket对象
        System.out.println("socket=" + socket.getClass());
    }

}

开启程序,并通过浏览器服务127.0.0.1:9999

tomcat原理刨析之手写tomcat

可以看到这里控制台返回了数据,证明链接成功,因此我们可以找到浏览器其实就是一个客户端,重写tomcat的第一步我们就已经算是成功了,及与浏览器交互

Socket类

构造方法
Socket(String host, int port)  #创建一个流套接字并将其连接到指定主机上的指定端口号
Socket(InetAddress host, int port)  #创建一个流套接字并将其连接到指定 IP 地址的指定端口号
Socket(String host, int port, InetAddress localAddress, int localPort)  #创建一个套接字并将其连接到指定远程主机上的指定远程端口
Socket(InetAddress host, int port, InetAddress localAddress, int localPort)  #创建一个套接字并将其连接到指定远程地址上的指定远程端口
Socket()  #通过系统默认类型的 SocketImpl 创建未连接套接字

常用方法
connect(SocketAddress host, int timeout)  #将此套接字连接到服务器,并指定一个超时值
getInetAddress()  # 返回套接字连接的地址
getPort()  #返回此套接字连接到的远程端口
getLocalPort()  #返回此套接字绑定到的本地端口
getRemoteSocketAddress()  #返回此套接字连接的端点的地址,如果未连接则返回 null
getInputStream()  #返回此套接字的输入流
getOutputStream()  #返回此套接字的输出流
close()  #关闭此套接字

建议的客户端

package com.socke.demo;

import java.io.IOException;
import java.net.Socket;

public class SocketClientTcpDemo {
    public static void main(String[] args) throws IOException {
        //1.连接服务端(ip,端口)
        Socket socket = new Socket("127.0.0.1",9999);
        //2.返送数据
        socket.getOutputStream().write("6666".getBytes());
        //3.关闭socket
        socket.close();
    }

}

带回显内容的服务端

package com.socke.demo;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServerTcpDemo {
    public static void main(String[] args) throws IOException {
        //1.在本机的9999端口监听,
        ServerSocket serverSocket = new ServerSocket(9999);
        //2.接受数据,如果没有数据接受会进行阻塞
        Socket socket = serverSocket.accept();
        //3.有链接,则返回Socket对象
        System.out.println("socket=" + socket.getClass());
        //4.读取数据
        InputStream inputStream = socket.getInputStream();
        int readLen = 0;
        byte[] buf = new byte[1024];
        while((readLen = inputStream.read(buf))!=-1){
            System.out.println(new String(buf,0,readLen));
        }
        //5.关闭资源
        socket.close();
        inputStream.close();
        serverSocket.close();

    }

}

启动服务,并利用浏览器服务127.0.0.1:9999

tomcat原理刨析之手写tomcat

可以看到返回的内容为一个http请求头,还请求头是由浏览器生成发送的,但是由于我们并没有,返回数据给浏览器因此,浏览器一直获取不到任何内容,处于加载状态,接下来就是,返回数据至浏览器上

自写tomcat之实现与浏览器交互

SocketServerTcpDemo

程序入口,只要用于实现多线程

package com.demo;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class SocketServerTcpDemo {
    public final static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(25,100,60,TimeUnit.SECONDS,new LinkedBlockingQueue());


    public static void start() throws IOException {
        //1.在本机的9999端口监听,
        ServerSocket serverSocket = new ServerSocket(9999);
        while(true){
            //2.接受数据,如果没有数据接受会进行阻塞
            Socket socket = serverSocket.accept();
            //3.并行,线程池
            threadPoolExecutor.execute(new SocketProcessor(socket));
        }
        }

    public static void main(String[] args) throws IOException {
        start();
    }

}

SocketProcessor

socket进程任务,为socket数据发送与处理

package com.demo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.Socket;

public class SocketProcessor implements Runnable{

    Socket connection;

    public SocketProcessor(Socket connection){
        this.connection = connection;
    }
    public void run() {
        byte[] requestBody = new byte[1024];
        try {
            connection.getInputStream().read(requestBody);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String requestString = new String(requestBody);
        //调用http工厂类,处理http请求
        HttpFactory httpFactory = new HttpFactory();
        HttpServletRequest httpServletRequest = httpFactory.createRequest(requestString.getBytes());
        //获取请求方法和url
        System.out.println(httpServletRequest.getServletPath());
        System.out.println(httpServletRequest.getMethod());

        //发送响应
        HttpServletResponse httpServletResponse = httpFactory.createResponse(connection);
        try {
            String content = "输入的uri为:"+httpServletRequest.getServletPath()+"输入的参数为"+httpServletRequest.getParameter("name");
            httpServletResponse.getOutputStream().write("HTTP/1.1 200 OKrn".getBytes());
            httpServletResponse.getOutputStream().write(("Content-type: text/plain;charset=utf-8rn").getBytes());
            httpServletResponse.getOutputStream().write(("Content-Length: "+(content.getBytes("utf-8").length)+"rnrn").getBytes());
            httpServletResponse.getOutputStream().write(content.getBytes());

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

    }
}

HttpFactory

http工厂类,用于将socket数据进行封装成http数据,方便发送以及处理

package com.demo;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.security.Principal;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;

public class HttpFactory{
    public HttpServletRequest createRequest(final byte[] requestBody){
        return new HttpServletRequest() {
        ....
            //获取请求方法
            public String getMethod() {
                String requestString = new String(requestBody);
                return requestString.split("rn")[0].split(" ")[0];

            }
        ....

            //获取请求路径
            public String getServletPath() {
                String requestString = new String(requestBody);
                return requestString.split("rn")[0].split(" ")[1];
            }
        ....
        };
            //获取参数内容
            public String getParameter(String s) {
                String requestString = new String(requestBody);
                String uri =  requestString.split("rn")[0].split(" ")[1];
                System.out.println(uri);
                String parameters = uri.split("\?")[1];
                String[] keyValues = parameters.split("&");
                Map<String,String> map = new HashMap<String, String>();
                for(String str : keyValues){
                    System.out.println(str.split("=")[0]);
                    map.put(str.split("=")[0],str.split("=")[1]);
                }
                return map.get(s);
            }

    }

    public HttpServletResponse createResponse(final Socket connect){
        return new HttpServletResponse() {
        ....
            public ServletOutputStream getOutputStream() throws IOException {
                return new ServletOutputStream(){
                    public void write(int b) throws IOException {
                        connect.getOutputStream().write(b);
                    }
                };
            }
        ....
        };
    }

效果

tomcat原理刨析之手写tomcat

这里由于需要重写的方法太多,在这里就只重写了,后面经常用到的方法

到目前为止我们的tomcat已经可以完整的获取,处理,发送http数据。

项目找寻

在前面我们已经解决了网络通信的问题,接下来就是对接项目,及通过url访问我们的serverlet

ProjectConfigBean

ProjectConfigBean,通过读取xml获取serverlet信息

package com.demo;

import jdk.internal.org.xml.sax.helpers.DefaultHandler;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.demo.BootStraper.work_space;

public class ProjectConfigBean extends DefaultHandler {

    //Servlet 集合
    Map<String,String> servlets = new HashMap<String, String>();

    //Servlet 实例化
    Map<String,Object> servletInstances = new HashMap<String, Object>();

    //servlet 映射
    Map<String,String> servletMapping = new HashMap<String, String>();

    //项目
    private String project;

    //xml文件地址
    private String xmlDir;

    public ProjectConfigBean(String project) throws IOException, JDOMException {
        //1.创建SAXBuilder对象
        SAXBuilder saxBuilder = new SAXBuilder();
        //2.创建输入流
        InputStream is = new FileInputStream(new File(work_space+project+"/WEB-INF/web.xml"));
        //3.将输入流加载到build中
        Document document = saxBuilder.build(is);
        //4.获取根节点
        Element rootElement = document.getRootElement();
        //5.获取子节点
        List<Element> children = rootElement.getChildren();
        for (Element child : children) {
            //获取<servlet>中的标签
            List<Element> childrenList = child.getChildren();
            //判断servlet以及servlet-mapping
            if(child.getName().equals("servlet")){
                //将servlet添加至servlets
                servlets.put(childrenList.get(0).getValue(),childrenList.get(1).getValue());

            }
            else if (child.getName().equals("servlet-mapping")){
                //将servlet添加至servletMapping
                servletMapping.put(childrenList.get(1).getValue(),childrenList.get(0).getValue());
            }
        }

    }

    public ProjectConfigBean loadXml() {
        return this;
    }

}

ProjectLoader

加载依赖以及class文件

package com.demo;

import org.jdom.JDOMException;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Map;

public class ProjectLoader {
    //项目地址
    private String project;
    //类加载器
    URLClassLoader loader = null;
    
    public ProjectLoader(String project) throws MalformedURLException {
        this.project = project;

        //创建一个存储需要加载的class和jar包的数组
        ArrayList<URL> urls = new ArrayList<URL>();

        //读取lib下的jar包列表
        File libs = new File(BootStraper.work_space+"/"+project+"WEB-INF/lib");
        if(libs.exists()){
            for (String lib: libs.list()){
                urls.add(new URL("file:"+BootStraper.work_space+"/"+project+"WEB-INF/lib/"+lib));
            }
        }
        //读取classes文件列表
        urls.add(new URL("file:"+BootStraper.work_space+"/"+project+"WEB-INF/classes/"));

        //加载项目的class和jar包
        this.loader = new URLClassLoader(urls.toArray(urls.toArray(new URL[]{})));
    }

    //加载类
    public ProjectLoader load(ProjectConfigBean projectConfigBean) throws IOException, JDOMException, ClassNotFoundException, IllegalAccessException, InstantiationException, ServletException {
        //设置所有线程都使用刚创建的加载器
        Thread.currentThread().setContextClassLoader(this.loader);
        //遍历出所有的类
        for (Map.Entry<String,String> entry:projectConfigBean.servlets.entrySet()){
            //利用反射加载对象
            Class<?> clazz = loader.loadClass(entry.getValue());
            //实例化
            Servlet servlet = (Servlet) clazz.newInstance();
            //servlet初始化
            servlet.init(new ServletConfig() {
                public String getServletName() {
                    return null;
                }

                public ServletContext getServletContext() {
                    return null;
                }

                public String getInitParameter(String s) {
                    return null;
                }

                public Enumeration<String> getInitParameterNames() {
                    return null;
                }
            });
            //保存对象,如果不保存在projectConfigBean中,创建的对象就会被销毁掉,前面做的所有都将是徒劳
            projectConfigBean.servletInstances.put(entry.getKey(),servlet);
        }
        return this;
    }
}

SocketProcessor

关联servlet和url

package com.demo;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.Socket;

public class SocketProcessor implements Runnable{

    Socket connection;

    public SocketProcessor(Socket connection){
        this.connection = connection;
    }
    public void run() {
        byte[] requestBody = new byte[1024];
        try {
            connection.getInputStream().read(requestBody);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String requestString = new String(requestBody);
        //调用http工厂类,处理http请求
        HttpFactory httpFactory = new HttpFactory();
        HttpServletRequest httpServletRequest = httpFactory.createRequest(requestString.getBytes());
        //获取请求方法和url
        System.out.println(httpServletRequest.getServletPath());
        System.out.println(httpServletRequest.getMethod());

        //发送响应
        HttpServletResponse httpServletResponse = httpFactory.createResponse(connection);

        //获取请求路径,匹配servlet
        try {
            BootStraper bootStraper = new BootStraper();
            // 1.处理/
            String servletName = bootStraper.projectConfigBeans.get("").servletMapping.get("/");

            //2.通过url加载
            if(servletName == null){
                servletName = bootStraper.projectConfigBeans.get("").servletMapping.get(httpServletRequest.getServletPath());
            }
            //3.如果没有找到
            if (servletName == null){
                try {
                    httpServletResponse.getOutputStream().write("404".getBytes());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            //通过Servlet名称,获取对应的servlet实例
            Servlet servlet = (Servlet)bootStraper.projectConfigBeans.get("").servletInstances.get(servletName);

            //将httpServletRequest,httpServletResponse传入servlet
            try {
                servlet.service(httpServletRequest,httpServletResponse);
            } catch (ServletException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

SocketServerTcpDemo

package com.demo;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class SocketServerTcpDemo {
    public final static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(25,100,60,TimeUnit.SECONDS,new LinkedBlockingQueue());


    public static void start() throws IOException {
        //1.在本机的9999端口监听,
        ServerSocket serverSocket = new ServerSocket(9999);
        while(true){
            //2.接受数据,如果没有数据接受会进行阻塞
            Socket socket = serverSocket.accept();
            //3.并行,线程池
            threadPoolExecutor.execute(new SocketProcessor(socket));
        }
        }

    public static void main(String[] args) throws IOException {
        start();
    }
}

MyServlet

package com.naihe;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;


public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException 
{
        String content = "这里是MyServlet,输入的uri为:"+req.getServletPath()+"输入的参数为"+req.getParameter("name");
        resp.getOutputStream().write("HTTP/1.1 200 OKrn".getBytes());
        resp.getOutputStream().write(("Content-type: text/plain;charset=utf-8rn").getBytes());
        resp.getOutputStream().write(("Content-Length: "+(content.getBytes("utf-8").length)+"rnrn").getBytes());
        resp.getOutputStream().write(content.getBytes());
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException 
{
        resp.getOutputStream().write("666".getBytes());
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


  <!--注册Servlet-->
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.naihe.MyServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>hello2</servlet-name>
    <servlet-class>com.naihe.MyServlet</servlet-class>
  </servlet>
  <!--Servlet的请求路径-->
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>hello2</servlet-name>
    <url-pattern>/hello2</url-pattern>
  </servlet-mapping>
</web-app>

tomcat原理刨析之手写tomcat

加下方wx,拉你一起进群学习

tomcat原理刨析之手写tomcat

往期推荐

ETW的攻与防

SEH异常之编译器原理探究

初探UAF漏洞

什么?你还不会webshell免杀?(三)

初探栈溢出

windows环境下的自保护探究

记一次内部红队渗透——定位张三

对抗无落地的shellcode注入


tomcat原理刨析之手写tomcat

原文始发于微信公众号(红队蓝军):tomcat原理刨析之手写tomcat

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年7月22日09:29:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  tomcat原理刨析之手写tomcat https://cn-sec.com/archives/1190506.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: