How tomcat work连载2:简易的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,看下修改后是否能通过。