《How_Tomcat_Worsk》第二章笔记 一个简单的servlet容器

看了how_tomcat_works, 第二章,实现一个简单的Servlet容器,大概实现了一遍,了解了一下一个简单的Servlet容器是如何工作的。

基础知识:Socket通信,之前有实现过

这是一个最最基础的Servlet容器,没有实现复杂的功能,主要模拟当我在浏览器中输入有效网址后进行的一系列操作,这个操作很简单:可以总结为如下步骤:
1.输入网址:例如http://localhost:8080/servlet/PrimitiveServlet
2.判断网址(这里分了两种情况,一种是请求静态资源的,一种是请求一个servlet的),为了简单的区分网址,这里定义以/servlet/ 开头的网址时请求servlet的,其他都是请求静态资源的。
3.返回相应的信息。

Example1

   这里先来实现最简单的一个样例,主要包括如下几个类:
   PrimitiveServlet //继承于java.servlet.Servlet接口。
   HttpServer1
   Request
   Response
   ServletProcessor1
   StaticResourceProcessor
   Constants //用来存放一些全局变量之类的
   
   Servlet编程是通过javax.servlet和javax.servlet.http两个包的类和接口来实现的,一个重要的接口就是javax.servlet.Servlet接口了,所有的servlet都应该implement 该接口或者extend继承了该接口的类。
   Servlet接口主要有以下几个方法:

   public void init(ServletConfig config) throws ServletException;
   public ServletConfig getServletConfig();
   public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException;
   public String getServletInfo();
   public void destroy();

init,service 和 destroy 是 servlet 的生命周期方法
init方法:当servlet类被实例化后,init方法会被servlet容器所调用。 servlet 容器只调用一次,以此表明servlet 已经被加载进服务中。 init 方法必须在 servlet 可以接受任何请求之前成功运行完毕。
service方法:servlet 容 器 传 递 一 个javax.servlet.ServletRequest 对象和 javax.servlet.ServletResponse 对象。ServletRequest对象包括客户端的 HTTP 请求信息,而 ServletResponse 对象封装 servlet 的响应。在 servlet的生命周期中, service 方法将会给调用多次。
destroy方法:这通常发生在servlet 容器正在被关闭或者 servlet 容器需要一些空闲内存的时候。仅仅在所有 servlet 线程的 service 方法已经退出或者超时淘汰的时候,这个方法才被调用。在 servlet 容器已经调用完destroy 方法之后,在同一个 servlet 里边将不会再调用 service 方法。 destroy 方法提供了一个机会来清理任何已经被占用的资源,例如内存,文件句柄和线程,并确保任何持久化状态和servlet 的内存当前状态是同步的。
  以下是PrimitiveServlet 的 servlet 的代码:

package com.zhoufenqin.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class PrimitiveServlet implements Servlet{

    @Override
    public void destroy() {
        System.out.println("destroy");

    }

    @Override
    public ServletConfig getServletConfig() {

        return null;
    }

    @Override
    public String getServletInfo() {

        return null;
    }

    @Override
    public void init(ServletConfig arg0) throws ServletException {
        System.out.println("init_zfq");

    }

    @Override
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = arg1.getWriter();
        out.println("Hello. Roses are read");
        out.println("Violets are blue");

    }

}

   这里,servlet容器的UML图如下所示:
 这里写图片描述
  
  应用程序入口点在HttpServer1中,创建一个ServerSocket等待客户端的请求。每次请求成功时创建一个Request对象和Response对象。并根据需求分发到StaticResourceProcessor或者ServletProcessor实例中。(若请求的是静态资源则发送到StaticResourceProcessor,如果是一个servlet则发送到ServletProcessor1中)

HttpServer1类

  HttpServer1类,用户接受客户端的请求。当请求静态资源时,调用StaticResourceProcessor类的process方法进行处理。否则调用ServletProcessor1类的process方法进行处理。
  请求静态资源时,可以再浏览器中输入类似网址:
  http://localhost:8080/staticResource
  与之区分,若要请求一个servlet,可使用下面的网址:
  http://localhost:8080/servlet/PrimitiveServlet
  

package com.zhoufenqin.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;
import java.net.UnknownHostException;
public class HttpServer1 {

    //终止服务器的运行
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
    private boolean shutdown = false;
    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 (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {   
            e.printStackTrace();
            System.exit(1);
        }

        while(!shutdown) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;
            try {
                System.out.println("Begin");
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();

                //Create Request object
                Request request = new Request(input);
                request.parse();

                //Create Response object
                Response response = new Response(output);
                response.setRequest(request);

                // check if this is a request for a servlet or a static resource
                // a request for a servlet begins with "/servlet/"
                if(request.getUri() == null) continue;
                //请求一个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 (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }

        }

    }
}

Request类

  servlet 的 service 方法从 servlet 容器中接收一个 javax.servlet.ServletRequest 实例和一个 javax.servlet.ServletResponse 实例。这就是说对于每一个 HTTP 请求, servlet 容器必须构造一个 ServletRequest 对象和一个 ServletResponse 对象并把它们传递给正在服务的servlet 的 service 方法。
  Request类实现了javax.servlet.ServletRequest。

package com.zhoufenqin.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;

import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class Request implements ServletRequest{

    private InputStream input;
    private String uri;
    public Request(InputStream input) {
        this.input = input;
    }
    public String getUri() {
        return uri;
    }

    //解析URL
    /*
     * requestString一般格式如下
     * GET /servlet/PrimitiveServlet HTTP/1.1
        Host: localhost:8080
        Connection: keep-alive
        Cache-Control: max-age=0
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*\/*;q=0.8
        Upgrade-Insecure-Requests: 1
        User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
        Accept-Encoding: gzip, deflate, sdch
        Accept-Language: zh-CN,zh;q=0.8
        Cookie: Telerik.Skin=Windows7
     * */
    public String parseUri(String requestString) {
        //GET /servlet/PrimitiveServlet HTTP/1.1 取出中间的url
        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.println("request toString: " + request.toString());
        uri = parseUri(request.toString());
    }

    //implementation of ServletRequest
    @Override
    public AsyncContext getAsyncContext() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Object getAttribute(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Enumeration<String> getAttributeNames() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getCharacterEncoding() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getContentLength() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public String getContentType() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public DispatcherType getDispatcherType() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getLocalAddr() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getLocalName() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getLocalPort() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public Locale getLocale() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Enumeration<Locale> getLocales() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getParameter(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String[] getParameterValues(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getProtocol() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getRealPath(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getRemoteAddr() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getRemoteHost() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getRemotePort() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getScheme() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getServerName() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getServerPort() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public ServletContext getServletContext() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean isAsyncStarted() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isAsyncSupported() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isSecure() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void removeAttribute(String arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setAttribute(String arg0, Object arg1) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setCharacterEncoding(String arg0)
            throws UnsupportedEncodingException {
        // TODO Auto-generated method stub

    }

    @Override
    public AsyncContext startAsync() throws IllegalStateException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1)
            throws IllegalStateException {
        // TODO Auto-generated method stub
        return null;
    }

}

Response类

  Response类实现了javax.servlet.ServletResponse。

 package com.zhoufenqin.servlet;

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 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 static pages */
    public void sendStaticResponse() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        try {
            //找到相应的文件,然后把里面的内容读出来显示到页面上
            //例如在当前工程目录下有一个index.html,当输入http://localhost:8080/index.html时,将index.html中的内容显示出来
            File file = new File(Constants.WEB_ROOT,request.getUri());
            fis = new FileInputStream(file);
            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) {
            //如果没有找到相应的页面,就会返回如下html页面
            System.out.println("没有找到相应的文件");
            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
     */
    @Override
    public void flushBuffer() throws IOException {
        // TODO Auto-generated method stub

    }

    @Override
    public int getBufferSize() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public String getCharacterEncoding() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getContentType() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Locale getLocale() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        // TODO Auto-generated method stub
        return null;
    }

    //自己重写
    @Override
    public PrintWriter getWriter() throws IOException {
        // TODO Auto-generated method stub
        //return null;
        writer = new PrintWriter(output,true);
        return writer;
    }

    @Override
    public boolean isCommitted() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void reset() {
        // TODO Auto-generated method stub

    }

    @Override
    public void resetBuffer() {
        // TODO Auto-generated method stub

    }

    @Override
    public void setBufferSize(int arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setCharacterEncoding(String arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setContentLength(int arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setContentType(String arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setLocale(Locale arg0) {
        // TODO Auto-generated method stub

    }

}

ServletProcessor1类

  ServletProcessor1类用于处理servlet的HTTP请求。

package com.zhoufenqin.servlet;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class ServletProcessor1 {

    public void process(Request request,Response response) {
        String uri = request.getUri();
        //获得自己定义的servlet对象,本例中定义了PrimitiveServlet,所以只有从URL中好的PrimitiveServlet时才会处理,否则输出错误。
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        URLClassLoader loader = null;
        System.out.println("servletName = " + servletName);
        try {
            URL url = new URL("file","127.0.0.1",Constants.WEB_ROOT);
            URL[] urls = new URL[]{url};
            loader = new URLClassLoader(urls);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        Class<?> myClass = null;
        try {
            //loadClass时记得把包名带上,不要试图把报名写再WEB_ROOT路径中,这样是加载不了类的。
            myClass = loader.loadClass("com.zhoufenqin.servlet." + servletName);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        Servlet servlet = null;

        try {
            //实例化primitiveServlet类并调用其service()方法。
            servlet = (Servlet) myClass.newInstance();
            servlet.service((ServletRequest)request, (ServletResponse)response);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ServletException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

StaticResourceProcessor类

  StaticResourceProcessor类用来处理静态资源请求。调用Response的sendStaticResource()方法。

package com.zhoufenqin.servlet;

import java.io.IOException;

public class StaticResourceProcessor {
    public void process(Request request, Response response) {
        try {
            response.sendStaticResponse();
        } catch (IOException e) {   
            e.printStackTrace();
        }
    }
}

Constants类

  根据自己的项目,定义变量,这里我把WEB_ROOT定义如下:

package com.zhoufenqin.servlet;

import java.io.File;

public class Constants {
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "WebContent";

}

因为我项目的结构如下所示:
这里写图片描述

  到此为止,一个简单的servlet容器就完成了,现在来看看效果。
  (1)请求静态资源
  //没有找到相应的资源
  这里写图片描述
  
  //找到相应的资源文件
  这里写图片描述

  (2)请求一个servlet
  这里写图片描述

Example2

  在Example1中,我们在ServletProcessor1使用向上转型的方法,将Request类转为ServletRequest类,Response类转为ServletResponse类,作为参数传入到service函数。

servlet.service((ServletRequest)request, (ServletResponse)response);

  但是这样做比较危险,万一有人知道Servlet内部的操作,就可以将ServletRequest类向下转化成Request,然后调用Request类中的函数,比如parse()函数,因为该函数是public的。这样可能就泄露了信息。如下所示:

@Override
    public void service(ServletRequest arg0, ServletResponse arg1)
            throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = arg1.getWriter();
        out.println("Hello. Roses are read");
        out.println("Violets are blue");
        Request req = (Request) arg0;//可以转化为Request类并调用其相关函数,危险
        req.parse();    
    }

  当然一种解决方法是将这些会被别人引用到的函数定义为私有的,不过还有更好的解决方式。(这里用到的设计模式我还不太明白,因为不懂设计模式。。待查完资料后来补充)
  我们引入facade类,来更好的解决这个问题,UML图如下所示:
  这里写图片描述
  在程序中增加两个类。RequestFacade和ResponseFacade。
  RequestFacade 实 现 了 ServletRequest 接 口 并 通 过 在 构 造 方 法 中 传 递 一 个 引 用 了ServletRequest 对象的 Request 实例作为参数来实例化。 ResponseFacade也同理。
  具体实现如下:
  在Example1的基础上,保持Constants类,PrimitiveServlet类,StaticResourceProcessor,Requset,Response类不变。
  增加HttpServlet2,RequsetFacade,ResponseFacade,ServletProcessor2类。

HttpServlet2

  HttpServlet2由HttpServlet1变化而来,需要做一点改变,如下:

public class HttpServer2 {
        ...
        public static void main(String[] args) {
            HttpServer2 server = new HttpServer2();//替换成HttpServer2 
            server.await();
        }
        public void await() {
            ServerSocket serverSocket = null;
            int port = 8080;
            ...

            while(!shutdown) {
                ...
                    if(request.getUri().startsWith("/servlet/")) {

                        ServletProcessor2 processor = new ServletProcessor2();//替换
                        processor.process(request, response);
                    }
                    else{

                        StaticResourceProcessor processor = new StaticResourceProcessor();
                        processor.process(request, response);
                    }
                    ...
                } catch (IOException e) {
                    e.printStackTrace();
                    System.exit(1);
                }

            }

        }
}

RequestFacade

package com.zhoufenqin.servlet;

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.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class RequestFacade implements ServletRequest{
    //将request定义为私有的
    private ServletRequest request = null;
    public RequestFacade(Request request) {
        this.request = request;
    }
    @Override
    public AsyncContext getAsyncContext() {
        // TODO Auto-generated method stub
        return null;
    }
    ...
}

ResponseFacade

package com.zhoufenqin.servlet;

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{
    //将response定义为私有的
    private ServletResponse response = null;
    public ResponseFacade(Response response) {
        this.response = response;
    }
    @Override
    public void flushBuffer() throws IOException {
        // TODO Auto-generated method stub

    }
    ...
}

ServletProcessor2

  ServletProcessor2由ServletProcessor1变化而来,将原先传入service函数的参数进行更改。


Servlet servlet = null;
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
try {
    servlet = (Servlet) myClass.newInstance();
    //将原来参数由Requset类型转化为ServletRequest类型变成由RequsetFacade类型转化为ServletRequest类型。Response亦然。
    servlet.service((ServletRequest)requestFacade, (ServletResponse)responseFacade);
} catch (InstantiationException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalAccessException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (ServletException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值