JavaSE 手写 Web 服务器(二)
原文地址:JavaSE 手写 Web 服务器(二)
博客地址:http://www.extlight.com
一、背景
在上一篇文章 《JavaSE 手写 Web 服务器(一)》 中介绍了编写 web 服务器的初始模型,封装请求与响应和多线程处理的内容。但是,还是遗留一个问题:如何根据不同的请求 url 去触发不同的业务逻辑。
这个问题将在本篇解决。
二、涉及知识
XML:将配置信息写到 XML 文件,解决硬编码问题。
反射:读取 XML 文件配置并实例化对象。
三、封装控制器
目前手写的 web 容器只能处理一种业务请求,无论发送什么 url 的请求都是只返回一个内容相似的页面。
为了能很好的扩展 web 容器处理不同请求的功能,我们需要将 request 和 response 封装到控制器,让各个业务的控制器处理对应请求和响应。
3.1 抽离控制器
创建一个父类控制器:
public class Servlet {
public void service(Request request, Response response) {
doGet(request,response);
doPost(request,response);
}
protected void doGet(Request request, Response response) {
}
protected void doPost(Request request, Response response) {
}
}
父类中使用了模板方法的设计模式,将业务处理的行为交给子类去处理。
当我们需要一个控制器去处理登陆请求时,我们创建一个 LoginServlet 类去继承 Servlet 并重写 doGet 或 doPost 方法:
public class LoginServlet extends Servlet {
@Override
protected void doPost(Request request, Response response) {
response.println("<!DOCTYPE html>")
.println("<html lang=\"zh\">")
.println(" <head> ")
.println(" <meta charset=\"UTF-8\">")
.println(" <title>测试</title>")
.println(" </head> ")
.println(" <body> ")
.println(" <h3>Hello " + request.getParameter("username") + "</h3>")// 获取登陆名
.println(" </body> ")
.println("</html>");
}
}
如果需要处理注册请求,创建一个 RegisterServlet 类继承 Servlet 并重写 doGet 或 doPost 方法即可。
3.2 创建 web 容器上下文
为了能更好的管理多个控制器实例以及请求 url 与控制器的对应关系,我们需要一个类对其封装和管理。
/**
* web 容器上下文
* @author Light
*
*/
public class ServletContext {
// 存储处理不同请求的 servlet
// servletName -> servlet 子类
private Map<String,Servlet> servletMap;
// 存储请求 url 与 servlet 的对应关系
// 请求 url -> servletName
private Map<String,String> mappingMap;
public ServletContext() {
this.servletMap = new HashMap<String, Servlet>();
this.mappingMap = new HashMap<String, String>();
}
public Map<String, Servlet> getServletMap() {
return servletMap;
}
public void setServletMap(Map<String, Servlet> servletMap) {
this.servletMap = servletMap;
}
public Map<String, String> getMappingMap() {
return mappingMap;
}
public void setMappingMap(Map<String, String> mappingMap) {
this.mappingMap = mappingMap;
}
}
3.3 创建配置类
虽然有了 web 容器上下文,但是 web 容器上下文并不是一开始就存放配置信息的。配置信息在 web 容器启动时被注册或写入到上下文中,因此需要一个管理配置的类负责该操作:
/**
* 配置文件类
* @author Light
*
*/
public class WebApp {
private static ServletContext context;
static {
context = new ServletContext();
Map<String,Servlet> servletMap = context.getServletMap();
Map<String,String> mappingMap = context.getMappingMap();
// 注册 登陆控制器
servletMap.put("login", new LoginServlet());
mappingMap.put("/login", "login");
// 如有更多请求需要处理,在此配置对应的控制器即可
}
/**
* 通过请求 url 获取对应的 Servlet 对象
* @param url
* @return
*/
public static Servlet getServlet(String url) {
if (url == null || "".equals(url.trim())) {
return null;
}
String servletName = context.getMappingMap().get(url);
Servlet servlet = context.getServletMap().get(servletName);
return servlet;
}
}
改造 Dispatcher 的 run 方法,从 WebApp 类中获取控制器实例:
@Override
public void run() {
// 获取控制器
Servlet servlet = WebApp.getServlet(this.request.getUrl());
// 处理请求
servlet.service(request, response);
try {
this.response.pushToClient(code);
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
四、解决硬编码问题
在 WebApp 类中,我们配置了 LoginServlet 类以及相关信息,这种写法属于硬编码,且这个两个类发生了耦合。
为了解决上述问题,我们可以将 LoginServlet 类的配置写到一个 xml 文件中,WebApp 类负责读取和解析 xml 文件中的信息并将信息写入到 web 容器上下文中,在 Dispatcher 类中通过反射实例化控制器对象处理请求。
创建 web.xml 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.light.controller.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
</web-app>
创建两个类用于封装 web.xml 配置文件中的数据,即 <servlet> 和 <servlet-mapping> 相关标签的内容:
/**
* 封装 web.xml 中 <servlet> 配置信息
* @author Light
*
*/
public class ServletXml {
private String servletName;
private String servletClazz;
public String getServletName() {
return servletName;
}
public void setServletName(String servletName) {
this.servletName = servletName;
}
public String getServletClazz() {
return servletClazz;
}
public void setServletClazz(String servletClazz) {
this.servletClazz = servletClazz;
}
}
/**
* 封装 web.xml 中 <servlet-mapping> 配置信息
* @author Light
*
*/
public class ServletMappingXml {
private String servletName;
private List<String> urlPattern = new ArrayList<>();
public String getServletName() {
return servletName;
}
public void setServletName(String servletName) {
this.servletName = servletName;
}
public List<String> getUrlPattern() {
return urlPattern;
}
public void setUrlPattern(List<String> urlPattern) {
this.urlPattern = urlPattern;
}
}
创建 xml 文件解析器,用于解析 web.xml 配置文件:
/**
* xml文件解析器
* @author Light
*
*/
public class WebHandler extends DefaultHandler{
private List<ServletXml> servletXmlList;
private List<ServletMappingXml> mappingXmlList;
private ServletXml servletXml;
private ServletMappingXml servletMappingXml;
private String beginTag;
private boolean isMapping;
@Override
public void startDocument() throws SAXException {
servletXmlList = new ArrayList<>();
mappingXmlList = new ArrayList<>();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (qName != null) {
beginTag = qName;
if ("servlet".equals(qName)) {
isMapping = false;
servletXml = new ServletXml();
} else if ("servlet-mapping".equals(qName)){
isMapping = true;
servletMappingXml = new ServletMappingXml();
}
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (this.beginTag != null) {
String str = new String(ch,start,length);
if(isMapping) {
if("servlet-name".equals(beginTag)) {
servletMappingXml.setServletName(str);
} else if ("url-pattern".equals(beginTag)) {
servletMappingXml.getUrlPattern().add(str);
}
} else {
if("servlet-name".equals(beginTag)) {
servletXml.setServletName(str);
} else if ("servlet-class".equals(beginTag)) {
servletXml.setServletClazz(str);
}
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (qName != null) {
if ("servlet".equals(qName)) {
this.servletXmlList.add(this.servletXml);
} else if ("servlet-mapping".equals(qName)){
this.mappingXmlList.add(this.servletMappingXml);
}
}
this.beginTag = null;
}
public List<ServletXml> getServletXmlList() {
return servletXmlList;
}
public List<ServletMappingXml> getMappingXmlList() {
return mappingXmlList;
}
}
改造 ServletContext 类:
// 存储处理不同请求的 servlet
// servletName -> servlet 子类的全名
private Map<String,String> servletMap;
即 将 private Map<String,Servlet> servletMap 改成 private Map<String,String> servletMap 。
注意,get 和 set 方法都需要修改。
改造 WebApp 类中注册控制器相关代码:
/**
* 配置文件类
* @author Light
*
*/
public class WebApp {
private static ServletContext context;
static {
context = new ServletContext();
Map<String,String> servletMap = context.getServletMap();
Map<String,String> mappingMap = context.getMappingMap();
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser sax = factory.newSAXParser();
WebHandler handler = new WebHandler();
// 读取和解析 xml 文件
sax.parse(Thread
.currentThread()
.getContextClassLoader()
.getResourceAsStream("com/light/server/web.xml"),
handler);
// 注册 servlet
List<ServletXml> servletXmlList = handler.getServletXmlList();
for (ServletXml servletXml : servletXmlList) {
servletMap.put(servletXml.getServletName(), servletXml.getServletClazz());
}
// 注册 mapping
List<ServletMappingXml> mappingXmlList = handler.getMappingXmlList();
for (ServletMappingXml mapping : mappingXmlList) {
List<String> urls = mapping.getUrlPattern();
for (String url : urls) {
mappingMap.put(url, mapping.getServletName());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 通过请求 url 获取对应的 Servlet 对象
* @param url
* @return
*/
public static String getServletClazz(String url) {
if (url == null || "".equals(url.trim())) {
return null;
}
String servletName = context.getMappingMap().get(url);
String servletClazz = context.getServletMap().get(servletName);
return servletClazz;
}
}
改造 Dispatcher 类的 run 方法,通过反射实例化对象:
@Override
public void run() {
try {
// 获取控制器包名
String servletClazz = WebApp.getServletClazz(this.request.getUrl());
// 通过反射实例化控制器对象
Servlet servlet = (Servlet) Class.forName(servletClazz).newInstance();
// 处理请求
servlet.service(request, response);
this.response.pushToClient(code);
this.socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
五、总结
手写 web 容器的内容到此结束。
文章中主要介绍编写 web 容器的大致流程,代码中还有许多地方需要考虑(过滤器、监听器、日志打印等)和优化,仅作为笔者对 web 容器的理解与分享,并不是为了教读者重复造轮子。
学习东西要知其然,更要知其所以然。
六、源码
JavaSE 手写 Web 服务器(二)的更多相关文章
- JavaSE 手写 Web 服务器(一)
原文地址:JavaSE 手写 Web 服务器(一) 博客地址:http://www.extlight.com 一.背景 某日,在 Java 技术群中看到网友讨论 tomcat 容器相关内容,然后想到自 ...
- HTTP网络协议与手写Web服务容器
Http协议 1.深入概念 Http:HyperText Transfer Protocol,即是超文本传输协议. 2.浅出概念(使用浏览器访问服务器端网页时需要遵循的一系列规则) Http:将各种不 ...
- tensorflow笔记(五)之MNIST手写识别系列二
tensorflow笔记(五)之MNIST手写识别系列二 版权声明:本文为博主原创文章,转载请指明转载地址 http://www.cnblogs.com/fydeblog/p/7455233.html ...
- 黑马vue---40、结合Node手写JSONP服务器剖析JSONP原理
黑马vue---40.结合Node手写JSONP服务器剖析JSONP原理 一.总结 一句话总结: 服务端可以返回js代码给script标签,那么标签会执行它,并且可带json字符串作为参数,这样就成功 ...
- opencv 手写选择题阅卷 (二)字符识别
opencv 手写选择题阅卷 (二)字符识别 选择题基本上只需要识别ABCD和空五个内容,理论上应该识别率比较高的,识别代码参考了网上搜索的代码,因为参考的网址比较多,现在也弄不清是参考何处的代码了, ...
- 利用神经网络算法的C#手写数字识别(二)
利用神经网络算法的C#手写数字识别(二) 本篇主要内容: 让项目编译通过,并能打开图片进行识别. 1. 从上一篇<利用神经网络算法的C#手写数字识别>中的源码地址下载源码与资源, ...
- 【项目】手写FTP服务器-C++实现FTP服务器
X_FTP_server 手写FTP服务器-C++实现FTP服务器 项目Gitee链接:https://gitee.com/hsby/ftp_Server 简介 一个基于libevent的高并发FTP ...
- 手写AVL平衡二叉搜索树
手写AVL平衡二叉搜索树 二叉搜索树的局限性 先说一下什么是二叉搜索树,二叉树每个节点只有两个节点,二叉搜索树的每个左子节点的值小于其父节点的值,每个右子节点的值大于其左子节点的值.如下图: 二叉搜索 ...
- 手写Tomcat服务器
预备知识 编写服务器用到的知识点 1) Socket 编程2) HTML3) HTTP 协议4) 反射5) XML 解析6) 服务器编写 Socket编程 https://www.cnblogs.co ...
随机推荐
- 如何把数字字符'1'转换为整数(java 实现)
在一些表达式计算时,如 “3+2” 表达式自身是个字符串,通过切片得到的是数字字符和操作符,不能直接进行计算,在表达式计算中需要进行一步操作是,把数字字符'2','3'转化为整数. 如何操作? ...
- OpenGL全景视频
全景视频其实在实现上和一般的视频播放基本差不多,解码可以用ffmpeg,只是对解码后的图片在绘制的时候要绘制在一个球上(我这里是球,好像有说有的格式要绘制在四面体上的,美做深入研究),而不是画在一个表 ...
- UVA-10047 The Monocycle (图的BFS遍历)
题目大意:一张图,问从起点到终点的最短时间是多少.方向转动也消耗时间. 题目分析:图的广度优先遍历... 代码如下: # include<iostream> # include<cs ...
- Valgrind查找内存泄露利器
Valgrind是一个GPL的软件,用于Linux(For x86, amd64 and ppc32)程序的内存调试和代码剖析.你可以在它的环境中运行你的程序来监视内存的使用情况,比如C 语言中的ma ...
- h5和app原生联调触发方法
//路径跳转 urlHref(item) {//人物.访谈.动态是一个页面 var para = {}; para.title = "动态详情"; para.type = &quo ...
- eclipse背景设置什么颜色缓解眼睛疲劳之一
Eclipse操作界面默认颜色为白色.对于我们长期使用电脑编程的人来说,白色很刺激我们的眼睛,如果把颜色改成绿色的颜色就会缓解眼睛的疲劳. 设置方法如下: 1.打开window->Prefere ...
- 【转】netlink socket编程实例
[转]netlink socket编程实例 转自:http://blog.chinaunix.net/uid-14753126-id-2983915.html 关于Netlink IPC方式的介绍,请 ...
- Spring整合hibernate:3、使用XML进行声明式的事务管理
配置applicationContext.xml文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 2 ...
- kmeans实现文本聚类
需求 拿到的需求是输入n个文本,对文本进行聚类,由于这些输入不能通过历史数据进行训练,所以这个主要就是用无监督学习来解决. kmeans 谈到聚类就会想到kmeans,它的核心思想是给定的K值和K个初 ...
- [Linux] 虚拟环境的配置和使用 virtualenv
1.安装 sudo apt-get install python-virtualenv 2.使用 创建虚拟环境: virtualenv [虚拟环境名称] 例如: virtualenv env_test ...