在上一篇《深入剖析Tomcat-自定义Tomcat(1)》中,通过三个类自定义了一个简单的Web服务器,这一篇继续对其进行改进。之前的Web服务器仅能处理静态资源请求,这一篇我们将增加对Servlet的处理。本篇的代码是在前一篇的基础上进行改进的。
一、实现Servlet
编写一个Servlet需要实现javax.servlet.Servlet接口。Servlet接口中声明了5个方法,分别为init()、service()、destroy()、getServletConfig()、getServletInfo()。其中init()、service()、destroy()是servlet生命周期相关的方法,init()和destory()分别是servlet创建初始化和servlet被移除前调用。当一个客户端请求到达后,Servlet容器就调用相应servlet的service()方法,并将javax.servlet.servletRequest对象和javax.servlet.servletResponse对象作为参数传入。ServletRequest包含客户端Http请求的信息,ServletResponse则封装响应信息。
以下实现的Servlet功能非常简单,当接收到请求后,通过PrintWriter向客户端返回HTTP协议的“Hello Servlet”字符串。
public class PrimitiveServlet implements Servlet {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
System.out.println("servlet初始化...");
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("service方法被执行...");
PrintWriter out = response.getWriter();
out.println("HTTP/1.1 200 OK");
out.println("Content-Type: text/html");
out.println("");
out.println("<html><body><h1>Hello Servlet</h1></body></html>");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
System.out.println("servlet被销毁了...");
}
}
二、Web服务器
我们在上一篇的Web服务器的基础上,增加StaticResourceProcessor和ServletProcessor1,分别用于处理静态资源和Servlet。整个应用主要包含这几个类:HttpServer(web服务器)、Request(请求对象)、Response(响应对象)、ServletProcessor1(servlet处理)、StaticResourceProcessor(静态资源处理)。
1. HttpServer
当HttpServer接收请求时,程序会根据请求的Url来判断是响应静态资源还是交由Servlet处理请求。
(1)当请求的路径为:http://ip:port/staticResource时由StaticResourceProcessor响应静态资源。
(2)当请求路径为:http://ip:port/servlet/servletName时则由ServletProcessor1处理servlet请求。
public class HttpServer {
/**
* 关闭命令。若要关闭服务器,可以在请求路径后面拼接SHUTDOWN命令,比如:http://localhost:8080/SHUTDOWN
*/
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
/**
* 是否收到关闭的命令
*/
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!shutdown) {
Socket socket;
InputStream input;
OutputStream output;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
Request request = new Request(input);
request.parse();
Response response = new Response(output);
response.setRequest(request);
/**
* 改进代码:判断是否是servlet,如果请求路径以/servlet/开头表示是一个servlet,否则是请求一个静态资源
*/
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
} else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
socket.close();
// 接收到关闭命令
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
}
上面的代码在上一篇的基础上主要增加了这一段逻辑处理:
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
} else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
2. Request
继承javax.servlet.ServletRequest。基本功能与上一篇一致。
public class Request implements ServletRequest {
private String uri;
private InputStream input;
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public Request(InputStream input) {
this.input = input;
}
/**
* 用于解析http请求中的原始数据
*/
public void parse() {
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
} catch (Exception e) {
e.printStackTrace();
i = -1;
}
for (int j = 0; j < i; j++) {
request.append((char) buffer[j]);
}
System.out.println(request);
uri = parseUri(request.toString());
}
/**
* 从http请求行中获取uri
*
* @param requestString
* @return
*/
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 + 1, index2);
}
}
return null;
}
/**省略其他实现方法*/
......
}
3. Response
继承javax.servlet.ServletRequest。基本功能与上一篇一致。对getWriter()创建一个PrintWriter对象并返回。
public class Response implements ServletResponse {
private static final int BUFFER_SIZE = 1024;
Request request;
OutputStream output;
PrintWriter writer;
public Response(OutputStream output) {
this.output = output;
}
public void setRequest(Request request) {
this.request = request;
}
/**
* 返回一个静态资源到浏览器
*/
public void sendStaticResource() {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(Constants.WEB_ROOT, request.getUri());
if (file.exists()) {
String header = "HTTP/1.1 200 OK\n" +
"Content-Type: text/html\n" +
"\r\n";
output.write(header.getBytes());
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch != -1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
} else {
String errorMessage = "HTTP/1.1 404 File Not Found\n" +
"Content-Type: text/html\n" +
"\r\n" +
"<html><body><h1>File Not Found</h1></body></html>";
output.write(errorMessage.getBytes());
output.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public PrintWriter getWriter() throws IOException {
writer = new PrintWriter(output, true);
return writer;
}
/**省略其他实现方法*/
......
}
4. Servlet处理
ServletProcessor1这个类主要是处理Servlet的请求。
(1)首先从请求的URL中获取servlet的类名,在前面设计的URL:http://ip:port/servlet/servletName中servletName即为servlet的类名。
(2)然后通过URLClassLoader在Constants.WEB_ROOT设置的路径下查找servlet类。通过loader.loadClass(servletName)载入类。
(3)最后创建servlet示例并调用其service()方法。
public class ServletProcessor1 {
public void process(Request request, Response response) {
String uri = request.getUri();
// 获取一个servlet得类名
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
// 类载入器
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
File classPath = new File(Constants.WEB_ROOT);
// 类载入器查找servlet的目录
String repository = new URL("file", null, classPath.getCanonicalPath() + File.separator).toString();
urls[0] = new URL(null, repository);
loader = new URLClassLoader(urls);
} catch (Exception e) {
e.printStackTrace();
}
Class myClass = null;
try {
// 这里会先从target/class下找,找不到才会在webroot下找
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service(request, response);
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
编写好servlet后,将其编译成class,然后放入Constants.WEB_ROOT设置的路径下。
5. 静态资源处理
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
response.sendStaticResource();
} catch (Exception e) {
e.printStackTrace();
}
}
}
6. 查看效果
启动项目并访问:http://localhost:8080/servlet/PrimitiveServlet
三、存在的问题
1. 问题描述
上面应用程序存在一个严重的问题。ServletProcessor1中的process()方法。会将Request对象和Response对象向上造型为javax.servlet.ServletRequest和javax.servlet.ServletResponse。
try {
servlet = (Servlet) myClass.newInstance();
servlet.service(request, response);
}
这是非常不安全的做法,了解原理的程序员在自己的Servlet中可以再向下造型为Request和Response对象,就能使用其内部的parse()和sendStaticResource()方法。比如:
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
Request res = (Request) request;
res.parse();
}
但我们却又不能将parse()、sendStaticResource()设置为私有方法,因为他们会被其他类调用。所以针对这个问题我们对其做出改进。
2. 改进措施
我们增加两个包装类RequestFacade和ResponseFacade,作为对Request和Response的封装。包装类分别实现javax.servlet.ServletRequest和javax.servlet.ServletResponse。包装类所有方法都调用Request和Response的对应方法。这样,在servlet中只能调用包装类的方法了。
- RequestFacade
public class RequestFacade implements ServletRequest {
private ServletRequest request = null;
public RequestFacade(ServletRequest request) {
this.request = request;
}
@Override
public Object getAttribute(String s) {
return request.getAttribute(s);
}
/**省略其他实现方法*/
......
}
- ResponseFacade
public class ResponseFacade implements ServletResponse {
private ServletResponse response;
public ResponseFacade(ServletResponse response) {
this.response = response;
}
@Override
public String getCharacterEncoding() {
return response.getCharacterEncoding();
}
/**省略其他实现方法*/
......
}
- ServletProcessor1
ServletProcessor1中的process()方法修改为使用包装类。
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
/**
* 这里改为使用包装类
*/
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
servlet.service(requestFacade, responseFacade);
}