手写Tomcat+实现Servlet规范

一、前言

​ 本文章是对tomcat底层逻辑servlet规范进行手动实现

​ 目的是掌握tomcat作为一个Java服务是如何通过封装请求和响应,并通过配置文件进而去找到servlet的流程。可以更好地掌握web开发本质。为后面学习框架打下基础

​ 技术要点:网络编程(socket编程)、IO、多线程、xml解析(Dom4j)、反射

​ 学习前知识掌握:Java基础、Tomcat+Servlet基本使用

二、Tomcat

2.1、Tomcat本质

​ tomca本质t是一个Java服务,他的工作是帮我们封装浏览器的http请求数据,通过请求解析出web应用(servlet)并通过http响应返回。

​ 那么tomcat和servlet是如何一起工作的呢?tomcat实现了servlet的一套规范 例如servlet的生命周期。tomcat很智能,当得知你访问的资源是动态的,就会交给servlet去实现;反之如果是静态资源(html、css、js)的话,则不会走servlet,而是直接返回静态资源(后面会使用nginx成为替换方案)

	总结:tomcat是一个java服务,通过端口监听浏览器请求,并处理请求交给servlet(具体看2.1),之后再封装http响应返回浏览器

2.2、Tomcat工作流程

​ tomcat则是通过web.xml去解析并找到servlet的

<servlet>
    <servlet-name>HelloServlet</servlet-name>
    <!--包路径需要让tomcat进行反射得到-->
    <servlet-class>com.org.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>HelloServlet</servlet-name>
    <!--配置路径 tomcat才知道要将请求发送给哪个Servlet-->
    <url-pattern>/hello</url-pattern>
</servlet-mapping>

如果是第一次请求(Tomcat):

  1. 查询 web.xml
  2. 看看请求的资源 /hello, 在web.xml 配置 urlpattern
  3. 如果找到 url-pattern,就会得到 servlet-name: HelloServlet
  4. Tomcat 维护了一个大的HashMap<id, Servlet>,查询该Hashmap看看有没有这个Servlet实例
  5. 如果没有查询到该serletname 对应的id,即没有这个Servlet实例
  6. 就根据servlet-name 去得到serlvet-classs: 类的全路径
  7. 使用反射技术,将 servlet实例化->init0),并放入到 Tomcat 维护的HashMap<id, Servlet>

如果是第2次(以后)请求(Tomcat):

  1. 查询 web.xml
  2. 看看请求的资源 /hello, 在web.xml 配置 url-pattern
  3. 如果找到 url-pattern, 就会得到 servlet-name: HelloServlet
  4. Tomcat 维护了一个大的HashMap<id,Servlet>,查询该Hashmap看看有没有这个Servlet实例
  5. 如果查询到,就直接调用该Servlet的service()
  6. 结果显示

2.3、Tomcat调用service定位到doXxx

Servlet继承关系图
Servlet

定义一套Servlet规范

package javax.servlet;

import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

当我们自己的Servlet通过继承HttpServlet进行开发会比直接继承Servlet开发更加的容易

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

/**
 * @author Csir
 */
@WebServlet(urlPatterns = {"/hi", "/hi2"})
public class HiServlet extends HttpServlet {

    private int get = 0;
    private int post = 0;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        get++;
        System.out.println("get is = " + get);
        System.out.println("post is = " + post);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        post++;
        System.out.println("get is = " + get);
        System.out.println("post is = " + post);
    }
}

如何定位到doGet/doPost

​ 首先Tomcat通过web.xml定位到HiServlet,然后需要调用service方法,HiServlet没有重写,就会找到HttpServlet去调用:

​ 第一个调用Servlet的init方法只会调用一次,接下来如果继续调用该Servlet每次都会创建新的HttpServletRequest 对象 和 HttpServletResponse 对象 传递给service,然后在通过doXxx方法动态绑回我们自己的实现的doXxx方法,并将req和resp作为参数传递

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);  
            if (lastModified == -1L) {
                this.doGet(req, resp);  // 动态绑定 调用HiServlet的doGet方法
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }
    }

三、手写Tomcat

3.1、流程梳理

流程图

3.2、第一版本

在这里插入图片描述

  1. **ServerSocket:**在服务端监听指定端口,如果浏览器/客户端连接该端口,则建立连接,返回Socket对象
  2. **Socket:**表示 服务端和客户端/浏览器间的连接,通过Socket可以得到InputStream和OutputStream 流对象

我们创建了第一个版本MyTomcatV1,用于监听8080端口接收数据并响应数据回去

package com.org.tomcat;

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

/**
 * @author Csir
 * @version 1.0
 * 可以完成,接受浏览器请求,并返回信息
 */
public class MyTomcatV1 {
    public static void main(String[] args) throws IOException {
        // 创建ServerSocket 在8080监听
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=====MyTomcatV1在8080端口监听=====");
        while (!serverSocket.isClosed()) {
            // 等待浏览器/客户端连接
            // 如果由连接来 创建一个socket(服务器和浏览器的连接通道)
            Socket socket = serverSocket.accept();

            // 先接受浏览器发送的数据
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            String msg = null;
            System.out.println("=====开始接收客户端请求=====");
            while ((msg = bufferedReader.readLine()) != null) {
                if (msg.length() == 0) {
                    break;
                }
                System.out.println(msg);
            }

            OutputStream outputStream = socket.getOutputStream();
            // 构建一个http响应头
            // \r\n 表示换行
            String respHeader = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String respBody = "<h1>Hello,你好啊</h1>";

            String resp = respHeader + respBody;

            System.out.println("======我们给浏览器响应数据=====");
            System.out.println(resp);

            outputStream.write(resp.getBytes());

            outputStream.flush();
            outputStream.close();
            inputStream.close();
            socket.close();
        }
    }
}

3.3、第二版本

​ 第一个版本只有一个单线程在提供服务,很显然这样子的效率是非常低的,我们希望在第二个版本添加进多线程的知识。

​ 我们采用了BIO的方式:也就是当服务器接受socket连接后,每一个socket连接就会创建一个线程并由该线程去处理。MyTomcat只起到了转发作用

我们创建接受Sokcet的处理器MyHttpHandler,实现Runnable接口

package com.org.tomcat.handler;

import java.io.*;
import java.net.Socket;

/**
 * @author Csir
 * 1、MyHttpHandler 对象是一个线程对象
 * 2、处理htpp请求
 */
public class MyHttpHandler implements Runnable{

    Socket socket = null;

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

    @Override
    public void run() {
        try {
            InputStream inputStream = socket.getInputStream();

            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            // 不同线程在和客户端交互
            System.out.println("当前线程名为:" + Thread.currentThread().getName());
            System.out.println("=====MyHttpHandler开始接收请求数据=====");
            String msg = null;
            while ( (msg = bufferedReader.readLine()) != null ){
                if (msg.length() <= 0) {
                    break;
                }
                System.out.println(msg);
            }

            OutputStream outputStream = socket.getOutputStream();
            String respHeader = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/html;charset=utf-8\r\n\r\n";
            String respBody = "<h1>Hello,你好啊</h1>";

            String resp = respHeader + respBody;
            System.out.println("=====MyHttpHandler开始响应数据=====");
            System.out.println(resp);
            
            outputStream.flush();
            outputStream.close();
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

我们第二个版本MyTomcatV2,用于监听8080端口得到的socket创建线程去处理

package com.org.tomcat;

import com.org.tomcat.handler.MyHttpHandler;

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

/**
 * @author Csir
 * @version 2.0
 * BIO 实现多线程  但是BIO这里每次来一个请求就会创建的一个线程
 * NIO则是多路复用 为多个请求服务  所以效率比NIO高
 */
public class MyTomcatV2 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("=====MyTomcatV2在8080端口监听=====");
        while (!serverSocket.isClosed()) {
            Socket socket = serverSocket.accept();
            new Thread(new MyHttpHandler(socket)).start();
        }
    }
}

3.4、第三版本

真正要引入Servlet规范
在这里插入图片描述

3.4.1、创建Servlet规范并测试

1、MyServlet接口:对标的是Servlet接口

package com.org.tomcat.servlet;

import com.org.tomcat.http.MyRequest;
import com.org.tomcat.http.MyResponse;

/**
 * @author Csir
 * 相当于 Servlet接口
 */
public interface MyServlet {

    void init() throws Exception;

    void service(MyRequest request, MyResponse response) throws Exception;

    void destroy();

}

2、MyRequest:对标的是HttpServletRequest,用于封装浏览器发送的http请求

package com.org.tomcat.http;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;

/**
 * @author Csir
 * 1、MyRequest 作用封装http请求的数据
 *    get /myUserServelt?username=zs&age=20
 * 2、比如 method(get)、uri(/myUserServelt)、参数列表(username=zs&age=20)
 * 3、MyRequest 作用等价原生的servlet 中的 HttpServletRequest
 */
public class MyRequest {

    private InputStream inputStream;
    private String method;
    private String uri;
    private HashMap<String, String> parametersMapping = new HashMap<>();

    // 构造器
    // InputStream 必须是和 http的socket关联的
    public MyRequest(InputStream inputStream) {
        this.inputStream = inputStream;
        // 完成对http请求数据的封装
        encapRequest();
    }

    /**
     * 封装http数据 并提供get方法
     */
    private void encapRequest(){
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            // 读取请求行:  GET /myUserServlet?username=zs&age=20 HTTP/1.1
            String requestLine = bufferedReader.readLine();
            String[] requestLineArr = requestLine.split(" ");

            method = requestLineArr[0];
            // 解析得到 /myUserServlet
            // 1、先看看uri 有没有参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1) {
                uri = requestLineArr[1];
            }else {
                uri = requestLineArr[1].substring(0, index);
                // parameters => username=zs&age=20
                String parameters = requestLineArr[1].substring(index + 1);
                // parametersPair => [ "username=zs", "age=20" ]
                String[] parametersPair = parameters.split("&");
                if ( parametersPair != null && !"".equals(parametersPair) ){
                    for (String parameterPair : parametersPair) {
                        String[] parameterValue = parameterPair.split("=");
                        if (parameterValue.length == 2) {
                            parametersMapping.put(parameterValue[0], parameterValue[1]);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getMethod() {
        return method;
    }

    public String getUri() {
        return uri;
    }

    public String getParameter(String name) {
        if (parametersMapping.containsKey(name)){
            return parametersMapping.get(name);
        }
        else {
            return null;
        }
    }

    @Override
    public String toString() {
        return "MyRequest{" +
                "method='" + method + '\'' +
                ", uri='" + uri + '\'' +
                ", parametersMapping=" + parametersMapping +
                '}';
    }
}

3、MyResponse:对标HttpServletResponse,提供与socket相关联的OutputStream可以用来获取并进行调用

package com.org.tomcat.http;

import java.io.OutputStream;

/**
 * @author Csir
 * 1、MyResponse 对象 封装 OutputStream(和socket关联)
 * 2、即可以通过 MyResponse 对象  返回Http响应给浏览器/客户端
 * 3、MyResponse对象 等价原生servlet的 HttpServletResponse
 */
public class MyResponse {

    private OutputStream outputStream;
    public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    // 创建MyResponse对象,传入的outputStream和socket关联
    public MyResponse(OutputStream outputStream){
        this.outputStream = outputStream;
    }
    // 当我们需要给浏览器返回数据时,可以通过MyResponse的输出流完成
    public OutputStream getOutputStream(){
        return outputStream;
    }

}

4、MyHttpServlet抽象类:对标HttpServlet和GenericServlet

package com.org.tomcat.servlet;

import com.org.tomcat.http.MyRequest;
import com.org.tomcat.http.MyResponse;

import java.io.IOException;

/**
 * @author Csir
 * 相当于 HttpServelt和 GenericServlet
 */
public abstract class MyHttpServlet implements MyServlet{

    @Override
    public void service(MyRequest request, MyResponse response) throws IOException {
        if ("GET".equalsIgnoreCase(request.getMethod())) {
            this.doGet(request, response);
        }else if ("POST".equalsIgnoreCase(request.getMethod())) {
            this.doPost(request, response);
        }
    }

    // 使用模板设计模式  让子类实现方法
    public abstract void doGet(MyRequest request, MyResponse response);
    public abstract void doPost(MyRequest request, MyResponse response);
}

5、测试:改造MyHttpHandler

package com.org.tomcat.handler;

import com.org.tomcat.http.MyRequest;
import com.org.tomcat.http.MyResponse;

import java.io.*;
import java.net.Socket;

/**
 * @author Csir
 * 1、MyHttpHandler 对象是一个线程对象
 * 2、处理htpp请求
 */
public class MyHttpHandler implements Runnable{

    Socket socket = null;

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

    @Override
    public void run() {
        try {

            MyRequest request = new MyRequest(socket.getInputStream());
            System.out.println("======MyHttpHandler获取请求数据=====");
            System.out.println(request);


            MyResponse response = new MyResponse(socket.getOutputStream());
            String respBody = "<h1>Hello,第二个版本</h1>";
            System.out.println("======MyHttpHandler给浏览器响应数据=====");

            String resp = MyResponse.respHeader + respBody;
            System.out.println(resp);

            OutputStream outputStream = response.getOutputStream();
            outputStream.write(resp.getBytes());

            outputStream.flush();
            outputStream.close();

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

}

3.4.2、引入自己写的Servlet并测试

package com.org.tomcat.servlet;

import com.org.tomcat.http.MyRequest;
import com.org.tomcat.http.MyResponse;

import java.io.IOException;
import java.io.OutputStream;

/**
 * @author Csir
 */
public class MyTestServlet extends MyHttpServlet{

    @Override
    public void doGet(MyRequest request, MyResponse response) {
        int num1 = Integer.parseInt(request.getParameter("num1"));
        int num2 = Integer.parseInt(request.getParameter("num2"));

        int sum = num1 + num2;

        OutputStream outputStream = response.getOutputStream();
        String respMsg = MyResponse.respHeader +
                "<h1>" + num1 + " + " + num2 + " = " + sum + "</h1>";
        try {
            outputStream.write(respMsg.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(MyRequest request, MyResponse response) {
        doGet(request, response);
    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() {

    }
}

在MyHttpHandler先硬编码创建MyTestServlet 后面使用反射获得

package com.org.tomcat.handler;

import com.org.tomcat.http.MyRequest;
import com.org.tomcat.http.MyResponse;
import com.org.tomcat.servlet.MyTestServlet;

import java.io.*;
import java.net.Socket;

/**
 * @author Csir
 * 1、MyHttpHandler 对象是一个线程对象
 * 2、处理htpp请求
 */
public class MyHttpHandler implements Runnable{

    Socket socket = null;

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

    @Override
    public void run() {
        try {

            MyRequest request = new MyRequest(socket.getInputStream());
            System.out.println("======MyHttpHandler获取请求数据=====");
            System.out.println(request);

            MyResponse response = new MyResponse(socket.getOutputStream());

            // 创建 MyTestServlet  -》 一会使用反射获取
            MyTestServlet myTestServlet = new MyTestServlet();
            myTestServlet.doGet(request,response);

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

}

3.4.3、使用web.xml进行反射

1、编写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-name>MyTestServlet</servlet-name>
        <servlet-class>com.org.tomcat.servlet.MyTestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>MyTestServlet</servlet-name>
        <url-pattern>/test</url-pattern>
    </servlet-mapping>

</web-app>

2、创建第三个版本的MyTomcatV3,维护两个Map对象,一个存放容器名和容器实例映射、一个存放uri路径和容器名映射,通过读取web.xml配置文件添加到两个Map对象

package com.org.tomcat;

import com.org.tomcat.handler.MyHttpHandler;
import com.org.tomcat.servlet.MyHttpServlet;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Csir
 * @version 3.0
 * 实现 通过 xml+反射来初始化容器
 */
public class MyTomcatV3 {

    // 1、存放容器 servletMapping
    public static final ConcurrentHashMap<String, MyHttpServlet> servletMapping = new ConcurrentHashMap<>();

    // 2、存放路径映射 servletUrlMapping
    public static final ConcurrentHashMap<String, String> servletUrlMapping = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        MyTomcatV3 myTomcatV3 = new MyTomcatV3();
        myTomcatV3.init();
        myTomcatV3.run();
    }

    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("=====MyTomcatV3在8080端口监听=====");
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                MyHttpHandler myHttpHandler = new MyHttpHandler(socket);
                new Thread(myHttpHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void init(){
        // 读取 web.xml => dom4j
        // 得到的web.xml文件路径 => 拷贝一份到 D:/person/out/production/person/
        String path = MyTomcatV3.class.getResource("/").getPath();
        // 读取xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            // 获取根元素
            Element rootElement = document.getRootElement();
            // 得到根目录下的所有元素
            List<Element> elements = rootElement.elements();
            // 遍历并过滤到 servlet  servlet-mapping
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    // 使用反射 将servlet实例放入 servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(
                            servletName.getText(),
                            (MyHttpServlet) Class.forName(servletClass.getText().trim()).newInstance()
                    );
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    Element servletName = element.element("servlet-name");
                    Element urlPattern = element.element("url-pattern");
                    servletUrlMapping.put(
                            urlPattern.getText(),
                            servletName.getText()
                    );
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("servletMapping" + servletMapping);
        System.out.println("servletUrlMapping" + servletUrlMapping);

    }

}

3、在MyHttpHandler通过反射获取容器

package com.org.tomcat.handler;

import com.org.tomcat.MyTomcatV3;
import com.org.tomcat.http.MyRequest;
import com.org.tomcat.http.MyResponse;
import com.org.tomcat.servlet.MyHttpServlet;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * @author Csir
 * 1、MyHttpHandler 对象是一个线程对象
 * 2、处理htpp请求
 */
public class MyHttpHandler implements Runnable{

    Socket socket = null;

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

    @Override
    public void run() {
        try {

            MyRequest request = new MyRequest(socket.getInputStream());

            MyResponse response = new MyResponse(socket.getOutputStream());
            
            String uri = request.getUri();  // uri 即 url-pattern
            String servletName = MyTomcatV3.servletUrlMapping.get(uri);
            if (servletName != null) {  // ConcurrentHashMap 取不到null
                // 编译类型为 MyHttpServlet    运行类型为子类 MyTestServlet
                MyHttpServlet myHttpServlet = MyTomcatV3.servletMapping.get(servletName);
                if (myHttpServlet != null) {
                    myHttpServlet.service(request, response);
                }
            }else {
                String respMsg = MyResponse.respHeader + "<h1>404 Not Found</h1>";
                outputStream.write(respMsg.getBytes(StandardCharsets.UTF_8));
                OutputStream outputStream = response.getOutputStream();
                outputStream.flush();
                outputStream.close();
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}


4、测试,启动MyTomcatV3,浏览器发送路径 http://127.0.0.1:8080/test?num1=10&num2=20

如果返回下面 则成功

在这里插入图片描述

你可以自己再写一个乘法的的Servlet通过实现MyHttpServlet。 也完全可以实现的

最后,如果这篇文章对你有帮助的话,请点个赞~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值