java 尝试实现一个简单的servlet容器

怎么做?试着写一些思路。

感谢:http://www.cnblogs.com/chenpi/p/5603072.html

 

本文是此篇文章的拓展:

http://www.xie4ever.com/2017/05/13/java-%E5%B0%9D%E8%AF%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%99%A8/

上次实现的是静态资源的处理。这次尝试编写的servlet容器可以对动态内容进行处理。

一、什么是servlet

既然要实现一个servlet容器,那么首先要了解什么是servlet。

servlet的本质是一个Java对象,这个对象拥有一系列的方法来处理HTTP请求。

浏览器发送一个HTTP请求后,Web容器就会把请求分配给特定的servlet进行处理。

servlet常见的方法有doGet(),doPost()等。Web容器中包含了多个servlet,特定的HTTP请求该由哪一个servlet来处理是由Web容器中的web.xml来决定的。

这篇文章尝试实现的就是这里的“Web容器”,也就是servlet容器。

二、具体实现思路

(1)创建一个ServerSocket对象。

(2)调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待。

(3)从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应。

(4)处理请求:读取InputStream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息。

(5)处理响应(分两种类型,静态资源请求响应或servlet请求响应):

如果是静态资源请求,则根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中。

如果是Servlet请求,则首先生成一个URLClassLoader类加载器,加载请求的servlet类,创建servlet对象,执行service方法(往OutputStream写入响应的数据)。

(6)关闭Socket对象。

(7)转到步骤2,继续等待连接请求。

三、具体实现

本项目结构为:

7

1.添加servlet依赖

既然我们要实现一个servlet容器,那么这个容器要用来装什么?当然是装servlet对象。那么servlet对象从哪里来?当然得由我们自己引入啦。

因为我使用maven管理项目,所以添加依赖:

2.修改上次的HttpServer类

如果请求的是servlet对象,就交给servlet处理器进行处理。如果请求的是静态资源,就交给静态资源处理器处理。

那么问题来了,我怎么区分请求的对象?我怎么知道请求的是servlet还是静态资源?

我的解决方案比较简单。通过请求的uri来区分。如果uri中带有servlet路径,就代表请求servlet资源,否则就代表请求静态资源。

HttpServer.java

3.修改Request类,实现ServletRequest接口

因为要接收servlet请求,所以Request需要实现ServletRequest接口,才能被servlet的service方法处理。

Request.java

4.修改Response类,实现ServletResponse接口

因为要响应servlet请求,所以Response需要实现ServletResponse接口,才能被servlet的service方法处理。

Response.java

要注意实现getWriter方法。在我们自己的Servlet类中,需要调用这个方法实现字符串的输出。

5.实现静态资源处理类

只需要调用Response类中的sendStaticResource方法即可。

StaticProcessor.java

6.实现我们要调用的Servlet类

Hello.java

这个Servlet类名为Hello,只要访问http://localhost:8081/servlet/Hello就可以调用这个Servlet类,返回相应的结果(输出Hello xie4ever !字符串)。

7.实现Servlet处理类

这个处理类是本文的重点。

ServletProcessor.java

代码分析如下:

(1)获取uri中请求的servlet类的名称:

如果请求的路径为http://localhost:8081/servlet/Hello,则uri的值为servlet/Hello,servletName的值为Hello,即请求的servlet类名为Hello。

(2)定义类加载器,尝试加载servlet类的class文件:

类加载器将文件路径设为”file:” + Constant.WEB_SERVLET_ROOT,对我来说,就是本地路径D:/EclipseWorkspace/testServer/target/classes。

此路径为java文件编译后生成的class文件存放的位置。类加载器会尝试在此目录下加载servlet类的class文件:

如果找不到这个servlet类的class文件,就会报出找不到class的错误。

(3)通过service方法返回结果

在这里,我们实例化了类加载器加载的Servlet类,并且调用其service方法,从而输出结果。

(4)为什么要new一个不被使用的Servlet类?

这个名为Hello的Servlet类根本没有被使用,为什么要new?

这确实是一个很奇怪的问题。

从上文中,我们知道类加载器要在D:/EclipseWorkspace/testServer/target/classes路径下加载servlet类的class文件。但是,如果这个servlet类没有被使用,这个类就不会被编译(不会生成class文件),该路径下就不会有相应的class文件,就会报出找不到class的错误。

所以,我们要“使用”这个servlet类(虽然根本就用不上),于是new一下:

从而保证类加载路径下存在这个servlet类的class文件。

(5)loadClass方法的路径问题

要读取Hello.class,必须把类加载器的路径设置为:D:/EclipseWorkspace/testServer/target/classes,然后读取com.xie.testServer.server.Hello类:

只有这种方法是正确的。

之前我异想天开,直接把类加载器的路径设为:D:\EclipseWorkspace\testServer\target\classes\com\xie\testServer\server,读取Hello:

结果换来了一个NoClassDefFoundError:

错误很明显,不能这样加载class文件。

四、未解决的问题

非常奇怪的问题,firefox和ie中都可以输出“Hello xie4ever”,但是chrome直接报错“该网页无法正常运作”,错误为ERR_EMPTY_RESPONSE。

查了一下发现这肯定是服务端存在问题,但是为什么就chrome无法输出,别的浏览器都没有问题呢?我尝试了各种办法,还是不知道为什么。例如:

1.在outputstream中加上ContentType参数指定输出为”text/html”。

2.将println方法换成write方法。使用flush方法输出缓存。

3.查了很多PrintWriter无法输出的相关问题。

4.打了各种断点,写了一堆try catch尝试捕获错误。

很遗憾,全部都失败了。因为卡在这个问题上太久,我果断去忙别的事情了,到现在还是没搞明白为什么。

五、总结

这次实现确实遇到了很多没想到的小bug,还留下了一个没解决的大bug,现在很难受。希望之后能补上为什么。

发表评论

电子邮件地址不会被公开。 必填项已用*标注