手写个Tomcat雏型

在这里插入图片描述

前言

目的:

网络编程在编程领域随处可见,本文尝试手写一个简单的HttpServer,了解网络请求后台的大致思路及为学习Tomcat打好基础。

准备知识:

  1. OOP:面向对象编程思想
  2. collection:Java常用容器
  3. IO:网络IO操作
  4. Thread:多线程
  5. Socket:网络编程
  6. XML:配置文件解析
  7. reflect:框架基石反射
  8. HTML:HTML基本语法
  9. HTTP:网络传输协议

思路

先说下大致流程:

  1. 服务端开启一个服务,进入一个死循环来给前端提供服务。
  2. 客户端发送一个HTTP请求。
  3. 服务器端收到请求后,加载一次XML文件解析,主要目的是获得客户的请求网址对应的那个Servlet(服务器端小程序)。
  4. 找到对应的Servlet后通过反射映射出实例,然后业务处理。
  5. 最终Socket通信返回结果。

注意:光理论是不够的,在此送大家十套2020最新Java架构实战教程+大厂面试题库,进裙: 783802103 在裙文件下载一起交流进步哦

流程图:

在这里插入图片描述

代码结构

在这里插入图片描述

代码实现

1. XML解析

XML文件解析的方式有多种,本文以SAXException来解析。

xml文件信息
<?xml version="1.0" encoding="UTF-8"?>  
 <web-app>
 <servlet>
	  <servlet-name>login</servlet-name>
	  <servlet-class>com.sowhat.user.LoginServlet</servlet-class>
 </servlet>   
  <servlet-mapping>
	  <servlet-name>login</servlet-name>
	  <url-pattern>/login</url-pattern> 
 </servlet-mapping>
    
 <servlet>
	  <servlet-name>reg</servlet-name>
	  <servlet-class>com.sowhat.user.RegisterServlet</servlet-class>
 </servlet>   
  <servlet-mapping>
	  <servlet-name>reg</servlet-name>
	  <url-pattern>/r</url-pattern> 
 </servlet-mapping>
    
 </web-app>
Servlet类

xml文件中servlet体对应的Java类

package com.sowhat.server.core;
/**
 * 功能:servlet 映射
  <servlet>
  <servlet-name>login</servlet-name>
  <servlet-class>com.shsxt.LoginServlet</servlet-class>
  </servlet>
 */
public class Entity {
	private String name;
	private String clz;
	public Entity() {
		// TODO Auto-generated constructor stub
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getClz() {
		return clz;
	}
	public void setClz(String clz) {
		this.clz = clz;
	}
}
Servlet-mapping类

servlet-mapping对应的Java类

package com.sowhat.server.core;

import java.util.HashSet;
import java.util.Set;
/**
 * 功能: 请求网址对应的 servlet
 <servlet-mapping>
  <servlet-name>login</servlet-name>
  <url-pattern>/login</url-pattern> 
  <url-pattern>/g</url-pattern> 
 </servlet-mapping>
 *
 */
public class Mapping {
	private String name;
	private Set<String> patterns ;
	public Mapping() {
		patterns = new HashSet<String>();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Set<String> getPatterns() {
		return patterns;
	}
	public void setPatterns(Set<String> patterns) {
		this.patterns = patterns;
	}
	public void addPattern(String pattern) {
		this.patterns.add(pattern);
	}
}
WebHandler

解析xml文件代码:WebHandler

package com.sowhat.server.core;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.ArrayList;
import java.util.List;

/**
 *  解析XML文件的 处理器
 */
public class WebHandler extends DefaultHandler {
    private List<Entity> entitys = new ArrayList<Entity>();
    private List<Mapping> mappings = new ArrayList<Mapping>();
    private Entity entity;
    private Mapping mapping;
    private String tag; //存储操作标签
    private boolean isMapping = false;


    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if (null != qName) {
            tag = qName; //存储标签名
            if (tag.equals("servlet")) {
                entity = new Entity();
                isMapping = false;
            } else if (tag.equals("servlet-mapping")) {
                mapping = new Mapping();
                isMapping = true;
            }
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        String contents = new String(ch, start, length).trim();
        if (null != tag) { //处理了空
            if (isMapping) { //操作servlet-mapping
                if (tag.equals("servlet-name")) {
                    mapping.setName(contents);
                } else if (tag.equals("url-pattern")) {
                    mapping.addPattern(contents);
                }
            } else { //操作servlet
                if (tag.equals("servlet-name")) {
                    entity.setName(contents);
                } else if (tag.equals("servlet-class")) {
                    entity.setClz(contents);
                }
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (null != qName) {
            if (qName.equals("servlet")) {
                entitys.add(entity);
            } else if (qName.equals("servlet-mapping")) {
                mappings.add(mapping);
            }
        }
        tag = null; //tag丢弃了
    }

    public List<Entity> getEntitys() {
        return entitys;
    }

    public List<Mapping> getMappings() {
        return mappings;
    }
}
WebContext

对解析结果优化映射服务:WebContext

package com.sowhat.server.core;

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

/***
 * 功能: 解析XML结果 HTTP网页请求跟 对应Java代码中哪个类来处理
 * 目的: 对XML解析后,以后输入一个请求的网址 就可以返回对应的处理类
 */

public class WebContext {
    private List<Entity> entitys = null;
    private List<Mapping> mappings = null;

    //key-->servlet-name  value -->servlet-class
    private Map<String, String> entityMap = new HashMap<String, String>();
    //key -->url-pattern value -->servlet-name
    private Map<String, String> mappingMap = new HashMap<String, String>();

    public WebContext(List<Entity> entitys, List<Mapping> mappings) {
        this.entitys = entitys;
        this.mappings = mappings;

        //将entity 的List转成了对应map
        for (Entity entity : entitys) {
            entityMap.put(entity.getName(), entity.getClz());
        }
        //将map 的List转成了对应map
        for (Mapping mapping : mappings) {
            for (String pattern : mapping.getPatterns()) {
                mappingMap.put(pattern, mapping.getName());
            }
        }
    }
    /**
     * 通过URL的路径找到了对应class
     */
    public String getClz(String pattern) {
        String name = mappingMap.get(pattern);
        return entityMap.get(name);
    }
}
WebApp
package com.sowhat.server.core;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

/**
 * 统筹 WebContext 跟 WebHandler
 * */

public class WebApp {
    private static WebContext webContext;

    static {
        try {
            //SAX解析
            //1、获取解析工厂
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //2、从解析工厂获取解析器
            SAXParser parse = factory.newSAXParser();
            //3、编写处理器
            //4、加载文档 Document 注册处理器
            WebHandler handler = new WebHandler();
            //5、解析
            parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("web.xml"), handler);
            //获取数据
            webContext = new WebContext(handler.getEntitys(), handler.getMappings());
        } catch (Exception e) {
            System.out.println("解析配置文件错误");
        }
    }

    /**
     * 通过url获取配置文件对应的servlet
     * @param url
     * @return
     */
    public static Servlet getServletFromUrl(String url) {
        //假设你输入了 /login
        String className = webContext.getClz("/" + url);
        Class clz;
        try {
            clz = Class.forName(className);
            Servlet servlet = (Servlet) clz.getConstructor().newInstance();
            return servlet;
        } catch (Exception e) {

        }
        return null;
    }
}

2. Request Response

Request 解析
package com.sowhat.server.core;

import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.*;

/**
 * 封装请求协议: 封装请求参数为Map
 */
public class Request {
    //协议信息
    private String requestInfo;
    //请求方式
    private String method;
    //请求url
    private String url;
    //请求参数
    private String queryStr;
    //存储参数
    private Map<String, List<String>> parameterMap;
    private final String CRLF = "\r\n";

    public Request(Socket client) throws IOException {
        this(client.getInputStream());
		System.out.println("request 构造函数");
    }

    public Request(InputStream is) {
        parameterMap = new HashMap<String, List<String>>();
        byte[] datas = new byte[1024*1024];
        int len;
        try {
            len = is.read(datas);
            this.requestInfo = new String(datas,0,len);
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        //分解字符串
        parseRequestInfo();
    }

    //分解字符串
    private void parseRequestInfo() {
        this.method = this.requestInfo.substring(0, this.requestInfo.indexOf("/")).toLowerCase();
        this.method = this.method.trim();
        //1)、获取/的位置
        int startIdx = this.requestInfo.indexOf("/") + 1;
        //2)、获取 HTTP/的位置
        int endIdx = this.requestInfo.indexOf("HTTP/");
        //3)、分割字符串
        this.url = this.requestInfo.substring(startIdx, endIdx).trim();
        //4)、获取?的位置
        int queryIdx = this.url.indexOf("?");
        if (queryIdx >= 0) {//表示存在请求参数
            String[] urlArray = this.url.split("\\?");
            this.url = urlArray[0];
            queryStr = urlArray[1];
        }
        System.out.println(this.url);


        if (method.equals("post")) {
            String qStr = this.requestInfo.substring(this.requestInfo.lastIndexOf(CRLF)).trim();
            System.out.println(qStr + "-->");
            if (null == queryStr) {
                queryStr = qStr;
            } else {
                queryStr += "&" + qStr;
            }
        }
        queryStr = null == queryStr ? "" : queryStr;
        System.out.println(method + "-->" + url + "-->" + queryStr);
        //转成Map fav=1&fav=2&uname=sowhat&age=18&others=
        convertMap();
    }

    //处理请求参数为Map
    private void convertMap() {
        //1、分割字符串 &
        String[] keyValues = this.queryStr.split("&");
        for (String queryStr : keyValues) {
            //2、再次分割字符串  =
            String[] kv = queryStr.split("=");
            kv = Arrays.copyOf(kv, 2);
            //获取key和value
            String key = kv[0];
            String value = kv[1] == null ? null : decode(kv[1], "utf-8");
            //存储到map中
            if (!parameterMap.containsKey(key)) { //第一次
                parameterMap.put(key, new ArrayList<String>());
            }
            parameterMap.get(key).add(value);
        }
    }

    private String decode(String value, String enc) {
        try {
            return java.net.URLDecoder.decode(value, enc);
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }

    public String[] getParameterValues(String key) {
        List<String> values = this.parameterMap.get(key);
        if (null == values || values.size() < 1) {
            return null;
        }
        return values.toArray(new String[0]);
    }

    public String getParameter(String key) {
        String[] values = getParameterValues(key);
        return values == null ? null : values[0];
    }

    public String getMethod() {
        return method;
    }

    public String getUrl() {
        return url;
    }
    public String getQueryStr() {
        return queryStr;
    }
}
Response 解析
package com.sowhat.server.core;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Date;

/**
 * 封装返回信息:
 */

public class Response {
	private BufferedWriter bw;
	//正文
	private StringBuilder content;
	//协议头(状态行与请求头 回车)信息
	private StringBuilder headInfo;
	private int len; //正文的字节数
	
	private final String BLANK =" ";
	private final  String CRLF = "\r\n";
	private Response() {
		content =new StringBuilder();
		headInfo=new StringBuilder();
		len =0;
	}
	public Response(Socket client) {
		this();
		try {
			bw=new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
		} catch (IOException e) {
			e.printStackTrace();
			headInfo = null;
		}
	}
	
	public Response(OutputStream os) {
		this();
		bw=new BufferedWriter(new OutputStreamWriter(os));
	}
	//动态添加内容
	public Response print(String info) {
		content.append(info);
		len+=info.getBytes().length;
		return this;
	}
	public Response println(String info) {
		content.append(info).append(CRLF);
		len+=(info+CRLF).getBytes().length;
		return this;
	}
	
	// 推送响应信息
	public void pushToBrowser(int code) throws IOException {
		if(null ==headInfo) {
			code = 505;
		}
		createHeadInfo(code);
		bw.append(headInfo);
		bw.append(content);
		bw.flush();
	}
		
	// 构建头信息
	private void createHeadInfo(int code) {
		//1、响应行: HTTP/1.1 200 OK
		headInfo.append("HTTP/1.1").append(BLANK);
		headInfo.append(code).append(BLANK);
		switch(code) {
			case 200:
				headInfo.append("OK").append(CRLF);
				break;
			case 404:
				headInfo.append("NOT FOUND").append(CRLF);
				break;	
			case 505:
				headInfo.append("SERVER ERROR").append(CRLF);
				break;	
		}
		//2、响应头(最后一行存在空行):
		headInfo.append("Date:").append(new Date()).append(CRLF);
		headInfo.append("Server:").append("shsxt Server/0.0.1;charset=GBK").append(CRLF);
		headInfo.append("Content-type:text/html").append(CRLF);
		headInfo.append("Content-length:").append(len).append(CRLF);
		headInfo.append(CRLF);		
	}
}

3. Servlet

Servlet接口
package com.sowhat.server.core;
/**
 * 服务器小脚本接口
 */
public interface Servlet {
	void service(Request request, Response response);
}
LoginServlet
package com.sowhat.server.user;

import com.sowhat.server.core.Request;
import com.sowhat.server.core.Response;
import com.sowhat.server.core.Servlet;

public class LoginServlet implements Servlet {
    @Override
    public void service(Request request, Response response) {
        response.print("<html>");
        response.print("<head>");
        response.print("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");
        response.print("<title>");
        response.print("第一个servlet");
        response.print("</title>");
        response.print("</head>");
        response.print("<body>");
        response.print("欢迎回来:" + request.getParameter("uname"));
        response.print("</body>");
        response.print("</html>");
    }
}
RegisterServlet
package com.sowhat.server.user;

import com.sowhat.server.core.Request;
import com.sowhat.server.core.Response;
import com.sowhat.server.core.Servlet;

public class RegisterServlet implements Servlet {
    @Override
    public void service(Request request, Response response) {
        //关注业务逻辑
        String uname = request.getParameter("uname");
        String[] favs = request.getParameterValues("fav");
        response.print("<html>");
        response.print("<head>");
        response.print("<meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">");
        response.print("<title>");
        response.print("注册成功");
        response.print("</title>");
        response.print("</head>");
        response.print("<body>");
        response.println("你注册的信息为:" + uname);
        response.println("你喜欢的类型为:");
        for (String v : favs) {
            if (v.equals("0")) {
                response.print("萝莉型");
            } else if (v.equals("1")) {
                response.print("豪放型");
            } else if (v.equals("2")) {
                response.print("经济节约型");
            }
        }
        response.print("</body>");
        response.print("</html>");
    }
}
package com.sowhat.server.core;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

/**
 * 统筹 WebContext 跟 WebHandler
 * */

public class WebApp {
    private static WebContext webContext;

    static {
        try {
            //SAX解析
            //1、获取解析工厂
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //2、从解析工厂获取解析器
            SAXParser parse = factory.newSAXParser();
            //3、编写处理器
            //4、加载文档 Document 注册处理器
            WebHandler handler = new WebHandler();
            //5、解析
            parse.parse(Thread.currentThread().getContextClassLoader().getResourceAsStream("web.xml"), handler);
            //获取数据
            webContext = new WebContext(handler.getEntitys(), handler.getMappings());
        } catch (Exception e) {
            System.out.println("解析配置文件错误");
        }
    }

    /**
     * 通过url获取配置文件对应的servlet
     * @param url
     * @return
     */
    public static Servlet getServletFromUrl(String url) {
        //假设你输入了 /login
        String className = webContext.getClz("/" + url);
        Class clz;
        try {
            System.out.println(url + "-->" + className + "-->");
            clz = Class.forName(className);
            Servlet servlet = (Servlet) clz.getConstructor().newInstance();
            return servlet;
        } catch (Exception e) {

        }
        return null;
    }
}

开启服务

package com.sowhat.server.core;

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

/**
 * 开启一个服务仅此而已
 */
public class Server {
    private ServerSocket serverSocket;
    private boolean isRunning;

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    //启动服务
    public void start() {
        try {
            serverSocket = new ServerSocket(8888);
            isRunning = true;
            receive();
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器启动失败....");
            stop();
        }
    }

    //接受连接处理
    public void receive() {
        while (isRunning) {
            try {
                Socket client = serverSocket.accept();
                System.out.println("一个客户端建立了连接....");
                //多线程处理
                new Thread(new Dispatcher(client)).start(); // 此处是 一个服务入口 重点
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("客户端错误");
            }
        }
    }

    //停止服务
    public void stop() {
        isRunning = false;
        try {
            this.serverSocket.close();
            System.out.println("服务器已停止");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

HTML

Error.HTML
<html>
	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8">	
		<title>404页面</title>
	</head>
	<body>
		<h1>404错误</h1>
	</body>
</html>
index.html
<html>
	<head>
		<meta http-equiv="content-type" content="text/html;charset=utf-8">	
		<title>首页</title>
	</head>
	<body>
		<h1>欢迎使用服务器简易版哈哈</h1>
	</body>
</html>

demo

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意:光理论是不够的,在此送大家十套2020最新Java架构实战教程+大厂面试题库,进裙: 783802103 在裙文件下载一起交流进步哦

延伸

上面只是个简易版的Tomcat雏型,可大致了解到Tomcat的流程,Tomcat无非就是 在基础版本上添加了各种Servlet,Request,Response,Session,Cookie,ServletContext,ServletConfig,EL,JSTL,Filter,Listener,JSP等这些东西,有空再写两章Tomcat源码底层的东西。

参考

源码密码:afnd
Tomcat入门

原创文章 354 获赞 3488 访问量 172万+

猜你喜欢

转载自blog.csdn.net/qq_31821675/article/details/106033015