淺從System.Web.Http.Owin的HttpMessageHandlerAdapter看適配器模式

来源:http://www.cnblogs.com/tdws/archive/2017/06/28/7087831.html
-Advertisement-
Play Games

本文版權歸博客園和作者吳雙本人共同所有 轉載和爬蟲請註明原文地址 www.cnblogs.com/tdws 一.寫在前面 適配器模式(Adapter) 可用來在現有介面和不相容的類之間進行適配。有助於避免大規模改寫現有客戶代碼,其工作機制是對現有類的介面進行包裝,這樣客戶程式就能使用這個並非為其量身 ...


本文版權歸博客園和作者吳雙本人共同所有 轉載和爬蟲請註明原文地址 www.cnblogs.com/tdws

一.寫在前面

 適配器模式(Adapter)

可用來在現有介面和不相容的類之間進行適配。有助於避免大規模改寫現有客戶代碼,其工作機制是對現有類的介面進行包裝,這樣客戶程式就能使用這個並非為其量身打造的類而又無需為此大動手術。                  ----《JS設計模式》

將一個類的介面,轉換成客戶期望的另一個介面。適配器讓原本介面不相容的類可以合作無間。

                       ----《Head First設計模式》

這兩本書中對適配器模式定義如此,適配器模式在多種設計模式當中屬於比較容易理解的一種,其目的或者說可以解決的問題是新功能/新類型,不受原有類型/方法/功能的相容,有了適配器這種巧妙地經驗,我們可以保證對修改封閉,對拓展開放。而達到此目的,正需要面向介面,並保持職責的單一性。也許對C#開發者來說,見的最多的就是SqlDataAdapter。

                  

二.認識UseWebApi

本文所涉及OWIN,.NetFramework,Webapi 開源源碼下載地址為:

https://github.com/aspnet/AspNetKatana

https://github.com/ASP-NET-MVC/aspnetwebstack

https://github.com/dotnet/corefx 

熟悉OWIN體系的小伙伴們,一定都在Startup.cs中見過也使用過app.UseWebApi吧。app是IAppBuilder的對象

Startup.cs是OWIN katana實現的啟動類,剛說的UseWebApi顧名思義,就是將WebApi作為一個OWIN中間件放在整個處理流程中。app是IAppBuilder的對象,其創建由IAppBuilderFactory負責。IAppBuilder定義了三個方法,分別為Build,New和Use.   這三個方法分別負責什麼呢?

Build,返回OWIN管道入口點的一個實例,由 Microsoft.Owin.Host.SystemWeb中的Init方法調用。其返回實例將被轉換為AppFun類型,AppFun( using AppFunc = Func<IDictionary<string, object>, Task>;)是什麼呢?它是OWIN伺服器與應用程式交互的應用程式委托,我們看到這個方法在OWIN.Host中調用,應該就能大概猜到個所以然。

New,用於返回一個AppBuilder實例,由IAppBuilderFactory調用並返回。

Use,就是我們在OWIN體系中,經常使用到的方法,我們可以定義自己的OWIN中間件,按照其定義規範,並Use到處理管道中,比如用戶操作日誌中間件,用戶身份校驗中間件等。

說到這裡,我們應該很清晰的瞭解到WebApi是OWIN的一個中間件而已了吧。舉個慄子:

 1 public partial class Startup
 2     {
 3 
 4         public void Configuration(IAppBuilder app)
 5         {
 6             // This must happen FIRST otherwise CORS will not work. 
 7             // 引入OWin.Cors 解決跨域訪問問題
 8             app.UseCors(CorsOptions.AllowAll);
 9 
10             GlobalConfiguration.Configure(WebApiConfig.Register);
11             FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
12             
13             ConfigureAuth(app);
14 
15             app.Use<UserContextMiddleware>();
16             app.Use<UserOperationMiddleware>();
17             app.UseWebApi(GlobalConfiguration.Configuration);
18         }
19     }

三.UseWebApi的實現

 看到這裡你一定會問,為什麼IAppBuilder中沒有定義UseWebapi方法呢,UseWebapi的實現在System.Web.Http.Owin的WebApiAppBuilderExtensions.cs中,UseWebApi是一個C# this拓展方法,和你所想到的答案並無差。在其實現中,調用了  builder.Use(typeof(HttpMessageHandlerAdapter), options);  

到這裡,一定要啰嗦幾句不要怪我,Adapter的實現步驟:為了使一個類或者一個功能,相容已有類/介面,那麼

1.被適配器實現目標客戶的介面或抽象方法,以便參數的傳入

2.所實現介面/抽象類的方法中調用目標客戶的方法

HttpMessageHandlerAdapter 這個主角終於出現了,對Adapter模式瞭解後的小伙伴們也一定能想得到,既然是HttpMessageHandlerAdapter,那麼 在其類中 一定定義了一個private的欄位,並且類型為HttpMessageHandler,你也一定能想得到這個Adapter繼承了OwinMiddleware這個抽象類型並且實現其Invoke抽象方法,在HttpMessageHandlerAdapter的一個方法中一定調用了HttpMessageHandler的方法。那麼通過源碼我們瞭解到HttpMessageHandler的欄位名為_messageHandler。(是不是和上面所說的Adapter實現方式類似呢,實現方式可能概括的不好,建議參閱更多文章和範例)

Asp.Net Webapi的消息處理管道是由HttpMessageHandler的委托鏈所組成的處理管道

HttpMessageHandler抽象類當中頂一個一個唯一的抽象方法用於實現,其入參為HttpRequestMessage,其出參為HttpResponseMessage。

1 protected internal abstract Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);

DelegatingHandler實現了HttpMessageHandler,其構造函數中傳入HttpMessageHandler,並由同類對象innerHandler構成委托鏈。

推薦一篇MS文檔 https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/http-message-handlers,有興趣可以稍微參照下。

 1         protected DelegatingHandler(HttpMessageHandler innerHandler)
 2         {
 3             InnerHandler = innerHandler;
 4         }
 5 
 6         protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
 7         {
 8             if (request == null)
 9             {
10                 throw new ArgumentNullException(nameof(request), SR.net_http_handler_norequest);
11             }
12             SetOperationStarted();
13             return _innerHandler.SendAsync(request, cancellationToken);
14         }

中間啰嗦了一串,為了說明HttpMessageHandler的作用,這樣我們能進一步理解,為什麼要有HttpMessageHandlerAdapter的存在,併在Use (WebApi中間件)的時候,將該類型傳入。

在HttpMessageHandlerAdapter構造函數中,_messageHandler被包裝為HttpMessageInvoker類型,這個類型的目的是提供一個專門的類,用於調用SendAsync方法。

剛纔我們已經瞭解到HttpMessageHandlerAdapter實現了OWinMiddleware, 那麼我們從源碼中瞭解下,在其實現的抽象方法Invoke中,做了什麼事情:其調用同類下的InvokeCore方法,InvokeCore中Create了HttpRequestMessage,並將其對象作為SendAsync的入參,最後得到HttpResponseMessage對象。

四.寫在最後

      就是這樣,一次通過源碼的閱讀,再次對Adapter的理解,HttpMessageHandlerAdapter最終通過對OwinMiddleware的實現,在Invoke中通過HttpMessageInvoker調用執行SendAsync,丟入HttpRequestMessage,拿到ResponseMessage.最終附上HttpMessageHandlerAdapter源碼,更多源碼,還是從第二段的連接中下載吧。

  1 // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
  2 
  3 using System.Collections.Generic;
  4 using System.Diagnostics;
  5 using System.Diagnostics.CodeAnalysis;
  6 using System.Diagnostics.Contracts;
  7 using System.IO;
  8 using System.Net;
  9 using System.Net.Http;
 10 using System.Net.Http.Headers;
 11 using System.Runtime.ExceptionServices;
 12 using System.Security.Principal;
 13 using System.Threading;
 14 using System.Threading.Tasks;
 15 using System.Web.Http.Controllers;
 16 using System.Web.Http.ExceptionHandling;
 17 using System.Web.Http.Hosting;
 18 using System.Web.Http.Owin.ExceptionHandling;
 19 using System.Web.Http.Owin.Properties;
 20 using Microsoft.Owin;
 21 
 22 namespace System.Web.Http.Owin
 23 {
 24     /// <summary>
 25     /// Represents an OWIN component that submits requests to an <see cref="HttpMessageHandler"/> when invoked.
 26     /// </summary>
 27     public class HttpMessageHandlerAdapter : OwinMiddleware, IDisposable
 28     {
 29         private readonly HttpMessageHandler _messageHandler;
 30         private readonly HttpMessageInvoker _messageInvoker;
 31         private readonly IHostBufferPolicySelector _bufferPolicySelector;
 32         private readonly IExceptionLogger _exceptionLogger;
 33         private readonly IExceptionHandler _exceptionHandler;
 34         private readonly CancellationToken _appDisposing;
 35 
 36         private bool _disposed;
 37 
 38         /// <summary>Initializes a new instance of the <see cref="HttpMessageHandlerAdapter"/> class.</summary>
 39         /// <param name="next">The next component in the pipeline.</param>
 40         /// <param name="options">The options to configure this adapter.</param>
 41         public HttpMessageHandlerAdapter(OwinMiddleware next, HttpMessageHandlerOptions options)
 42             : base(next)
 43         {
 44             if (options == null)
 45             {
 46                 throw new ArgumentNullException("options");
 47             }
 48 
 49             _messageHandler = options.MessageHandler;
 50 
 51             if (_messageHandler == null)
 52             {
 53                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 54                     typeof(HttpMessageHandlerOptions).Name, "MessageHandler"), "options");
 55             }
 56 
 57             _messageInvoker = new HttpMessageInvoker(_messageHandler);
 58             _bufferPolicySelector = options.BufferPolicySelector;
 59 
 60             if (_bufferPolicySelector == null)
 61             {
 62                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 63                     typeof(HttpMessageHandlerOptions).Name, "BufferPolicySelector"), "options");
 64             }
 65 
 66             _exceptionLogger = options.ExceptionLogger;
 67 
 68             if (_exceptionLogger == null)
 69             {
 70                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 71                     typeof(HttpMessageHandlerOptions).Name, "ExceptionLogger"), "options");
 72             }
 73 
 74             _exceptionHandler = options.ExceptionHandler;
 75 
 76             if (_exceptionHandler == null)
 77             {
 78                 throw new ArgumentException(Error.Format(OwinResources.TypePropertyMustNotBeNull,
 79                     typeof(HttpMessageHandlerOptions).Name, "ExceptionHandler"), "options");
 80             }
 81 
 82             _appDisposing = options.AppDisposing;
 83 
 84             if (_appDisposing.CanBeCanceled)
 85             {
 86                 _appDisposing.Register(OnAppDisposing);
 87             }
 88         }
 89 
 90         /// <summary>Initializes a new instance of the <see cref="HttpMessageHandlerAdapter"/> class.</summary>
 91         /// <param name="next">The next component in the pipeline.</param>
 92         /// <param name="messageHandler">The <see cref="HttpMessageHandler"/> to submit requests to.</param>
 93         /// <param name="bufferPolicySelector">
 94         /// The <see cref="IHostBufferPolicySelector"/> that determines whether or not to buffer requests and
 95         /// responses.
 96         /// </param>
 97         /// <remarks>
 98         /// This constructor is retained for backwards compatibility. The constructor taking
 99         /// <see cref="HttpMessageHandlerOptions"/> should be used instead.
100         /// </remarks>
101         [Obsolete("Use the HttpMessageHandlerAdapter(OwinMiddleware, HttpMessageHandlerOptions) constructor instead.")]
102         public HttpMessageHandlerAdapter(OwinMiddleware next, HttpMessageHandler messageHandler,
103             IHostBufferPolicySelector bufferPolicySelector)
104             : this(next, CreateOptions(messageHandler, bufferPolicySelector))
105         {
106         }
107 
108         /// <summary>Gets the <see cref="HttpMessageHandler"/> to submit requests to.</summary>
109         public HttpMessageHandler MessageHandler
110         {
111             get { return _messageHandler; }
112         }
113 
114         /// <summary>
115         /// Gets the <see cref="IHostBufferPolicySelector"/> that determines whether or not to buffer requests and
116         /// responses.
117         /// </summary>
118         public IHostBufferPolicySelector BufferPolicySelector
119         {
120             get { return _bufferPolicySelector; }
121         }
122 
123         /// <summary>Gets the <see cref="IExceptionLogger"/> to use to log unhandled exceptions.</summary>
124         public IExceptionLogger ExceptionLogger
125         {
126             get { return _exceptionLogger; }
127         }
128 
129         /// <summary>Gets the <see cref="IExceptionHandler"/> to use to process unhandled exceptions.</summary>
130         public IExceptionHandler ExceptionHandler
131         {
132             get { return _exceptionHandler; }
133         }
134 
135         /// <summary>Gets the <see cref="CancellationToken"/> that triggers cleanup of this component.</summary>
136         public CancellationToken AppDisposing
137         {
138             get { return _appDisposing; }
139         }
140 
141         /// <inheritdoc />
142         public override Task Invoke(IOwinContext context)
143         {
144             if (context == null)
145             {
146                 throw new ArgumentNullException("context");
147             }
148 
149             IOwinRequest owinRequest = context.Request;
150             IOwinResponse owinResponse = context.Response;
151 
152             if (owinRequest == null)
153             {
154                 throw Error.InvalidOperation(OwinResources.OwinContext_NullRequest);
155             }
156             if (owinResponse == null)
157             {
158                 throw Error.InvalidOperation(OwinResources.OwinContext_NullResponse);
159             }
160 
161             return InvokeCore(context, owinRequest, owinResponse);
162         }
163 
164         private async Task InvokeCore(IOwinContext context, IOwinRequest owinRequest, IOwinResponse owinResponse)
165         {
166             CancellationToken cancellationToken = owinRequest.CallCancelled;
167             HttpContent requestContent;
168 
169             bool bufferInput = _bufferPolicySelector.UseBufferedInputStream(hostContext: context);
170 
171             if (!bufferInput)
172             {
173                 owinRequest.DisableBuffering();
174             }
175 
176             if (!owinRequest.Body.CanSeek && bufferInput)
177             {
178                 requestContent = await CreateBufferedRequestContentAsync(owinRequest, cancellationToken);
179             }
180             else
181             {
182                 requestContent = CreateStreamedRequestContent(owinRequest);
183             }
184 
185             HttpRequestMessage request = CreateRequestMessage(owinRequest, requestContent);
186             MapRequestProperties(request, context);
187 
188             SetPrincipal(owinRequest.User);
189 
190             HttpResponseMessage response = null;
191             bool callNext;
192 
193             try
194             {
195                 response = await _messageInvoker.SendAsync(request, cancellationToken);
196 
197                 // Handle null responses
198                 if (response == null)
199                 {
200                     throw Error.InvalidOperation(OwinResources.SendAsync_ReturnedNull);
201                 }
202 
203                 // Handle soft 404s where no route matched - call the next component
204                 if (IsSoftNotFound(request, response))
205                 {
206                     callNext = true;
207                 }
208                 else
209                 {
210                     callNext = false;
211 
212                     // Compute Content-Length before calling UseBufferedOutputStream because the default implementation
213                     // accesses that header and we want to catch any exceptions calling TryComputeLength here.
214 
215                     if (response.Content == null
216                         || await ComputeContentLengthAsync(request, response, owinResponse, cancellationToken))
217                     {
218                         bool bufferOutput = _bufferPolicySelector.UseBufferedOutputStream(response);
219 
220                         if (!bufferOutput)
221                         {
222                             owinResponse.DisableBuffering();
223                         }
224                         else if (response.Content != null)
225                         {
226                             response = await BufferResponseContentAsync(request, response, cancellationToken);
227                         }
228 
229                         if (await PrepareHeadersAsync(request, response, owinResponse, cancellationToken))
230                         {
231                             await SendResponseMessageAsync(request, response, owinResponse, cancellationToken);
232                         }
233                     }
234                 }
235             }
236             finally
237             {
238                 request.DisposeRequestResources();
239                 request.Dispose();
240                 if (response != null)
241                 {
242                     response.Dispose();
243                 }
244             }
245 
246             // Call the next component if no route matched
247             if (callNext && Next != null)
248             {
249                 await Next.Invoke(context);
250             }
251         }
252 
253         private static HttpContent CreateStreamedRequestContent(IOwinRequest owinRequest)
254         {
255             // Note that we must NOT dispose owinRequest.Body in this case. Disposing it would close the input
256             // stream and prevent cascaded components from accessing it. The server MUST handle any necessary
257             // cleanup upon request completion. NonOwnedStream prevents StreamContent (or its callers including
258             // HttpRequestMessage) from calling Close or Dispose on owinRequest.Body.
259             return new StreamContent(new NonOwnedStream(owinRequest.Body));
260         }
261 
262         private static async Task<HttpContent> CreateBufferedRequestContentAsync(IOwinRequest owinRequest,
263             CancellationToken cancellationToken)
264         {
265             // We need to replace the request body with a buffered stream so that other components can read the stream.
266             // For this stream to be useful, it must NOT be diposed along with the request. Streams created by
267             // StreamContent do get disposed along with the request, so use MemoryStream to buffer separately.
268             MemoryStream buffer;
269             int? contentLength = owinRequest.GetContentLength();
270 
271             if (!contentLength.HasValue)
272             {
273                 buffer = new MemoryStream();
274             }
275             else
276             {
277                 buffer = new MemoryStream(contentLength.Value);
278             }
279 
280             cancellationToken.ThrowIfCancellationRequested();
281 
282             using (StreamContent copier = new StreamContent(owinRequest.Body))
283             {
284                 await copier.CopyToAsync(buffer);
285             }
286 
287             // Provide the non-disposing, buffered stream to later OWIN components (set to the stream's beginning).
288             buffer.Position = 0;
289             owinRequest.Body = buffer;
290 
291             // For MemoryStream, Length is guaranteed to be an int.
292             return new ByteArrayContent(buffer.GetBuffer(), 0, (int)buffer.Length);
293         }
294 
295         private static HttpRequestMessage CreateRequestMessage(IOwinRequest owinRequest, HttpContent requestContent)
296         {
	   

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

-Advertisement-
Play Games
更多相關文章
  • 1、回顧基礎命令 2、腳本 3、變數 4、別名 5、條件判斷 6、test判斷 一、回顧基礎命令 shutdown --關機/重啟 exit --退出當前shell rmdir --刪除空目錄 du --查看目錄占用的存儲空間 df --查看已 經掛載的文件系統的空間使用情況 ln --創建鏈接 c ...
  • 很多電腦愛好者對於Win10內置的PIN碼功能不太瞭解,很多朋友都還沒有使用。其實,創建PIN碼可以提到密碼使用,當你登錄到Windows和其它應用服務時,可以通過PIN碼替代輸入賬戶密碼,提升安全性。話不多說,以下是Win10開啟PIN碼設置使用教程,步驟如下。 一、從Win10左下角的開始菜單, ...
  • 對於游戲玩家來說,對顯卡的關註度要高於電腦其它硬體,一般來說,顯卡越好,游戲性能往往越強。不過要持續發揮顯卡的最佳游戲性能,經常更新顯卡驅動也是很有必要的。那麼筆記本顯卡驅動怎麼更新?下麵小編以自己的Win10筆記本為例,教大家如何升級筆記本顯卡驅動。 Win10筆記本顯卡驅動更新升級方法 升級筆記 ...
  • 1.下載最新的openssh包 http://www.openssh.com/portable.html#http 2.升級openssh之前要先打開伺服器telnet,通過telnet登錄伺服器,因為升級過程中會導致ssh暫時不能用 打開linux telnet服務: 查看telnet是否已經安裝 ...
  • 環境:筆記本 + 家用WIFI + 公司WIFI + VMware + CentOS6.8 + Xshell 問題描述:初學Linux時,用筆記本裝了虛擬機(單網卡),想實現linux在家和公司都能夠無線連網,但又不想上網地點變動之後每次手動輸入IP登錄Xshell。 解決思路:增加一塊網卡(eth ...
  • 一、簡介 1、認識 加密網頁(https): tcp:443 明文網頁(http): tcp:80 survey.netcraft.net --這個網站上可以查到最新的網站伺服器的使用率 超文本傳輸協議(HTTP,HyperText Transfer Protocol)是互聯網上應用最為廣泛的一種網 ...
  • select系統調用的的用途是:在一段指定的時間內,監聽用戶感興趣的文件描述符上可讀、可寫和異常等事件。 select 機制的優勢 為什麼會出現select模型? 先看一下下麵的這句代碼: 這是用來接收數據的,在預設的阻塞模式下的套接字里,recv會阻塞在那裡,直到套接字連接上有數據可讀,把數據讀到 ...
  • 在ASP.NET MVC中來實現主題的切換一般有兩種方式,一種是通過切換皮膚的css和js引用,一種就是通過重寫視圖引擎。通過重寫視圖引擎的方式更加靈活,因為我不僅可以在不同主題下麵佈局和樣式不一樣,還可以讓不同的主題下麵顯示的數據條目不一致,就是說可以在某些主題下麵添加一下個性化的東西。 本篇我將 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...