RestSharp печатает необработанные запросы и заголовки ответов

Я использую 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;
}

Таким образом, можно ли распечатать необработанный запрос и ответ?

Ответ 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, body и т.д. все считаются параметрами для 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 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

.net обеспечивает собственную, но мощную функцию ведения журнала. Это можно включить через файл конфигурации.

Я нашел этот совет здесь. Джон Шихан указал на статью Как настроить трассировку сети. (примечание: я отредактировал предоставленную конфигурацию, отключил ненужный (для меня) журнал низкого уровня).

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

Ответ 3

Вам нужно пройти через список 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.

Ответ 4

Я только что нашел код ниже в примерах RestSharp. Он позволяет печатать ваш сырой ответ.

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

Ответ 5

Вы можете использовать Fiddler для захвата HTTP-запросов.

Ответ 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 authenticators - это первое, что вызывается при вызове RestClient.Execute или RestClient.Execute. Метод Authenticate передается выполняемому в настоящее время RestRequest, предоставляя вам доступ ко всем частям данных запроса (заголовкам, параметрам и т.д.) Из вики RestSharp

Это означает, что в методе Authenticate вы можете зарегистрировать запрос.

Ответ 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 в конце).