一个简单的Servlet 容器
该章中介绍了怎么编写自己的容器,实现了两个Servlet容器,第一个容器设计的尽可能简单,第二个容器相对复杂一些,这两个容器都支持处理静态资源和Servlet,测试PrimitiveServlet被放在工程的webroot目录下。
2.1 Javax.servlet.Servlet接口
Servlet编程都必须通过javax.servlet和javax.servlet.http包,其中最重要的是javax.servlet.Servlet接口,所有的Servlet都必须实现这个接口。Servlet接口有下面五个方法:
public void destroy()
public void init(ServletConfig config) throws ServletException
public ServletConfig getServletConfig()
public String getServletInfo()
public void service(ServletRequest request, ServletResponse response)throws ServletException, IOException
在Servlet容器在实例化servlet时调用init方法,当客服端发送一个请求的时候,Servlet容器会调用servlet的service方法,并传送ServletRequest和ServletResponse对象,ServletRequest包含客户端的HTTP请求信息,ServletResponse封装了servlet的响应,当Servlet容器从服务中移除servlet的时候调用destroy方法。
一个完整的容器处理HTTP请求的过程:
1. 当第一次调用servlet的时候,加载servlet类,并调用servlet初始化方法;
2. 对于每个请求,构造一个ServletRequest实例和一个ServletResponse实例
3. 调用servlet的service方法,并传入请求和响应对象
4. 当servlet关闭(shut down)的时候,调用其destroy对象并卸载(unload)servlet类
2.2 应用一
在第一个servlet容器中没用正式容器那么复杂,不会去servlet的调用init和destory方法,主要做下面的几件事情:
1. 监听客户端的请求;
2. 构造ServletRequest和ServletResponse对象;
3. 如果是请求静态资源,则调用静态资源处理器的process方法,并传入ServletRequest和ServletResponse对象;
4. 如果是请求servlet,则加载servlet类调用其的service方法,并传入ServletRequest和ServletResponse对象。
在这里有一个问题,即每次请求的时候都会重新加载servlet,在正式容器中是不被允许的,肯定会影响性能。
这个应用包括以下六个类:
HttpServer1
Request
Response
StaticResourceProcessor
ServletProcessor1
Constants
类似于第一章,应用的入口也在HttpServer1,main方法创建HttpServer1实例并调用其await方法
2.2.1 HttpServer1类
HttpServer1与前一章的HttpServer相类似,仅添加了处理servlet的逻辑。
具体类见:
package com.ex02.pyfrmont;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器实现类,await方法,该方法监听制定端口的Http请求,
* 处理以后返回给客户端,直到收到shutdown命令
* @author peng_yf
* @version 1.0,2011-08-20
* @since 1.0
*/
public class HttpServer1 {
/**
* WEB_ROOT is the directory where our HTML and other files reside.
* For this packge,WEB_ROOT is the "webroot" directory under the
* working directory.
* The working directory is the location in the file system from
* where the java command was invoked.
*/
/**
* shutdown command
*/
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
/**
* the shutdown command received
*/
private boolean shutdown = false;
/**
* @param args
*/
public static void main(String[] args) {
HttpServer1 server = new HttpServer1();
server.await();
}
private 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);
//check if this is a request for a servlet or
//a static resource
//a request for a servlet begings with "/servlet/"
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();
continue;
}
}
}
}
2.2.2 Request类
Request代表客户端的请求,必须实现javax.servlet.ServletRequest接口,除了第一章Request中的方法,其它方法都是空实现:
package com.ex02.pyfrmont;
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.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
/**
* 代表一个HTTP请求,用Socket中与客户端通信的输入流来构造,可
* 以通过其中的read方法读取HTTP格式数据
* @author Peng_yf
* @version 1.0,2011-08-20
* @since 1.0
*/
public class Request implements ServletRequest{
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
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());
uri = parseUri(request.toString());
}
private String parseUri(String requestString){
int index1,index2;
index1 = requestString.indexOf(' ');
while(index1 != -1){
index2 = requestString.indexOf(' ', index1+1);
if(index1 < index2){
return requestString.substring(index1+1,index2);
}
}
return null;
}
public String getUri(){
return this.uri;
}
@Override
public Object getAttribute(String arg0) {
return null;
}
@Override
public Enumeration getAttributeNames() {
return null;
}
@Override
public String getCharacterEncoding() {
return null;
}
@Override
public int getContentLength() {
return 0;
}
@Override
public String getContentType() {
return null;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return null;
}
@Override
public String getLocalAddr() {
return null;
}
@Override
public String getLocalName() {
return null;
}
@Override
public int getLocalPort() {
return 0;
}
@Override
public Locale getLocale() {
return null;
}
@Override
public Enumeration getLocales() {
return null;
}
@Override
public String getParameter(String arg0) {
return null;
}
@Override
public Map getParameterMap() {
return null;
}
@Override
public Enumeration getParameterNames() {
return null;
}
@Override
public String[] getParameterValues(String arg0) {
return null;
}
@Override
public String getProtocol() {
return null;
}
@Override
public BufferedReader getReader() throws IOException {
return null;
}
@Override
public String getRealPath(String arg0) {
return null;
}
@Override
public String getRemoteAddr() {
return null;
}
@Override
public String getRemoteHost() {
return null;
}
@Override
public int getRemotePort() {
return 0;
}
@Override
public RequestDispatcher getRequestDispatcher(String arg0) {
return null;
}
@Override
public String getScheme() {
return null;
}
@Override
public String getServerName() {
return null;
}
@Override
public int getServerPort() {
return 0;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public void removeAttribute(String arg0) {
}
@Override
public void setAttribute(String arg0, Object arg1) {
}
@Override
public void setCharacterEncoding(String arg0)
throws UnsupportedEncodingException {
}
}
2.2.3 Response类
Response代表客户端的请求,必须实现javax.servlet.ServletResponse接口,除了上一章Response中的方法,其它均为空实现:
package com.ex02.pyfrmont;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
/**
* HTTP Response = Status-Line
* *((general-header|response-header|entity-header)CRLF)
* CRLF
* [message body]
* Status-Line=HTTP-Version SP Status-Code SP Response-Phrase CRLF
* @author Peng_yf
* @version 1.0, 2011-08-20
* @since 1.0
*/
public class Response implements ServletResponse{
private static final int BUFFER_SIZE = 1024;
OutputStream output;
Request request;
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()){
fis = new FileInputStream(file);
int ch = fis.read(bytes,0,BUFFER_SIZE);
if(ch != -1){
output.write(bytes, 0, ch);
ch = fis.read(bytes,0,BUFFER_SIZE);
}
}else{
//file not found
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());
}
} catch (Exception e) {
//thrown if cannot instantiate a File object.
System.out.println(e.getMessage());
e.printStackTrace();
}finally{
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public PrintWriter getWriter() throws IOException{
//autoflush is true, println() will flush
//but print() will not.
writer = new PrintWriter(output,true);
return writer;
}
@Override
public void flushBuffer() throws IOException {
}
@Override
public int getBufferSize() {
return 0;
}
@Override
public String getCharacterEncoding() {
return null;
}
@Override
public String getContentType() {
return null;
}
@Override
public Locale getLocale() {
return null;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return null;
}
@Override
public boolean isCommitted() {
return false;
}
@Override
public void reset() {
}
@Override
public void resetBuffer() {
}
@Override
public void setBufferSize(int arg0) {
}
@Override
public void setCharacterEncoding(String arg0) {
}
@Override
public void setContentLength(int arg0) {
}
@Override
public void setContentType(String arg0) {
}
@Override
public void setLocale(Locale arg0) {
}
}
2.2.4 StaticResourceProcessor类
StaticResourceProcessor类是静态资源处理类,当请求的是静态资源的时候调用该类的process方法:
package com.ex02.pyfrmont;
/**
* 静态资源处理器
* @author Peng_yf
*/
public class StaticResourceProcessor {
/**
* 静态资源处理
* @param request
* @param response
*/
public void process(Request request, Response response) {
response.sendStaticResource();
}
}
2.2.5 ServletProcessor1类
ServletProcessor1类servlet请求处理器,当请求的是servlet类的时候方法该类的process方法,加载servlet类,并调用其中的service方法:
package com.ex02.pyfrmont;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
/**
* servlet处理器
* @author Peng_yf
*/
public class ServletProcessor1 {
/**
* 用来处理servlet请求
* @param request
* @param response
*/
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/")+1);
URLClassLoader loader = null;
try{
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
//the forming of repository take from the createClassLoader methed in
//org.apache.catalina.startup.ClassLoaderFactory
String repository =
(new URL("file",null,classPath.getCanonicalPath() + File.separator)).toString();
//the code for forming the URL is taken from the addRepository methed in
//org.apache.catalina.startup.StandardClassLoader.
urls[0] = new URL(null,repository,streamHandler);
loader = new URLClassLoader(urls);
}catch (IOException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Servlet servlet = null;
try {
servlet = (Servlet)myClass.newInstance();
servlet.service(request, response);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
在servlet 容器中,用到了repository模型,类加载器查找servlet类的路径叫储存库(repository),这里就只有一个地址。
2.2.6 运行应用
要运行应用,需要导入servlet-api.jar(servlet2.4以后)或servlet.jar(servlet2.3),同时在需要把PrimitiveServlet.java编译后放在webroot下,在页面浏览器中输入
http://localhost:8080/servlet/PrimitiveServlet
将会在页面输出:
Hello.Roses are red. Violets are blue.
要运行同时还需要添加常量类:
package com.ex02.pyfrmont;
import java.io.File;
/**
* 常量类
* @author Peng_yf
*/
public class Constants {
/**
* webroot目录
*/
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
}
2.3 应用二
在应用一中ServletProcessor1中的parse方法,是直接把HttpServer1传过来的Request和Response直接传递给servlet的service方法,这样可能会有一个安全隐患,在servlet类中知道具体事例的程序员可以直接把ServletRequest和ServletResponse事例向下转型为Request和Response,这用就有可以调用它们的parse和sendStaticResource方法,对于这个问题,可以使用门面模式来解决,分别为Request和Response提供一个实现于ServletRequest和ServletResponse门面类RequestFacade和ResponseFacade:
RequestFacade类:
package com.ex02.pyfrmont;
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.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
/**
* request门面类
* @author Peng_yf
* @version 1.0, 2011-08-21
* @since 1.0
*/
public class RequestFacade implements ServletRequest {
private Request request = null;
public RequestFacade(Request request){
this.request = request;
}
@Override
public Object getAttribute(String arg0) {
return request.getAttribute(arg0);
}
@Override
public Enumeration getAttributeNames() {
return request.getAttributeNames();
}
@Override
public String getCharacterEncoding() {
return request.getCharacterEncoding();
}
@Override
public int getContentLength() {
return request.getContentLength();
}
@Override
public String getContentType() {
return request.getContentType();
}
@Override
public ServletInputStream getInputStream() throws IOException {
return request.getInputStream();
}
@Override
public String getLocalAddr() {
return request.getLocalAddr();
}
@Override
public String getLocalName() {
return request.getLocalName();
}
@Override
public int getLocalPort() {
return request.getLocalPort();
}
@Override
public Locale getLocale() {
return request.getLocale();
}
@Override
public Enumeration getLocales() {
return request.getLocales();
}
@Override
public String getParameter(String arg0) {
return request.getParameter(arg0);
}
@Override
public Map getParameterMap() {
return request.getParameterMap();
}
@Override
public Enumeration getParameterNames() {
return request.getParameterNames();
}
@Override
public String[] getParameterValues(String arg0) {
return request.getParameterValues(arg0);
}
@Override
public String getProtocol() {
return request.getProtocol();
}
@Override
public BufferedReader getReader() throws IOException {
return request.getReader();
}
@Override
public String getRealPath(String arg0) {
return request.getRealPath(arg0);
}
@Override
public String getRemoteAddr() {
return request.getRemoteAddr();
}
@Override
public String getRemoteHost() {
return request.getRemoteHost();
}
@Override
public int getRemotePort() {
return request.getRemotePort();
}
@Override
public RequestDispatcher getRequestDispatcher(String arg0) {
return request.getRequestDispatcher(arg0);
}
@Override
public String getScheme() {
return request.getScheme();
}
@Override
public String getServerName() {
return request.getServerName();
}
@Override
public int getServerPort() {
return request.getServerPort();
}
@Override
public boolean isSecure() {
return request.isSecure();
}
@Override
public void removeAttribute(String arg0) {
request.removeAttribute(arg0);
}
@Override
public void setAttribute(String arg0, Object arg1) {
request.setAttribute(arg0, arg1);
}
@Override
public void setCharacterEncoding(String arg0)
throws UnsupportedEncodingException {
request.setCharacterEncoding(arg0);
}
}
ResponseFacade类:
package com.ex02.pyfrmont;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
/**
* Response门面类
* @author Peng_yf
* @version 1.0, 2011-08-21
*/
public class ResponseFacade implements ServletResponse {
private Response response = null;
public ResponseFacade(Response response){
this.response = response;
}
@Override
public void flushBuffer() throws IOException {
response.flushBuffer();
}
@Override
public int getBufferSize() {
return response.getBufferSize();
}
@Override
public String getCharacterEncoding() {
return response.getCharacterEncoding();
}
@Override
public String getContentType() {
return response.getContentType();
}
@Override
public Locale getLocale() {
return response.getLocale();
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return response.getOutputStream();
}
@Override
public PrintWriter getWriter() throws IOException {
return response.getWriter();
}
@Override
public boolean isCommitted() {
return response.isCommitted();
}
@Override
public void reset() {
response.reset();
}
@Override
public void resetBuffer() {
response.resetBuffer();
}
@Override
public void setBufferSize(int arg0) {
response.setBufferSize(arg0);
}
@Override
public void setCharacterEncoding(String arg0) {
response.setCharacterEncoding(arg0);
}
@Override
public void setContentLength(int arg0) {
response.setContentLength(arg0);
}
@Override
public void setContentType(String arg0) {
response.setContentType(arg0);
}
@Override
public void setLocale(Locale local) {
response.setLocale(local);
}
}
修改应用一中的servlet处理器,在调用servlet的service方法时构造门面类传入:
package com.ex02.pyfrmont;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
/**
* servlet处理器
* @author Peng_yf
* @version 1.0, 2011-08-21
* @since 1.0
*/
public class ServletProcessor2 {
/**
* 用来处理servlet请求
* @param request
* @param response
*/
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/")+1);
URLClassLoader loader = null;
try{
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
//the forming of repository take from the createClassLoader methed in
//org.apache.catalina.startup.ClassLoaderFactory
String repository =
(new URL("file",null,classPath.getCanonicalPath() + File.separator)).toString();
//the code for forming the URL is taken from the addRepository methed in
//org.apache.catalina.startup.StandardClassLoader.
urls[0] = new URL(null,repository,streamHandler);
loader = new URLClassLoader(urls);
}catch (IOException e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
Class myClass = null;
try {
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
servlet = (Servlet)myClass.newInstance();
servlet.service(requestFacade, responseFacade);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
HttpServer2与HttpServer基本相同,在servlet请求时调用ServletProcessor2处理器:
if(request.getUri().startsWith("/servlet")){
ServletProcessor2 processor = new ServletProcessor2();
processor.process(request,response);
}
运行与测试方法与应用相同。