How tomcat work连载二:简易的Servlet容器

       以下代码是仿照《How tomcat work》第二章:如何创建一个简易的Servlet容器,当然会是在上一篇文章http://bestchenwu.iteye.com/blog/1513984的基础上创建的。

 

       代码示例如下:

       先创建一个简易的Servlet,如下所示:

       package ex02.pyrmont;

import static util.DateUtil.getCurrentDateInTranditional;

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;

import org.apache.log4j.Logger;

/**
 * 类PrimeServlet.java的实现描述:
 */
public class PrimeServlet implements Servlet {

    private static final Logger log = Logger.getLogger("actionLog");

    @Override
    public void init(ServletConfig config) throws ServletException {
        if (log.isInfoEnabled()) {
            log.info("PrimeServlet init on:" + getCurrentDateInTranditional());
        }
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        if (log.isInfoEnabled()) {
            log.info("PrimeServlet service on:" + getCurrentDateInTranditional());
        }
        PrintWriter pw = res.getWriter();
        pw.println("Hello world");
        pw.flush();
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {
        if (log.isInfoEnabled()) {
            log.info("PrimeServlet destroy on:" + getCurrentDateInTranditional());
        }
    }

}

 

       这里我引入了一个自己编写的日期操作类的静态方法,代码如下所示:

      package util;

import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 类DateUtil.java的实现描述:
 */
public class DateUtil {

    private static final Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 返回当前时间
     * 
     * @return {@link java.util.Date}
     */
    public static Date getCurrentDate() {
        return new Date();
    }

    /**
     * 返回日期的表示时间,类似与yyyy-MM-dd hh:MM:ss
     * 
     * @return 格式化后的日期
     */
    public static String getCurrentDateInTranditional() {
        return format.format(getCurrentDate());
    }
}

 

      然后我们还是按照HttpServer、Request、Response的方式来创建:

      先创建HttpServer:

      import static ex02.pyrmont.HttpConstants.SHUT_DOWN;

import static util.DateUtil.getCurrentDateInTranditional;

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;

import org.apache.log4j.Logger;

/**
 * 类HttpServer.java的实现描述:
 */
public class HttpServer {

    private static final Logger log = Logger.getLogger("actionLog");
    private ServerSocket        httpServer;

    /**
     * 初始化监听Server
     * 
     * @param port 监听端口
     * @param backLog 监听队列的最大长度
     * @param serverName 监听机器的域名
     */
    public HttpServer(int port, int backLog, String serverName){
        try {
            httpServer = new ServerSocket(port, backLog, InetAddress.getByName(serverName));
            if (log.isInfoEnabled()) {
                log.info("HttpServer init on:" + getCurrentDateInTranditional());
            }
        } catch (UnknownHostException e) {
            log.error(e.getMessage(), e);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    public static void main(String[] args) {
        HttpServer httpServer = new HttpServer(HttpConstants.port, 1, "127.0.0.1");
        httpServer.listen();
    }

    /**
     * 启动HttpServer的监听程序
     */
    public void listen() {
        if (httpServer == null) {
            throw new IllegalStateException("httpServer is null");
        }
        boolean isShutdown = false;
        Socket client;
        InputStream is;
        OutputStream os;
        while (!isShutdown) {
            try {
                client = httpServer.accept();
                is = client.getInputStream();
                os = client.getOutputStream();
                Request request = new Request(is);
                request.parse();
                Response response = new Response(request);
                response.setOutput(os);
                /** 检查uri是否是Servelt **/
                if (request.checkIsServlet()) {
                    /** 发送动态消息 **/
                    Processor processor = new DynamicProcessor(request, response);
                    processor.sendMessage();
                } else {
                    /** 发送静态消息 **/
                    Processor processor = new StaticProcessor(request, response);
                    processor.sendMessage();
                }
                client.close();
                /** 判断是否是关闭命令 **/
                isShutdown = request.getUri().equalsIgnoreCase(SHUT_DOWN);
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        /** 监听结束,调用关闭HttpServer程序 **/
        close();
    }

    /**
     * 关闭HttpServer
     */
    public void close() {
        if (httpServer != null && !httpServer.isClosed()) {
            try {
                httpServer.close();
                if (log.isInfoEnabled()) {
                    log.info("httpServer destroy on:" + getCurrentDateInTranditional());
                }
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }
}
 

 

      看到与之前的HttpServer的区别没?这里的HttpServer尝试去判断uri是否是去请求一个servlet,我们假定所有的Servlet请求都是以/servlet/Servelt类名来结尾的。

      接下来是Request、Response,这次Request改为实现ServletRequest、ServletResponse,当然很多方法我们都是仅提供默认实现或者空实现。

      Request代码如下所示:

      package ex02.pyrmont;

import static ex02.pyrmont.HttpConstants.BUFFER_SIZE;
import static ex02.pyrmont.HttpConstants.SERVLET_PREFIX;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;

/**
 * 类Request.java的实现描述:
 */
public class Request implements HttpServletRequest {

    private static final Logger log = Logger.getLogger("actionLog");
    private InputStream         is;
    private String              uri;

    public Request(InputStream is){
        this.is = is;
    }

    /**
     * 将读取到的HttpRequest提取出uri
     */
    public void parse() {
        StringBuffer sb = new StringBuffer(BUFFER_SIZE);
        byte[] buffers = new byte[BUFFER_SIZE];
        int ch;
        try {
            ch = is.read(buffers);
            if (ch != -1) {
                for (int i = 0; i < ch; i++) {
                    sb.append((char) buffers[i]);
                }
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        if (log.isDebugEnabled()) {
            log.debug("Request:" + sb.toString());
        }
        this.uri = parseUri(sb.toString());
    }

    /**
     * 提取出request中第一行的uri 类似于GET/POST /page/index.html HTTP1.1/1.0
     * 
     * @param request
     */
    private String parseUri(String request) {
        int index1, index2;
        index1 = request.indexOf(' ');
        if (index1 > -1) {
            index2 = request.indexOf(' ', index1 + 1);
            if (index2 > index1) {
                return request.substring(index1 + 1, index2);
            }
        }
        return "";
    }

    /**
     * @return the uri
     */
    public String getUri() {
        return uri;
    }

    /**
     * 检查请求的uri是否是Servlet<br/>
     * 即uri是否是
     * 
     * @return boolean true
     */
    public boolean checkIsServlet() {
        return this.getUri().startsWith(SERVLET_PREFIX);
    }
   //...  一些
}
 

 

      Response代码如下所示:

      package ex02.pyrmont;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Locale;

import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 类Response.java的实现描述:
 */
public class Response implements ServletResponse {

    private Request      request;
    private OutputStream os;

    public Response(ServletRequest request){
        this.request = (Request) request;
    }

    public void setOutput(OutputStream os) {
        this.os = os;
    }

    public OutputStream getOutput() {
        return this.os;
    }
  
   /**
    * 这个方法覆盖是必须的,用来向页面输出
    */  
   @Override
    public PrintWriter getWriter() throws IOException {
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(os));
        return pw;
    }
}

 

 

      常量接口类如下所示:

      package ex02.pyrmont;

import java.io.File;

/**
 * 类HttpConstants.java的实现描述:
 */
public interface HttpConstants {

    /** HttpServer监听的端口 **/
    public static final int    port           = 8773;

    /** 读取或者写入的缓冲区大小 **/
    public static final int    BUFFER_SIZE    = 2048;

    /** servlet请求时候的前缀 **/
    public static final String SERVLET_PREFIX = "/servlet/";

    /** 静态文件的公共目录 **/
    public static final String WEB_ROOT       = System.getProperty("user.dir") + File.separator + "webRoot";

    /** HttpServer关闭命令 **/
    public static final String SHUT_DOWN      = "/shutdown";
  
    /**  静态文件找不到的时候的错误提示页面 **/
    public static final String ERROR_HTML     = "<html><body><div><h1>can't find your request</h1></div></body></html>";
}
 

       接下来是分别针对于静态文件和Servlet的处理类,抽象类实现如下所示:

       package ex02.pyrmont;

import static ex02.pyrmont.HttpConstants.SHUT_DOWN;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.log4j.Logger;

/**
 * 类Processor.java的实现描述:
  */
public abstract class Processor {

    protected static final Logger log = Logger.getLogger("actionLog");
    protected Request             request;
    protected Response            response;

    public Processor(ServletRequest request, ServletResponse response){
        this.request = (Request) request;
        this.response = (Response) response;
    }

    /**
     * 检查uri是否合格
     * 
     * @return boolean
     */
    protected boolean checkUriIsValid() {
        String uri = request.getUri();
        if (uri.equalsIgnoreCase(SHUT_DOWN)) {
            return false;
        }
        return true;
    }

    /**
     * 发送消息
     */
    public abstract void sendMessage();

    @Override
    public String toString() {
        return String.format("Processor=%s[request=%s,response=%s]", this.getClass().getSimpleName(), this.request,
                             this.response);
    }
}
 

 

      静态文件的处理类如下所示:

       package ex02.pyrmont;

import static ex02.pyrmont.HttpConstants.BUFFER_SIZE;
import static ex02.pyrmont.HttpConstants.ERROR_HTML;
import static ex02.pyrmont.HttpConstants.WEB_ROOT;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

/**
 * 类StaticProcessor.java的实现描述:
 */
public class StaticProcessor extends Processor {

    public StaticProcessor(ServletRequest request, ServletResponse response){
        super(request, response);
    }

    @Override
    public void sendMessage() {
        if (!super.checkUriIsValid()) {
            return;
        }
        try {
            OutputStream os = response.getOutput();
            File file = new File(WEB_ROOT, request.getUri());
            if (!file.exists()) {
                log.error("can't find uri:" + request.getUri());
                os.write(ERROR_HTML.getBytes());
            } else {
                int ch;
                InputStream is = new FileInputStream(file);
                byte[] buffers = new byte[BUFFER_SIZE];
                while ((ch = is.read(buffers, 0, BUFFER_SIZE)) != -1) {
                    /** 输出页面到前台展示页面 **/
                    os.write(buffers, 0, ch);
                }
                closeIO(is);
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 关闭I/O资源
     * 
     * @param is
     */
    private void closeIO(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }
}
 

       动态文件的处理类如下所示:

 

       至此,一个简易的 Servlet容器创建完毕,先启动HttpServer,然后在浏览器里输入:

       http://127.0.0.1:8773/servlet/ex02.pyrmont.PrimeServlet

       http://127.0.0.1:8773/pages/index.html

       看下是不是可以看到我们的响应输出

 

       当然这里所述的文件寻址目录都位于工程下的webRoot一级目录,所以你需要确保所有的静态文件、servlet在运行期间都会存在于该目录下,比较简单的方法就是把静态文件也放到src目录下,为它们创建一个单独的目录,比如pages

 

        这个容器还有个小问题,就是我们的servlet去服务的时候,传入的参数还是Request、Response类型,这里我们可以创建一个适配所有ServletRequest 、ServletResponse的接口,让我们的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.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;

/**
 * 类RequestFacade.java的实现描述:
 */
public class RequestFacade implements ServletRequest {

    private ServletRequest request;

    public RequestFacade(Request request){
        this.request = request;
    }
}

 

         response也是这样

         这样的话,servlet处理器只需要像下面这样修改:

            Class<Servlet> servletClass = (Class<Servlet>) loader.loadClass(servletClassName);
            Servlet servlet = (Servlet) servletClass.newInstance();
            /** 初始化Servlet **/
            servlet.init(null);
            /** 执行它的服务方法 **/
            RequestFacade requestFacade = new RequestFacade(request);
            ResponseFacade responseFacade = new ResponseFacade(response);
            servlet.service(requestFacade, responseFacade);
            /** 销毁Servlet **/
            servlet.destroy();

 

          再次启动HttpServer,看下修改后是否能通过。

 

 

 

 

posted @ 2012-05-09 09:47  老去的JAVA程序员  阅读(157)  评论(0编辑  收藏  举报