首页 > 代码库 > (2)自己写一个简单的servle容器

(2)自己写一个简单的servle容器

自己写一个简单的servlet,能够跑一个简单的servlet,说明一下逻辑。

首先是写一个简单的servlet,这就关联到javax.servlet和javax.servlet.http这两个包的类,其中一个比较重要的接口就是:javax.servlet.Servlet,所有的servlet必须实现实现或者继承实现该接口的类。 Servlet接口有五个方法:

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()

在Servlet的五个方法中,init,service和destroy是servlet的生命周期方法。在servlet类已经初始化之后,init方法将会被servlet容器所调用。servlet容器只调用一次,以此表明servlet已经被加载进服务中。init方法必须在servlet可以接受任何请求之前成功运行完毕。一个servlet程序员可以通过覆盖这个方法来写那些仅仅只要运行一次的初始化代码,例如加载数据库驱动,值初始化等等。在其他情况下,这个方法通常是留空的。

servlet容器为servlet请求调用它的service方法。servlet容器传递一个javax.servlet.ServletRequest对象和javax.servlet.ServletResponse对象。ServletRequest对象包括客户端的HTTP请求信息,而ServletResponse对象封装servlet的响应。

在servlet的生命周期中,service方法将会给调用多次。 当从服务中移除一个servlet实例的时候,servlet容器调用destroy方法。这通常发生在servlet容器正在被关闭或者servlet容器需要一些空闲内存的时候。仅仅在所有servlet线程的service方法已经退出或者超时淘汰的时候,这个方法才被调用。在servlet容器已经调用完destroy方法之后,在同一个servlet里边将不会再调用service方法。destroy方法提供了一个机会来清理任何已经被占用的资源,例如内存,文件句柄和线程,并确保任何持久化状态和servlet的内存当前状态是同步的。

我们可以写一个比较简单的servlet:

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;  }}

现在,让我们从一个servlet容器的角度来研究一下servlet编程。总的来说,一个全功能的servlet容器会为servlet的每个HTTP请求做下面一些工作:

? 1.当第一次调用servlet的时候,加载该servlet类并调用servlet的init方法(仅仅一次)。

? 2. 对每次请求,构造一个javax.servlet.ServletRequest实例和一个javax.servlet.ServletResponse实例。

     3.调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。

? 4.当servlet类被关闭的时候,调用servlet的destroy方法并卸载servlet类。

我们的第一个servlet容器不是全功能的。因此,她非常简单的servlet,而且也不调用servlet的init方法和destroy方法。相反它做了下面的事情:

1. 等待HTTP请求。

2.  构造一个ServletRequest对象和一个ServletResponse对象。

3.  假如该请求需要一个静态资源的话,调用StaticResourceProcessor实例的process方法,同时传递ServletRequest和ServletResponse对象。

4.  假如该请求需要一个servlet的话,加载servlet类并调用servlet的service方法,同时传递ServletRequest和ServletResponse对象。

所以我们的第一个简单的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;/** * @author zhailzh * @created 2014-08-22 * */public class HttpServerServlet1 {  // response时的文件地址  public static final String WEB_ROOT = "D:/"+"webroot/";  // 关闭的标记   private static final String SHUTDOWN_COMMAND = "/shutDown";  private boolean shutdown = false;  public static void main(String[] args) {    HttpServerServlet1 server = new HttpServerServlet1();    //服务器端采用的比较简单的循环等待的方式       server.await();  }  public void await() {    ServerSocket serverSocket = null;    //弄一个比较特殊的端口    int port = 12345;    try {      serverSocket =  new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));    }    catch (IOException e) {      e.printStackTrace();      System.exit(1);    }    //循环等待,直到uri传过来关闭的标记    while (!shutdown) {      Socket socket = null;      InputStream input = null;      OutputStream output = null;      try {        // 得到套接字              socket = serverSocket.accept();        //输入对象,用来request        input = socket.getInputStream();        //输出对象,用来response        output = socket.getOutputStream();        // 创建request对象,解析HTTP请求的原始数据??        Request request = new Request(input);        request.parse();        // 创建response对象,把返回的数据,即是服务器返回的数据塞到输出对象中,        // 返回客户端数据        Response response = new Response(output);        response.setRequest(request);        /**         * 和我们在tomcat里面配置uripattern对应的servletName,然后servletName对应不同的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 (Exception e) {        e.printStackTrace();        continue;      }    }  }}

其中的ServletProcessor1 的代码是:

package ex02.pyrmont;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.ServletRequest;import javax.servlet.ServletResponse;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 {      // create a URLClassLoader      URL[] urls = new URL[1];      URLStreamHandler streamHandler = null;      File classPath = new File(Constants.WEB_ROOT);      // the forming of repository is taken from the createClassLoader method 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 method in      // org.apache.catalina.loader.StandardClassLoader class.      urls[0] = new URL(null, repository, streamHandler);      loader = new URLClassLoader(urls);    }    catch (IOException e) {      System.out.println(e.toString() );    }    Class myClass = null;    try {        /**         * 这个对应的也就是tomcat里面已经编译好的类的加载的问题,这个和java的跨平台行扯上关系。         * */      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());    }  }}

使用的Response 和 Request 关键的代码,如下面所示,基本上和上一篇里面的Response 和 Request 没有太大的区别,其中常量Constants.WEB_ROOT = “D:/webroot/”,这个地址是我充当服务器返回数据存储的地址,具体的代码见附件。

package ex02.pyrmont;import java.io.OutputStream;import java.io.IOException;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.File;import java.io.PrintWriter;import java.util.Locale;import javax.servlet.ServletResponse;import javax.servlet.ServletOutputStream;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();    }  }  /** implementation of ServletResponse  */  public void flushBuffer() throws IOException {  }  public int getBufferSize() {    return 0;  }  public String getCharacterEncoding() {    return null;  }  public Locale getLocale() {    return null;  }  public ServletOutputStream getOutputStream() throws IOException {    return null;  }  public PrintWriter getWriter() throws IOException {    // autoflush is true, println() will flush,    // but print() will not.    writer = new PrintWriter(output, true);    return writer;  }  public boolean isCommitted() {    return false;  }}

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());  }
///………………

测试的路径是:http://127.0.0.1:12345/servlet/PrimitiveServlet

需要把编译好的class文件放到D:/webroot/(这个是我的数据存储的地方)

结果是:

image

在看看我们的servlet容器,还是有很多地方可以优化的,例如在加载servlet,传入参数处理的过程中:

Servlet servlet = null;
   try {
     servlet = (Servlet) myClass.newInstance();
     servlet.service((ServletRequest) request, (ServletResponse) response);
   }

这会危害安全性。知道这个servlet容器的内部运作的Servlet程序员可以分别把ServletRequest和ServletResponse实例向下转换为ex02.pyrmont.Request和ex02.pyrmont.Response,并调用他们的公共方法。拥有一个Request实例,它们就可以调用parse方法。拥有一个Response实例,就可以调用sendStaticResource方法。 你不可以把parse和sendStaticResource方法设置为私有的,因为它们将会被其他的类调用。不过,这两个方法是在个servlet内部是不可见的。其中一个解决办法就是让Request和Response类拥有默认访问修饰,另外比较推荐的一种方法,就是增加一个类:

public class RequestFacade implements ServletRequest

public Class ResponseFacade implements ServletRequest

这样的代码相应的变化为:

Servlet servlet = null;
    RequestFacade requestFacade = new RequestFacade(request);
    ResponseFacade responseFacade = new ResponseFacade(response);
    try {
      servlet = (Servlet) myClass.newInstance();
      servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
    }

ok,到这里我们的一个简单的servlet容器,已经完成了,还是比较的简单的。

附件:源代码

(2)自己写一个简单的servle容器