本章主要通过两个应用讲述如何编写自己的Servler容器,第一个比较见到,只是为了理解一下Servlet容器是如何工作的。然后引出较为复杂的第二个例子。
javax.servlet.Servlet 接口
先来看一个常见的Servlet类:
//how tomcat works 一书出版时间较早,此书中使用的接口可能和日常的有所出入,但是原理上是一样的。
public class PrimitiveServlet implements Servlet {
@Override
public void destroy() {
System.out.println("destroy");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("init");
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("from service");
PrintWriter out = response.getWriter();
out.println("Hello. Roses are red.");
out.print("Violets are blue.");
}
}
要理解这些代码,需要对javax.servlet.Servlet Interface
有一定的了解,该接口中包含有下面五个方法:
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()
在这些方法中,init,service,destroy方法是Servlet的生命周期方法,当Servlet类被初始化后会立即调用init方法,执行一些初始化的操作,表明Servlet已经被放置到服务中。在Servlet接受request请求之时,init必须已经执行完毕。
当请求Servlet时,service方法就会被调用。servlet容器包含javax.servlet.ServletRequest
对象和javax.servlet.ServletResponse
对象(关于这两个对象和Servlet的实质可以参考上一章的代码)。ServletRequest
包含有HTTP请求的一些信息,而ServletResponse
包含有响应的一些信息。
将servlet实例从服务中移除之前会调用destroy方法,这一般是在servlet容器被关闭或者servlet容器需要更多的内存空间时被执行。这个方法的调用要求所有包含有service方法的线程都被退出。destroy方法存在使得servlet可以进行一些清理资源的操作。
应用1
从上面的例子可以看到,对于每一个HTTP请求,Servlet容器做了下面的一些事情:
- servlet被请求的时候,加载Servlet类并调用init方法(执行一次)
- 对于每一个请求,构造
javax.servlet.ServletRequest
对象和javax.servlet.ServletResponse
对象 - 执行service方法,传递
ServletRequest
和ServletResponse
对象 - servlet类被关闭时,调用destroy方法,移除servlet类
下面要编写的servlet容器功能并不完全,他只能完成一个简单的servlet的容器的功能,并且并不调用init和destroy方法。他的功能如下:
- 等待HTTP请求
- 构建Request对象和Response对象
- 如果Request请求的是静态资源,创建StaticResourceProcessor实例,传入Request和Response对象
- 如果Request请求的是Servlet,加载servlet类调用service方法并传入Request和Response对象
注:在此servlet容器中,每次request请求servlet类都会被加载。
第一个应用包含有6个类:
- HttpServer1
- Request
- Response
- StaticResourceProcessor
- ServletProcessor1
- Constants
uml结构如下:
应用的入口(main方法)在HttpServer1中,HttpServer1的实现和第一章中HttpServer类 类似,只是HttpServer1不仅可以处理静态文件的请求,也可以处理servlet的请求
代码:
public class HttpServer1 {
// shutdown command
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
// the shutdown command received
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer1 server = new HttpServer1();
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);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
//判断uri是以什么开头的
if (request.getUri().startsWith("/servlet/")) {
ServletProcessor1 processor = new ServletProcessor1();
processor.process(request, response);
}
else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
Request类代码:
public class Request implements ServletRequest {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public String getUri() {
return 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 + 1, index2);
}
return null;
}
public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
//....其他来自父类的抽象使用空实现
}
Response类:
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;
}
/* This method is used to serve a static page */
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
/* request.getUri has been replaced by request.getRequestURI */
File file = new File(Constants.WEB_ROOT, request.getUri());
fis = new FileInputStream(file);
/*
HTTP Response = Status-Line
*(( general-header | response-header | entity-header ) CRLF)
CRLF
[ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
*/
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch!=-1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
catch (FileNotFoundException e) {
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
finally {
if (fis!=null)
fis.close();
}
}
public PrintWriter getWriter() throws IOException {
// autoflush is true, println() will flush,
// but print() will not.
writer = new PrintWriter(output, true);
return writer;
}
//......其他父类中抽象方法为空实现
}
StaticResourceProcessor 访问静态文件处理器:
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
//和上章一样,这里处理静态文件使用response中的方法
response.sendStaticResource();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
ServletProcessor1 处理对servlet的请求:
public class ServletProcessor1 {
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
// 创建URL类加载器
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
// getCanonicalPath()返回抽象路径名的规范路径名字符串
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
urls[0] = new URL(null, repository, streamHandler);
loader = new URLClassLoader(urls);
}
catch (IOException e) {
System.out.println(e.toString() );
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
}
catch (ClassNotFoundException e) {
System.out.println(e.toString());
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
}
catch (Exception e) {
System.out.println(e.toString());
}
catch (Throwable e) {
System.out.println(e.toString());
}
}
}
Constants类:
public class Constants {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
}
启动HttpServer1:
在浏览器中输入http://localhost:8080/index.html 将显示项目目录webroot下的index.html
在浏览器中输入http://localhost:8080/servlet/PrimitiveServlet 可以对servlet进行访问,由此次应用简单,并且使用的是直接加载类,所以需在此路下先编译PrimitiveServlet.java。
应用2:
在上一个应用中有一个较为严重的问题,就是向上转型的问题:
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request,
(ServletResponse) response);
}
这段代码对应用的安全构成产生了一定的影响,如果知道此容器内部工作原理的程序员,可能会将此ServletRequest
对象或者ServletResponse
对象再次向下转型成为Request
或者Response
对象 并调用里面的public方法(parse或者sendStaticResource) 这将导致程序有可能崩溃。
可以通过新建2个类来解决次问题:
public class RequestFacade implements ServletRequest {
private ServletRequest request = null;
//构造中传入 request
public RequestFacade(Request request) {
this.request = request;
}
/* ServletRequest其他抽象方法*/
public Object getAttribute(String attribute) {
return request.getAttribute(attribute);
}
public Enumeration getAttributeNames() {
return request.getAttributeNames();
}
//.......其他方法类似 就不一一列举了
}
public class ResponseFacade implements ServletResponse {
private ServletResponse response;
public ResponseFacade(Response response) {
this.response = response;
}
public void flushBuffer() throws IOException {
response.flushBuffer();
}
public int getBufferSize() {
return response.getBufferSize();
}
//......其他方法类似 就不一一列举
}
上述有问题的代码使用此段代码替换:
Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) requestFacade,
(ServletResponse) responseFacade);
}
RequestFacade
和ResponseFacade
即可解决此问题 方法都是来自父类,无论是向上还是父类向下转型都是较为安全的。