C#/.NET5 单HTTP请求读取多个HTTP响应的实现方法
问题描述
我正在对接一个监听POST请求并返回数据的现有服务,该服务有多个已部署在生产现场的版本,不支持对服务端进行任何修改。
通过HTTP/1.1协议发送<Get_Data>类型的POST请求时,服务存在两类响应逻辑:
- 场景1:请求数据量较小时,单次请求/接收周期即可完成处理,可直接从响应流获取预期返回值
- 场景2:请求数据量过大无法即时返回时,服务会先以约4秒为间隔返回N个
Progress_Info进度数据包,所有进度包发送完成后才会返回真实有效载荷
通过WireShark抓包分析待复刻的现有C++程序通信流程,观测到中间进度数据包具备以下特征: - 响应头标记
Content-Length: 102 - 属于分片HTTP响应的中间分片(抓包标记如HTTP response 3/4)
- 存在前序、后续响应帧的关联标记
核心诉求:保持响应流处于打开状态,接收并丢弃中间的Progress_Info消息,直到获取并保留包含目标数据的最终响应。
当前已实现的发送/接收代码如下:
public string Send_SOAP_Request_And_Receive_Response( string iInterface, string iRequestMessage ) { string oResponseText = string.Empty; try { HttpWebRequest request = Create_Message( MESSAGE_TYPE_POST, iInterface ); byte[] bytes; bytes = System.Text.Encoding.ASCII.GetBytes( iRequestMessage ); request.ContentLength = bytes.Length; Stream requestStream = request.GetRequestStream(); requestStream.Write( bytes, 0, bytes.Length ); HttpWebResponse response = ( HttpWebResponse )request.GetResponse(); Stream responseStream = response.GetResponseStream(); StreamReader reader = new StreamReader( responseStream ); if ( response.StatusCode == HttpStatusCode.OK ) { oResponseText = reader.ReadToEnd(); // MessageBox.Show( oResponseText ); } // *NEED CODE TO CONTINUE READING THE STREAM UNTIL REAL MESSAGE IS SENT* // requestStream.Close(); } catch (Exception ex) { Global.Display_Message( ex.Message ); } return oResponseText; }
解决方案
你现有代码无法读取后续响应的核心原因是:HttpWebRequest默认在读取完当前Content-Length标记的完整响应段后,会认为本次响应结束,不会主动等待同TCP连接上后续推送的响应段。按以下步骤修改即可,不需要调整服务端逻辑:
- 先调整
HttpWebRequest的基础配置,关闭默认的自动连接释放、100-continue等可能导致连接提前断开的行为:// 在创建request对象后新增以下配置 request.KeepAlive = true; request.ProtocolVersion = HttpVersion.Version11; request.ServicePoint.Expect100Continue = false; request.AllowReadStreamBuffering = false; request.ServicePoint.SetTcpKeepAlive(true, 1000, 1000); - 替换原有单次
ReadToEnd()的逻辑,改为循环逐段读取响应:每读完一段就判断是否为Progress_Info进度包,是则丢弃继续读取,否则作为最终结果返回。同时修正资源释放逻辑,避免连接泄漏。
修正后的完整实现代码:public string Send_SOAP_Request_And_Receive_Response( string iInterface, string iRequestMessage ) { string oResponseText = string.Empty; HttpWebRequest request = null; HttpWebResponse response = null; Stream requestStream = null; Stream responseStream = null; StreamReader reader = null; try { request = Create_Message(MESSAGE_TYPE_POST, iInterface); // 基础连接配置 request.KeepAlive = true; request.ProtocolVersion = HttpVersion.Version11; request.ServicePoint.Expect100Continue = false; request.AllowReadStreamBuffering = false; byte[] bytes = Encoding.ASCII.GetBytes(iRequestMessage); request.ContentLength = bytes.Length; // 写入请求数据,写完立即关闭请求流 requestStream = request.GetRequestStream(); requestStream.Write(bytes, 0, bytes.Length); requestStream.Close(); // 循环读取连续响应段 while (true) { response = (HttpWebResponse)request.GetResponse(); if (response.StatusCode != HttpStatusCode.OK) { throw new Exception($"请求返回异常状态码:{response.StatusCode}"); } responseStream = response.GetResponseStream(); reader = new StreamReader(responseStream, Encoding.ASCII); string currentSegment = reader.ReadToEnd(); // 释放当前段的读取资源,准备读取下一段 reader.Close(); responseStream.Close(); response.Close(); // 按抓包特征判断是否为进度包:固定长度102+包含Progress_Info标记 if (currentSegment.Trim().Length == 102 && currentSegment.Contains("Progress_Info")) { Thread.Sleep(200); continue; } // 命中最终有效载荷,退出循环 oResponseText = currentSegment; break; } } catch (Exception ex) { Global.Display_Message(ex.Message); } finally { // 统一释放所有资源 reader?.Dispose(); responseStream?.Dispose(); response?.Dispose(); requestStream?.Dispose(); request?.ServicePoint?.CloseConnectionGroup(request.ConnectionGroupName); } return oResponseText; } - 进度包判断条件可以根据你实际抓包的内容调整,比如进度包有固定的XML/JSON结构,就把判断条件写得更严格,避免误判包含
Progress_Info字符串的有效载荷。
该实现可以同时兼容两种响应场景:小数据量请求时第一次读取就会拿到有效载荷,不会进入跳过逻辑;大数据量请求时会自动丢弃所有进度包,直到拿到最终数据。
内容的提问来源于stack exchange,提问作者Mike Weber
相关产品推荐
相关产品推荐

