sqtsqt 发表于 2017-2-7 08:56:15

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;
int ch;
try {
ch = is.read(buffers);
if (ch != -1) {
for (int i = 0; i < ch; i++) {
sb.append((char) buffers);
}
}
} 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", 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;
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,看下修改后是否能通过。
页: [1]
查看完整版本: How tomcat work连载二:简易的Servlet容器