首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C#字符串编码问题通过模拟的HttpResponseMessage

C#字符串编码问题通过模拟的HttpResponseMessage
EN

Stack Overflow用户
提问于 2022-02-25 10:53:38
回答 1查看 267关注 0票数 0

我正在尝试测试从远程API中获取数据。我按照以下方式设置了HttpClient:

代码语言:javascript
运行
复制
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中获取内容如下:

代码语言:javascript
运行
复制
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方法:

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
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);
        }
    }
}

该示例的输出:

代码语言:javascript
运行
复制
content before: {
    "test": "true"
}
content after: "{\r\n    \"test\": \"true\"\r\n}"
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-02-25 12:52:12

这和莫克无关..。这都是关于HttpRequestMessageExtensions.CreateResponse()的,它接受您的字符串并将其编码为JSON。

下面是一个更小的例子(作为.NET 6控制台应用程序;您可以忍受它抱怨恢复net461的目标,或者为.NET 4.7.1或类似的目标重新定向,并添加一些项目选项和使用指令;我认为让它以.NET 6为目标更简单)。

代码语言:javascript
运行
复制
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}");

输出:

代码语言:javascript
运行
复制
Before: { "test": "true" }
After: "{ \"test\": \"true\" }"

我认为让您困惑的是,在调试器中,您正在查看response.Content,将ObjectContent<string>中的Value视为您想要的字符串,并假定这是将要写入响应的数据。事实并非如此。这是在格式化之前的数据。

解决这个问题的最简单方法是将响应的内容作为StringContent提供。由于这一点,您不需要任何依赖项-下面的代码是一个很小的示例,它打印“前”和“后”的相同文本:

代码语言:javascript
运行
复制
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的使用)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/71264752

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档