【Tomcat】Tomcat的实现,tomcat内部是怎么实现的,它是怎么做到对Servlet请求的处理的。

让你想手写一个tomcat容器 能做到吗

Tomcat是Servlet容器,这句话真的是精髓,但有怎么理解呢?

Tomcat的代码实现,原理

尝试自己手写一份tomcat服务,类似tomcat的功能。
tomcat是servlet容器。所以我们自己写的tomcat也要满足这个功能,能接受处理servlet请求。
这一段的代码还是很经典的,让我们可以真正的明白“tomcat是Servlet容器”这样一句话。
Service 容器包括了 Engine容器
Engine 容器包括了Host容器
Host 容器包括了Context容器

可以想象,Service容器是在外层的容器,他还有一个对外的Connector 接口,提供对外的连接服务。

大致思路是这样的:
1. 先创建一个 Tomcat 对象
2. Tomcat 对象创建一个 Server对象
3. Server对象创建一个Service容器
4. Service容器内创建一个Engine容器
5. Engine容器内创建一个Host容器
6. Host容器内创建一个Context容器

这样相当于我们手写了一个tomcat服务,实现对servlet请求的接收,处理工作。

创建一个tomcat容器

package com.simulate.protocol.http;

import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;

public class HttpServer {

    public void start(String hostname, Integer port) {

        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(port);

        Engine engine = new StandardEngine();
        engine.setDefaultHost(hostname);

        Host host = new StandardHost();
        host.setName(hostname);

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        // 接收到的所有请求最终都会转发到 DispatcherServlet 上面去处理
        tomcat.addServlet(contextPath, "dispatcher", new DispatcherServlet());
        context.addServletMappingDecoded("/*", "dispatcher");


        try {
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }


    }
}

Servlet转发

package com.simulate.protocol.http;

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 DispatcherServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        new HttpServerHandler().handler(req, resp);
    }
}

Servlet的处理工作

package com.simulate.protocol.http;

import com.alibaba.fastjson.JSONObject;
import com.simulate.framework.Invocation;
import com.simulate.provider.api.HelloService;
import com.simulate.provider.impl.HelloServiceImpl;
import org.apache.commons.io.IOUtils;
import com.simulate.provider.LocalRegister;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class HttpServerHandler {

    // FIXME 这里应该是 服务启动的时候,自动注入进去,而不是在这个类中才注入,如果其他地方有用到这些对象的话,已经晚了
    public HttpServerHandler (){
        LocalRegister.regist(HelloService.class.getName(), HelloServiceImpl.class);
    }


    public void handler(HttpServletRequest req, HttpServletResponse resp) {

        // 获取到Request 请求中的url参数信息,然后可以转发给具体的servlet对象,进行处理。这里只做转发的工作。
        // 这里可以做很多工作,比如访问数据库,数据写入文件等,而我这里做的是通过反射,创建对象,调用对象的方法。
        try {
            Invocation invocation = JSONObject.parseObject(req.getInputStream(), Invocation.class);
            var interfaceName = invocation.getInterfaceName();
            var implClass = LocalRegister.get(interfaceName);
            var method = implClass.getMethod(invocation.getMethodName(), invocation.getParamType());
            var result = (String) method.invoke(implClass.newInstance(), invocation.getParams());

            System.out.println("tomcat:" + result);
            IOUtils.write(result, resp.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }
}

怎么启动这个tomcat server端呢?可以这样做:

package com.simulate.protocol.http;

import com.simulate.framework.URL;

public class HttpServerTest {

    public static void main(String[] args) {
        URL url = new URL("localhost", 9090);
        HttpServer httpServer = new HttpServer();
        httpServer.start(url.getHostname(), url.getPort());
        System.out.println("==========[httpserver start!]==============");
    }
}

上面的 tomcat启动完成后,就处于等待监听的状态,本地端口是9090的等待状态。等待什么?等待客户端的请求,如果有客户端请求 这个地址 http://localhost:9090/xxx 就会被这个tomcat容器捕捉到,进行后续的处理。

那我们继续看看客户端怎么发起请求呢?

我这里用的是jdk11, 在jdk11中,把HttpClient这个对象纳入进去了,而不需要像以前一样,还要依赖第三方的jar包。如果你用的不是jdk11,可以使用下面注释的代码,就是原生的HttpURLConnection 对象进行 http 连接,请求操作。

package com.simulate.protocol.http;

import com.alibaba.fastjson.JSONObject;
import com.simulate.framework.Invocation;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class HttpClient {

    public String send(String hostname, Integer port, Invocation invocation) {

        try {
            var request = HttpRequest.newBuilder()
                    .uri(new URI("http", null, hostname, port, "/", null, null))
                    .POST(HttpRequest.BodyPublishers.ofString(JSONObject.toJSONString(invocation)))
                    .build();
            var client = java.net.http.HttpClient.newHttpClient();

            HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());

            String result = response.body();

//            URL url = new URL("http", hostname, port, "/");
//            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
//
//            httpURLConnection.setRequestMethod("POST");
//            httpURLConnection.setDoOutput(true);
//
//            OutputStream outputStream = httpURLConnection.getOutputStream();
//            ObjectOutputStream oos = new ObjectOutputStream(outputStream);
//
//            oos.writeObject(invocation);
//            oos.flush();
//            oos.close();
//
//            InputStream inputStream = httpURLConnection.getInputStream();
//            String result = IOUtils.toString(inputStream);
            return result;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }

        return null;

    }
}

那这个客户端怎么使用呢?

package com.simulate.protocol.http;

import com.simulate.framework.Invocation;
import com.simulate.framework.URL;
import com.simulate.provider.api.HelloService;

public class HttpClientTest {

    public static void main(String[] args) {
        URL url = new URL("localhost", 9090);
        HttpClient httpClient = new HttpClient();
        String data = httpClient.send("localhost", 9090, new Invocation(HelloService.class.getName(), "sayHello",
                new Object[]{"World"}, new Class[]{String.class}));

        // LocalRegister.regist(HelloService.class.getName(), HelloServiceImpl.class);
        System.out.println("===========");
        System.out.println(data);
        System.out.println("===========");
    }
}

其他代码:

package com.simulate.framework;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;

@Data
@AllArgsConstructor
public class Invocation implements Serializable {

    private String interfaceName;
    private String methodName;
    private Object[] params;
    private Class[] paramType;
}



package com.simulate.provider.api;

public interface HelloService {

    String sayHello(String userName);
}



package com.simulate.provider.impl;

import com.simulate.provider.api.HelloService;

public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String userName) {
        return "Hello: " + userName;
    }
}

先启动HttpServerTest中的main方法

在启动HttpClientTest 中的main方法, 就能看到结果了。

这样相当于我们手写了一个tomcat服务,实现对servlet请求的接收,处理工作。




    4.0.0 

    com.simulate
    simulate
    1.0-SNAPSHOT


    
        
            io.netty
            netty-all
            4.1.16.Final
        

        
            org.apache.tomcat.embed
            tomcat-embed-core
            9.0.12
        

        
            org.apache.commons
            commons-io
            1.3.2
        

        
        
            org.projectlombok
            lombok
            1.18.4
            provided
        

        
        
            com.alibaba
            fastjson
            1.2.51
        

        
            org.apache.curator
            curator-framework
            4.1.0
        
        
            org.apache.curator
            curator-client
            4.1.0
        
        
            org.apache.curator
            curator-recipes
            4.1.0
        
        
            org.apache.zookeeper
            zookeeper
            3.4.13
        
    

你可能感兴趣的:(tomcat,servlet,tomcat,spring)