Chapter 2: A Simple Servlet Container

一、这一章从头构建一个简单的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
        
    }

}
View Code

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
View Code

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();
    }
}
View Code

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();
        }
    }
}
View Code

该类主要实现的工作是加载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;
  }

}
View Code

三、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);
    }

}
View Code

该类包含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);
    }

}
View Code

该类包含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();
        }
    }
}
View Code
原文地址:https://www.cnblogs.com/wenwujuncheng/p/3523092.html