RestSharp 打印原始请求和响应标头

Posted

技术标签:

【中文标题】RestSharp 打印原始请求和响应标头【英文标题】:RestSharp print raw request and response headers 【发布时间】:2013-03-19 00:14:44 【问题描述】:

我正在使用RestSharp 调用网络服务。一切都很好,但我想知道是否可以打印发出的原始请求标头和正文以及返回的原始响应标头和响应正文。

这是我创建请求并获得响应的代码

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;

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

【问题讨论】:

你想每次都这样做还是只是为了调试一些东西?如果只是一次性的,那么使用 fiddler 来回获取原始请求 不是一个完整的答案,但您可以编写自己的序列化器/反序列化器并在那里记录生成/使用的 JSON。但是你最好使用上面建议的“嗅探”代理。 @wal 我一直在使用提琴手。我想每次都在我的 .net 应用程序中执行此操作。 你需要正文还是只需要标题? “一切”似乎有点矫枉过正,但我​​不知道你到底想要什么。 @wal 我绝对想要 HTTP 响应代码和响应正文。对于请求,我需要方法、url 和请求正文。 【参考方案1】:

RestSharp 没有提供一种机制来准确实现您想要的功能,并且激活 .Net 跟踪对 IMO 来说有点矫枉过正。

出于记录(调试)目的(例如,我可以在 PROD 中打开一段时间)我发现这种方法非常有用(尽管它有一些关于如何调用它的详细信息,请阅读下面的代码):

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)));

注意事项:

标头、Url 段、QueryString 参数、正文等都被视为 RestSharp 的参数,它们都出现在请求的参数集合中,并具有相应的类型。 必须在请求发生后调用日志方法。这是需要的,因为 RestSharp 的工作方式,Execute 方法将添加标头,运行身份验证器(如果已配置)等,所有这些都将修改请求。因此,为了记录所有发送的真实参数,应该在记录请求之前调用 Execute 方法。 RestSharp 本身永远不会抛出(相反错误会保存在 response.ErrorException 属性中),但我认为反序列化可能会抛出(不确定)而且我需要记录原始响应,所以我选择实现自己的反序列化。 请记住,RestSharp 在转换参数值以生成 Uri 时使用自己的格式,因此序列化参数以记录它们可能不会显示与放入 Uri 中的完全相同的内容。这就是为什么 IRestClient.BuildUri 方法可以很酷地获取实际调用的 Uri(包括基本 url、替换的 url 段、添加的 queryString 参数等)。 编辑: 另外请记住,RestSharp 用于正文的序列化程序可能与此代码使用的不同,所以我猜代码可以调整为使用 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 ***!\"",
            "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

希望你觉得这很有用!

【讨论】:

非常实用且紧凑 我将您的代码修改为 IRestClient 接口而不是基类的装饰器,并使用 log4net 而不是 NLog。 log4net 在写出匿名类型的数组时需要更多帮助,但除此之外它工作得很好。 @DavidKaveny 太棒了!我对向 IRestClient 接口添加装饰器不熟悉,最后哪个类使用它?你有任何链接来检查实施吗?还要检查我是否添加了一个额外的要点;)感谢您的评论。 谢谢。效果很好! @LucasG.Devescovi 你能分享你的装饰器代码吗? 有一个用于自动记录 RestSharp 请求和对 Serilog 响应的 Nuget 包:nuget.org/packages/RestSharp.Serilog.Auto【参考方案2】:

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

我发现了这个提示here。 John Sheehan 指向How to: Configure Network Tracing 文章。 (注:我编辑了提供的配置,关闭了不必要的(对我而言)低级日志记录)。

  <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>

【讨论】:

注意:请删除侦听器名称中的空格 - 否则诊断会抛出“未找到共享侦听器异常”。 输出将显示在具有上述配置的输出窗口的“调试”提要中。请务必在开始调试之前将其打开。 不幸的是,如果您使用的是 Mono 运行时,这将无法正常工作。 对我来说很棒! 这个设置拯救了我的一天。【参考方案3】:

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

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

【讨论】:

【参考方案4】:

您必须遍历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 参数枚举。

【讨论】:

【参考方案5】:

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

【讨论】:

有时,在尝试解决客户在未安装 Fiddler 的情况下遇到的问题时,能够记录这些类型的内容会很好。 这里我们如何使用fiddler,我们需要在配置中改变一些东西吗? @singsuyash 对不起,但我不记得为什么我一年前这么回答了。我再次阅读了这个问题,现在我认为这不是一个好的答案。但我看到我投了这个 asnwer ***.com/a/21989958/1277458 Fiddler 可以工作,但有时您必须调整设置(例如解密流量)【参考方案6】:

一个选项是使用您自己的身份验证器。 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 让您可以访问请求数据的每一部分(标头、 参数等) from RestSharp's wiki

这意味着您可以在 Authenticate 方法中记录请求。

【讨论】:

我需要获取原始请求值,与发送的完全一样,所以我想知道如何实现身份验证器对我有帮助,你能帮忙吗?谢谢 @Andre 我添加了如何实现身份验证器。一般记录header和body就够了,不然还是可以访问到请求的所有参数的。【参考方案7】:

作为部分解决方案,可以使用RestClient的BuildUri方法:

var response = client.Execute(request);
if (response.StatusCode != HttpStatusCode.OK)
    throw new Exception($"Failed to send request: client.BuildUri(request)");

【讨论】:

【参考方案8】:

你可以试试

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

获取请求和

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

请求不一样,正如 Fiddler 所示,但它包含所有数据并且是可读的(最后有一些 RestSharp 垃圾)。

【讨论】:

Trace.WriteLine(request.JsonSerializer.Serialize(request)) 不会给你 raw 请求,它只会序列化对象,这是完全不同的事情。

以上是关于RestSharp 打印原始请求和响应标头的主要内容,如果未能解决你的问题,请参考以下文章

页面重定向时 HttpWebRequest 和 Restsharp 不返回响应

在 AWS Cloudfront 源请求中返回带有 set-cookie 标头的响应

如何使用 Apache 在代理设置中重写位置响应标头?

使用 urllib2.urlopen 时如何访问包含重定向的原始响应标头

使用 java servlet 在重定向响应中丢失 HTTP 自定义标头

我可以使用 tcpdump 获取 HTTP 请求、响应标头和响应正文吗?