一個簡單的Servlet容器實現

来源:http://www.cnblogs.com/chenpi/archive/2016/06/21/5603072.html
-Advertisement-
Play Games

上篇寫了一個簡單的Java web伺服器實現,只能處理一些靜態資源的請求,本篇文章實現的Servlet容器基於前面的伺服器做了個小改造,增加了Servlet請求的處理。 程式執行步驟 代碼實現: 添加依賴: 伺服器代碼: 常量類: Request: package ex02.pyrmont; imp ...


  上篇寫了一個簡單的Java web伺服器實現,只能處理一些靜態資源的請求,本篇文章實現的Servlet容器基於前面的伺服器做了個小改造,增加了Servlet請求的處理。

程式執行步驟

  1. 創建一個ServerSocket對象;
  2. 調用ServerSocket對象的accept方法,等待連接,連接成功會返回一個Socket對象,否則一直阻塞等待;
  3. Socket對象中獲取InputStream和OutputStream位元組流,這兩個流分別對應request請求和response響應;
  4. 處理請求:讀取InputStream位元組流信息,轉成字元串形式,並解析,這裡的解析比較簡單,僅僅獲取uri(統一資源標識符)信息;
  5. 處理響應(分兩種類型,靜態資源請求響應或servlet請求響應):如果是靜態資源請求,則根據解析出來的uri信息,從WEB_ROOT目錄中尋找請求的資源資源文件, 讀取資源文件,並將其寫入到OutputStream位元組流中;如果是Servlet請求,則首先生成一個URLClassLoader類載入器,載入請求的servlet類,創建servlet對象,執行service方法(往OutputStream寫入響應的數據);
  6. 關閉Socket對象;
  7. 轉到步驟2,繼續等待連接請求;

代碼實現:

添加依賴:

        <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.3</version>
        </dependency>

伺服器代碼:

package ex02.pyrmont.first;

import java.net.Socket;
import java.net.ServerSocket;
import java.net.InetAddress;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;

import ex02.pyrmont.Request;
import ex02.pyrmont.Response;
import ex02.pyrmont.StaticResourceProcessor;

public class HttpServer1 {

    // 關閉服務命令
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

    public static void main(String[] args) {
        HttpServer1 server = new HttpServer1();
        //等待連接請求
        server.await();
    }

    public void await() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
            //伺服器套接字對象
            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

        // 迴圈等待請求
        while (true) {
            Socket socket = null;
            InputStream input = null;
            OutputStream output = null;
            try {
                //等待連接,連接成功後,返回一個Socket對象
                socket = serverSocket.accept();
                input = socket.getInputStream();
                output = socket.getOutputStream();

                // 創建Request對象並解析
                Request request = new Request(input);
                request.parse();
                // 檢查是否是關閉服務命令
                if (request.getUri().equals(SHUTDOWN_COMMAND)) {
                    break;
                }
                
                // 創建 Response 對象
                Response response = new Response(output);
                response.setRequest(request);

                if (request.getUri().startsWith("/servlet/")) {
                    //請求uri以/servlet/開頭,表示servlet請求
                    ServletProcessor1 processor = new ServletProcessor1();
                    processor.process(request, response);
                } else {
                    //靜態資源請求
                    StaticResourceProcessor processor = new StaticResourceProcessor();
                    processor.process(request, response);
                }

                // 關閉 socket
                socket.close();

            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }
}

常量類:

package ex02.pyrmont;

import java.io.File;

public class Constants {
    public static final String WEB_ROOT = System.getProperty("user.dir")
            + File.separator + "webroot";
    public static final String WEB_SERVLET_ROOT = System.getProperty("user.dir")
            + File.separator + "target" + File.separator + "classes";
            
}

Request:

package ex02.pyrmont;

import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
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;

public class Request implements ServletRequest {

    private InputStream input;
    private String uri;

    public Request(InputStream input) {
        this.input = input;
    }

    public String getUri() {
        return uri;
    }
    /**
     * 
     * requestString形式如下:
     * GET /index.html HTTP/1.1
     * Host: localhost:8080
     * Connection: keep-alive
     * Cache-Control: max-age=0
     * ...
     * 該函數目的就是為了獲取/index.html字元串
     */
    private String parseUri(String requestString) {
        int index1, index2;
        index1 = requestString.indexOf(' ');
        if (index1 != -1) {
            index2 = requestString.indexOf(' ', index1 + 1);
            if (index2 > index1)
                return requestString.substring(index1 + 1, index2);
        }
        return null;
    }
    //從InputStream中讀取request信息,並從request中獲取uri值
    public void parse() {
        // Read a set of characters from the socket
        StringBuffer request = new StringBuffer(2048);
        int i;
        byte[] buffer = new byte[2048];
        try {
            i = input.read(buffer);
        } catch (IOException e) {
            e.printStackTrace();
            i = -1;
        }
        for (int j = 0; j < i; j++) {
            request.append((char) buffer[j]);
        }
        System.out.print(request.toString());
        uri = parseUri(request.toString());
    }

    /* implementation of the ServletRequest */
    public Object getAttribute(String attribute) {
        return null;
    }

    public Enumeration<?> getAttributeNames() {
        return null;
    }

    public String getRealPath(String path) {
        return null;
    }

    public RequestDispatcher getRequestDispatcher(String path) {
        return null;
    }

    public boolean isSecure() {
        return false;
    }

    public String getCharacterEncoding() {
        return null;
    }

    public int getContentLength() {
        return 0;
    }

    public String getContentType() {
        return null;
    }

    public ServletInputStream getInputStream() throws IOException {
        return null;
    }

    public Locale getLocale() {
        return null;
    }

    public Enumeration<?> getLocales() {
        return null;
    }

    public String getParameter(String name) {
        return null;
    }

    public Map<?, ?> getParameterMap() {
        return null;
    }

    public Enumeration<?> getParameterNames() {
        return null;
    }

    public String[] getParameterValues(String parameter) {
        return null;
    }

    public String getProtocol() {
        return null;
    }

    public BufferedReader getReader() throws IOException {
        return null;
    }

    public String getRemoteAddr() {
        return null;
    }

    public String getRemoteHost() {
        return null;
    }

    public String getScheme() {
        return null;
    }

    public String getServerName() {
        return null;
    }

    public int getServerPort() {
        return 0;
    }

    public void removeAttribute(String attribute) {
    }

    public void setAttribute(String key, Object value) {
    }

    public void setCharacterEncoding(String encoding)
            throws UnsupportedEncodingException {
    }

}
View Code

Response:

package ex02.pyrmont;

import java.io.OutputStream;
import java.io.IOException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.File;
import java.io.PrintWriter;
import java.util.Locale;
import javax.servlet.ServletResponse;
import javax.servlet.ServletOutputStream;

public class Response implements ServletResponse {

    private static final int BUFFER_SIZE = 1024;
    Request request;
    OutputStream output;
    PrintWriter writer;

    public Response(OutputStream output) {
        this.output = output;
    }

    public void setRequest(Request request) {
        this.request = request;
    }

    //將web文件寫入到OutputStream位元組流中
    public void sendStaticResource() throws IOException {
        byte[] bytes = new byte[BUFFER_SIZE];
        FileInputStream fis = null;
        try {
            /* request.getUri has been replaced by request.getRequestURI */
            File file = new File(Constants.WEB_ROOT, request.getUri());
            fis = new FileInputStream(file);
            /*
             * HTTP Response = Status-Line(( general-header | response-header |
             * entity-header ) CRLF) CRLF [ message-body ] Status-Line =
             * HTTP-Version SP Status-Code SP Reason-Phrase CRLF
             */
            int ch = fis.read(bytes, 0, BUFFER_SIZE);
            while (ch != -1) {
                output.write(bytes, 0, ch);
                ch = fis.read(bytes, 0, BUFFER_SIZE);
            }
        } catch (FileNotFoundException e) {
            String errorMessage = "HTTP/1.1 404 File Not Found\r\n"
                    + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n"
                    + "\r\n" + "<h1>File Not Found</h1>";
            output.write(errorMessage.getBytes());
        } finally {
            if (fis != null)
                fis.close();
        }
    }

    /** implementation of ServletResponse */
    public void flushBuffer() throws IOException {
    }

    public int getBufferSize() {
        return 0;
    }

    public String getCharacterEncoding() {
        return null;
    }

    public Locale getLocale() {
        return null;
    }

    public ServletOutputStream getOutputStream() throws IOException {
        return null;
    }

    public PrintWriter getWriter() throws IOException {
        // autoflush is true, println() will flush,
        // but print() will not.
        writer = new PrintWriter(output, true);
        return writer;
    }

    public boolean isCommitted() {
        return false;
    }

    public void reset() {
    }

    public void resetBuffer() {
    }

    public void setBufferSize(int size) {
    }

    public void setContentLength(int length) {
    }

    public void setContentType(String type) {
    }

    public void setLocale(Locale locale) {
    }
}
View Code

靜態資源請求處理:

package ex02.pyrmont;

import java.io.IOException;

public class StaticResourceProcessor {

    public void process(Request request, Response response) {
        try {
            response.sendStaticResource();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Servlet請求處理:

package ex02.pyrmont.first;

import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import ex02.pyrmont.Constants;
import ex02.pyrmont.Request;
import ex02.pyrmont.Response;

public class ServletProcessor1 {

    public void process(Request request, Response response) {

        String uri = request.getUri();
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        
        //類載入器,用於從指定JAR文件或目錄載入類
        URLClassLoader loader = null;
        try {
            URLStreamHandler streamHandler = null;
            //創建類載入器
            loader = new URLClassLoader(new URL[]{new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, streamHandler)});
        } catch (IOException e) {
            System.out.println(e.toString());
        }
        
        Class<?> myClass = null;
        try {
            //載入對應的servlet類
            myClass = loader.loadClass(servletName);
        } catch (ClassNotFoundException e) {
            System.out.println(e.toString());
        }

        Servlet servlet = null;

        try {
            //生產servlet實例
            servlet = (Servlet) myClass.newInstance();
            //執行ervlet的service方法
            servlet.service((ServletRequest) request,(ServletResponse) response);
        } catch (Exception e) {
            System.out.println(e.toString());
        } catch (Throwable e) {
            System.out.println(e.toString());
        }

    }
}

Servlet類:

import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;

public class PrimitiveServlet implements Servlet {

    public void init(ServletConfig config) throws ServletException {
        System.out.println("init");
    }

    public void service(ServletRequest request, ServletResponse response)
            throws ServletException, IOException {
        System.out.println("from service");
        PrintWriter out = response.getWriter();
        out.println("Hello. Roses are red.");
        out.print("Violets are blue.");
    }

    public void destroy() {
        System.out.println("destroy");
    }

    public String getServletInfo() {
        return null;
    }

    public ServletConfig getServletConfig() {
        return null;
    }

}

結果測試:

靜態資源請求:

servlet請求(因為只是第一個字元串被刷新到瀏覽器,所以你不能看到第二個字元串Violets are blue。我們將在後續完善該容器):

改進

  前面實現的Servlet容器有一個嚴重的問題,用戶在servlet里可以直接將ServletRequest、ServletResponse向下轉 型為Request和Response類型,並直接調用其內部的public方法,這是一個不好的設計,改進方法是給Request、Response 增加外觀類,這樣,用戶只能訪問外觀類里定義的public方法。

Request外觀類

package ex02.pyrmont.second;

import java.io.IOException;
import java.io.BufferedReader;
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;

import ex02.pyrmont.Request;

public class RequestFacade implements ServletRequest {

    private ServletRequest request = null;

    public RequestFacade(Request request) {
        this.request = request;
    }

    /* implementation of the ServletRequest */
    public Object getAttribute(String attribute) {
        return request.getAttribute(attribute);
    }

    public Enumeration<?> getAttributeNames() {
        return request.getAttributeNames();
    }

    @SuppressWarnings("deprecation")
    public String getRealPath(String path) {
        return request.getRealPath(path);
    }

    public RequestDispatcher getRequestDispatcher(String path) {
        return request.getRequestDispatcher(path);
    }

    public boolean isSecure() {
        return request.isSecure();
    }

    public String getCharacterEncoding() {
        return request.getCharacterEncoding();
    }

    public int getContentLength() {
        return request.getContentLength();
    }

    public String getContentType() {
        return request.getContentType();
    }

    public ServletInputStream getInputStream() throws IOException {
        return request.getInputStream();
    }

    public Locale getLocale() {
        return request.getLocale();
    }

    public Enumeration<?> getLocales() {
        return request.getLocales();
    }

    public String getParameter(String name) {
        return request.getParameter(name);
    }

    public Map<?, ?> getParameterMap() {
        return request.getParameterMap();
    }

    public Enumeration<?> getParameterNames() {
        return request.getParameterNames();
    }

    public String[] getParameterValues(String parameter) {
        return request.getParameterValues(parameter);
    }

    public String getProtocol() {
        return request.getProtocol();
    }

    public BufferedReader getReader() throws IOException {
        return request.getReader();
    }

    public String getRemoteAddr() {
        return request.getRemoteAddr();
    }

    public String getRemoteHost() {
        return request.getRemoteHost();
    }

    public String getScheme() {
        return request.getScheme();
    }

    public String getServerName() {
        return request.getServerName();
    }

    public int getServerPort() {
        return request.getServerPort();
    }

    public void removeAttribute(String attribute) {
        request.removeAttribute(attribute);
    }

    public void setAttribute(String key, Object value) {
        request.setAttribute(key, value);
    }

    public void setCharacterEncoding(String encoding)
            throws UnsupportedEncodingException {
        request.setCharacterEncoding(encoding);
    }

}
View Code

Response外觀類

package ex02.pyrmont.second;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;

import javax.servlet.ServletResponse;
import javax.servlet.ServletOutputStream;

import ex02.pyrmont.Response;

public class ResponseFacade implements ServletResponse {

    private ServletResponse response;

    public ResponseFacade(Response response) {
        this.response = response;
    }

    public void flushBuffer() throws IOException {
        response.flushBuffer();
    }

    public int getBufferSize() {
        return response.getBufferSize();
    }

    public String getCharacterEncoding() {
        return response.getCharacterEncoding();
    }

    public Locale getLocale() {
        return response.getLocale();
    }

    public ServletOutputStream getOutputStream() throws IOException {
        return response.getOutputStream();
    }

    public PrintWriter getWriter() throws IOException {
        return response.getWriter();
    }

    public boolean isCommitted() {
        return response.isCommitted();
    }

    public void reset() {
        response.reset();
    }

    public void resetBuffer() {
        response.resetBuffer();
    }

    public void setBufferSize(int size) {
        response.setBufferSize(size);
    }

    public void setContentLength(int length) {
        response.setContentLength(length);
    }

    public void setContentType(String type) {
        response.setContentType(type);
    }

    public void setLocale(Locale locale) {
        response.setLocale(locale);
    }

}
View Code

處理Servlet請求類:

package ex02.pyrmont.second;

import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;
import java.io.IOException;

import javax.servlet.Servlet;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import ex02.pyrmont.Constants;
import ex02.pyrmont.Request;
import ex02.pyrmont.Response;

public class ServletProcessor2 {

    public void process(Request request, Response response) {

        String uri = request.getUri();
        String servletName = uri.substring(uri.lastIndexOf("/") + 1);
        // 類載入器,用於從指定JAR文件或目錄載入類
        URLClassLoader loader = null;
        try {
            URLStreamHandler streamHandler = null;
            // 創建類載入器
            loader = new URLClassLoader(new URL[] { new URL(null, "file:"
                    + Constants.WEB_SERVLET_ROOT, streamHandler) });
        } catch (IOException e) {
            System.out.println(e.toString());
        }

        Class<?> myClass = null;
        try {
            // 載入對應的servlet類
            myClass = loader.loadClass(servletName);
        } catch (ClassNotFoundException e) {
            System.out.println(e.toString());
        }

        Servlet servlet = null;
        //給request、response增加外觀類,安全性考慮,防止用戶在servlet里直接將ServletRequest、ServletResponse向下轉型為Request和Response類型,
        //並直接調用其內部的public方法,因為RequestFacade、ResponseFacade里不會有parse、sendStaticResource等方法;
        RequestFacade requestFacade = new RequestFacade(request);
        ResponseFacade responseFacade = new ResponseFacade(response);
        try {
            servlet = (Servlet) myClass.newInstance();
            servlet.service((ServletRequest) requestFacade, (ServletResponse) responseFacade);
        } catch (Exception e) {
            System.out.println(e.toString());
        } catch (Throwable e) {
            System.out.println(e.toString());
        }

    }
}
View Code

其它代碼與前面實現的Servlet容器基本一致。

驗證程式,分別請求靜態資源和Servlet,發現結果與前面實現的容器一致;

 

 參考資料:《深入剖析Tomcat》


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 另外,有時候可能需要轉換成字典處理,則list(set(['a','b'])) ...
  • Java只支持單繼承,但是我們在應用中需要類的多繼承,由此Java中就提供了實現介面的方法來實現多繼承。 介面的本質——介面是一種特殊的抽象類,這種抽象類裡面只包含常量和方法的定義,而沒有變數和方法的實現。 介面總結:介面和介面之間可以相互繼承,類和類之間可以相互繼承,類和介面之間,只能是類來實現接 ...
  • 課本源碼部分 第9章 查找 - B樹 ——《數據結構》-嚴蔚敏.吳偉民版 源碼使用說明 鏈接☛☛☛ 《數據結構-C語言版》(嚴蔚敏,吳偉民版)課本源碼+習題集解析使用說明 課本源碼合輯 鏈接☛☛☛ 《數據結構》課本源碼合輯 習題集全解析 鏈接☛☛☛ 《數據結構題集》習題解析合輯 本源碼引入的文件 鏈 ...
  • http://jingyan.baidu.com/article/f3e34a128551b7f5ea653544.html bin目錄主要是用來存放tomcat的命令,主要有兩大類,一類是以.sh結尾的(linux命令),另一類是以.bat結尾的(windows命令)。 logs目錄用來存放tom ...
  • 課程簡介 * 正則表達式是軟體開發必須掌握的一門語言,掌握後才能很好地理解到它的威力; * 課程採用概念和實驗操作 4/6 分隔,幫助大家理解概念後再使用大量的實例加深對概念的理解; * 實例操作是對概念最好的理解,也是學習新語言最有效的辦法; * 在課程中也穿插著大量軟體開發的技巧和大家分享; *... ...
  • PHP分頁代碼在各種程式開發中都是必須要用到的,在網站開發中更是必選的一項。要想寫出分頁代碼,首先你要理解SQL查詢語句:select * from goods limit 2,7。PHP分頁代碼核心就是圍繞這條語句展開的,SQL語句說明:查詢goods數據表從第2條數據開始取出7條數據。在分頁代碼 ...
  • 1. 從Python官網到獲取Python3的包, 切換到目錄/usr/local/src 2. 使用命令如下命令進行解壓縮: 3. 在/usr/local路徑下創建目錄--python3.5, 為第4步的安裝目錄 4. 編譯安裝 5. 進入安裝的絕對路徑,檢查是否安裝成功 6.查看環境變數,啟動p ...
  • 1.PHP中可以靜態調用非靜態方法麽? 今天我被問到PHP中可不可以使用 className::methodName() 的方法來調用一個沒有聲明Static的方法。在我的印象中,我好像是見過這種用法,但又有些不確定。大家都知道,在手冊或者教程里,方法被分為靜態方法和非靜態方法,通常我們靜態調用的方 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...