深入学习Java Web服务器系列二
一个简单的servlet容器
在上一篇博客已经介绍了如何去实现一个简单的静态web容器,我们实现了一个可以解析静态html资源的web容器,下面,我们将进一步实现一个简单的servlet容器,来了解一下servlet容器的基本原理。
我们将在系列一的基础上进行相应的修改。服务器启动监听,当用户在浏览器输入URL发送http请求时,服务器进行解析requst,判断请求的资源类型,如果是静态资源并则返回请求的静态资源,如果是servlet资源,则进行解析并返回页面。系统的时序图如下所示:
下面我们一起来实现这个servlet容器吧,这篇博文分成三个部分,第一部分介绍servlet的生命周期,这个可以帮助我们明确容器的功能需求,第二部分介绍了这个简单的servlet容器的功能和相应的类关系,第三部分就是进行容器的代码实现了。
1. servlet的生命周期
Servlet编程是通过javax.servlet和javax.servlet.http这两个包的类和接口来实现的。其中一个至关重要的就是javax.servlet.Servlet接口了。所有的servlet必须实现实现或者继承实现该接口的类。
Servlet接口有五个方法,其用法如下。
public void init(ServletConfig config) throws ServletException
public void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
public void destroy()
public ServletConfig getServletConfig()
public java.lang.String getServletInfo()
在Servlet的五个方法中,init,service和destroy是servlet的生命周期方法。在servlet类已经初始化之后,init方法将会被servlet容器所调用。servlet容器只调用一次,以此表明servlet已经被加载进服务中。init方法必须在servlet可以接受任何请求之前成功运行完毕。一个servlet程序员可以通过覆盖这个方法来写那些仅仅只要运行一次的初始化代码,例如加载数据库驱动,值初始化等等。在其他情况下,这个方法通常是留空的。
servlet容器为servlet请求调用它的service方法。servlet容器传递一个javax.servlet.ServletRequest对象和javax.servlet.ServletResponse对象。ServletRequest对象包括客户端的HTTP请求信息,而ServletResponse对象封装servlet的响应。在servlet的生命周期中,service方法将会给调用多次。
当从服务中移除一个servlet实例的时候,servlet容器调用destroy方法。这通常发生在servlet容器正在被关闭或者servlet容器需要一些空闲内存的时候。仅仅在所有servlet线程的service方法已经退出或者超时淘汰的时候,这个方法才被调用。在servlet容器已经调用完destroy方法之后,在同一个servlet里边将不会再调用service方法。destroy方法提供了一个机会来清理任何已经被占用的资源,例如内存,文件句柄和线程,并确保任何持久化状态和servlet的内存当前状态是同步的。
2. 结构分析
功能分析
通过上面servlet的生命周期,我们可以知道,一个全功能的servlet容器会为servlet的每个HTTP请求做下面一些工作:
- 当第一次调用servlet的时候,加载该servlet类并调用servlet的init方法(仅仅一次)。
- 对每次请求,构造一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。
- 调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
- 当servlet类被关闭的时候,调用servlet的destroy方法并卸载servlet类。
我们下面实现的简单的servlet容器并不是一个全功能的servlet容器,我们先实现一个简单的servlet容器,实现的功能如下:
- 等待HTTP请求。
- 构造一个ServletRequest对象和一个ServletResponse对象。
- 假如该请求需要一个静态资源的话,调用StaticResourceProcessor实例的process方法,同时传递ServletRequest和ServletResponse对象。
- 假如该请求需要一个servlet的话,加载servlet类并调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。
类图关系
我们可以把这个容器分成6个类,
- HttpServer
- Request
- Response
- StaticResourceProcessor
- ServletProcessor
- Constants
这个web容器的入口点(静态main方法)可以在HttpServer类里边找到。main方法创建了一个HttpServer的实例并调用了它的await方法。await方法等待HTTP请求,为每次请求创建一个Request对象和一个Response对象,并把他们分发到一个StaticResourceProcessor实例或者一个ServletProcessor实例中去,这取决于请求一个静态资源还是一个servlet。 Constants类包括涉及其他类的静态final变量WEB_ROOT。WEB_ROOT显示了PrimitiveServlet和这个容器可以提供的静态资源的位置。
3. 代码实现
- Constants
这里存放web资源的指定目录,用来约定资源的存放位置。
import java.io.File;
public class Constants {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
}
- HttpServer
HttpServer包含服务器的启动方法,这里要实现监听,当有http请求到达时,启动request的解析,获取请求资源uri,判断请求资源的类型,如果是静态资源,则交给StaticResourceProcessor处理,如果是servlet,则交给ServletProcessor处理。
import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
public class HttpServer {
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8100;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
//while循环管等待socket请求并进行处理
while (true) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// 创建一个request
Request request = new Request(input);
request.parse();
// 创建一个response
Response response = new Response(output);
response.setRequest(request);
// 检查请求资源是静态资源还是servlet,servlet的请求以"/servlet/"开头
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
socket.close();
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
- Request
Request类实现了ServletRequest接口,这是为了方便我们进行servlet的构建,request实现解析http请求获取请求资源uri。
import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class Request implements ServletRequest{
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public String getUri() {
return uri;
}
//解析获取请求uri
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 +