你的位置:首页 > Java教程

[Java教程]一个简单的Servlet容器实现


  上篇写了一个简单的Java web服务器实现,只能处理一些静态资源的请求,本篇文章实现的Servlet容器基于前面的服务器做了个小改造,增加了Servlet请求的处理。

程序执行步骤

  1. 创建一个ServerSocket对象;
  2. 调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待;
  3. 从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应;
  4. 处理请求:读取InputStream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息;
  5. 处理响应(分两种类型,静态资源请求响应或servlet请求响应):如果是静态资源请求,则根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中;如果是Servlet请求,则首先生成一个URLClassLoader类加载器,加载请求的servlet类,创建servlet对象,执行service方法(往OutputStream写入响应的数据);
  6. 关闭Socket对象;
  7. 转到步骤2,继续等待连接请求;

代码实现:

添加依赖:

    <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->    <dependency>      <groupId>javax.servlet</groupId>      <artifactId>servlet-api</artifactId>      <version>2.3</version>    </dependency>

服务器代码:

package ex02.pyrmont.first;import java.net.Socket;import java.net.ServerSocket;import java.net.InetAddress;import java.io.InputStream;import java.io.OutputStream;import java.io.IOException;import ex02.pyrmont.Request;import ex02.pyrmont.Response;import ex02.pyrmont.StaticResourceProcessor;public class HttpServer1 {  // 关闭服务命令  private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";  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 (IOException e) {      e.printStackTrace();      System.exit(1);    }    // 循环等待请求    while (true) {      Socket socket = null;      InputStream input = null;      OutputStream output = null;      try {        //等待连接,连接成功后,返回一个Socket对象        socket = serverSocket.accept();        input = socket.getInputStream();        output = socket.getOutputStream();        // 创建Request对象并解析        Request request = new Request(input);        request.parse();        // 检查是否是关闭服务命令        if (request.getUri().equals(SHUTDOWN_COMMAND)) {          break;        }                // 创建 Response 对象        Response response = new Response(output);        response.setRequest(request);        if (request.getUri().startsWith("/servlet/")) {          //请求uri以/servlet/开头,表示servlet请求          ServletProcessor1 processor = new ServletProcessor1();          processor.process(request, response);        } else {          //静态资源请求          StaticResourceProcessor processor = new StaticResourceProcessor();          processor.process(request, response);        }        // 关闭 socket        socket.close();      } catch (Exception e) {        e.printStackTrace();        System.exit(1);      }    }  }}

常量类:

package ex02.pyrmont;import java.io.File;public class Constants {  public static final String WEB_ROOT = System.getProperty("user.dir")      + File.separator + "webroot";  public static final String WEB_SERVLET_ROOT = System.getProperty("user.dir")      + File.separator + "target" + File.separator + "classes";      }

Request:

package ex02.pyrmont;import java.io.InputStream;import java.io.IOException;import java.io.BufferedReader;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 input;  private String uri;  public Request(InputStream input) {    this.input = input;  }  public String getUri() {    return uri;  }  /**   *   * requestString形式如下:   * GET /index.html HTTP/1.1   * Host: localhost:8080   * Connection: keep-alive   * Cache-Control: max-age=0   * ...   * 该函数目的就是为了获取/index.html字符串   */  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;  }  //从InputStream中读取request信息,并从request中获取uri值  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());  }  /* implementation of the ServletRequest */  public Object getAttribute(String attribute) {    return null;  }  public Enumeration<?> getAttributeNames() {    return null;  }  public String getRealPath(String path) {    return null;  }  public RequestDispatcher getRequestDispatcher(String path) {    return null;  }  public boolean isSecure() {    return false;  }  public String getCharacterEncoding() {    return null;  }  public int getContentLength() {    return 0;  }  public String getContentType() {    return null;  }  public ServletInputStream getInputStream() throws IOException {    return null;  }  public Locale getLocale() {    return null;  }  public Enumeration<?> getLocales() {    return null;  }  public String getParameter(String name) {    return null;  }  public Map<?, ?> getParameterMap() {    return null;  }  public Enumeration<?> getParameterNames() {    return null;  }  public String[] getParameterValues(String parameter) {    return null;  }  public String getProtocol() {    return null;  }  public BufferedReader getReader() throws IOException {    return null;  }  public String getRemoteAddr() {    return null;  }  public String getRemoteHost() {    return null;  }  public String getScheme() {    return null;  }  public String getServerName() {    return null;  }  public int getServerPort() {    return 0;  }  public void removeAttribute(String attribute) {  }  public void setAttribute(String key, Object value) {  }  public void setCharacterEncoding(String encoding)      throws UnsupportedEncodingException {  }}

View Code

Response:

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;  }  //将web文件写入到OutputStream字节流中  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;  }  public void reset() {  }  public void resetBuffer() {  }  public void setBufferSize(int size) {  }  public void setContentLength(int length) {  }  public void setContentType(String type) {  }  public void setLocale(Locale locale) {  }}

View Code

静态资源请求处理:

package ex02.pyrmont;import java.io.IOException;public class StaticResourceProcessor {  public void process(Request request, Response response) {    try {      response.sendStaticResource();    } catch (IOException e) {      e.printStackTrace();    }  }}

Servlet请求处理:

package ex02.pyrmont.first;import java.net.URL;import java.net.URLClassLoader;import java.net.URLStreamHandler;import java.io.IOException;import javax.servlet.Servlet;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import ex02.pyrmont.Constants;import ex02.pyrmont.Request;import ex02.pyrmont.Response;public class ServletProcessor1 {  public void process(Request request, Response response) {    String uri = request.getUri();    String servletName = uri.substring(uri.lastIndexOf("/") + 1);        //类加载器,用于从指定JAR文件或目录加载类    URLClassLoader loader = null;    try {      URLStreamHandler streamHandler = null;      //创建类加载器      loader = new URLClassLoader(new URL[]{new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, streamHandler)});    } catch (IOException e) {      System.out.println(e.toString());    }        Class<?> myClass = null;    try {      //加载对应的servlet类      myClass = loader.loadClass(servletName);    } catch (ClassNotFoundException e) {      System.out.println(e.toString());    }    Servlet servlet = null;    try {      //生产servlet实例      servlet = (Servlet) myClass.newInstance();      //执行ervlet的service方法      servlet.service((ServletRequest) request,(ServletResponse) response);    } catch (Exception e) {      System.out.println(e.toString());    } catch (Throwable e) {      System.out.println(e.toString());    }  }}

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请求(因为只是第一个字符串被刷新到浏览器,所以你不能看到第二个字符串Violets are blue。我们将在后续完善该容器):

改进

  前面实现的Servlet容器有一个严重的问题,用户在servlet里可以直接将ServletRequest、ServletResponse向下转 型为Request和Response类型,并直接调用其内部的public方法,这是一个不好的设计,改进方法是给Request、Response 增加外观类,这样,用户只能访问外观类里定义的public方法。

Request外观类

package ex02.pyrmont.second;import java.io.IOException;import java.io.BufferedReader;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;import ex02.pyrmont.Request;public class RequestFacade implements ServletRequest {  private ServletRequest request = null;  public RequestFacade(Request request) {    this.request = request;  }  /* implementation of the ServletRequest */  public Object getAttribute(String attribute) {    return request.getAttribute(attribute);  }  public Enumeration<?> getAttributeNames() {    return request.getAttributeNames();  }  @SuppressWarnings("deprecation")  public String getRealPath(String path) {    return request.getRealPath(path);  }  public RequestDispatcher getRequestDispatcher(String path) {    return request.getRequestDispatcher(path);  }  public boolean isSecure() {    return request.isSecure();  }  public String getCharacterEncoding() {    return request.getCharacterEncoding();  }  public int getContentLength() {    return request.getContentLength();  }  public String getContentType() {    return request.getContentType();  }  public ServletInputStream getInputStream() throws IOException {    return request.getInputStream();  }  public Locale getLocale() {    return request.getLocale();  }  public Enumeration<?> getLocales() {    return request.getLocales();  }  public String getParameter(String name) {    return request.getParameter(name);  }  public Map<?, ?> getParameterMap() {    return request.getParameterMap();  }  public Enumeration<?> getParameterNames() {    return request.getParameterNames();  }  public String[] getParameterValues(String parameter) {    return request.getParameterValues(parameter);  }  public String getProtocol() {    return request.getProtocol();  }  public BufferedReader getReader() throws IOException {    return request.getReader();  }  public String getRemoteAddr() {    return request.getRemoteAddr();  }  public String getRemoteHost() {    return request.getRemoteHost();  }  public String getScheme() {    return request.getScheme();  }  public String getServerName() {    return request.getServerName();  }  public int getServerPort() {    return request.getServerPort();  }  public void removeAttribute(String attribute) {    request.removeAttribute(attribute);  }  public void setAttribute(String key, Object value) {    request.setAttribute(key, value);  }  public void setCharacterEncoding(String encoding)      throws UnsupportedEncodingException {    request.setCharacterEncoding(encoding);  }}

View Code

Response外观类

package ex02.pyrmont.second;import java.io.IOException;import java.io.PrintWriter;import java.util.Locale;import javax.servlet.ServletResponse;import javax.servlet.ServletOutputStream;import ex02.pyrmont.Response;public class ResponseFacade implements ServletResponse {  private ServletResponse response;  public ResponseFacade(Response response) {    this.response = response;  }  public void flushBuffer() throws IOException {    response.flushBuffer();  }  public int getBufferSize() {    return response.getBufferSize();  }  public String getCharacterEncoding() {    return response.getCharacterEncoding();  }  public Locale getLocale() {    return response.getLocale();  }  public ServletOutputStream getOutputStream() throws IOException {    return response.getOutputStream();  }  public PrintWriter getWriter() throws IOException {    return response.getWriter();  }  public boolean isCommitted() {    return response.isCommitted();  }  public void reset() {    response.reset();  }  public void resetBuffer() {    response.resetBuffer();  }  public void setBufferSize(int size) {    response.setBufferSize(size);  }  public void setContentLength(int length) {    response.setContentLength(length);  }  public void setContentType(String type) {    response.setContentType(type);  }  public void setLocale(Locale locale) {    response.setLocale(locale);  }}

View Code

处理Servlet请求类:

package ex02.pyrmont.second;import java.net.URL;import java.net.URLClassLoader;import java.net.URLStreamHandler;import java.io.IOException;import javax.servlet.Servlet;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import ex02.pyrmont.Constants;import ex02.pyrmont.Request;import ex02.pyrmont.Response;public class ServletProcessor2 {  public void process(Request request, Response response) {    String uri = request.getUri();    String servletName = uri.substring(uri.lastIndexOf("/") + 1);    // 类加载器,用于从指定JAR文件或目录加载类    URLClassLoader loader = null;    try {      URLStreamHandler streamHandler = null;      // 创建类加载器      loader = new URLClassLoader(new URL[] { new URL(null, "file:"          + Constants.WEB_SERVLET_ROOT, streamHandler) });    } catch (IOException e) {      System.out.println(e.toString());    }    Class<?> myClass = null;    try {      // 加载对应的servlet类      myClass = loader.loadClass(servletName);    } catch (ClassNotFoundException e) {      System.out.println(e.toString());    }    Servlet servlet = null;    //给request、response增加外观类,安全性考虑,防止用户在servlet里直接将ServletRequest、ServletResponse向下转型为Request和Response类型,    //并直接调用其内部的public方法,因为RequestFacade、ResponseFacade里不会有parse、sendStaticResource等方法;    RequestFacade requestFacade = new RequestFacade(request);    ResponseFacade responseFacade = new ResponseFacade(response);    try {      servlet = (Servlet) myClass.newInstance();      servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);    } catch (Exception e) {      System.out.println(e.toString());    } catch (Throwable e) {      System.out.println(e.toString());    }  }}

View Code

其它代码与前面实现的Servlet容器基本一致。

验证程序,分别请求静态资源和Servlet,发现结果与前面实现的容器一致;

 

 参考资料:《深入剖析Tomcat》