我正在尝试测试从远程API中获取数据。我按照以下方式设置了HttpClient:
HttpClient httpClient = SetupHttpClient((HttpRequestMessage request) =>
{
FileStream file = new FileStream("API_Data.json"), FileMode.Open, FileAccess.Read);
StreamReader sr = new StreamReader(file, true);
var response = request.CreateResponse(HttpStatusCode.OK, sr.ReadToEnd());
response.Content.Headers.ContentEncoding.Add("UTF-8");
return Task.FromResult(response);
});
SetupHttpClient
在这里与此无关-重要的是传递的响应,正如您所看到的,它是通过从FileStream创建一个StreamReader并将该流读入响应来创建的。
使用Visualizer文本,我可以看到该文件已成功地读入响应流中,所有特殊字符(如新行、制表符和双引号)都正确显示,如下屏幕快照所示:
另一方面,我从HttpResponseMessage中获取内容如下:
Stream responseStream = await response.Content.ReadAsStreamAsync();
StreamReader responseReader = null;
if (response.Content.Headers.ContentEncoding.Count > 0)
responseReader = new StreamReader(responseStream, System.Text.Encoding.GetEncoding(response.Content.Headers.ContentEncoding.First()));
else
responseReader = new StreamReader(responseStream, true);
string content = await responseReader.ReadToEndAsync();
return content;
此时再次悬停调试响应,显示数据仍然正常:
显示的与上面的第一个屏幕截图完全相同。问题来了--尽管响应内容是字符串,但我无法访问Value属性,而且response.Content提供的所有检索机制都是通过流提供的。好吧,那么我是通过Stream获得内容的,但是在浏览了Stream之后,所有特殊的字符现在都是双转义的,如下所示:
这意味着我现在必须取消转义所有这些特殊字符,以便能够以json的形式使用返回的字符串--如果我不取消--转义它,那么当我试图反序列化它时,JsonDeserializer会阻塞它。StreamReader还添加了一个(单转义)双引号作为良好度量的第一个和最后一个字符。
在谷歌上我能找到的就是使用正确编码的引用。因此,我确保将源文件保存为UTF-8,发送“UTF-8”作为HttpResponseMessage (response.Content.Headers.ContentEncoding.Add("UTF-8");
)的编码,并确保在解码响应'UTF-8‘时再次用作编码(responseReader = new StreamReader(responseStream, System.Text.Encoding.GetEncoding(response.Content.Headers.ContentEncoding.First()));
) --如您所见,这没有达到获得一个不是双转义的字符串的预期效果。
当我从流中获得响应字符串时,我不想做一个“手动”不转义所有特殊字符的操作--这是一个可怕的攻击,然而,这似乎是唯一的选择--或者使用反射来获取response.Content.Value
属性的内容,如果我检测到response.Content
是一个字符串--再一次我不想做的黑客攻击。
如何确保在通过一个response.Content
获取StreamReader值时不会得到双转义的特殊字符?
编辑:为了清晰起见,下面是SetupHttpClient方法:
public HttpClient SetupHttpClient(Func<HttpRequestMessage, Task<HttpResponseMessage>> response)
{
var configuration = new HttpConfiguration();
var clientHandlerStub = new HttpDelegatingHandlerStub((request, cancellationToken) =>
{
request.SetConfiguration(configuration);
return response(request);
});
HttpClient httpClient = new HttpClient(clientHandlerStub);
mockHttpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
return httpClient;
}
和HttpDelegatingHandlerStub
public class HttpDelegatingHandlerStub : DelegatingHandler
{
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public HttpDelegatingHandlerStub()
{
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public HttpDelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
{
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handlerFunc(request, cancellationToken);
}
}
EDIT2:一个最小的、可重复的示例--这需要以下软件包-- Microsoft.AspNet.WebApi.Core、Microsoft.Extensions.Http、Moq:
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;
using Moq;
namespace StreamReaderEncoding
{
internal class Program
{
static Mock<IHttpClientFactory> mockHttpClientFactory;
static void Main(string[] args)
{
MainAsync().Wait();
}
static async Task MainAsync()
{
mockHttpClientFactory = new Mock<IHttpClientFactory>();
string content = @"{
""test"": ""true""
}";
Console.WriteLine($"content before: {content}");
HttpClient httpClient = SetupHttpClient((HttpRequestMessage request) =>
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(content);
writer.Flush();
stream.Position = 0;
StreamReader sr = new StreamReader(stream, true);
var response = request.CreateResponse(HttpStatusCode.OK, sr.ReadToEnd());
response.Content.Headers.ContentEncoding.Add("UTF-8");
return Task.FromResult(response);
});
HttpResponseMessage response = await httpClient.GetAsync("https://www.test.com");
Stream responseStream = await response.Content.ReadAsStreamAsync();
StreamReader responseReader = null;
if (response.Content.Headers.ContentEncoding.Count > 0)
responseReader = new StreamReader(responseStream, System.Text.Encoding.GetEncoding(response.Content.Headers.ContentEncoding.First()));
else
responseReader = new StreamReader(responseStream, true);
content = await responseReader.ReadToEndAsync();
Console.WriteLine($"content after: {content}");
}
static HttpClient SetupHttpClient(Func<HttpRequestMessage, Task<HttpResponseMessage>> response)
{
var configuration = new HttpConfiguration();
var clientHandlerStub = new HttpDelegatingHandlerStub((request, cancellationToken) =>
{
request.SetConfiguration(configuration);
return response(request);
});
HttpClient httpClient = new HttpClient(clientHandlerStub);
mockHttpClientFactory.Setup(_ => _.CreateClient(It.IsAny<string>())).Returns(httpClient);
return httpClient;
}
}
internal class HttpDelegatingHandlerStub : DelegatingHandler
{
private readonly Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _handlerFunc;
public HttpDelegatingHandlerStub()
{
_handlerFunc = (request, cancellationToken) => Task.FromResult(request.CreateResponse(HttpStatusCode.OK));
}
public HttpDelegatingHandlerStub(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> handlerFunc)
{
_handlerFunc = handlerFunc;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return _handlerFunc(request, cancellationToken);
}
}
}
该示例的输出:
content before: {
"test": "true"
}
content after: "{\r\n \"test\": \"true\"\r\n}"
发布于 2022-02-25 12:52:12
这和莫克无关..。这都是关于HttpRequestMessageExtensions.CreateResponse()
的,它接受您的字符串并将其编码为JSON。
下面是一个更小的例子(作为.NET 6控制台应用程序;您可以忍受它抱怨恢复net461的目标,或者为.NET 4.7.1或类似的目标重新定向,并添加一些项目选项和使用指令;我认为让它以.NET 6为目标更简单)。
using System.Web.Http;
var request = new HttpRequestMessage();
request.SetConfiguration(new HttpConfiguration());
string json = "{ \"test\": \"true\" }";
Console.WriteLine($"Before: {json}");
var response = request.CreateResponse(json);
string text = await response.Content.ReadAsStringAsync();
Console.WriteLine($"After: {text}");
输出:
Before: { "test": "true" }
After: "{ \"test\": \"true\" }"
我认为让您困惑的是,在调试器中,您正在查看response.Content
,将ObjectContent<string>
中的Value
视为您想要的字符串,并假定这是将要写入响应的数据。事实并非如此。这是在格式化之前的数据。
解决这个问题的最简单方法是将响应的内容作为StringContent
提供。由于这一点,您不需要任何依赖项-下面的代码是一个很小的示例,它打印“前”和“后”的相同文本:
var request = new HttpRequestMessage();
string json = "{ \"test\": \"true\" }";
Console.WriteLine($"Before: {json}");
var response = new HttpResponseMessage { Content = new StringContent(json) };
string text = await response.Content.ReadAsStringAsync();
Console.WriteLine($"After: {text}");
当然,您很可能希望在响应上设置一些其他标题,但我相信这证明了导致问题的是CreateResponse
方法(以及它对ObjectContent
的使用)。
https://stackoverflow.com/questions/71264752
复制相似问题