????????在通訊應(yīng)用中很多時(shí)候需要和已有標(biāo)準(zhǔn)的應(yīng)用協(xié)議進(jìn)行通訊,針對(duì)這情況就要針對(duì)相應(yīng)協(xié)議的實(shí)現(xiàn);標(biāo)準(zhǔn)協(xié)議上考慮的情況比較多,所以協(xié)議的復(fù)雜度也相對(duì)高些,對(duì)比之前的Protobuf通訊的簡(jiǎn)單協(xié)議來(lái)說(shuō)則會(huì)復(fù)雜。接下來(lái)用組件去實(shí)現(xiàn)一個(gè)簡(jiǎn)單的HTTP協(xié)議服務(wù),讓瀏覽器可以去訪問(wèn)它。
HTTP協(xié)議
????????對(duì)于HTTP協(xié)議的介紹相信也不用過(guò)多描述,畢竟這個(gè)協(xié)議已經(jīng)應(yīng)用了N年了,網(wǎng)上針對(duì)這一協(xié)議的介紹也非常多。這協(xié)議的版本有1.0,1.1和2.0,接下來(lái)實(shí)現(xiàn)的是HTTP1.1。其實(shí)更符合多場(chǎng)景應(yīng)用是2.0,不過(guò)2.0的協(xié)議復(fù)雜度就比較高了,所以就不在這里實(shí)現(xiàn)介紹了。
??????? HTTP 1.1協(xié)議只允許同一時(shí)間處理一個(gè)請(qǐng)求,就是當(dāng)服務(wù)端接收到請(qǐng)求后直到響應(yīng)完成才會(huì)處理下一下請(qǐng)求。為了滿(mǎn)足這需要針對(duì)通訊協(xié)議制定了Request和Response對(duì)象。
Request對(duì)象
????????該對(duì)象主要用于收集HTTP的請(qǐng)求信息,定義如下:
class HttpRequest{//當(dāng)前HTTP版本信息public string HttpVersion { get; set; }//請(qǐng)求的方法public string Method { get; set; }//基礎(chǔ)的urlpublic string BaseUrl { get; set; }//客戶(hù)端IPpublic string ClientIP { get; set; }//請(qǐng)求路徑public string Path { get; set; }//Url參數(shù)public string QueryString { get; set; }//完整URLpublic string Url { get; set; }//頭部信息public Dictionary<string, string> Headers { get; private set; } = new Dictionary<string, string>();//HTTP內(nèi)容public byte[] Body { get; set; }//HTTP內(nèi)容長(zhǎng)度public int ContentLength { get; set; }//請(qǐng)求對(duì)象狀態(tài)public RequestStatus Status { get; set; } = RequestStatus.None;}
以上是一個(gè)HTTP請(qǐng)求的簡(jiǎn)單描述對(duì)象,服務(wù)會(huì)根據(jù)網(wǎng)絡(luò)數(shù)據(jù)根據(jù)HTTP協(xié)議轉(zhuǎn)換成相應(yīng)的對(duì)象消息。
HttpResponse對(duì)象
? ? ? ? 該對(duì)象用于設(shè)置請(qǐng)求響應(yīng)內(nèi)容,定義如下:
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();//寫(xiě)入響應(yīng)狀態(tài)pipeStream.WriteLine($"{HttpVersion} {Status} {StatusMessage}");//寫(xiě)入頭部信息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("");//寫(xiě)入響應(yīng)消息體if (bodyData != null){pipeStream.Write(bodyData, 0, bodyData.Length);}Completed?.Invoke(this);}public Action<IWriteHandler> Completed { get; set; }}
對(duì)象實(shí)現(xiàn)了IWriteHandler接口,用于告訴組件提供自定義流輸出實(shí)現(xiàn)。
協(xié)議實(shí)現(xiàn)
????????在這個(gè)示例中協(xié)議分析并沒(méi)有實(shí)現(xiàn)IPacket,而是直接接管SessionReceive方法來(lái)對(duì)流進(jìn)行HTTP協(xié)議分析,具體實(shí)現(xiàn)代碼如下:
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)
{//分析HTTP請(qǐng)求信息LoadRequestLine(request, stream);//分析頭信息LoadRequestHeader(request, stream);//加載BodyLoadRequestBody(request, stream);return request.Status;
}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;}}
}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();}}
}
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;}}
}
在分析過(guò)程中最常用的方法是TryReadLine,主要原因HTTP的頭信息數(shù)據(jù)都是以換行的方式來(lái)描述,直到讀取一個(gè)空行表明頭部已結(jié)。如果存在Content-Length頭信息描述說(shuō)明存在消息體(HTTP有兩種描述消息體的情況,這里就不多作介紹了)。
服務(wù)處理
? ? ? ? 協(xié)議處理好后就可以集成在服務(wù)中,相對(duì)于協(xié)議分析來(lái)說(shuō)集成就更簡(jiǎn)單了。
public static void Main(string[] args)
{mServer = SocketFactory.CreateTcpServer<Program>();mServer.Options.DefaultListen.Port = 80;mServer.Options.AddListenSSL("ssl.pfx", "123456");mServer.Open();System.Threading.Thread.Sleep(-1);
}
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);
}
在服務(wù)中把默認(rèn)監(jiān)聽(tīng)的端口改成80,然后添加一個(gè)SSL監(jiān)聽(tīng)用于支持HTTPS訪問(wèn);示例中通過(guò)OnCompleted方法響應(yīng)請(qǐng)求內(nèi)容,主要返回的內(nèi)容是把當(dāng)前請(qǐng)求的請(qǐng)求詳細(xì)信息輸出。
訪問(wèn)結(jié)果
????????啟動(dòng)服務(wù)后可以通過(guò)瀏覽器訪問(wèn)相關(guān)地址:
由于證書(shū)是自己創(chuàng)建的,所以會(huì)被瀏覽器標(biāo)記為不安全。
下載
鏈接:https://pan.baidu.com/s/1P7QTSjJH4Q1ftHiRoQT8MQ?
提取碼:7kig?
【BeetleX通訊框架代碼詳解】
BeetleX
開(kāi)源跨平臺(tái)通訊框架(支持TLS)
輕松實(shí)現(xiàn)高性能:tcp、http、websocket、redis、rpc和網(wǎng)關(guān)等服務(wù)應(yīng)用
https://beetlex.io
總結(jié)
以上是生活随笔為你收集整理的BeetleX之简单HTTP/HTTPS实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。