<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    BinCat V3 - 實現Servlet3.x API

    BinCat V3 - 實現Servlet3.x API

    V1V2我們完成了一個簡單的文件訪問服務和請求參數解析服務,V3我們繼續添加Servlet API,從而理解Servlet的工作原理。

    添加Servlet3.x依賴:

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.0.1</version>
    </dependency>

    創建com.anbai.sec.server.servlet.BinCatRequest類并繼承javax.servlet.http.HttpServletRequest,然后需要實現HttpServletRequest接口方法,作為一個非標準的Servlet容器我們自然是沒必要嚴格的是實現里面的所有方法,選擇幾個方法實現一下就行了。

    注意:示例以下中省去了解析協議Servlet接口的代碼,完整代碼請參考:com.anbai.sec.server.servlet包下的完整實現代碼。

    HttpServletRequest實現

    BinCatRequest.java示例代碼片段:

    package com.anbai.sec.server.servlet;
    
    import org.javaweb.utils.StringUtils;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    import java.net.Socket;
    import java.net.URLDecoder;
    import java.security.Principal;
    import java.util.*;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.logging.Logger;
    
    /**
     * BinCat 請求解析實現對象,解析Http請求協議和參數
     */
    public class BinCatRequest implements HttpServletRequest {
    
        // 客戶端Socket連接對象
        private final Socket clientSocket;
    
        // Socket輸入流對象
        private final InputStream socketInputStream;
    
        // Http請求頭對象
        private Map<String, String> headerMap;
    
        // Http請求參數對象
        private Map<String, String[]> parameterMap;
    
        // Http請求attribute對象
        private final Map<String, Object> attributeMap = new ConcurrentHashMap<String, Object>();
    
        // Http請求Cookie對象
        private Cookie[] cookie;
    
        // Http請求Cookie對象
        private final Map<String, String> cookieMap = new ConcurrentHashMap<String, String>();
    
        // Http請求Session對象
        private final Map<String, BinCatSession> sessionMap = new ConcurrentHashMap<String, BinCatSession>();
    
        // Http請求方法類型
        private String requestMethod;
    
        // Http請求URL
        private String requestURL;
    
        // Http請求QueryString
        private String queryString;
    
        // Http請求協議版本信息
        private String httpVersion;
    
        // 是否已經解析過Http請求參數,防止多次解析請求參數
        private volatile boolean parsedParameter = false;
    
        // Http請求內容長度
        private int contentLength;
    
        // Http請求內容類型
        private String contentType;
    
        // 存儲Session的ID名稱
        private static final String SESSION_ID_NAME = "JSESSIONID";
    
        // Http請求主機名
        private String host;
    
        // Http請求主機端口
        private int port;
    
        private static final Logger LOG = Logger.getLogger("info");
    
        public BinCatRequest(Socket clientSocket) throws IOException {
            this.clientSocket = clientSocket;
            this.socketInputStream = clientSocket.getInputStream();
    
            // 解析Http協議
            parse();
        }
    
        /**
         * 解析Http請求協議,不解析Body部分
         *
         * @throws IOException
         */
        private void parse() throws IOException {
            // 此處省略Http請求協議解析、參數解析等內容...
        }
    
        /**
         * 解析Http請求參數
         *
         * @throws IOException Http協議解析異常
         */
        private synchronized void parseParameter() {
            // 此處省略Http請求協議解析、參數解析等內容...
        }
    
      // 此處省略HttpServletRequest接口中的大部分方法,僅保留幾個示例方法...
    
        public String getHeader(String name) {
            return this.headerMap.get(name);
        }
    
        public ServletInputStream getInputStream() throws IOException {
            return new ServletInputStream() {
                @Override
                public int read() throws IOException {
                    return socketInputStream.read();
                }
            };
        }
    
        public String getParameter(String name) {
            if (!parsedParameter) {
                this.parseParameter();
            }
    
            if (parameterMap.containsKey(name)) {
                return this.parameterMap.get(name)[0];
            }
    
            return null;
        }
    
        public String getRemoteAddr() {
            return clientSocket.getInetAddress().getHostAddress();
        }
    
        public void setAttribute(String name, Object o) {
            attributeMap.put(name, o);
        }
    
    }

    HttpServletResponse實現

    BinCatResponse.java示例代碼片段:

    package com.anbai.sec.server.servlet;
    
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletResponse;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.net.Socket;
    import java.net.URLEncoder;
    import java.util.*;
    
    public class BinCatResponse implements HttpServletResponse {
    
        private final Socket socket;
    
        private final Map<String, String> header;
    
        private final ByteArrayOutputStream out;
    
        private int status = 404;
    
        private String statusMessage = "Not Found";
    
        private String charset = "UTF-8";
    
        private int contentLength = 0;
    
        private String contentType = "text/html; charset=UTF-8";
    
        private String location;
    
        public BinCatResponse(Socket socket, Map<String, String> header, ByteArrayOutputStream out) {
            this.socket = socket;
            this.header = header;
            this.out = out;
        }
    
      // 此處省略HttpServletResponse接口中的大部分方法,僅保留幾個示例方法...
    
        public void setHeader(String name, String value) {
            this.header.put(name, value);
        }
    
        public String getHeader(String name) {
            return header.get(name);
        }
    
        public PrintWriter getWriter() throws IOException {
            return new PrintWriter(out);
        }
    
    }

    HttpSession實現

    BinCatSession.java示例代碼片段:

    package com.anbai.sec.server.servlet;
    
    import javax.servlet.ServletContext;
    import javax.servlet.http.HttpSession;
    import javax.servlet.http.HttpSessionContext;
    import java.util.Enumeration;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    /**
     * BinCat Session實現
     */
    public class BinCatSession implements HttpSession {
    
        private final String sessionID;
    
        // Http請求Session對象
        private final Map<String, Object> sessionMap = new ConcurrentHashMap<String, Object>();
    
        public BinCatSession(String sessionID) {
            this.sessionID = sessionID;
        }
    
      // 此處省略HttpSession接口中的大部分方法,僅保留幾個示例方法...
    
        public Object getAttribute(String name) {
            return this.sessionMap.get(name);
        }
    
        public void setAttribute(String name, Object value) {
            this.sessionMap.put(name, value);
        }
    
    }

    Servlet類注冊

    Servlet3.0支持web.xml和注解兩種方式配置,但不管是通過那種方式都需要知道Servlet的處理類和映射的URL地址,這里為了方法理解我將解析web.xml和掃描@WebServlet注解的步驟省略了,直接改成了手動配置一個Servlet映射類對象。

    注冊Servlet類對象代碼片段:

    // 初始化Servlet映射類對象
    final Set<Class<? extends HttpServlet>> servletList = new HashSet<Class<? extends HttpServlet>>();
    
    // 手動注冊Servlet類
    servletList.add(TestServlet.class);
    servletList.add(CMDServlet.class);

    當接收到瀏覽器請求時候我們需要根據請求的URL地址來動態調用Servlet類相關的代碼。

    調用Servlet類處理Http請求代碼片段:

    // 處理Http請求URL
    for (Class<? extends HttpServlet> clazz : servletList) {
        WebServlet webServlet  = clazz.getAnnotation(WebServlet.class);
        String[]   urlPatterns = webServlet.urlPatterns();
    
        for (String urlPattern : urlPatterns) {
            try {
              // 檢測請求的URL地址和Servlet的地址是否匹配
              if (Pattern.compile(urlPattern).matcher(uri).find()) {
                  // 修改狀態碼
                  response.setStatus(200, "OK");
    
                  // 創建Servlet類實例
                  HttpServlet httpServlet = clazz.newInstance();
    
                  // 調用Servlet請求處理方法
                  httpServlet.service(request, response);
                  break;
              }
            } catch (IOException e) {
               // 修改狀態碼
                       response.setStatus(500, "Internal Server Error");
                  e.printStackTrace();
            }
        }
    }

    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實現代碼:

    package com.anbai.sec.server;
    
    import com.anbai.sec.server.servlet.BinCatRequest;
    import com.anbai.sec.server.servlet.BinCatResponse;
    import com.anbai.sec.server.test.servlet.CMDServlet;
    import com.anbai.sec.server.test.servlet.TestServlet;
    import org.javaweb.utils.StringUtils;
    
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.logging.Logger;
    import java.util.regex.Pattern;
    
    /**
     * ServerSocket Http 服務器示例
     */
    public class BinCatServerV3 {
    
        private static final Logger LOG = Logger.getLogger("info");
    
        public static void main(String[] args) {
            try {
                // 設置服務監聽端口
                int port = 8080;
    
                // 設置服務名稱
                String serverName = "BinCat-0.0.3";
    
                // 創建ServerSocket,監聽本地端口
                ServerSocket ss = new ServerSocket(port);
    
                // 初始化Servlet映射類對象
                final Set<Class<? extends HttpServlet>> servletList = new HashSet<Class<? extends HttpServlet>>();
    
                // 手動注冊Servlet類
                servletList.add(TestServlet.class);
                servletList.add(CMDServlet.class);
    
                LOG.info(serverName + " 啟動成功,監聽端口: " + port);
    
                while (true) {
                    // 等待客戶端連接
                    Socket socket = ss.accept();
    
                    try {
                        // 獲取Socket輸入流對象
                        InputStream in = socket.getInputStream();
    
                        // 獲取Socket輸出流對象
                        OutputStream out = socket.getOutputStream();
    
                        // 創建BinCat請求處理對象
                        BinCatRequest request = new BinCatRequest(socket);
    
                        // 創建BinCat請求處理結果輸出流
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
                        // 創建BinCat請求處理結果Header對象
                        Map<String, String> responseHeader = new ConcurrentHashMap<String, String>();
    
                        // 創建BinCat響應處理對象
                        BinCatResponse response = new BinCatResponse(socket, responseHeader, baos);
    
                        // 請求URI地址
                        String uri = request.getRequestURI();
    
                        // 處理Http請求URL
                        for (Class<? extends HttpServlet> clazz : servletList) {
                            WebServlet webServlet  = clazz.getAnnotation(WebServlet.class);
                            String[]   urlPatterns = webServlet.urlPatterns();
    
                            for (String urlPattern : urlPatterns) {
                                try {
                                    // 檢測請求的URL地址和Servlet的地址是否匹配
                                    if (Pattern.compile(urlPattern).matcher(uri).find()) {
                                        // 修改狀態碼
                                        response.setStatus(200, "OK");
    
                                        // 創建Servlet類實例
                                        HttpServlet httpServlet = clazz.newInstance();
    
                                        // 調用Servlet請求處理方法
                                        httpServlet.service(request, response);
                                        break;
                                    }
                                } catch (Exception e) {
                                    // 修改狀態碼
                                    response.setStatus(500, "Internal Server Error");
                                    e.printStackTrace();
    
                                    baos.write(("<pre>" + StringUtils.exceptionToString(e) + "</pre>").getBytes());
                                }
                            }
                        }
    
                        // 處理Http響應內容
                        out.write(("HTTP/1.1 " + response.getStatus() + " " + response.getMessage() + "\n").getBytes());
                        // 輸出Web服務器信息
                        out.write(("Server: " + serverName + "\n").getBytes());
                        // 輸出返回的消息類型
                        out.write(("Content-Type: " + response.getContentType() + "\n").getBytes());
                        // 輸出返回字節數
                        out.write(("Content-Length: " + baos.size() + "\n").getBytes());
    
                        // 輸出用戶自定義的Header
                        for (String key : responseHeader.keySet()) {
                            out.write((key + ": " + responseHeader.get(key) + "\n").getBytes());
                        }
    
                        // 寫入換行
                        out.write("\n".getBytes());
                        // 將讀取到的數據寫入到客戶端Socket
                        out.write(baos.toByteArray());
    
                        in.close();
                        out.close();
                    } catch (Exception e) {
                        LOG.info("處理客戶端請求異常:" + e);
                    } finally {
                        socket.close();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }

    Servlet功能測試

    為了驗證BinCat是否真的具備了Servlet處理能力,我們寫兩個測試用例:TestServletCMDServlet

    TestServlet示例代碼:

    package com.anbai.sec.server.test.servlet;
    
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.OutputStream;
    
    @WebServlet(name = "TestServlet", urlPatterns = "/TestServlet/")
    public class TestServlet extends HttpServlet {
    
       @Override
       public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
          doPost(request, response);
       }
    
       @Override
       public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
          OutputStream out = response.getOutputStream();
          out.write(("Hello....<br/>Request Method:" + request.getMethod() + "<br/>Class:" + this.getClass()).getBytes());
       }
    
    }

    瀏覽器請求http://localhost:8080/TestServlet/:

    image-20200910201502285

    CMDServlet示例代碼:

    package com.anbai.sec.server.test.servlet;
    
    import org.javaweb.utils.IOUtils;
    
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.OutputStream;
    
    @WebServlet(name = "CMDServlet", urlPatterns = "/CMD/")
    public class CMDServlet extends HttpServlet {
    
       @Override
       public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
          doPost(request, response);
       }
    
       @Override
       public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
          String cmd   = request.getParameter("cmd");
          byte[] bytes = IOUtils.toByteArray(Runtime.getRuntime().exec(cmd).getInputStream());
    
          OutputStream out = response.getOutputStream();
          out.write(bytes);
          out.flush();
          out.close();
       }
    
    }

    瀏覽器請求http://localhost:8080/CMD/?cmd=whoami:

    image-20200910201725672

    使用curl發送POST請求:curl -i localhost:8080/CMD/ -d "cmd=pwd",服務器可以正常接收POST參數,處理結果如圖:

    image-20200910203406943

    請求一個錯誤服務:

    image-20200910203858328

    至此,我們已經實現了一個非常初級的Servlet容器了。

    本文章首發在 網安wangan.com 網站上。

    上一篇 下一篇
    討論數量: 0
    只看當前版本


    暫無話題~
    亚洲 欧美 自拍 唯美 另类