一、这一章从头构建一个简单的Servlet容器,可以处理Servlet和静态资源(如html文件/图片等)。
要处理Servlet,必须遵循javax.servlet.Servlet规范,而处理静态资源同第一章。
关键是模仿tomcat的结构,来合理组织代码。
首先,servlet规范规定javax.servlet.Servlet接口有5个方法,签名如下:
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生命周期方法。
这一章有两个示例Application。Application1和Application2。
二、Application1-UML图:
HttpServer1中有await方法,等待HTTP请求,对于每个请求创建Request和Response对象,并且分派到ServletProcess1或者StaticRequestProcessor(根据请求url)。
HttpServer1:
public class HttpServer1 { private boolean shutdown = false; public static void main(String[] args) { new HttpServer1().await(); } public void await() { ServerSocket serverSocket = null; int port = 8080; try { serverSocket = new ServerSocket(port, 1, InetAddress .getByName("localhost")); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } while (!shutdown) { Socket socket = null; InputStream inputStream = null; OutputStream outputStream = null; try { socket = serverSocket.accept(); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); //create request object Request request = new Request(inputStream); request.parse(); //create response object Response response = new Response(outputStream); response.setRequest(request); //check if this is a request for a servlet or for a static resource if(request.getUri().startsWith("/servlet/")) { ServletProcessor1 servletProcessor1 = new ServletProcessor1(); servletProcessor1.process(request, response); } else { StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor(); staticResourceProcessor.process(request, response); } //close the socket socket.close(); // check if the url is a shutdown command shutdown = request.getUri().equals("SHUTDOWN"); } catch (IOException e) { e.printStackTrace(); } } } }
HttpServer1主要是创建ServerSocket,绑定在8080端口,等待socket请求,并根据uri判断是静态资源请求还是请求servlet。
Request:
package ex02.pyrmont; 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; public class Request implements ServletRequest { private InputStream inputStream; private String uri; public Request(InputStream inputStream) { this.inputStream = inputStream; } public void parse() { // read a set of characters from the socket StringBuffer request = new StringBuffer(2048); byte[] buffer = new byte[2048]; int len = 0; try { len = inputStream.read(buffer); } catch (IOException e) { e.printStackTrace(); len = -1; } for(int j=0; j<len; j++) { request.append((char)buffer[j]); } System.out.println(request.toString()); uri = praseUri(request.toString()); } private String praseUri(String string) { int index1, index2; index1 = string.indexOf(" "); if(index1 != -1) { index2 = string.indexOf(" ", index1 + 1); if(index2 > index1) { return string.substring(index1 + 1, index2); } } return null; } public String getUri() { return uri; } public Object getAttribute(String arg0) { // TODO Auto-generated method stub return null; } public Enumeration getAttributeNames() { // TODO Auto-generated method stub return null; } public String getCharacterEncoding() { // TODO Auto-generated method stub return null; } public int getContentLength() { // TODO Auto-generated method stub return 0; } public String getContentType() { // TODO Auto-generated method stub return null; } public ServletInputStream getInputStream() throws IOException { // TODO Auto-generated method stub return null; } public Locale getLocale() { // TODO Auto-generated method stub return null; } public Enumeration getLocales() { // TODO Auto-generated method stub return null; } public String getParameter(String arg0) { // TODO Auto-generated method stub return null; } public Map getParameterMap() { // TODO Auto-generated method stub return null; } public Enumeration getParameterNames() { // TODO Auto-generated method stub return null; } public String[] getParameterValues(String arg0) { // TODO Auto-generated method stub return null; } public String getProtocol() { // TODO Auto-generated method stub return null; } public BufferedReader getReader() throws IOException { // TODO Auto-generated method stub return null; } public String getRealPath(String arg0) { // TODO Auto-generated method stub return null; } public String getRemoteAddr() { // TODO Auto-generated method stub return null; } public String getRemoteHost() { // TODO Auto-generated method stub return null; } public RequestDispatcher getRequestDispatcher(String arg0) { // TODO Auto-generated method stub return null; } public String getScheme() { // TODO Auto-generated method stub return null; } public String getServerName() { // TODO Auto-generated method stub return null; } public int getServerPort() { // TODO Auto-generated method stub return 0; } public boolean isSecure() { // TODO Auto-generated method stub return false; } public void removeAttribute(String arg0) { // TODO Auto-generated method stub } public void setAttribute(String arg0, Object arg1) { // TODO Auto-generated method stub } public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException { // TODO Auto-generated method stub } }
Request包含Socket的输入流,解析出来的请求uri,在控制台打印出请求的HTTP协议。如下:
GET /servlet/PrimitiveServlet HTTP/1.1 Accept: text/html, application/xhtml+xml, */* Accept-Language: zh-CN User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko Accept-Encoding: gzip, deflate Host: localhost:8080 Connection: Keep-Alive
Response:
package ex02.pyrmont; 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 OutputStream outputStream; private PrintWriter printWriter = null; private Request request; public Response(OutputStream outputStream) { this.outputStream = outputStream; } /** * this method is used to server static pages */ public void sendStaticResource() { byte[] buff = new byte[1024]; FileInputStream fis = null; File file = new File(Constants.WEB_ROOT, request.getUri()); try { fis = new FileInputStream(file); int ch = fis.read(buff, 0, 1024); while(ch != -1) { outputStream.write(buff, 0, ch); ch = fis.read(buff, 0, 1024); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } public void setRequest(Request request) { this.request = request; } public void flushBuffer() throws IOException { // TODO Auto-generated method stub } public int getBufferSize() { // TODO Auto-generated method stub return 0; } public String getCharacterEncoding() { // TODO Auto-generated method stub return null; } public Locale getLocale() { // TODO Auto-generated method stub return null; } public ServletOutputStream getOutputStream() throws IOException { // TODO Auto-generated method stub return null; } public PrintWriter getWriter() throws IOException { printWriter = new PrintWriter(outputStream, true); return printWriter; } public boolean isCommitted() { // TODO Auto-generated method stub return false; } public void reset() { // TODO Auto-generated method stub } public void resetBuffer() { // TODO Auto-generated method stub } public void setBufferSize(int arg0) { // TODO Auto-generated method stub } public void setContentLength(int arg0) { // TODO Auto-generated method stub } public void setContentType(String arg0) { // TODO Auto-generated method stub } public void setLocale(Locale arg0) { // TODO Auto-generated method stub } }
Response包含socket的输出流,并实现了ServletReponse接口的getWriter方法,包含方法sendStaticResource发送静态资源到客户端。
StaticResourceProcessor类:
package ex02.pyrmont; public class StaticResourceProcessor { public void process(Request request, Response response) { response.sendStaticResource(); } }
StaticResourceProcessor包含process方法,通过传入的request和response对象,调用response的sendStaticResource。
ServeltProcess1类:
package ex02.pyrmont; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import javax.servlet.Servlet; import javax.servlet.ServletException; public class ServletProcessor1 { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); URLClassLoader loader = null; // create a URL ClassLoader URL[] urls = new URL[1]; URLStreamHandler urlStreamHandler = null; File classPath = new File(Constants.WEB_ROOT); String spec; try { spec = new URL("file", null, classPath.getCanonicalPath() + File.separator).toString(); urls[0] = new URL(null, spec, urlStreamHandler); loader = new URLClassLoader(urls); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //new instance Class myClass = null; try { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { e.printStackTrace(); } //invoke the service method 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) { // TODO Auto-generated catch block e.printStackTrace(); } } }
该类主要实现的工作是加载public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"目录下的servlet class,并调用该servlet的service方法。
这里测试的是PrimitiveServlet.class,代码如下:
import javax.servlet.*; import java.io.IOException; import java.io.PrintWriter; public class PrimitiveServlet implements Servlet { public void init(ServletConfig config) throws ServletException { System.out.println("init"); } 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."); } public void destroy() { System.out.println("destroy"); } public String getServletInfo() { return null; } public ServletConfig getServletConfig() { return null; } }
三、Application2
在Application1中有个潜在的问题,ServletProcessor1传入Request对象和Response对象到调用的Servlet的service方法中,如果编程者知道实现逻辑,那么在service方法,它可以通过把ServletRequest向下转型到Request对象,把ServletResponse向下转型到Response对象,这样就可以调用Request对象和Response对象中的public方法,如parse方法。
解决的办法是用Facade类。UML图如下:
在Application2,我们添加两个facade类:RequestFacade和ReponseFacade。
RequestFacade类:
package ex02.pyrmont; 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; public class RequestFacade implements ServletRequest { private ServletRequest request; public RequestFacade(Request request) { this.request = request; } /** implementaion of ServletRequest **/ public Object getAttribute(String name) { return this.request.getAttribute(name); } public Enumeration getAttributeNames() { return this.request.getAttributeNames(); } public String getCharacterEncoding() { return this.request.getCharacterEncoding(); } public int getContentLength() { return this.request.getContentLength(); } public String getContentType() { return this.request.getContentType(); } public ServletInputStream getInputStream() throws IOException { return this.request.getInputStream(); } public Locale getLocale() { return this.request.getLocale(); } public Enumeration getLocales() { return this.request.getLocales(); } public String getParameter(String name) { return this.request.getParameter(name); } public Map getParameterMap() { return this.request.getParameterMap(); } public Enumeration getParameterNames() { return this.request.getParameterNames(); } public String[] getParameterValues(String name) { return this.request.getParameterValues(name); } public String getProtocol() { return this.request.getProtocol(); } public BufferedReader getReader() throws IOException { return this.request.getReader(); } public String getRealPath(String path) { return this.request.getRealPath(path); } public String getRemoteAddr() { return this.request.getRemoteAddr(); } public String getRemoteHost() { return this.request.getRemoteHost(); } public RequestDispatcher getRequestDispatcher(String path) { return this.request.getRequestDispatcher(path); } public String getScheme() { return this.request.getScheme(); } public String getServerName() { return this.request.getServerName(); } public int getServerPort() { return this.request.getServerPort(); } public boolean isSecure() { return this.request.isSecure(); } public void removeAttribute(String name) { this.request.removeAttribute(name); } public void setAttribute(String name, Object o) { this.request.setAttribute(name, o); } public void setCharacterEncoding(String env) throws UnsupportedEncodingException { this.request.setCharacterEncoding(env); } }
该类包含Request对象,但是过滤了Request的parse等方法。
ResponseFacade类:
package ex02.pyrmont; 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 { private ServletResponse response; public ResponseFacade(Response response) { this.response = response; } /** implementaion of ServletResponse **/ public void flushBuffer() throws IOException { this.response.flushBuffer(); } public int getBufferSize() { return this.response.getBufferSize(); } public String getCharacterEncoding() { return this.response.getCharacterEncoding(); } public Locale getLocale() { return this.response.getLocale(); } public ServletOutputStream getOutputStream() throws IOException { return this.response.getOutputStream(); } public PrintWriter getWriter() throws IOException { return this.response.getWriter(); } public boolean isCommitted() { return this.response.isCommitted(); } public void reset() { this.response.reset(); } public void resetBuffer() { this.response.resetBuffer(); } public void setBufferSize(int size) { this.response.setBufferSize(size); } public void setContentLength(int len) { this.response.setContentLength(len); } public void setContentType(String type) { this.response.setContentType(type); } public void setLocale(Locale loc) { this.response.setLocale(loc); } }
该类包含Response对象,但是过滤了Reponse的sendStaticResponse等方法。
修改后的ServletProcessor类如下:
package ex02.pyrmont; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLStreamHandler; import javax.servlet.Servlet; import javax.servlet.ServletException; public class ServletProcessor2 { public void process(Request request, Response response) { String uri = request.getUri(); String servletName = uri.substring(uri.lastIndexOf("/") + 1); URLClassLoader loader = null; // create a URL ClassLoader URL[] urls = new URL[1]; URLStreamHandler urlStreamHandler = null; File classPath = new File(Constants.WEB_ROOT); String spec; try { spec = new URL("file", null, classPath.getCanonicalPath() + File.separator).toString(); urls[0] = new URL(null, spec, urlStreamHandler); loader = new URLClassLoader(urls); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //new instance Class myClass = null; try { myClass = loader.loadClass(servletName); } catch (ClassNotFoundException e) { e.printStackTrace(); } //invoke the service method Servlet servlet = null; try { servlet = (Servlet)myClass.newInstance(); // use facade class RequestFacade requestFacade = new RequestFacade(request); ResponseFacade responseFacade = new ResponseFacade(response); servlet.service(requestFacade, responseFacade); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ServletException e) { e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }