RestSharp打印原始请求和响应头

88

我正在使用RestSharp调用web服务。一切正常,但我想知道是否有可能打印发送出去的原始请求头和主体,以及收到的原始响应头和响应主体。

以下是我创建请求并获取响应的代码:

public static TResponse ExecutePostCall<TResponse, TRequest>(String url, TRequest requestData, string token= "") where TResponse : new()
{
    RestRequest request = new RestRequest(url, Method.POST);
    if (!string.IsNullOrWhiteSpace(token))
    {
        request.AddHeader("TOKEN", token);
    }


    request.RequestFormat = DataFormat.Json;
    request.AddBody(requestData);

    // print raw request here

    var response = _restClient.Execute<TResponse>(request);

    // print raw response here

    return response.Data;
}

那么,是否有可能打印原始的请求和响应?


4
你希望每次都这样做还是只是为了调试?如果只是一次性的话,可以使用 Fiddler 来获取原始的请求和响应。 - wal
不是完整的答案,但您可以编写自己的序列化程序/反序列化程序,并在那里记录生成/消耗的JSON。但是,如上所建议,使用“嗅探”代理可能更好。 - NilsH
@wal,我一直在使用Fiddler。我想在我的.NET应用程序中每次都这样做。 - Professor Chaos
你需要正文还是只需要头部信息?“一切”似乎有点过度,但我不知道你确切想要什么。 - wal
@wal 我肯定需要HTTP响应代码和响应正文.. 对于请求,我需要方法、URL和请求正文。 - Professor Chaos
10个回答

94

RestSharp没有提供一个实现你所需的机制,而激活.Net跟踪则有些过度了。

对于记录(调试)目的(例如我可以在生产环境中长时间保持开启),我发现这种方法非常有用(虽然在如何调用它方面有一些细节,请阅读代码下面的说明):

private void LogRequest(IRestRequest request, IRestResponse response, long durationMs)
{
        var requestToLog = new
        {
            resource = request.Resource,
            // Parameters are custom anonymous objects in order to have the parameter type as a nice string
            // otherwise it will just show the enum value
            parameters = request.Parameters.Select(parameter => new
            {
                name = parameter.Name,
                value = parameter.Value,
                type = parameter.Type.ToString()
            }),
            // ToString() here to have the method as a nice string otherwise it will just show the enum value
            method = request.Method.ToString(),
            // This will generate the actual Uri used in the request
            uri = _restClient.BuildUri(request),
        };

        var responseToLog = new
        {
            statusCode = response.StatusCode,
            content = response.Content,
            headers = response.Headers,
            // The Uri that actually responded (could be different from the requestUri if a redirection occurred)
            responseUri = response.ResponseUri,
            errorMessage = response.ErrorMessage,
        };

        Trace.Write(string.Format("Request completed in {0} ms, Request: {1}, Response: {2}",
                durationMs, 
                JsonConvert.SerializeObject(requestToLog),
                JsonConvert.SerializeObject(responseToLog)));
}

注意事项:

  • Headers、Url段、QueryString参数、body等都被视为RestSharp的参数,所有这些出现在请求的参数集合中,带有它们相应类型。
  • 日志方法必须在请求完成后调用。这是因为RestSharp的工作方式,Execute方法将添加头部、运行验证器(如果已配置),等等,所有这些都将修改请求。因此,在记录请求之前,应先调用Execute方法,以使发送的所有真实参数均被记录。
  • RestSharp本身永远不会抛出异常(而是错误保存在response.ErrorException属性中),但我认为反序列化可能会抛出异常(不确定),此外,我需要记录原始响应,所以我选择实现自己的反序列化。
  • 请注意,当将参数值转换为生成Uri时,RestSharp使用其自己的格式,因此将参数序列化以记录它们可能不会显示与Uri中放置的完全相同的内容。这就是为什么IRestClient.BuildUri方法非常酷,可以获取实际调用的Uri(包括基础url、替换的url段、添加的queryString参数等)。
  • 编辑:还要注意,RestSharp用于body的序列化器可能与此代码使用的不同,因此我想代码可以调整使用request.JsonSerializer.Serialize()来渲染body参数(我还没有尝试过这个)。
  • 需要一些自定义代码来在日志中实现枚举值的良好描述。
  • StopWatch的使用方式可以调整,以包括反序列化测量。

以下是一个基本完整的带有日志记录的基类示例(使用NLog):

using System;
using System.Diagnostics;
using System.Linq;
using NLog;
using Newtonsoft.Json;
using RestSharp;

namespace Apis
{
    public abstract class RestApiBase
    {
        protected readonly IRestClient _restClient;
        protected readonly ILogger _logger;

        protected RestApiBase(IRestClient restClient, ILogger logger)
        {
            _restClient = restClient;
            _logger = logger;
        }

        protected virtual IRestResponse Execute(IRestRequest request)
        {
            IRestResponse response = null;
            var stopWatch = new Stopwatch();

            try
            {
                stopWatch.Start();
                response = _restClient.Execute(request);
                stopWatch.Stop();

                // CUSTOM CODE: Do more stuff here if you need to...

                return response;
            }
            catch (Exception e)
            {
                // Handle exceptions in your CUSTOM CODE (restSharp will never throw itself)
            }
            finally
            {
                LogRequest(request, response, stopWatch.ElapsedMilliseconds);
            }

            return null;
        }

        protected virtual T Execute<T>(IRestRequest request) where T : new()
        {
            IRestResponse response = null;
            var stopWatch = new Stopwatch();

            try
            {
                stopWatch.Start();
                response = _restClient.Execute(request);
                stopWatch.Stop();

                // CUSTOM CODE: Do more stuff here if you need to...

                // We can't use RestSharp deserialization because it could throw, and we need a clean response
                // We need to implement our own deserialization.
                var returnType = JsonConvert.DeserializeObject<T>(response.Content);
                return returnType;
            }
            catch (Exception e)
            {
                // Handle exceptions in your CUSTOM CODE (restSharp will never throw itself)
                // Handle exceptions in deserialization
            }
            finally
            {
                LogRequest(request, response, stopWatch.ElapsedMilliseconds);
            }

            return default(T);
        }

        private void LogRequest(IRestRequest request, IRestResponse response, long durationMs)
        {
            _logger.Trace(() =>
            {
                var requestToLog = new
                {
                    resource = request.Resource,
                    // Parameters are custom anonymous objects in order to have the parameter type as a nice string
                    // otherwise it will just show the enum value
                    parameters = request.Parameters.Select(parameter => new
                    {
                        name = parameter.Name,
                        value = parameter.Value,
                        type = parameter.Type.ToString()
                    }),
                    // ToString() here to have the method as a nice string otherwise it will just show the enum value
                    method = request.Method.ToString(),
                    // This will generate the actual Uri used in the request
                    uri = _restClient.BuildUri(request),
                };

                var responseToLog = new
                {
                    statusCode = response.StatusCode,
                    content = response.Content,
                    headers = response.Headers,
                    // The Uri that actually responded (could be different from the requestUri if a redirection occurred)
                    responseUri = response.ResponseUri,
                    errorMessage = response.ErrorMessage,
                };

                return string.Format("Request completed in {0} ms, Request: {1}, Response: {2}",
                    durationMs, JsonConvert.SerializeObject(requestToLog),
                    JsonConvert.SerializeObject(responseToLog));
            });
        }
    }
}

这个类将记录类似以下格式的内容(为了方便粘贴,已经进行了漂亮的格式化):
Request completed in 372 ms, Request : {
    "resource" : "/Event/Create/{hostId}/{startTime}",
    "parameters" : [{
            "name" : "hostId",
            "value" : "116644",
            "type" : "UrlSegment"
        }, {
            "name" : "startTime",
            "value" : "2016-05-18T19:48:58.9744911Z",
            "type" : "UrlSegment"
        }, {
            "name" : "application/json",
            "value" : "{\"durationMinutes\":720,\"seats\":100,\"title\":\"Hello StackOverflow!\"}",
            "type" : "RequestBody"
        }, {
            "name" : "api_key",
            "value" : "123456",
            "type" : "QueryString"
        }, {
            "name" : "Accept",
            "value" : "application/json, application/xml, text/json, text/x-json, text/javascript, text/xml",
            "type" : "HttpHeader"
        }
    ],
    "method" : "POST",
    "uri" : "http://127.0.0.1:8000/Event/Create/116644/2016-05-18T19%3A48%3A58.9744911Z?api_key=123456"
}, Response : {
    "statusCode" : 200,
    "content" : "{\"eventId\":2000045,\"hostId\":116644,\"scheduledLength\":720,\"seatsReserved\":100,\"startTime\":\"2016-05-18T19:48:58.973Z\"",
    "headers" : [{
            "Name" : "Access-Control-Allow-Origin",
            "Value" : "*",
            "Type" : 3
        }, {
            "Name" : "Access-Control-Allow-Methods",
            "Value" : "POST, GET, OPTIONS, PUT, DELETE, HEAD",
            "Type" : 3
        }, {
            "Name" : "Access-Control-Allow-Headers",
            "Value" : "X-PINGOTHER, Origin, X-Requested-With, Content-Type, Accept",
            "Type" : 3
        }, {
            "Name" : "Access-Control-Max-Age",
            "Value" : "1728000",
            "Type" : 3
        }, {
            "Name" : "Content-Length",
            "Value" : "1001",
            "Type" : 3
        }, {
            "Name" : "Content-Type",
            "Value" : "application/json",
            "Type" : 3
        }, {
            "Name" : "Date",
            "Value" : "Wed, 18 May 2016 17:44:16 GMT",
            "Type" : 3
        }
    ],
    "responseUri" : "http://127.0.0.1:8000/Event/Create/116644/2016-05-18T19%3A48%3A58.9744911Z?api_key=123456",
    "errorMessage" : null
}

希望您会发现这个对您有用!

2
非常实用且紧凑 - Konstantin Chernov
2
谢谢。非常好用!@LucasG.Devescovi,您能分享一下您的装饰器代码吗? - Misiu
我很高兴它对你有用!关于装饰器代码,是@DavidKeaveny编写的,我没有。谢谢 ;) - LucasMetal
2
有一个Nuget包可以自动记录RestSharp请求和响应到Serilog:https://www.nuget.org/packages/RestSharp.Serilog.Auto/ - yallie
RestSharp 现在支持使用 RestRequest.OnBeforeRequest 事件。 - Andrew Richesson
显示剩余5条评论

31

.net提供了自己的强大日志记录功能。 这可以通过配置文件打开。

我在这里找到了这个提示here。 John Sheehan指向了如何配置网络跟踪文章。 (注意:我编辑了提供的配置,关闭了我不需要的低级别日志记录)。

  <system.diagnostics>
    <sources>
      <source name="System.Net" tracemode="protocolonly" maxdatasize="1024">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.Cache">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
      <source name="System.Net.Http">
        <listeners>
          <add name="System.Net"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net" value="Verbose"/>
      <add name="System.Net.Cache" value="Verbose"/>
      <add name="System.Net.Http" value="Verbose"/>
      <add name="System.Net.Sockets" value="Verbose"/>
      <add name="System.Net.WebSockets" value="Verbose"/>
    </switches>
    <sharedListeners>
      <add name="System.Net"
        type="System.Diagnostics.TextWriterTraceListener"
        initializeData="network.log"
      />
    </sharedListeners>
    <trace autoflush="true"/>
  </system.diagnostics>

请注意:请删除侦听器名称中的空格 - 否则诊断会抛出“未找到共享侦听器异常”。 - Juri
1
输出将在上述配置的“调试”窗口中显示。请确保在开始调试之前打开它。 - Joshua Enfield
1
不幸的是,如果您正在使用Mono运行时,这并不起作用。 - Kris Oye
对我来说非常好用! - Tilendor
这个设置救了我的一天。 - user1641519

8

您需要循环遍历request.Parameters列表,并将其格式化为您喜欢的任何格式的字符串。

var sb = new StringBuilder();
foreach(var param in request.Parameters)
{
    sb.AppendFormat("{0}: {1}\r\n", param.Name, param.Value);
}
return sb.ToString();

如果您希望输出显示请求头,然后类似于Fiddler的请求正文,您只需要按照请求头和请求体的顺序排序集合。集合中的参数(Parameter)对象具有类型(Type)参数枚举。

7

我在RestSharp的示例代码中找到了下面的代码。它允许您打印原始响应。

client.ExecuteAsync(request, response =>
                   {
                       Console.WriteLine(response.Content);
                   });

3
我知道这篇帖子已经快10年了,对此表示抱歉。 现在你可以在发送请求之前和接收响应之后指定代理。
这是我实现这个特定需求的方式。
public class TraceRequest : RestRequest
{

    #region Properties

    private bool TraceStreamContent { get; set; }

    #endregion

    #region Constructor

    public TraceRequest(string pResource, bool pTraceStreamContent)
        : base(pResource)
    {
        this.TraceStreamContent = pTraceStreamContent;
        this.InitializeLogs();
    }

    #endregion

    #region Methods

    private void InitializeLogs()
    {
        this.OnBeforeRequest = this.OnBeforeRequestMethod;
        this.OnAfterRequest = this.OnAfterRequestMethod;
    }

    private ValueTask OnBeforeRequestMethod(HttpRequestMessage pMessage)
    {
        var builder = new StringBuilder();

        builder.AppendLine("------------------------------");
        builder.AppendLine("REQUEST [{0}] {1}", pMessage.Method, pMessage.RequestUri);

        foreach (var header in pMessage.Headers)
        {
            builder.AppendLine("  {0}: {1}", header.Key, string.Join(';', header.Value));
        }

        if (this.TraceStreamContent)
        {
            var stream = pMessage.Content.ReadAsStream();

            this.ReadStream(stream, builder);
        }
        else
        {
            this.ReadContent(pMessage.Content, builder);
        }

        builder.AppendLine("------------------------------");

        var content = builder.ToString();

        Console.WriteLine(content);

        return ValueTask.CompletedTask;
    }

    private void ReadContent(HttpContent pContent, StringBuilder pBuilder)
    {
        foreach (var header in pContent.Headers)
        {
            pBuilder.AppendLine("  {0}: {1}", header.Key, string.Join(';', header.Value));
        }

        this.ReadContent(pContent as StreamContent, pBuilder);
        this.ReadContent(pContent as StringContent, pBuilder);
        this.ReadContent(pContent as MultipartFormDataContent, pBuilder);

        Console.WriteLine();
    }

    private void ReadContent(MultipartFormDataContent pContent, StringBuilder pBuilder)
    {
        if (pContent != null)
        {
            foreach (var content in pContent)
            {
                pBuilder.AppendLine();
                this.ReadContent(content, pBuilder);
            }
        }
    }

    private void ReadContent(StreamContent pContent, StringBuilder pBuilder)
    {
        if (pContent != null)
        {
            var stream = pContent.ReadAsStream();
            pBuilder.AppendLine("  contains {0} bytes", stream.Length);
        }
    }

    private void ReadContent(StringContent pContent, StringBuilder pBuilder)
    {
        if (pContent != null)
        {
            var stream = pContent.ReadAsStream();
            pBuilder.Append("  ");
            this.ReadStream(stream, pBuilder);
        }
    }

    private void ReadStream(Stream pStream, StringBuilder pBuilder)
    {
        var index = 0L;
        var length = pStream.Length;
        var buffer = new byte[1024];

        while (index < length - 1)
        {
            var read = pStream.Read(buffer, 0, 1024);
            var result = Encoding.UTF8.GetString(buffer, 0, read);

            pBuilder.Append(result);

            index += read;
        }

        pBuilder.AppendLine();

        pStream.Seek(0L, SeekOrigin.Begin);
    }

    private ValueTask OnAfterRequestMethod(HttpResponseMessage pMessage)
    {
        var builder = new StringBuilder();

        builder.AppendLine("------------------------------");
        builder.AppendLine("RESPONSE {2} [{0}] {1}", pMessage.RequestMessage.Method, pMessage.RequestMessage.RequestUri, pMessage.StatusCode);

        foreach (var header in pMessage.Headers)
        {
            builder.AppendLine("  {0}: {1}", header.Key, string.Join(';', header.Value));
        }

        if (this.TraceStreamContent)
        {
            var stream = pMessage.Content.ReadAsStream();

            this.ReadStream(stream, builder);
        }
        else
        {
            this.ReadContent(pMessage.Content, builder);
        }

        builder.AppendLine("------------------------------");

        var content = builder.ToString();

        Console.WriteLine(content);

        return ValueTask.CompletedTask;
    }

    #endregion
}

现在您可以像这样使用TraceRequest类,布尔值TraceStreamContent将启用完整的HTTP跟踪。当发送附件文件时可能会太大。
        var client = new RestClient("https://dev.test.cloud:4511")
        {
            Authenticator = new HttpBasicAuthenticator("user", "password")
        };
        var request = new TraceRequest("test", true)
            .AddJsonBody(fax)
            .AddFile("first", new byte[] { 65, 66, 67, 68 }, "first.txt")
            .AddFile("second", new byte[] { 69, 70, 71, 72 }, "second.txt");

        var response = client.Post(request);

希望这能对某人有所帮助!
编辑
这是StringBuilder缺失的扩展方法。
    public static void AppendLine(this StringBuilder pBuilder, string pFormat, params object[] pArgs)
    {
        pBuilder.AppendFormat(pFormat, pArgs);
        pBuilder.AppendLine();
    }

1
代码能够正常运行,但我必须在builder.AppendLine中进行更改,我认为@Mad使用了一个扩展。 - Alexandre Swioklo
@Alexandre Swioklo 谢谢你,我已经进行了编辑。 - Mad hatter

3
您可以使用 Fiddler 工具来捕获 HTTP 请求。

1
有时候,当我们尝试解决客户遇到的问题而不想安装 Fiddler 时,记录这些类型的信息是很好的选择。 - The Muffin Man
1
我们在这里如何使用Fiddler,需要在配置中更改一些内容吗? - blogbydev
@singsuyash 抱歉,我不记得为什么一年前会这样回答了。我重新阅读了问题,现在认为那不是一个好的答案。但我看到我投票支持了这个答案 https://dev59.com/3mUo5IYBdhLWcg3w5Syl#21989958 - Neshta
Fiddler工具可以使用,但有时需要进行一些设置调整(例如:解密流量)。 - live-love

1
作为部分解决方案,您可以使用RestClient的BuildUri方法:
var response = client.Execute(request);
if (response.StatusCode != HttpStatusCode.OK)
    throw new Exception($"Failed to send request: {client.BuildUri(request)}");

0

如果您只想查看原始响应,请尝试覆盖反序列化程序(大部分内容来自restsharp 107.3.0):

using RestSharp.Serializers;
// …
public class StupidLogSerialiser  : IRestSerializer, ISerializer, IDeserializer {
    public string Serialize(object obj) => null;
    public string Serialize(Parameter bodyParameter) => Serialize(null);
    public T Deserialize<T>(RestResponse response) {
        Console.WriteLine(response.Content);
        return default(T);
    }
    public string ContentType { get; set; } = "application/json";
    public ISerializer         Serializer           => this;
    public IDeserializer       Deserializer         => this;
    public DataFormat          DataFormat           => DataFormat.Json;
    public string[]            AcceptedContentTypes => RestSharp.Serializers.ContentType.JsonAccept;
    public SupportsContentType SupportsContentType  => contentType => contentType.EndsWith("json", StringComparison.InvariantCultureIgnoreCase);
}
// …
client.UseSerializer(() => new StupidLogSerialiser());

0

一个选项是使用您自己的身份验证器。 RestSharp允许注入身份验证器:

var client = new RestClient();
client.Authenticator = new YourAuthenticator(); // implements IAuthenticator

public interface IAuthenticator
{
    void Authenticate(IRestClient client, IRestRequest request);
}

internal class YourAuthenticator: IAuthenticator
{
  public void Authenticate(IRestClient client, IRestRequest request)
  {
    // log request
  }
}

认证器的Authenticate方法是在调用RestClient.Execute或RestClient.Execute时第一个被调用的方法。Authenticate方法会传递当前正在执行的RestRequest,使您可以访问请求数据的每个部分(头部、参数等)。 来自RestSharp wiki。这意味着在Authenticate方法中,您可以记录请求。

1
我需要获取原始请求值,就像发送的那样,所以我想知道如何实现认证器将帮助我,你能帮忙吗?谢谢。 - Andre
@Andre 我添加了如何实现身份验证器的内容。通常记录头部和主体就足够了,否则仍然可以访问请求的所有参数。 - giganoide

-3

你可以尝试使用

Trace.WriteLine(request.JsonSerializer.Serialize(request));

获取请求并

response.Content(); // as Luo have suggested

请求不同于 Fiddler 显示的内容,但它包含所有数据并且可读(末尾可能有一些 RestSharp 垃圾字符)。


2
"Trace.WriteLine(request.JsonSerializer.Serialize(request))" 不会给你原始请求,它只会序列化对象,这是完全不同的事情。 - Alex.F

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接