BinCat V3-实现Servlet3.x API

V1V2我们完成了一个简单的文件访问服务和请求参数解析服务,V3我们继续添加Servlet API,从而理解Servlet的工作原理。

添加Servlet3.x依赖:

  1. <dependency>
  2. <groupId>javax.servlet</groupId>
  3. <artifactId>javax.servlet-api</artifactId>
  4. <version>3.0.1</version>
  5. </dependency>

创建com.anbai.sec.server.servlet.BinCatRequest类并继承javax.servlet.http.HttpServletRequest,然后需要实现HttpServletRequest接口方法,作为一个非标准的Servlet容器我们自然是没必要严格的是实现里面的所有方法,选择几个方法实现一下就行了。

注意:示例以下中省去了解析协议Servlet接口的代码,完整代码请参考:com.anbai.sec.server.servlet包下的完整实现代码。

HttpServletRequest实现

BinCatRequest.java示例代码片段:

  1. package com.anbai.sec.server.servlet;
  2. import org.javaweb.utils.StringUtils;
  3. import javax.servlet.*;
  4. import javax.servlet.http.*;
  5. import java.io.*;
  6. import java.net.Socket;
  7. import java.net.URLDecoder;
  8. import java.security.Principal;
  9. import java.util.*;
  10. import java.util.concurrent.ConcurrentHashMap;
  11. import java.util.logging.Logger;
  12. /**
  13. * BinCat 请求解析实现对象,解析Http请求协议和参数
  14. */
  15. public class BinCatRequest implements HttpServletRequest {
  16. // 客户端Socket连接对象
  17. private final Socket clientSocket;
  18. // Socket输入流对象
  19. private final InputStream socketInputStream;
  20. // Http请求头对象
  21. private Map<String, String> headerMap;
  22. // Http请求参数对象
  23. private Map<String, String[]> parameterMap;
  24. // Http请求attribute对象
  25. private final Map<String, Object> attributeMap = new ConcurrentHashMap<String, Object>();
  26. // Http请求Cookie对象
  27. private Cookie[] cookie;
  28. // Http请求Cookie对象
  29. private final Map<String, String> cookieMap = new ConcurrentHashMap<String, String>();
  30. // Http请求Session对象
  31. private final Map<String, BinCatSession> sessionMap = new ConcurrentHashMap<String, BinCatSession>();
  32. // Http请求方法类型
  33. private String requestMethod;
  34. // Http请求URL
  35. private String requestURL;
  36. // Http请求QueryString
  37. private String queryString;
  38. // Http请求协议版本信息
  39. private String httpVersion;
  40. // 是否已经解析过Http请求参数,防止多次解析请求参数
  41. private volatile boolean parsedParameter = false;
  42. // Http请求内容长度
  43. private int contentLength;
  44. // Http请求内容类型
  45. private String contentType;
  46. // 存储Session的ID名称
  47. private static final String SESSION_ID_NAME = "JSESSIONID";
  48. // Http请求主机名
  49. private String host;
  50. // Http请求主机端口
  51. private int port;
  52. private static final Logger LOG = Logger.getLogger("info");
  53. public BinCatRequest(Socket clientSocket) throws IOException {
  54. this.clientSocket = clientSocket;
  55. this.socketInputStream = clientSocket.getInputStream();
  56. // 解析Http协议
  57. parse();
  58. }
  59. /**
  60. * 解析Http请求协议,不解析Body部分
  61. *
  62. * @throws IOException
  63. */
  64. private void parse() throws IOException {
  65. // 此处省略Http请求协议解析、参数解析等内容...
  66. }
  67. /**
  68. * 解析Http请求参数
  69. *
  70. * @throws IOException Http协议解析异常
  71. */
  72. private synchronized void parseParameter() {
  73. // 此处省略Http请求协议解析、参数解析等内容...
  74. }
  75. // 此处省略HttpServletRequest接口中的大部分方法,仅保留几个示例方法...
  76. public String getHeader(String name) {
  77. return this.headerMap.get(name);
  78. }
  79. public ServletInputStream getInputStream() throws IOException {
  80. return new ServletInputStream() {
  81. @Override
  82. public int read() throws IOException {
  83. return socketInputStream.read();
  84. }
  85. };
  86. }
  87. public String getParameter(String name) {
  88. if (!parsedParameter) {
  89. this.parseParameter();
  90. }
  91. if (parameterMap.containsKey(name)) {
  92. return this.parameterMap.get(name)[0];
  93. }
  94. return null;
  95. }
  96. public String getRemoteAddr() {
  97. return clientSocket.getInetAddress().getHostAddress();
  98. }
  99. public void setAttribute(String name, Object o) {
  100. attributeMap.put(name, o);
  101. }
  102. }

HttpServletResponse实现

BinCatResponse.java示例代码片段:

  1. package com.anbai.sec.server.servlet;
  2. import javax.servlet.ServletOutputStream;
  3. import javax.servlet.http.Cookie;
  4. import javax.servlet.http.HttpServletResponse;
  5. import java.io.ByteArrayOutputStream;
  6. import java.io.IOException;
  7. import java.io.PrintWriter;
  8. import java.net.Socket;
  9. import java.net.URLEncoder;
  10. import java.util.*;
  11. public class BinCatResponse implements HttpServletResponse {
  12. private final Socket socket;
  13. private final Map<String, String> header;
  14. private final ByteArrayOutputStream out;
  15. private int status = 404;
  16. private String statusMessage = "Not Found";
  17. private String charset = "UTF-8";
  18. private int contentLength = 0;
  19. private String contentType = "text/html; charset=UTF-8";
  20. private String location;
  21. public BinCatResponse(Socket socket, Map<String, String> header, ByteArrayOutputStream out) {
  22. this.socket = socket;
  23. this.header = header;
  24. this.out = out;
  25. }
  26. // 此处省略HttpServletResponse接口中的大部分方法,仅保留几个示例方法...
  27. public void setHeader(String name, String value) {
  28. this.header.put(name, value);
  29. }
  30. public String getHeader(String name) {
  31. return header.get(name);
  32. }
  33. public PrintWriter getWriter() throws IOException {
  34. return new PrintWriter(out);
  35. }
  36. }

HttpSession实现

BinCatSession.java示例代码片段:

  1. package com.anbai.sec.server.servlet;
  2. import javax.servlet.ServletContext;
  3. import javax.servlet.http.HttpSession;
  4. import javax.servlet.http.HttpSessionContext;
  5. import java.util.Enumeration;
  6. import java.util.Map;
  7. import java.util.concurrent.ConcurrentHashMap;
  8. /**
  9. * BinCat Session实现
  10. */
  11. public class BinCatSession implements HttpSession {
  12. private final String sessionID;
  13. // Http请求Session对象
  14. private final Map<String, Object> sessionMap = new ConcurrentHashMap<String, Object>();
  15. public BinCatSession(String sessionID) {
  16. this.sessionID = sessionID;
  17. }
  18. // 此处省略HttpSession接口中的大部分方法,仅保留几个示例方法...
  19. public Object getAttribute(String name) {
  20. return this.sessionMap.get(name);
  21. }
  22. public void setAttribute(String name, Object value) {
  23. this.sessionMap.put(name, value);
  24. }
  25. }

Servlet类注册

Servlet3.0支持web.xml和注解两种方式配置,但不管是通过那种方式都需要知道Servlet的处理类和映射的URL地址,这里为了方法理解我将解析web.xml和扫描@WebServlet注解的步骤省略了,直接改成了手动配置一个Servlet映射类对象。

注册Servlet类对象代码片段:

  1. // 初始化Servlet映射类对象
  2. final Set<Class<? extends HttpServlet>> servletList = new HashSet<Class<? extends HttpServlet>>();
  3. // 手动注册Servlet类
  4. servletList.add(TestServlet.class);
  5. servletList.add(CMDServlet.class);

当接收到浏览器请求时候我们需要根据请求的URL地址来动态调用Servlet类相关的代码。

调用Servlet类处理Http请求代码片段:

  1. // 处理Http请求URL
  2. for (Class<? extends HttpServlet> clazz : servletList) {
  3. WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
  4. String[] urlPatterns = webServlet.urlPatterns();
  5. for (String urlPattern : urlPatterns) {
  6. try {
  7. // 检测请求的URL地址和Servlet的地址是否匹配
  8. if (Pattern.compile(urlPattern).matcher(uri).find()) {
  9. // 修改状态码
  10. response.setStatus(200, "OK");
  11. // 创建Servlet类实例
  12. HttpServlet httpServlet = clazz.newInstance();
  13. // 调用Servlet请求处理方法
  14. httpServlet.service(request, response);
  15. break;
  16. }
  17. } catch (IOException e) {
  18. // 修改状态码
  19. response.setStatus(500, "Internal Server Error");
  20. e.printStackTrace();
  21. }
  22. }
  23. }

BinCat V3实现

V3简单的封装了BinCatRequestBinCatResponseBinCatSession,还是先了部分的Servlet API从而实现了一个最初级的Servlet容器

V3处理流程:

  1. 创建服务端Socket连接(ServerSocket)。
  2. 手动注册Servlet类。
  3. 创建用于处理请求的BinCatRequest对象。
  4. BinCatRequest解析请求协议、请求头、请求参数、Cookie等。
  5. 创建用于处理请求的BinCatResponse对象。
  6. 解析Servlet类的@WebServlet注解,反射调用Servlet类方法处理Http请求。
  7. 输出响应信息以及Servlet处理结果。
  8. 关闭Socket连接。

BinCatServerV3实现代码:

  1. package com.anbai.sec.server;
  2. import com.anbai.sec.server.servlet.BinCatRequest;
  3. import com.anbai.sec.server.servlet.BinCatResponse;
  4. import com.anbai.sec.server.test.servlet.CMDServlet;
  5. import com.anbai.sec.server.test.servlet.TestServlet;
  6. import org.javaweb.utils.StringUtils;
  7. import javax.servlet.annotation.WebServlet;
  8. import javax.servlet.http.HttpServlet;
  9. import java.io.ByteArrayOutputStream;
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.io.OutputStream;
  13. import java.net.ServerSocket;
  14. import java.net.Socket;
  15. import java.util.HashSet;
  16. import java.util.Map;
  17. import java.util.Set;
  18. import java.util.concurrent.ConcurrentHashMap;
  19. import java.util.logging.Logger;
  20. import java.util.regex.Pattern;
  21. /**
  22. * ServerSocket Http 服务器示例
  23. */
  24. public class BinCatServerV3 {
  25. private static final Logger LOG = Logger.getLogger("info");
  26. public static void main(String[] args) {
  27. try {
  28. // 设置服务监听端口
  29. int port = 8080;
  30. // 设置服务名称
  31. String serverName = "BinCat-0.0.3";
  32. // 创建ServerSocket,监听本地端口
  33. ServerSocket ss = new ServerSocket(port);
  34. // 初始化Servlet映射类对象
  35. final Set<Class<? extends HttpServlet>> servletList = new HashSet<Class<? extends HttpServlet>>();
  36. // 手动注册Servlet类
  37. servletList.add(TestServlet.class);
  38. servletList.add(CMDServlet.class);
  39. LOG.info(serverName + " 启动成功,监听端口: " + port);
  40. while (true) {
  41. // 等待客户端连接
  42. Socket socket = ss.accept();
  43. try {
  44. // 获取Socket输入流对象
  45. InputStream in = socket.getInputStream();
  46. // 获取Socket输出流对象
  47. OutputStream out = socket.getOutputStream();
  48. // 创建BinCat请求处理对象
  49. BinCatRequest request = new BinCatRequest(socket);
  50. // 创建BinCat请求处理结果输出流
  51. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  52. // 创建BinCat请求处理结果Header对象
  53. Map<String, String> responseHeader = new ConcurrentHashMap<String, String>();
  54. // 创建BinCat响应处理对象
  55. BinCatResponse response = new BinCatResponse(socket, responseHeader, baos);
  56. // 请求URI地址
  57. String uri = request.getRequestURI();
  58. // 处理Http请求URL
  59. for (Class<? extends HttpServlet> clazz : servletList) {
  60. WebServlet webServlet = clazz.getAnnotation(WebServlet.class);
  61. String[] urlPatterns = webServlet.urlPatterns();
  62. for (String urlPattern : urlPatterns) {
  63. try {
  64. // 检测请求的URL地址和Servlet的地址是否匹配
  65. if (Pattern.compile(urlPattern).matcher(uri).find()) {
  66. // 修改状态码
  67. response.setStatus(200, "OK");
  68. // 创建Servlet类实例
  69. HttpServlet httpServlet = clazz.newInstance();
  70. // 调用Servlet请求处理方法
  71. httpServlet.service(request, response);
  72. break;
  73. }
  74. } catch (Exception e) {
  75. // 修改状态码
  76. response.setStatus(500, "Internal Server Error");
  77. e.printStackTrace();
  78. baos.write(("<pre>" + StringUtils.exceptionToString(e) + "</pre>").getBytes());
  79. }
  80. }
  81. }
  82. // 处理Http响应内容
  83. out.write(("HTTP/1.1 " + response.getStatus() + " " + response.getMessage() + "\n").getBytes());
  84. // 输出Web服务器信息
  85. out.write(("Server: " + serverName + "\n").getBytes());
  86. // 输出返回的消息类型
  87. out.write(("Content-Type: " + response.getContentType() + "\n").getBytes());
  88. // 输出返回字节数
  89. out.write(("Content-Length: " + baos.size() + "\n").getBytes());
  90. // 输出用户自定义的Header
  91. for (String key : responseHeader.keySet()) {
  92. out.write((key + ": " + responseHeader.get(key) + "\n").getBytes());
  93. }
  94. // 写入换行
  95. out.write("\n".getBytes());
  96. // 将读取到的数据写入到客户端Socket
  97. out.write(baos.toByteArray());
  98. in.close();
  99. out.close();
  100. } catch (Exception e) {
  101. LOG.info("处理客户端请求异常:" + e);
  102. } finally {
  103. socket.close();
  104. }
  105. }
  106. } catch (IOException e) {
  107. e.printStackTrace();
  108. }
  109. }
  110. }

Servlet功能测试

为了验证BinCat是否真的具备了Servlet处理能力,我们写两个测试用例:TestServletCMDServlet

TestServlet示例代码:

  1. package com.anbai.sec.server.test.servlet;
  2. import javax.servlet.annotation.WebServlet;
  3. import javax.servlet.http.HttpServlet;
  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpServletResponse;
  6. import java.io.IOException;
  7. import java.io.OutputStream;
  8. @WebServlet(name = "TestServlet", urlPatterns = "/TestServlet/")
  9. public class TestServlet extends HttpServlet {
  10. @Override
  11. public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  12. doPost(request, response);
  13. }
  14. @Override
  15. public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
  16. OutputStream out = response.getOutputStream();
  17. out.write(("Hello....<br/>Request Method:" + request.getMethod() + "<br/>Class:" + this.getClass()).getBytes());
  18. }
  19. }

浏览器请求http://localhost:8080/TestServlet/:

image-20200910201502285

CMDServlet示例代码:

  1. package com.anbai.sec.server.test.servlet;
  2. import org.javaweb.utils.IOUtils;
  3. import javax.servlet.annotation.WebServlet;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. import java.io.IOException;
  8. import java.io.OutputStream;
  9. @WebServlet(name = "CMDServlet", urlPatterns = "/CMD/")
  10. public class CMDServlet extends HttpServlet {
  11. @Override
  12. public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  13. doPost(request, response);
  14. }
  15. @Override
  16. public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
  17. String cmd = request.getParameter("cmd");
  18. byte[] bytes = IOUtils.toByteArray(Runtime.getRuntime().exec(cmd).getInputStream());
  19. OutputStream out = response.getOutputStream();
  20. out.write(bytes);
  21. out.flush();
  22. out.close();
  23. }
  24. }

浏览器请求http://localhost:8080/CMD/?cmd=whoami:

image-20200910201725672

使用curl发送POST请求:curl -i localhost:8080/CMD/ -d "cmd=pwd",服务器可以正常接收POST参数,处理结果如图:

image-20200910203406943

请求一个错误服务:

image-20200910203858328

至此,我们已经实现了一个非常初级的Servlet容器了。