手写个Tomcat雏型
前言
目的:
网络编程在编程领域随处可见,本文尝试手写一个简单的
HttpServer
,了解网络请求后台的大致思路及为学习Tomcat打好基础。
准备知识:
- OOP:面向对象编程思想
- collection:Java常用容器
- IO:网络IO操作
- Thread:多线程
- Socket:网络编程
- XML:配置文件解析
- reflect:框架基石反射
- HTML:HTML基本语法
- HTTP:网络传输协议
思路
先说下大致流程:
- 服务端开启一个服务,进入一个死循环来给前端提供服务。
- 客户端发送一个HTTP请求。
- 服务器端收到请求后,加载一次XML文件解析,主要目的是获得客户的请求网址对应的那个Servlet(服务器端小程序)。
- 找到对应的Servlet后通过反射映射出实例,然后业务处理。
- 最终Socket通信返回结果。
流程图:
代码结构
代码实现
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.sowhat.user.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;@Overridepublic 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;}}}@Overridepublic void characters(char[] ch, int start, int length) throws SAXException {String contents = new String(ch, start, length).trim();if (null != tag) { //处理了空if (isMapping) { //操作servlet-mappingif (tag.equals("servlet-name")) {mapping.setName(contents);} else if (tag.equals("url-pattern")) {mapping.addPattern(contents);}} else { //操作servletif (tag.equals("servlet-name")) {entity.setName(contents);} else if (tag.equals("servlet-class")) {entity.setClz(contents);}}}}@Overridepublic 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-classprivate Map<String, String> entityMap = new HashMap<String, String>();//key -->url-pattern value -->servlet-nameprivate Map<String, String> mappingMap = new HashMap<String, String>();public WebContext(List<Entity> entitys, List<Mapping> mappings) {this.entitys = entitys;this.mappings = mappings;//将entity 的List转成了对应mapfor (Entity entity : entitys) {entityMap.put(entity.getName(), entity.getClz());}//将map 的List转成了对应mapfor (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) {//假设你输入了 /loginString 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;//请求urlprivate 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();}//处理请求参数为Mapprivate void convertMap() {//1、分割字符串 &String[] keyValues = this.queryStr.split("&");for (String queryStr : keyValues) {//2、再次分割字符串 =String[] kv = queryStr.split("=");kv = Arrays.copyOf(kv, 2);//获取key和valueString 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 blocke.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 OKheadInfo.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 {@Overridepublic 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 {@Overridepublic 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>");}
}
Dispatcher
package com.sowhat.server.core;import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;public class Dispatcher implements Runnable {private Socket client;private Request request; // 处理网页请求private Response response; // 返回网页请求public Dispatcher(Socket client) {this.client = client;try {//获取请求协议//获取响应协议request = new Request(client);response = new Response(client);} catch (IOException e) {e.printStackTrace();this.release();}}@Overridepublic void run() {try {if (null == request.getUrl() || request.getUrl().equals("")) {InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("index.html");byte[] datas = new byte[ 1024 * 1024];int len = is.read(datas);String tmp = new String(datas, 0, len);response.print(tmp);response.pushToBrowser(200);is.close();return;}Servlet servlet = WebApp.getServletFromUrl(request.getUrl());if (null != servlet) {servlet.service(request, response);//关注了状态码response.pushToBrowser(200);} else {//错误....InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("error.html");byte[] datas = new byte[ 1024 * 1024];int len = is.read(datas);String tmp = new String(datas, 0, len);response.print(tmp);response.pushToBrowser(404);is.close();}} catch (Exception e) {try {response.println("你好我不好,我会马上好");response.pushToBrowser(500);} catch (IOException e1) {e1.printStackTrace();}}release();}//释放资源private void release() {try {client.close();} catch (IOException e1) {e1.printStackTrace();}}
}
开启服务
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
延伸
上面只是个简易版的Tomcat雏型,可大致了解到Tomcat的流程,Tomcat无非就是 在基础版本上添加了各种Servlet、Request、Response、Session、Cookie、ServletContext、ServletConfig、EL、JSTL、Filter、Listener、JSP等这些东西,有空再写两章Tomcat源码底层的东西。
参考
源码密码:afnd
Tomcat入门
手写个Tomcat雏型相关推荐
- Tomcat(二):手写嵌入式tomcat
一.什么是嵌入式tomcat 嵌入式tomcat:非传统的部署方式,将tomcat嵌入到主程序中进行运行. 二.嵌入式tomcat的优点 灵活部署.任意指定位置.通过复杂的条件判断 三.Maven集成 ...
- 【Tomcat】第十篇:手写嵌入式Tomcat插件(超简单)
1.两种类型tomcat 传统tomcat:需要jar部署,通过BootStrap启动 嵌入式tomcat:maven引入Tomcat类,自定义启动插件(Connector,Engin,Host,Co ...
- 大佬说:怎么手写一个Tomcat然后给这个Tomcat 部署项目并且成功处理请求 ? 我TM当时就吓尿了!
文章目录 前言 一.怎么部署项目? 二.分析以及思路 1.Tomcat的配置文件 2 web项目文件夹信息 2.初始化项目配置 定义映射类 加载配置文件 加载 server.xml 解析项目内容 解析 ...
- tomcat应用服务器有哪些,手写一个Tomcat应用服务器
mytomcat 项目简介 自己实现的简易的Tomcat Tomca 实现说明 Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打交道.这是一只神奇的猫,我们可以通过实现它来深 ...
- 手写嵌入式Tomcat(带详细注解)
导读 本文分为两部分 1. 实现思路分析部分 2. 实现代码部分 一. 实现思路分析 使用Tomcat的API实现, 步骤如下 新建一个Tomcat对象 设置Tomccat的端口号 设置Context ...
- tomcat配置url跳转_架构成长之路:还不清楚原理就晚了,Java技术大咖带你手写Tomcat...
推荐阅读 手把手教你手写SpringMVC,剑指优秀开源框架灵魂 纯手写实现高可用的RPC框架,Java架构师必备技能 前言 Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打 ...
- 【手写系列】写一个迷你版的Tomcat
前言 Tomcat,这只3脚猫,大学的时候就认识了,直到现在工作中,也常会和它打交道.这是一只神奇的猫,今天让我来抽象你,实现你! Tomcat Write MyTomcat Tomcat是非常流行的 ...
- 纯jsp实现评论功能_自己实现的java手写tomcat
这是一个java写的模拟tomcat工作原理的demo,是一个极简的tomcat服务器,也是我们培训班(邦邦IT教育)的讲义,是整个j2ee培训的精髓,理解了这个demo其实后面的很多东西都是可以自学 ...
- 从头开始实现一个小型spring框架——手写Spring之集成Tomcat服务器
手写Spring之集成Tomcat与Servlet 写在前面 一.Web服务模型及servlet 1.1 Web服务器 1.2 请求流程 二.实现 三.小结 写在前面 最近学习了一下spring的相关 ...
- 手写一个基于NIO的迷你版Tomcat
笔者也建立的自己的公众号啦,平时会分享一些编程知识,欢迎各位大佬支持~ 扫码或微信搜索北风IT之路关注 本文公众号地址:手写一个基于NIO的迷你版Tomcat 在很久之前看到了一篇文章写一个迷你版的T ...
最新文章
- 计算机代码坑人小程序bat,批处理写的关机小程序--bat
- java javac javap 命令使用简介
- python使用缩进作为语法边界一般建议怎样缩进-python基础自测挑战题,适合刚起步不久的小猿!...
- Caffe官方教程翻译(4):CIFAR-10 turorial
- azure devops中文显示乱码_Azure DevOps Server:Git权限设置
- Linux环境下查看IP不显示IPv4地址
- SQLi LABS Less 9 时间盲注
- Struts2的Action中访问servletAPI方式
- jprofiler 9注册码
- Java并发工具类之CountDownLatch
- C语言错题锦集(持续更新)
- SCM系统是什么?供应链管理系统有哪些优势?
- Mac系统的操作教程
- 【matlab图像处理】图像处理工具箱(1)
- 无人机pid调节顺口溜
- 必应每日一图php,轻量必应每日一图HTML源码
- YARN——NM重启后的任务恢复
- 服务器上设置密码策略不能修改,Window Server 2008 R2 在Active Directory域中不能更改服务器密码策略...
- 微信web端生成支付二维码
- Arturia CZ V(音频合成器)v1.0.0.2695官方版
热门文章
- 全球及中国单波束回声探测仪行业十四五发展规划及项目前景评估报告2022-2027年
- 如何求绝对值最小的数
- 计算机应用基础——计算机软件(二)
- 中南大学复试上机: 彩色气球
- Android 实现动态背景“五彩蛛网”特效,让你大开眼界!
- java simon_Java Simon
- 明月当空照,python3D编程初阶:坐标、模型、纹理与光
- 经济学和计算机考研哪个较难,经济学考研难吗,经济学为什么考研这么难!!!!?...
- 2021-3-18大学化学无机原理(3)热力学能与热力学第一定律
- 那年的夏天——致毕业