BeetleX实现HTTP协议详解
在傳統(tǒng)網(wǎng)絡(luò)服務(wù)中擴(kuò)展中需要處理Bytes來(lái)進(jìn)行協(xié)議的讀寫,這種原始的處理方式讓工作變得相當(dāng)繁瑣復(fù)雜,出錯(cuò)和調(diào)試的工作量都非常大;組件為了解決這一問題引用Stream讀寫方式,這種方式可以極大的簡(jiǎn)化網(wǎng)絡(luò)協(xié)議讀寫的工作量,并大大提高協(xié)議編寫效率。接下來(lái)就體驗(yàn)一下組件的PipeStream在處理一個(gè)完整的HTTP 1.1協(xié)議有多簡(jiǎn)便。
結(jié)構(gòu)定義
HTTP 1.1協(xié)議就不詳細(xì)介紹了,網(wǎng)上的資源非常豐富;在這里通過對(duì)象結(jié)束來(lái)描述這個(gè)協(xié)議
Request
class HttpRequest{public string HttpVersion { get; set; }public string Method { get; set; }public string BaseUrl { get; set; }public string ClientIP { get; set; }public string Path { get; set; }public string QueryString { get; set; }public string Url { get; set; }public Dictionary<string, string> Headers { get; private set; } = new Dictionary<string, string>();public byte[] Body { get; set; }public int ContentLength { get; set; }public RequestStatus Status { get; set; } = RequestStatus.None;}以上是描述一個(gè)HTTP請(qǐng)求提供了一些請(qǐng)求的詳細(xì)信息和對(duì)應(yīng)的請(qǐng)求內(nèi)容
Response
class HttpResponse : IWriteHandler{public HttpResponse(){Headers["Content-Type"] = "text/html";}public string HttpVersion { get; set; } = "HTTP/1.1";public int Status { get; set; }public string StatusMessage { get; set; } = "OK";public string Body { get; set; }public Dictionary<string, string> Headers = new Dictionary<string, string>();public void Write(Stream stream){var pipeStream = stream.ToPipeStream();pipeStream.WriteLine($"{HttpVersion} {Status} {StatusMessage}");foreach (var item in Headers)pipeStream.WriteLine($"{item.Key}: {item.Value}");byte[] bodyData = null;if (!string.IsNullOrEmpty(Body)){bodyData = Encoding.UTF8.GetBytes(Body);}if (bodyData != null){pipeStream.WriteLine($"Content-Length: {bodyData.Length}");}pipeStream.WriteLine("");if (bodyData != null){pipeStream.Write(bodyData, 0, bodyData.Length);}Completed?.Invoke(this);}public Action<IWriteHandler> Completed { get; set; }}以上是對(duì)應(yīng)請(qǐng)求的響應(yīng)對(duì)象,實(shí)現(xiàn)了IWriteHandler接口,這個(gè)接口是告訴組件如何輸出這個(gè)對(duì)象。
協(xié)議分析
結(jié)構(gòu)對(duì)象有了接下來(lái)的工作就是把網(wǎng)絡(luò)流中的HTTP協(xié)議數(shù)據(jù)讀取到結(jié)構(gòu)上.
請(qǐng)求基礎(chǔ)信息
private void LoadRequestLine(HttpRequest request, PipeStream stream){if (request.Status == RequestStatus.None){if (stream.TryReadLine(out string line)){var subItem = line.SubLeftWith(' ', out string value);request.Method = value;subItem = subItem.SubLeftWith(' ', out value);request.Url = value;request.HttpVersion = subItem;subItem = request.Url.SubRightWith('?', out value);request.QueryString = value;request.BaseUrl = subItem;request.Path = subItem.SubRightWith('/', out value);if (request.Path != "/")request.Path += "/";request.Status = RequestStatus.LoadingHeader;}}}以上方法主要是分解出Method,Url和QueryString等信息。由于TCP協(xié)議是流,所以在分析包的時(shí)候都要考慮當(dāng)前數(shù)據(jù)是否滿足要求,所以組件提供TryReadLine方法來(lái)判斷當(dāng)前內(nèi)容是否滿足一行的需求;還有通過組件提供的SubRightWith和SubLeftWith方法可以大簡(jiǎn)化了字符獲取問題。
頭部信息獲取
private void LoadRequestHeader(HttpRequest request, PipeStream stream){if (request.Status == RequestStatus.LoadingHeader){while (stream.TryReadLine(out string line)){if (string.IsNullOrEmpty(line)){if (request.ContentLength == 0){request.Status = RequestStatus.Completed;}else{request.Status = RequestStatus.LoadingBody;}return;}var name = line.SubRightWith(':', out string value);if (String.Compare(name, "Content-Length", true) == 0){request.ContentLength = int.Parse(value);}request.Headers[name] = value.Trim();}}}頭信息分析就更加簡(jiǎn)單,當(dāng)獲取一個(gè)空行的時(shí)候就說(shuō)明頭信息已經(jīng)解釋完成,接下來(lái)的就部分就是Body;由于涉及到Body所以需要判斷一個(gè)頭存不存在Content-Length屬性,這個(gè)屬性用于描述消息體的長(zhǎng)度;其實(shí)Http還有一種chunked編碼,這種編碼是分塊來(lái)處理最終以0\r\n\r\n結(jié)尾。這種方式一般是響應(yīng)用得比較多,對(duì)于提交則很少使用這種方式。
讀取消息體
private void LoadRequestBody(HttpRequest request, PipeStream stream){if (request.Status == RequestStatus.LoadingBody){if (stream.Length >= request.ContentLength){var data = new byte[request.ContentLength]; ;stream.Read(data, 0, data.Length);request.Body = data;request.Status = RequestStatus.Completed;}}}讀取消息體就簡(jiǎn)單了,只需要判斷當(dāng)前的PipeStream是否滿足提交的長(zhǎng)度,如果可以則直接獲取并設(shè)置到request.Data屬性中。這里只是獲了流數(shù)據(jù),實(shí)際上Http體的編碼也有幾種情況,在這里不詳細(xì)介紹。這些實(shí)現(xiàn)在FastHttpApi中都有具體的實(shí)現(xiàn)代碼,詳細(xì)可以查看?https://github.com/IKende/FastHttpApi/blob/master/src/Data/DataConvertAttribute.cs
整合到服務(wù)
以上針對(duì)Request和Response的協(xié)議處理已經(jīng)完成,接下來(lái)就集成到組件的TCP服務(wù)中
public override void SessionReceive(IServer server, SessionReceiveEventArgs e){var request = GetRequest(e.Session);var pipeStream = e.Stream.ToPipeStream();if (LoadRequest(request, pipeStream) == RequestStatus.Completed){OnCompleted(request, e.Session);}}private RequestStatus LoadRequest(HttpRequest request, PipeStream stream){LoadRequestLine(request, stream);LoadRequestHeader(request, stream);LoadRequestBody(request, stream);return request.Status;}private void OnCompleted(HttpRequest request, ISession session){HttpResponse response = new HttpResponse();StringBuilder sb = new StringBuilder();sb.AppendLine("<html>");sb.AppendLine("<body>");sb.AppendLine($"<p>Method:{request.Method}</p>");sb.AppendLine($"<p>Url:{request.Url}</p>");sb.AppendLine($"<p>Path:{request.Path}</p>");sb.AppendLine($"<p>QueryString:{request.QueryString}</p>");sb.AppendLine($"<p>ClientIP:{request.ClientIP}</p>");sb.AppendLine($"<p>Content-Length:{request.ContentLength}</p>");foreach (var item in request.Headers){sb.AppendLine($"<p>{item.Key}:{item.Value}</p>");}sb.AppendLine("</body>");sb.AppendLine("</html>");response.Body = sb.ToString();ClearRequest(session);session.Send(response);}只需要在SessionReceive接收事件中創(chuàng)建相應(yīng)的Request對(duì)象,并把PipeStream傳遞到相應(yīng)的解釋方法中,然后判斷完成情況;當(dāng)解釋完成后就調(diào)用OnCompleted輸出相應(yīng)的Response信息,在這里簡(jiǎn)單地把當(dāng)前請(qǐng)求信息輸出返回.(在這里為什么要清除會(huì)話的Request呢,因?yàn)镠ttp1.1是長(zhǎng)連接會(huì)話,必須每個(gè)請(qǐng)求都需要?jiǎng)?chuàng)建一個(gè)新的Request對(duì)象信息)。
這樣一個(gè)基于BeetleX解釋的Http服務(wù)就完成了,是不是很簡(jiǎn)單。接下來(lái)簡(jiǎn)單地測(cè)試一下性能,在一臺(tái)e3-1230v2+10Gb的環(huán)境壓力測(cè)試
測(cè)試結(jié)果的有15萬(wàn)的RPS,雖然這樣一個(gè)簡(jiǎn)單服務(wù)但效率并不理想,相對(duì)于也是基于組件擴(kuò)展的FastHttpApi來(lái)說(shuō)還是有些差距,為什么簡(jiǎn)單的代碼還沒有復(fù)雜的框架來(lái)得高效呢,其實(shí)原因很簡(jiǎn)單就是對(duì)象復(fù)用和string編碼緩存沒有用上,導(dǎo)致開銷過大(這些細(xì)節(jié)上的性能優(yōu)化有時(shí)間會(huì)在后續(xù)詳解)。
下載完整代碼?https://github.com/IKende/BeetleX-Samples/tree/master/TCP.BaseHttp
?使用優(yōu)惠口令「dotnet123」
????到手僅¥89,限前200人
總結(jié)
以上是生活随笔為你收集整理的BeetleX实现HTTP协议详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: .NET 状态机Automatonymo
- 下一篇: (1)解锁MongoDB replica