看了how_tomcat_works, 第二章,实现一个简单的Servlet容器,大概实现了一遍,了解了一下一个简单的Servlet容器是如何工作的。
基础知识:Socket通信,之前有实现过
这是一个最最基础的Servlet容器,没有实现复杂的功能,主要模拟当我在浏览器中输入有效网址后进行的一系列操作,这个操作很简单:可以总结为如下步骤:
1.输入网址:例如http://localhost:8080/servlet/PrimitiveServlet
2.判断网址(这里分了两种情况,一种是请求静态资源的,一种是请求一个servlet的),为了简单的区分网址,这里定义以/servlet/ 开头的网址时请求servlet的,其他都是请求静态资源的。
3.返回相应的信息。
Example1
这里先来实现最简单的一个样例,主要包括如下几个类:
PrimitiveServlet //继承于java.servlet.Servlet接口。
HttpServer1
Request
Response
ServletProcessor1
StaticResourceProcessor
Constants //用来存放一些全局变量之类的
Servlet编程是通过javax.servlet和javax.servlet.http两个包的类和接口来实现的,一个重要的接口就是javax.servlet.Servlet接口了,所有的servlet都应该implement 该接口或者extend继承了该接口的类。
Servlet接口主要有以下几个方法:
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
init,service 和 destroy 是 servlet 的生命周期方法
init方法:当servlet类被实例化后,init方法会被servlet容器所调用。 servlet 容器只调用一次,以此表明servlet 已经被加载进服务中。 init 方法必须在 servlet 可以接受任何请求之前成功运行完毕。
service方法:servlet 容 器 传 递 一 个javax.servlet.ServletRequest 对象和 javax.servlet.ServletResponse 对象。ServletRequest对象包括客户端的 HTTP 请求信息,而 ServletResponse 对象封装 servlet 的响应。在 servlet的生命周期中, service 方法将会给调用多次。
destroy方法:这通常发生在servlet 容器正在被关闭或者 servlet 容器需要一些空闲内存的时候。仅仅在所有 servlet 线程的 service 方法已经退出或者超时淘汰的时候,这个方法才被调用。在 servlet 容器已经调用完destroy 方法之后,在同一个 servlet 里边将不会再调用 service 方法。 destroy 方法提供了一个机会来清理任何已经被占用的资源,例如内存,文件句柄和线程,并确保任何持久化状态和servlet 的内存当前状态是同步的。
以下是PrimitiveServlet 的 servlet 的代码:
package com.zhoufenqin.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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 arg0) throws ServletException {
System.out.println("init_zfq");
}
@Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
System.out.println("from service");
PrintWriter out = arg1.getWriter();
out.println("Hello. Roses are read");
out.println("Violets are blue");
}
}
这里,servlet容器的UML图如下所示:
应用程序入口点在HttpServer1中,创建一个ServerSocket等待客户端的请求。每次请求成功时创建一个Request对象和Response对象。并根据需求分发到StaticResourceProcessor或者ServletProcessor实例中。(若请求的是静态资源则发送到StaticResourceProcessor,如果是一个servlet则发送到ServletProcessor1中)
HttpServer1类
HttpServer1类,用户接受客户端的请求。当请求静态资源时,调用StaticResourceProcessor类的process方法进行处理。否则调用ServletProcessor1类的process方法进行处理。
请求静态资源时,可以再浏览器中输入类似网址:
http://localhost:8080/staticResource
与之区分,若要请求一个servlet,可使用下面的网址:
http://localhost:8080/servlet/PrimitiveServlet
package com.zhoufenqin.servlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
public class HttpServer1 {
//终止服务器的运行
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
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 (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while(!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
System.out.println("Begin");
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
//Create Request object
Request request = new Request(input);
request.parse();
//Create Response object
Response response = new Response(output);
response.setRequest(request);
// check if this is a request for a servlet or a static resource
// a request for a servlet begins with "/servlet/"
if(request.getUri() == null) continue;
//请求一个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();
System.exit(1);
}
}
}
}
Request类
servlet 的 service 方法从 servlet 容器中接收一个 javax.servlet.ServletRequest 实例和一个 javax.servlet.ServletResponse 实例。这就是说对于每一个 HTTP 请求, servlet 容器必须构造一个 ServletRequest 对象和一个 ServletResponse 对象并把它们传递给正在服务的servlet 的 service 方法。
Request类实现了javax.servlet.ServletRequest。
package com.zhoufenqin.servlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
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;
}
//解析URL
/*
* requestString一般格式如下
* GET /servlet/PrimitiveServlet HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*\/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie: Telerik.Skin=Windows7
* */
public String parseUri(String requestString) {
//GET /servlet/PrimitiveServlet HTTP/1.1 取出中间的url
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.println("request toString: " + request.toString());
uri = parseUri(request.toString());
}
//implementation of ServletRequest
@Override
public AsyncContext getAsyncContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public Object getAttribute(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getContentLength() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
}
@Override
public DispatcherType getDispatcherType() {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletInputStream getInputStream() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public String getLocalAddr() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getLocalName() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getLocalPort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public Locale getLocale() {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<Locale> getLocales() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getParameter(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, String[]> getParameterMap() {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getParameterNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public String[] getParameterValues(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getProtocol() {
// TODO Auto-generated method stub
return null;
}
@Override
public BufferedReader getReader() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRealPath(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRemoteAddr() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRemoteHost() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getRemotePort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public RequestDispatcher getRequestDispatcher(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getScheme() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getServerName() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getServerPort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public ServletContext getServletContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isAsyncStarted() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAsyncSupported() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isSecure() {
// TODO Auto-generated method stub
return false;
}
@Override
public void removeAttribute(String arg0) {
// TODO Auto-generated method stub
}
@Override
public void setAttribute(String arg0, Object arg1) {
// TODO Auto-generated method stub
}
@Override
public void setCharacterEncoding(String arg0)
throws UnsupportedEncodingException {
// TODO Auto-generated method stub
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
// TODO Auto-generated method stub
return null;
}
@Override
public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1)
throws IllegalStateException {
// TODO Auto-generated method stub
return null;
}
}
Response类
Response类实现了javax.servlet.ServletResponse。
package com.zhoufenqin.servlet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
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 static pages */
public void sendStaticResponse() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
//找到相应的文件,然后把里面的内容读出来显示到页面上
//例如在当前工程目录下有一个index.html,当输入http://localhost:8080/index.html时,将index.html中的内容显示出来
File file = new File(Constants.WEB_ROOT,request.getUri());
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);
}
} catch (FileNotFoundException e) {
//如果没有找到相应的页面,就会返回如下html页面
System.out.println("没有找到相应的文件");
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();
}
}
/**
* implementation of ServletResponse
*/
@Override
public void flushBuffer() throws IOException {
// TODO Auto-generated method stub
}
@Override
public int getBufferSize() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
}
@Override
public Locale getLocale() {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
// TODO Auto-generated method stub
return null;
}
//自己重写
@Override
public PrintWriter getWriter() throws IOException {
// TODO Auto-generated method stub
//return null;
writer = new PrintWriter(output,true);
return writer;
}
@Override
public boolean isCommitted() {
// TODO Auto-generated method stub
return false;
}
@Override
public void reset() {
// TODO Auto-generated method stub
}
@Override
public void resetBuffer() {
// TODO Auto-generated method stub
}
@Override
public void setBufferSize(int arg0) {
// TODO Auto-generated method stub
}
@Override
public void setCharacterEncoding(String arg0) {
// TODO Auto-generated method stub
}
@Override
public void setContentLength(int arg0) {
// TODO Auto-generated method stub
}
@Override
public void setContentType(String arg0) {
// TODO Auto-generated method stub
}
@Override
public void setLocale(Locale arg0) {
// TODO Auto-generated method stub
}
}
ServletProcessor1类
ServletProcessor1类用于处理servlet的HTTP请求。
package com.zhoufenqin.servlet;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class ServletProcessor1 {
public void process(Request request,Response response) {
String uri = request.getUri();
//获得自己定义的servlet对象,本例中定义了PrimitiveServlet,所以只有从URL中好的PrimitiveServlet时才会处理,否则输出错误。
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
System.out.println("servletName = " + servletName);
try {
URL url = new URL("file","127.0.0.1",Constants.WEB_ROOT);
URL[] urls = new URL[]{url};
loader = new URLClassLoader(urls);
} catch (MalformedURLException e) {
e.printStackTrace();
}
Class<?> myClass = null;
try {
//loadClass时记得把包名带上,不要试图把报名写再WEB_ROOT路径中,这样是加载不了类的。
myClass = loader.loadClass("com.zhoufenqin.servlet." + servletName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Servlet servlet = null;
try {
//实例化primitiveServlet类并调用其service()方法。
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest)request, (ServletResponse)response);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
StaticResourceProcessor类
StaticResourceProcessor类用来处理静态资源请求。调用Response的sendStaticResource()方法。
package com.zhoufenqin.servlet;
import java.io.IOException;
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
response.sendStaticResponse();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Constants类
根据自己的项目,定义变量,这里我把WEB_ROOT定义如下:
package com.zhoufenqin.servlet;
import java.io.File;
public class Constants {
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "WebContent";
}
因为我项目的结构如下所示:
到此为止,一个简单的servlet容器就完成了,现在来看看效果。
(1)请求静态资源
//没有找到相应的资源
//找到相应的资源文件
(2)请求一个servlet
Example2
在Example1中,我们在ServletProcessor1使用向上转型的方法,将Request类转为ServletRequest类,Response类转为ServletResponse类,作为参数传入到service函数。
servlet.service((ServletRequest)request, (ServletResponse)response);
但是这样做比较危险,万一有人知道Servlet内部的操作,就可以将ServletRequest类向下转化成Request,然后调用Request类中的函数,比如parse()函数,因为该函数是public的。这样可能就泄露了信息。如下所示:
@Override
public void service(ServletRequest arg0, ServletResponse arg1)
throws ServletException, IOException {
System.out.println("from service");
PrintWriter out = arg1.getWriter();
out.println("Hello. Roses are read");
out.println("Violets are blue");
Request req = (Request) arg0;//可以转化为Request类并调用其相关函数,危险
req.parse();
}
当然一种解决方法是将这些会被别人引用到的函数定义为私有的,不过还有更好的解决方式。(这里用到的设计模式我还不太明白,因为不懂设计模式。。待查完资料后来补充)
我们引入facade类,来更好的解决这个问题,UML图如下所示:
在程序中增加两个类。RequestFacade和ResponseFacade。
RequestFacade 实 现 了 ServletRequest 接 口 并 通 过 在 构 造 方 法 中 传 递 一 个 引 用 了ServletRequest 对象的 Request 实例作为参数来实例化。 ResponseFacade也同理。
具体实现如下:
在Example1的基础上,保持Constants类,PrimitiveServlet类,StaticResourceProcessor,Requset,Response类不变。
增加HttpServlet2,RequsetFacade,ResponseFacade,ServletProcessor2类。
HttpServlet2
HttpServlet2由HttpServlet1变化而来,需要做一点改变,如下:
public class HttpServer2 {
...
public static void main(String[] args) {
HttpServer2 server = new HttpServer2();//替换成HttpServer2
server.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
...
while(!shutdown) {
...
if(request.getUri().startsWith("/servlet/")) {
ServletProcessor2 processor = new ServletProcessor2();//替换
processor.process(request, response);
}
else{
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
...
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
RequestFacade
package com.zhoufenqin.servlet;
import java.io.BufferedReader;
import java.io.IOException;
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 RequestFacade implements ServletRequest{
//将request定义为私有的
private ServletRequest request = null;
public RequestFacade(Request request) {
this.request = request;
}
@Override
public AsyncContext getAsyncContext() {
// TODO Auto-generated method stub
return null;
}
...
}
ResponseFacade
package com.zhoufenqin.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
public class ResponseFacade implements ServletResponse{
//将response定义为私有的
private ServletResponse response = null;
public ResponseFacade(Response response) {
this.response = response;
}
@Override
public void flushBuffer() throws IOException {
// TODO Auto-generated method stub
}
...
}
ServletProcessor2
ServletProcessor2由ServletProcessor1变化而来,将原先传入service函数的参数进行更改。
Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet = (Servlet) myClass.newInstance();
//将原来参数由Requset类型转化为ServletRequest类型变成由RequsetFacade类型转化为ServletRequest类型。Response亦然。
servlet.service((ServletRequest)requestFacade, (ServletResponse)responseFacade);
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServletException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}