Processing Requests and Responses with HttpClient

In a recent project, I wanted to log outgoing requests and incoming responses using my own (JSON) format. This can be accomplished using a custom handler derived from DelegatingHandler. Here is an implementation that can be used in e.g. a console program:

        internal class LoggingHandler : DelegatingHandler
        {
            private static int _requestNumber = 0;
            private static readonly string LogFolder;

            static LoggingHandler()
            {
                LogFolder = Path.Combine(Path.GetTempPath(), "Log");
                if (Directory.Exists(LogFolder))
                {
                    foreach (var file in Directory.GetFiles(LogFolder))
                    {
                        File.Delete(file);
                    }
                }
                else
                    Directory.CreateDirectory(LogFolder);
            }

            protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                try
                {
                    var response = await base.SendAsync(request, cancellationToken);

                    if (request.Method == HttpMethod.Post)
                    {
                        var logEntry = new LogEntry
                        {
                            Request = new Request
                            {
                                Method = request.Method,
                                Uri = request.RequestUri,
                                Headers = request.Headers,
                                Content = await GetContent(request.Content)
                            },
                            Response = new Response
                            {
                                Headers = response.Headers,
                                StatusCode = response.StatusCode,
                                Content = await GetContent(response.Content)
                            }
                        };

                        var logEntryString = JsonConvert.SerializeObject(logEntry, Formatting.Indented);
                        var parts = request.RequestUri.AbsolutePath.Split('/');
                        string controller = parts.Length >= 6 ? parts[5] : "";
                        string path = Path.Combine(LogFolder, string.Format("{0:0000}0_{1}_{2}_{3}.json", ++_requestNumber, logEntry.Request.Method, controller, logEntry.Response.StatusCode));
                        File.WriteAllText(path, logEntryString, Encoding.UTF8);
                        System.Diagnostics.Debug.WriteLine("Wrote {0:N0} characters to {1}", logEntryString.Length, path);
                    }

                    return response;
                }
                catch (Exception ex)
                {
                    logger.Fatal(ex);
                    throw;
                }
            }

            private static async Task<object> GetContent(HttpContent content)
            {
                try
                {
                    if (content == null)
                        return null;
                    string s = await content.ReadAsStringAsync();
                    s = s.Trim();
                    if (s.StartsWith("["))
                        return JArray.Parse(s);
                    else if (s.StartsWith("{"))
                        return JObject.Parse(s);
                    else
                        return s;
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
            }
        }

Usage is simple:

var httpClient = new HttpClient(new LoggingHandler());

However, the above implementation didn’t work in a web application. The call to base.SendAsync never returns for some reason. I suppose it has to do with a different threading model. Luckily, I found the solution, to use Task.ContinueWith, in the following blog post: http://byterot.blogspot.se/2012/05/aspnet-web-api-series-messagehandler.html. Here is the alternative implementation.

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                _logger.Debug($"{GetType().Name} sending request  '{request.RequestUri}'...");
                return base.SendAsync(request, cancellationToken).ContinueWith(task =>
                {
                    _logger.Debug($"Task status is {task.Status}");
                    var response = task.Result;
                    _logger.Debug($"{GetType().Name} got response from'{request.RequestUri}'.");

                    if (request.Method == HttpMethod.Post)
                    {
                        var logEntry = new LogEntry
                        {
                            Request = new Request
                            {
                                Method = request.Method,
                                Uri = request.RequestUri,
                                Headers = request.Headers,
                                Content = GetContent(request.Content)
                            },
                            Response = new Response
                            {
                                Headers = response.Headers,
                                StatusCode = response.StatusCode,
                                Content = GetContent(response.Content)
                            }
                        };

                        var logEntryString = JsonConvert.SerializeObject(logEntry, Formatting.Indented);
                        var controller = GetController(request);
                        string path = Path.Combine(LogFolder, $"{++_requestNumber:0000}0_{logEntry.Request.Method}_{controller}_{logEntry.Response.StatusCode}.json");
                        _logger.Debug($"Writing {logEntryString.Length:N0} characters to {path}...");
                        File.WriteAllText(path, logEntryString, Encoding.UTF8);
                        _logger.Debug($"Wrote {logEntryString.Length:N0} characters to {path}.");
                    }

                    return response;
                }, cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.Fatal(ex.Message, ex);
                throw;
            }
        }

        private static object GetContent(HttpContent content)
        {
            try
            {
                if (content == null)
                    return null;
                string s = content.ReadAsStringAsync().Result;
                s = s.Trim();
                if (s.StartsWith("["))
                    return JArray.Parse(s);
                else if (s.StartsWith("{"))
                    return JObject.Parse(s);
                else
                    return s;
            }
            catch (Exception ex)
            {
                return ex.Message;
            }
        }
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s