一个简单的 servlet 容器

  cheney

    每一个 Servlet 程序都实现了 javax.servlet.Servlet 接口,该接口中共有 5个方法:int,service,destroy,三个关系声明周期的方法,和getServletConfig,getServletInfo` 方法。

    servlet 容器要做的工作:
    - 当要请求的 servlet 没有加载时加载它,然后调用一次 init 方法。
    - 针对每个 request 请求,生成 Redquset 和 Response 对象。
    - 调用该 servlet 的service 方法。
    - 关闭时调用 destroy 方法,然后卸载类。

    servlet 接口定义在 javax.servlet-api

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
    </dependency>
    

    首先实现一个简单的 servlet 作为测试:(为了方便不写包名)

    import javax.servlet.*;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * Created by Cheney
     */
    public class TryServlet implements Servlet {
        @Override
        public void init(ServletConfig servletConfig) throws ServletException {
            System.out.println("init TryServlet");
        }
    
        @Override
        public ServletConfig getServletConfig() {
            return null;
        }
    
        @Override
        public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
            PrintWriter out = servletResponse.getWriter();
            out.println("Hello TryServlet");
        }
    
        @Override
        public String getServletInfo() {
            return null;
        }
    
        @Override
        public void destroy() {
            System.out.println("destroy TryServlet");
        }
    }
    

    Server 程序进行改造:(通过判断是否是 .do 来确定是静态文件还是 servlet )

    package me.gpio.tomcat;
    
    import java.io.*;
    import java.net.InetAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    /**
     * Created by Cheney
     */
    public class SimpleHttpServer {
    
        public static void main(String[] args) {
            SimpleHttpServer simpleHttpServer = new SimpleHttpServer();
            simpleHttpServer.await();
        }
        // 静态文件根目录
        public static final String WEB_ROOT = System.getProperty("user.dir");
    
    
        // 关闭服务命令
        public static final String CMD_SHUTDOWN = "/shutdown";
    
        // 等待响应请求的函数
        public void await(){
            ServerSocket serverSocket = null;
            String host = "127.0.0.1";
            int port = 1234;
    
            try {
                // 第二个参数是等待队列的长度
                serverSocket = new ServerSocket(port,1, InetAddress.getByName(host));
            } catch (UnknownHostException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            System.out.println(String.format("SimpleHTTPServer is running on %s:%d",host,port));
    
            // 等待客户端请求
            while (true){
                Socket socket = null;
                InputStream in = null;
                OutputStream out = null;
    
                try {
                    socket = serverSocket.accept();
                    in = socket.getInputStream();
                    out = socket.getOutputStream();
                    // 解析响应
                    Request request = new Request(in);
                    request.parse();
                    String uri = request.getUri();
                    if( CMD_SHUTDOWN.equals(uri) ){
                        break;
                    }
    
                    // 生成请求
                    Response response = new Response(out);
                    response.setRequest(request);
    
                    if (uri.endsWith(".do")){
                        ServletProcessor servletProcessor = new ServletProcessor();
                        servletProcessor.process(request,response);
                    } else {
                        StaticResourceProcessor staticResourceProcessor = new StaticResourceProcessor();
                        staticResourceProcessor.process(request,response);
                    }
    
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    Closeable[] needClose = {in,out,socket};
                    for( Closeable elem : needClose ){
                        if( null != elem ){
                            try {
                                elem.close();
                            } catch (IOException e) {
                            }
                        }
                    }
                }
            }
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    之前自定义的 Response 也实现了 ServletResponse 接口,自定义的 Request 也实现了 ServletRequest 接口,虽然方法绝大多数都空着。

    package me.gpio.tomcat;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.ServletResponse;
    import java.io.*;
    import java.util.Locale;
    
    /**
     * Created by Cheney
     */
    public class Response implements ServletResponse{
    
        private static final int MAX_BUFF_SIZE = 1024;
        private OutputStream out = null;
        private Request request = null;
        private PrintWriter writer = null;
    
        public Response(OutputStream out) {
            this.out = out;
        }
    
        public void setRequest(Request request){
            this.request = request;
        }
    
        public void SendStaticResource(){
            byte[] buff = new byte[MAX_BUFF_SIZE];
            FileInputStream fis = null;
            File file = null;
    
            try {
                String filePath = SimpleHttpServer.WEB_ROOT + request.getUri();
                System.out.println(filePath);
                file = new File( filePath );
                if( file.exists() && file.isFile()) {
                    fis = new FileInputStream(file);
                    int ch = fis.read(buff,0,MAX_BUFF_SIZE);
                    while (-1 != ch){
                        out.write(buff,0,ch);
                        ch = fis.read(buff,0,MAX_BUFF_SIZE);
                    }
                }else{
                    String errMsg = "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>";
                    out.write(errMsg.getBytes());
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if( null != fis ){
                    try {
                        fis.close();
                    } catch (IOException e) {
                    }
                }
            }
    
        }
    
        @Override
        public String getCharacterEncoding() {
            return null;
        }
    
        @Override
        public String getContentType() {
            return null;
        }
    
        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            return null;
        }
    
        @Override
        public PrintWriter getWriter() throws IOException {
            // 第二个参数是自动处理缓冲
            writer = new PrintWriter(out,true);
            return writer;
        }
    
        @Override
        public void setCharacterEncoding(String s) {
    
        }
    
        @Override
        public void setContentLength(int i) {
    
        }
    
        @Override
        public void setContentType(String s) {
    
        }
    
        @Override
        public void setBufferSize(int i) {
    
        }
    
        @Override
        public int getBufferSize() {
            return 0;
        }
    
        @Override
        public void flushBuffer() throws IOException {
    
        }
    
        @Override
        public void resetBuffer() {
    
        }
    
        @Override
        public boolean isCommitted() {
            return false;
        }
    
        @Override
        public void reset() {
    
        }
    
        @Override
        public void setLocale(Locale locale) {
    
        }
    
        @Override
        public Locale getLocale() {
            return null;
        }
    }
    
    package me.gpio.tomcat;
    
    import javax.servlet.*;
    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;
    
    /**
     * Created by Cheney
     */
    public class Request implements ServletRequest {
        private static final int MAX_BUFF_SIZE = 1024;
        private InputStream in;
        private String uri="";
        private String stringRequest;
    
        public Request(InputStream in) {
            this.in = in;
        }
    
        public void parse() {
            byte[] buff = new byte[MAX_BUFF_SIZE];
            StringBuffer stringBuffer = new StringBuffer(MAX_BUFF_SIZE);
            // 一般不会很长,读一次够了
            int ch = -1;
            try {
                ch = in.read(buff,0,MAX_BUFF_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
                ch = -1;
            }
    
            for( int i=0 ; i < ch ; i++ ){
                stringBuffer.append((char)buff[i]);
            }
    
            stringRequest = stringBuffer.toString();
            System.out.println(stringRequest);
    
            uri = parseUri();
    
        }
    
        private String parseUri() {
            assert null != stringRequest;
    
            int index1 = stringRequest.indexOf(" ");
            int index2 = stringRequest.indexOf(" ",index1 +1);
            if( index2 > index1 ){
                return stringRequest.substring(index1+1,index2);
            }
            return "";
        }
    
        public String getUri(){
            return uri;
        }
    
        @Override
        public Object getAttribute(String s) {
            return null;
        }
    
        @Override
        public Enumeration<String> getAttributeNames() {
            return null;
        }
    
        @Override
        public String getCharacterEncoding() {
            return null;
        }
    
        @Override
        public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
    
        }
    
        @Override
        public int getContentLength() {
            return 0;
        }
    
        @Override
        public String getContentType() {
            return null;
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            return null;
        }
    
        @Override
        public String getParameter(String s) {
            return null;
        }
    
        @Override
        public Enumeration<String> getParameterNames() {
            return null;
        }
    
        @Override
        public String[] getParameterValues(String s) {
            return new String[0];
        }
    
        @Override
        public Map<String, String[]> getParameterMap() {
            return null;
        }
    
        @Override
        public String getProtocol() {
            return null;
        }
    
        @Override
        public String getScheme() {
            return null;
        }
    
        @Override
        public String getServerName() {
            return null;
        }
    
        @Override
        public int getServerPort() {
            return 0;
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            return null;
        }
    
        @Override
        public String getRemoteAddr() {
            return null;
        }
    
        @Override
        public String getRemoteHost() {
            return null;
        }
    
        @Override
        public void setAttribute(String s, Object o) {
    
        }
    
        @Override
        public void removeAttribute(String s) {
    
        }
    
        @Override
        public Locale getLocale() {
            return null;
        }
    
        @Override
        public Enumeration<Locale> getLocales() {
            return null;
        }
    
        @Override
        public boolean isSecure() {
            return false;
        }
    
        @Override
        public RequestDispatcher getRequestDispatcher(String s) {
            return null;
        }
    
        @Override
        public String getRealPath(String s) {
            return null;
        }
    
        @Override
        public int getRemotePort() {
            return 0;
        }
    
        @Override
        public String getLocalName() {
            return null;
        }
    
        @Override
        public String getLocalAddr() {
            return null;
        }
    
        @Override
        public int getLocalPort() {
            return 0;
        }
    
        @Override
        public ServletContext getServletContext() {
            return null;
        }
    
        @Override
        public AsyncContext startAsync() throws IllegalStateException {
            return null;
        }
    
        @Override
        public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
            return null;
        }
    
        @Override
        public boolean isAsyncStarted() {
            return false;
        }
    
        @Override
        public boolean isAsyncSupported() {
            return false;
        }
    
        @Override
        public AsyncContext getAsyncContext() {
            return null;
        }
    
        @Override
        public DispatcherType getDispatcherType() {
            return null;
        }
    }
    

    独立的 Constants 类,存储常量。servlet 的编译后的 class 就要放在这个目录下的 classes 中。

    public class Constants {
        public static final String WEB_ROOT = System.getProperty("user.dir");
    }