:構(gòu)建高性能HTTP服務(wù)端與POST請求處理指南)
簡介本資源是一套基于Delphi 12.3開發(fā)的HTTP服務(wù)端與POST請求處理的完整示例工程面向Delphi中高級開發(fā)者適用于構(gòu)建輕量級本地API服務(wù)、嵌入式Web接口或IoT設(shè)備通信模塊等場景。壓縮包共含16個文件涵蓋可執(zhí)行程序2個exe、核心單元源碼pas/ddp/dfm、編譯產(chǎn)物dcu、項目配置cfg/dof/dpr、資源文件res及XML配置文檔全面覆蓋從設(shè)計、編碼、編譯到運行的全鏈路開發(fā)要素。資源大小為37.23MB結(jié)構(gòu)清晰便于快速理解TIdHTTPServer組件在Delphi 12中的實際集成方式與POST數(shù)據(jù)解析邏輯。已有45人學(xué)習(xí)下載讀者可直接運行示例程序觀察HTTP服務(wù)啟動、路由響應(yīng)及文件接收行為并通過源碼深入掌握請求體解析、JSON/Form數(shù)據(jù)提取、異常處理及跨版本兼容性適配等關(guān)鍵實踐細節(jié)。1. 項目背景與核心價值最近在整理一個遺留的Delphi項目需要實現(xiàn)一個輕量級的HTTP服務(wù)端用來接收一些設(shè)備上報的數(shù)據(jù)。一開始想著用現(xiàn)成的框架但考慮到部署環(huán)境的復(fù)雜性和對性能的極致要求最終還是決定回歸Delphi用自帶的THttpServer控件來搭建。這個決定讓我重新審視了Delphi 12.3在Web服務(wù)開發(fā)上的能力特別是處理HTTP POST請求這個看似基礎(chǔ)、實則暗藏玄機的環(huán)節(jié)。網(wǎng)上關(guān)于Delphi HttpServer的資料要么是遠古版本的要么語焉不詳尤其是處理表單數(shù)據(jù)、JSON POST這些現(xiàn)代Web交互時總感覺缺了臨門一腳的示例。所以我把這次實戰(zhàn)中搭建的HttpServer服務(wù)端以及配套的、用于測試各種POST請求的客戶端源代碼打包成了一個資源包。這個包的核心價值在于它不是一個簡單的“Hello World”演示而是一個可直接用于生產(chǎn)環(huán)境參考的、處理了多種POST數(shù)據(jù)格式的完整范例。無論是處理application/x-www-form-urlencoded表單、multipart/form-data文件上傳還是解析application/json的RESTful API請求你都能在里面找到清晰的實現(xiàn)路徑和避坑指南。對于還在使用Delphi進行工業(yè)控制、數(shù)據(jù)采集、內(nèi)部工具開發(fā)的同行來說這能省去大量重復(fù)造輪子和調(diào)試協(xié)議的時間。2. Delphi 12.3 THttpServer控件深度解析Delphi的THttpServer控件位于System.Net.HttpServer單元它是一個基于事件驅(qū)動的、阻塞式的HTTP/1.1服務(wù)器實現(xiàn)。很多人一聽到“阻塞式”就覺得性能不行這其實是個誤解。在連接數(shù)可控、追求極致穩(wěn)定和低延遲的工業(yè)或內(nèi)部應(yīng)用場景中這種簡單的模型反而更可靠資源消耗也更可預(yù)測。2.1 核心架構(gòu)與工作模式THttpServer的核心是OnCommandGet和OnCommandOther事件。在早期版本通常用OnCommandGet處理GET請求用OnCommandOther處理POST等其他方法。但在Delphi 12.3中更推薦的做法是主要使用OnCommandOther事件并在其內(nèi)部根據(jù)ARequest.Method方法來區(qū)分GET、POST等所有HTTP動詞。這樣做的好處是邏輯統(tǒng)一便于管理。它的工作線程模型是每個接入的客戶端連接服務(wù)器會為其分配一個獨立的工作線程。該線程會一直阻塞直到處理完當前請求并返回響應(yīng)或者連接超時。這意味著如果你的某個請求處理函數(shù)非常耗時它會獨占一個線程導(dǎo)致該線程無法處理其他連接。因此關(guān)鍵的設(shè)計原則是在OnCommandOther事件處理函數(shù)中邏輯應(yīng)盡可能快速避免長時間操作。對于耗時的任務(wù)如復(fù)雜的數(shù)據(jù)庫查詢、文件處理應(yīng)該將其放入隊列由后臺線程異步處理然后通過回調(diào)或狀態(tài)查詢的方式返回結(jié)果。2.2 關(guān)鍵屬性與實戰(zhàn)配置創(chuàng)建一個穩(wěn)定可用的HttpServer以下屬性的配置至關(guān)重要// 創(chuàng)建服務(wù)器實例 FHttpServer : THttpServer.Create(nil); try with FHttpServer do begin // 綁定地址和端口。0.0.0.0表示監(jiān)聽所有網(wǎng)絡(luò)接口 Bindings.Add(0.0.0.0, 8080); // 服務(wù)器名稱會出現(xiàn)在HTTP響應(yīng)頭Server字段中可自定義或留空 ServerName : MyDelphiServer/1.0; // 連接超時時間毫秒??蛻舳嗽诖藭r間內(nèi)未發(fā)送請求連接將被關(guān)閉。 // 對于設(shè)備上報場景不宜設(shè)置過短建議30-60秒。 ConnectionTimeout : 30000; // 請求體最大尺寸字節(jié)。這是防止惡意超大請求導(dǎo)致內(nèi)存溢出的關(guān)鍵 // 必須根據(jù)業(yè)務(wù)需求明確設(shè)置。例如文件上傳設(shè)為50MB50 * 1024 * 1024 MaxRequestBodySize : 52428800; // 啟用線程池管理推薦。True時服務(wù)器會復(fù)用已完成任務(wù)的線程提升性能。 UseThreadPool : True; // 線程池最小和最大線程數(shù)。需要根據(jù)預(yù)期的并發(fā)量調(diào)整。 // 初始值可以設(shè)為CPU核心數(shù)最大值為預(yù)期最大并發(fā)數(shù)但不宜過高。 ThreadPoolSize : TThreadPoolSize.Create(4, 50); // 注冊請求處理事件 OnCommandOther : HandleCommandOther; // 也可以選擇性處理異常請求 OnException : HandleServerException; Active : True; // 啟動服務(wù)器 end; except on E: Exception do Log(服務(wù)器啟動失敗: E.Message); end;這里最容易踩坑的就是MaxRequestBodySize。如果不設(shè)置默認值可能很小一旦遇到上傳文件的POST請求服務(wù)器會直接返回413 Request Entity Too Large錯誤。我一開始就忘了設(shè)調(diào)試了半天才發(fā)現(xiàn)問題根源。3. 全面攻克HTTP POST請求處理處理POST請求的難點不在于接收而在于如何正確、高效地解析出請求體Body中的數(shù)據(jù)。HTTP協(xié)議本身只負責傳輸字節(jié)流如何解讀這些字節(jié)流取決于Content-Type請求頭。我們的服務(wù)器必須能識別并處理常見的幾種類型。3.1 解析 application/x-www-form-urlencoded這是HTML表單默認的提交格式數(shù)據(jù)格式為key1value1key2value2并且進行了URL編碼。在THttpServer的事件參數(shù)中我們可以直接獲取到解析好的內(nèi)容。procedure TMainForm.HandleCommandOther(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var ParamValue: string; i: Integer; begin if ARequestInfo.Command POST then begin // 首先檢查Content-Type if Pos(application/x-www-form-urlencoded, ARequestInfo.ContentType) 0 then begin // 方式1通過Params屬性直接獲取已自動解析 ParamValue : ARequestInfo.Params.Values[username]; if ParamValue then Log(收到用戶名Params: ParamValue); // 方式2遍歷所有參數(shù) Log(所有表單參數(shù):); for i : 0 to ARequestInfo.Params.Count - 1 do Log( ARequestInfo.Params.Names[i] ARequestInfo.Params.ValueFromIndex[i]); // 構(gòu)建響應(yīng) AResponseInfo.ContentText : {status: ok, message: Form data received.}; AResponseInfo.ContentType : application/json; AResponseInfo.ResponseNo : 200; // OK end; end; end;注意TIdHTTPRequestInfo.Params屬性在application/x-www-form-urlencoded類型下會自動解析。但如果你同時處理GET和POST需要注意Params也會包含URL查詢字符串?后的參數(shù)。在純POST表單處理時這通常不是問題。3.2 解析 application/json (RESTful API)這是目前前后端交互和物聯(lián)網(wǎng)設(shè)備上報最常用的格式。THttpServer不會自動解析JSON我們需要從原始請求流中讀取。procedure TMainForm.HandleCommandOther(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var ContentStream: TStream; Bytes: TBytes; RawJson, DeviceID, SensorValue: string; JsonObj: TJSONObject; begin if (ARequestInfo.Command POST) and (Pos(application/json, ARequestInfo.ContentType) 0) then begin ContentStream : ARequestInfo.PostStream; if Assigned(ContentStream) then begin // 將流內(nèi)容讀取到字符串 SetLength(Bytes, ContentStream.Size); ContentStream.Position : 0; ContentStream.ReadBuffer(Bytes[0], ContentStream.Size); RawJson : TEncoding.UTF8.GetString(Bytes); try JsonObj : TJSONObject.ParseJSONValue(RawJson) as TJSONObject; if Assigned(JsonObj) then try DeviceID : JsonObj.GetValue(device_id).Value; SensorValue : JsonObj.GetValue(value).Value; Log(Format(設(shè)備%s上報數(shù)據(jù): %s, [DeviceID, SensorValue])); // 處理業(yè)務(wù)邏輯... // 返回JSON響應(yīng) AResponseInfo.ContentText : {code: 0, msg: success}; AResponseInfo.ContentType : application/json; AResponseInfo.ResponseNo : 200; finally JsonObj.Free; end else begin AResponseInfo.ContentText : {code: -1, msg: Invalid JSON}; AResponseInfo.ResponseNo : 400; // Bad Request end; except on E: Exception do begin Log(JSON解析異常: E.Message); AResponseInfo.ContentText : {code: -2, msg: Server error}; AResponseInfo.ResponseNo : 500; end; end; end; end; end;關(guān)鍵點流的位置PostStream在讀取前務(wù)必將其Position設(shè)置為0否則可能讀不到數(shù)據(jù)。編碼確保使用正確的編碼通常是UTF-8將字節(jié)流轉(zhuǎn)換為字符串。TEncoding.UTF8是最安全的選擇。異常處理JSON解析必須放在try...except塊中??蛻舳丝赡馨l(fā)送非法JSON字符串導(dǎo)致解析崩潰進而使整個工作線程異常退出這是服務(wù)器不穩(wěn)定的重要原因。內(nèi)存釋放TJSONObject等對象必須記得釋放否則會造成內(nèi)存泄漏。3.3 處理 multipart/form-data (文件上傳)文件上傳是最復(fù)雜的POST類型。數(shù)據(jù)被分成多個部分Part每個部分有自己的頭和體。Delphi的TIdHTTPRequestInfo提供了Files和Params屬性來簡化處理但需要正確配置。procedure TMainForm.HandleCommandOther(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); var i: Integer; UploadedFile: TIdHTTPUploadFile; SavePath, FieldName, FieldValue: string; SaveStream: TFileStream; begin if (ARequestInfo.Command POST) and (Pos(multipart/form-data, ARequestInfo.ContentType) 0) then begin // 1. 處理上傳的文件 Log(上傳文件列表:); for i : 0 to ARequestInfo.Files.Count - 1 do begin UploadedFile : ARequestInfo.Files[i]; Log(Format( 文件名: %s, 字段名: %s, 內(nèi)容類型: %s, 大小: %d bytes, [UploadedFile.FileName, UploadedFile.FieldName, UploadedFile.ContentType, UploadedFile.Stream.Size])); // 構(gòu)建保存路徑注意安全應(yīng)避免使用原始文件名防止路徑遍歷攻擊 SavePath : .\Uploads\ TPath.GetGUIDFileName(False) ExtractFileExt(UploadedFile.FileName); // 保存文件流 ForceDirectories(ExtractFilePath(SavePath)); SaveStream : TFileStream.Create(SavePath, fmCreate); try UploadedFile.Stream.Position : 0; SaveStream.CopyFrom(UploadedFile.Stream, UploadedFile.Stream.Size); finally SaveStream.Free; end; Log(文件已保存至: SavePath); end; // 2. 處理普通的表單字段 Log(普通表單字段:); for i : 0 to ARequestInfo.Params.Count - 1 do begin FieldName : ARequestInfo.Params.Names[i]; FieldValue : ARequestInfo.Params.ValueFromIndex[i]; // 注意這里獲取的Params可能也包含了文件字段的“文件名”信息但Files列表里的才是文件內(nèi)容。 // 通常我們以Files列表為準。 if ARequestInfo.Files.FindByField(FieldName) nil then // 如果不是文件字段 Log( FieldName FieldValue); end; AResponseInfo.ContentText : {status: success, files_received: IntToStr(ARequestInfo.Files.Count) }; AResponseInfo.ContentType : application/json; AResponseInfo.ResponseNo : 200; end; end;文件上傳的深坑與解決方案內(nèi)存占用大文件上傳會完全載入內(nèi)存。務(wù)必設(shè)置合理的MaxRequestBodySize并在生產(chǎn)環(huán)境考慮流式處理到磁盤的方案但這需要更底層的套接字操作。文件名安全切勿直接使用客戶端提交的UploadedFile.FileName作為保存路徑這可能導(dǎo)致路徑遍歷攻擊如文件名包含..\windows\system32\cmd.exe。應(yīng)使用GUID或時間戳生成新文件名僅保留原擴展名。臨時文件清理THttpServer或底層庫可能會生成臨時文件需要關(guān)注服務(wù)器運行期間的磁盤空間。4. 配套POST測試客戶端設(shè)計與源碼解讀一個健壯的服務(wù)器離不開全面的測試。我配套編寫的測試客戶端不是一個簡單的按鈕點擊而是一個支持多種POST格式、可自定義Header、能保存請求歷史的工具這對于調(diào)試API接口至關(guān)重要。4.1 客戶端核心功能模塊客戶端使用TIdHTTP組件發(fā)送請求主要包含以下功能URL與方法選擇支持輸入任意URL選擇GET或POST方法。請求體編輯器一個多行文本框用于直接輸入JSON、XML或表單格式的字符串。Content-Type選擇下拉框快速選擇application/json、application/x-www-form-urlencoded、text/plain等。自定義請求頭支持添加、編輯、刪除額外的HTTP Header例如Authorization: Bearer token。文件上傳集成multipart/form-data文件選擇與上傳功能。響應(yīng)展示清晰顯示HTTP狀態(tài)碼、響應(yīng)頭和響應(yīng)體支持JSON格式化高亮。歷史記錄將每次成功的請求配置URL、方法、頭、體保存下來方便重復(fù)測試。4.2 發(fā)送JSON POST請求的代碼示例procedure TTestClientForm.btnSendJSONClick(Sender: TObject); var IdHTTP: TIdHTTP; RequestStream: TStringStream; ResponseBody: string; begin IdHTTP : TIdHTTP.Create(nil); RequestStream : TStringStream.Create(mmoRequestBody.Text, TEncoding.UTF8); try // 配置HTTP客戶端 IdHTTP.Request.ContentType : application/json; charsetutf-8; IdHTTP.Request.CharSet : utf-8; // 添加自定義頭部 IdHTTP.Request.CustomHeaders.AddValue(X-Api-Key, your-api-key-here); // 發(fā)送POST請求 try ResponseBody : IdHTTP.Post(edtURL.Text, RequestStream); // 顯示結(jié)果 mmoResponseBody.Text : FormatJSON(ResponseBody); // 假設(shè)有JSON格式化函數(shù) lblStatusCode.Caption : 狀態(tài)碼: IntToStr(IdHTTP.ResponseCode); LogRequestToHistory(edtURL.Text, POST, mmoRequestBody.Text, IdHTTP.Request.ContentType); except on E: EIdHTTPProtocolException do begin // 處理HTTP協(xié)議錯誤如404, 500 mmoResponseBody.Text : E.ErrorMessage; lblStatusCode.Caption : 狀態(tài)碼: IntToStr(E.ErrorCode); end; on E: Exception do begin // 處理網(wǎng)絡(luò)等其他錯誤 mmoResponseBody.Text : 請求失敗: E.Message; end; end; finally RequestStream.Free; IdHTTP.Free; end; end;客戶端開發(fā)心得編碼一致性確保TStringStream和TIdHTTP.Request.CharSet使用相同的編碼強烈建議UTF-8否則中文等非ASCII字符會亂碼。異常細分EIdHTTPProtocolException是服務(wù)器返回錯誤狀態(tài)碼400時拋出的異常其ErrorCode和ErrorMessage包含了服務(wù)器的響應(yīng)信息這對于調(diào)試API錯誤非常有用。網(wǎng)絡(luò)超時、連接拒絕等則是其他異常類型。資源釋放TIdHTTP和TStream對象必須放在try...finally塊中確保釋放特別是在頻繁發(fā)送請求的客戶端程序中。4.3 模擬文件上傳請求procedure TTestClientForm.btnUploadFileClick(Sender: TObject); var IdHTTP: TIdHTTP; PostData: TIdMultiPartFormDataStream; ResponseBody: string; begin if not OpenDialog1.Execute then Exit; IdHTTP : TIdHTTP.Create(nil); PostData : TIdMultiPartFormDataStream.Create; try // 添加文件字段 PostData.AddFile(file, OpenDialog1.FileName, application/octet-stream); // 字段名文件路徑MIME類型 // 添加普通文本字段 PostData.AddFormField(description, 這是一個測試上傳的文件); // TIdMultiPartFormDataStream會自動設(shè)置正確的Content-Type ResponseBody : IdHTTP.Post(edtURL.Text, PostData); mmoResponseBody.Text : ResponseBody; lblStatusCode.Caption : 狀態(tài)碼: IntToStr(IdHTTP.ResponseCode); finally PostData.Free; IdHTTP.Free; end; end;使用TIdMultiPartFormDataStream可以極大地簡化文件上傳客戶端的編碼工作它內(nèi)部會處理好邊界生成和格式組裝。5. 生產(chǎn)環(huán)境部署與性能調(diào)優(yōu)實戰(zhàn)將Demo代碼轉(zhuǎn)化為一個7x24小時穩(wěn)定運行的生產(chǎn)服務(wù)還需要考慮很多工程化細節(jié)。5.1 日志記錄與問題排查沒有日志的服務(wù)器等于瞎子。一個基本的日志系統(tǒng)應(yīng)該記錄訪問日志客戶端IP、請求時間、方法、URL、狀態(tài)碼、處理時長。錯誤日志異常堆棧信息、非法請求內(nèi)容。業(yè)務(wù)日志關(guān)鍵業(yè)務(wù)操作如設(shè)備數(shù)據(jù)接收成功。我通常使用一個線程安全的日志隊列工作線程將日志寫入隊列由一個獨立的日志線程負責將其寫入文件或數(shù)據(jù)庫避免磁盤IO阻塞請求處理。// 簡化的線程安全日志示例 procedure LogMessage(const Msg: string); begin TThread.Queue(nil, procedure begin // 在主線程或日志線程中安全地寫入Memo或文件 mmoLog.Lines.Add(FormatDateTime(yyyy-mm-dd hh:nn:ss.zzz, Now) - Msg); // 同時寫入文件 WriteToFile(Msg); end); end; // 在請求處理中記錄 LogMessage(Format([%s] %s %s - %dms, [ARequestInfo.RemoteIP, ARequestInfo.Command, ARequestInfo.URI, ProcessingTime]));5.2 連接管理與資源釋放THttpServer在長時間運行后可能會出現(xiàn)內(nèi)存緩慢增長或句柄泄漏。需要關(guān)注全局對象管理確保在OnCommandOther事件中創(chuàng)建的臨時對象如TJSONObject,TMemoryStream都被正確釋放。連接狀態(tài)監(jiān)控可以定期輸出THttpServer的ActiveConnections等屬性監(jiān)控連接數(shù)是否在正常范圍。優(yōu)雅關(guān)閉在程序退出時先將Active設(shè)為False等待一段時間讓現(xiàn)有請求處理完畢再銷毀THttpServer對象。5.3 性能調(diào)優(yōu)要點線程池配置ThreadPoolSize的Max值不是越大越好。設(shè)置過大在突發(fā)高并發(fā)時可能創(chuàng)建大量線程導(dǎo)致系統(tǒng)資源緊張和頻繁的上下文切換反而降低性能。需要通過壓力測試找到應(yīng)用的“甜蜜點”。請求處理耗時用GetTickCount或TStopwatch記錄每個請求在事件處理函數(shù)中的耗時。如果發(fā)現(xiàn)某些請求特別慢就要考慮優(yōu)化業(yè)務(wù)邏輯或?qū)⑵洚惒交?。?nèi)存池對于頻繁創(chuàng)建釋放的小對象如字符串列表、小的內(nèi)存流可以考慮使用對象池來減少內(nèi)存分配開銷。使用更高效的JSON庫如果JSON解析是性能瓶頸可以考慮評估System.JSON以外的第三方庫如dsjson在某些場景下可能有更好的性能。6. 常見陷阱與終極調(diào)試技巧即使代碼看起來完美在實際網(wǎng)絡(luò)環(huán)境中還是會遇到各種古怪問題。以下是我踩過的一些坑和解決方法。6.1 請求體為空或讀取不完整現(xiàn)象ARequestInfo.PostStream為nil或Size為0但客戶端明確發(fā)送了數(shù)據(jù)。排查首先檢查客戶端發(fā)送的Content-Length頭是否正確。如果這個頭缺失或值為0THttpServer可能不會創(chuàng)建PostStream。檢查服務(wù)器端的MaxRequestBodySize是否設(shè)置得過小導(dǎo)致請求被截斷。在OnCommandOther事件最開始記錄下ARequestInfo.RawHTTPCommand和所有請求頭與客戶端發(fā)送的原始數(shù)據(jù)對比看是否一致。6.2 中文亂碼問題現(xiàn)象接收到的JSON或表單數(shù)據(jù)中的中文變成問號或亂碼。解決服務(wù)器端在解析字符串時始終明確指定編碼為UTF-8。不要依賴系統(tǒng)默認編碼。// 正確做法 RawString : TEncoding.UTF8.GetString(Bytes); // 錯誤做法 RawString : TStringStream(ContentStream).DataString; // 可能使用默認ANSI編碼客戶端確保TIdHTTP的Request.CharSet和Request.ContentType中聲明的編碼一致且都是utf-8。發(fā)送的TStringStream也要用TEncoding.UTF8創(chuàng)建。6.3 跨域請求 (CORS) 支持現(xiàn)象網(wǎng)頁通過JavaScript調(diào)用你的HttpServer API時瀏覽器報跨域錯誤。解決需要在服務(wù)器的響應(yīng)中添加CORS頭部。// 在返回響應(yīng)前添加以下頭部 AResponseInfo.CustomHeaders.AddValue(Access-Control-Allow-Origin, *); // 生產(chǎn)環(huán)境應(yīng)指定具體域名 AResponseInfo.CustomHeaders.AddValue(Access-Control-Allow-Methods, GET, POST, OPTIONS); AResponseInfo.CustomHeaders.AddValue(Access-Control-Allow-Headers, Content-Type, Authorization); // 對于OPTIONS預(yù)檢請求直接返回200 if ARequestInfo.Command OPTIONS then begin AResponseInfo.ResponseNo : 200; AResponseInfo.ContentText : ; Exit; end;6.4 使用網(wǎng)絡(luò)抓包工具進行終極調(diào)試當邏輯排查無法解決問題時必須祭出抓包工具。我推薦使用Wireshark或Fiddler。Fiddler配置為系統(tǒng)代理直接捕獲客戶端發(fā)出的HTTP/HTTPS請求原始報文。你可以清晰地看到請求頭、請求體的每一個字節(jié)與服務(wù)器端日志記錄的內(nèi)容進行逐字節(jié)對比。這是排查編碼、格式錯誤最直接的方法。Wireshark在更底層抓取網(wǎng)絡(luò)包。如果懷疑是網(wǎng)絡(luò)問題如粘包、拆包或想分析TCP連接狀態(tài)Wireshark是唯一選擇。調(diào)試步驟在客戶端發(fā)起一個錯誤請求的同時用抓包工具捕獲。對比工具中看到的“真實發(fā)送的數(shù)據(jù)”和你服務(wù)器代碼中ARequestInfo解析出來的數(shù)據(jù)。十有八九問題就出在客戶端組裝請求或服務(wù)器解析請求的某個細微環(huán)節(jié)上。通過這次從零搭建Delphi HttpServer并完善其POST處理能力的全過程我再次體會到看似古老的技術(shù)棧在特定的、追求可控和穩(wěn)定的場景下依然有著不可替代的價值。關(guān)鍵在于深入理解其原理細致地處理邊界情況并配以完善的工具鏈進行測試和監(jiān)控。希望這個資源包和其中的經(jīng)驗?zāi)軒椭阍谙乱粋€Delphi網(wǎng)絡(luò)服務(wù)項目中少走彎路。本文還有配套的精品資源點擊獲取