BinCat V3-实现Servlet3.x API

    添加Servlet3.x依赖:

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

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

    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. * 解析Http请求参数
    68. * @throws IOException Http协议解析异常
    69. */
    70. private synchronized void parseParameter() {
    71. // 此处省略Http请求协议解析、参数解析等内容...
    72. }
    73. // 此处省略HttpServletRequest接口中的大部分方法,仅保留几个示例方法...
    74. public String getHeader(String name) {
    75. return this.headerMap.get(name);
    76. }
    77. public ServletInputStream getInputStream() throws IOException {
    78. return new ServletInputStream() {
    79. @Override
    80. public int read() throws IOException {
    81. return socketInputStream.read();
    82. }
    83. };
    84. }
    85. public String getParameter(String name) {
    86. if (!parsedParameter) {
    87. this.parseParameter();
    88. }
    89. if (parameterMap.containsKey(name)) {
    90. return this.parameterMap.get(name)[0];
    91. }
    92. return null;
    93. }
    94. public String getRemoteAddr() {
    95. return clientSocket.getInetAddress().getHostAddress();
    96. }
    97. public void setAttribute(String name, Object o) {
    98. attributeMap.put(name, o);
    99. }
    100. }

    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. }

    BinCatSession.java示例代码片段:

    Servlet类注册

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

    1. // 初始化Servlet映射类对象
    2. final Set<Class<? extends HttpServlet>> servletList = new HashSet<Class<? extends HttpServlet>>();
    3. // 手动注册Servlet类
    4. 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. }

    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实现代码:

    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. }

    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. }

    浏览器请求:

    image-20200910201725672

    使用curl发送POST请求:,服务器可以正常接收POST参数,处理结果如图:

    请求一个错误服务:

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