From 96a84128c520fd86546138896acd08f3466f8fee Mon Sep 17 00:00:00 2001 From: Luk Vermeulen Date: Sat, 15 Apr 2017 18:34:45 +0200 Subject: [PATCH] add conneg --- src/Flurl.Http.Xml/CapturedXmlContent.cs | 11 +- src/Flurl.Http.Xml/FlurlClientExtensions.cs | 112 ++++++++------ .../HttpResponseMessageExtensions.cs | 139 +++++++++++------- src/Flurl.Http.Xml/UrlExtensions.cs | 56 +++---- .../StringExtensionsShould.cs | 1 - .../UrlExtensionsShould.cs | 16 ++ 6 files changed, 202 insertions(+), 133 deletions(-) diff --git a/src/Flurl.Http.Xml/CapturedXmlContent.cs b/src/Flurl.Http.Xml/CapturedXmlContent.cs index 8fea848..9a485db 100644 --- a/src/Flurl.Http.Xml/CapturedXmlContent.cs +++ b/src/Flurl.Http.Xml/CapturedXmlContent.cs @@ -9,10 +9,11 @@ namespace Flurl.Http.Xml /// public class CapturedXmlContent : CapturedStringContent { - /// - /// Initializes a new instance of the class. - /// - /// The XML. - public CapturedXmlContent(string xml) : base(xml, Encoding.UTF8, "application/xml") { } + /// + /// Initializes a new instance of the class. + /// + /// The XML. + /// The media-type. + public CapturedXmlContent(string xml, string mediaType) : base(xml, Encoding.UTF8, mediaType) { } } } \ No newline at end of file diff --git a/src/Flurl.Http.Xml/FlurlClientExtensions.cs b/src/Flurl.Http.Xml/FlurlClientExtensions.cs index 1eb1a55..be087c5 100644 --- a/src/Flurl.Http.Xml/FlurlClientExtensions.cs +++ b/src/Flurl.Http.Xml/FlurlClientExtensions.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -12,47 +14,49 @@ namespace Flurl.Http.Xml /// public static class FlurlClientExtensions { - /// - /// Sends an asynchronous GET request. - /// - /// - /// The client. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// A Task whose result is the XML response body deserialized to an object of type T. - /// - public static Task GetXmlAsync(this FlurlClient client, CancellationToken cancellationToken) + /// + /// Sends an asynchronous GET request. + /// + /// + /// The client. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// A Task whose result is the XML response body deserialized to an object of type T. + /// + public static Task GetXmlAsync(this IFlurlClient client, CancellationToken cancellationToken) { return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken).ReceiveXml(); } - /// - /// Sends an asynchronous GET request. - /// - /// A Task whose result is the XML response body deserialized to an object of type T. - public static Task GetXmlAsync(this FlurlClient client) + /// + /// Sends an asynchronous GET request. + /// + /// The client. + /// A Task whose result is the XML response body deserialized to an object of type T. + public static Task GetXmlAsync(this IFlurlClient client) { return client.SendAsync(HttpMethod.Get).ReceiveXml(); } - /// - /// Sends an asynchronous GET request. - /// - /// The client. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// A Task whose result is the XML response body parsed into an XDocument. - /// - public static Task GetXDocumentAsync(this FlurlClient client, CancellationToken cancellationToken) + /// + /// Sends an asynchronous GET request. + /// + /// The client. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// A Task whose result is the XML response body parsed into an XDocument. + /// + public static Task GetXDocumentAsync(this IFlurlClient client, CancellationToken cancellationToken) { return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken).ReceiveXDocument(); } - /// - /// Sends an asynchronous GET request. - /// - /// A Task whose result is the XML response body parsed into an XDocument. - public static Task GetXDocumentAsync(this FlurlClient client) + /// + /// Sends an asynchronous GET request. + /// + /// The client. + /// A Task whose result is the XML response body parsed into an XDocument. + public static Task GetXDocumentAsync(this IFlurlClient client) { return client.SendAsync(HttpMethod.Get).ReceiveXDocument(); } @@ -75,7 +79,7 @@ public static Task> GetXElementsFromXPath(this FlurlClient /// Sends an asynchronous GET request. /// /// A Task whose result is the XML response body parsed into a collection of XElements. - public static Task> GetXElementsFromXPath(this FlurlClient client, string expression) + public static Task> GetXElementsFromXPath(this IFlurlClient client, string expression) { return client.SendAsync(HttpMethod.Get).ReceiveXElementsFromXPath(expression); } @@ -90,7 +94,7 @@ public static Task> GetXElementsFromXPath(this FlurlClient /// /// A Task whose result is the XML response body parsed into a collection of XElements. /// - public static Task> GetXElementsFromXPath(this FlurlClient client, string expression, IXmlNamespaceResolver resolver, CancellationToken cancellationToken) + public static Task> GetXElementsFromXPath(this IFlurlClient client, string expression, IXmlNamespaceResolver resolver, CancellationToken cancellationToken) { return client.SendAsync(HttpMethod.Get, cancellationToken: cancellationToken).ReceiveXElementsFromXPath(expression, resolver); } @@ -99,23 +103,39 @@ public static Task> GetXElementsFromXPath(this FlurlClient /// Sends an asynchronous GET request. /// /// A Task whose result is the XML response body parsed into a collection of XElements. - public static Task> GetXElementsFromXPath(this FlurlClient client, string expression, IXmlNamespaceResolver resolver) + public static Task> GetXElementsFromXPath(this IFlurlClient client, string expression, IXmlNamespaceResolver resolver) { return client.SendAsync(HttpMethod.Get).ReceiveXElementsFromXPath(expression, resolver); } - /// - /// Sends an asynchronous POST request. - /// - /// The client. - /// Contents of the request body. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// A Task whose result is the received HttpResponseMessage. - /// - public static Task PostXmlAsync(this FlurlClient client, object data, CancellationToken cancellationToken) + private static string GetMediaType(this IFlurlClient client) + { + if (client.HttpClient.DefaultRequestHeaders.Accept.Any()) + { + // return media type of first accepted media type containing "xml", else of first accepted media type + var requestHeaders = client.HttpClient.DefaultRequestHeaders; + var accept = requestHeaders.Accept.First(x => x.MediaType.IndexOf("xml", StringComparison.OrdinalIgnoreCase) >= 0) + ?? requestHeaders.Accept.First(); + + return accept.MediaType; + } + + // no accepted media type present, return default + return "application/xml"; + } + + /// + /// Sends an asynchronous POST request. + /// + /// The client. + /// Contents of the request body. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// A Task whose result is the received HttpResponseMessage. + /// + public static Task PostXmlAsync(this IFlurlClient client, object data, CancellationToken cancellationToken) { - var content = new CapturedXmlContent(client.Settings.XmlSerializer().Serialize(data)); + var content = new CapturedXmlContent(client.Settings.XmlSerializer().Serialize(data), client.GetMediaType()); return client.SendAsync(HttpMethod.Post, content: content, cancellationToken: cancellationToken); } @@ -127,9 +147,9 @@ public static Task PostXmlAsync(this FlurlClient client, ob /// /// A Task whose result is the received HttpResponseMessage. /// - public static Task PostXmlAsync(this FlurlClient client, object data) + public static Task PostXmlAsync(this IFlurlClient client, object data) { - var content = new CapturedXmlContent(client.Settings.XmlSerializer().Serialize(data)); + var content = new CapturedXmlContent(client.Settings.XmlSerializer().Serialize(data), client.GetMediaType()); return client.SendAsync(HttpMethod.Post, content: content); } } diff --git a/src/Flurl.Http.Xml/HttpResponseMessageExtensions.cs b/src/Flurl.Http.Xml/HttpResponseMessageExtensions.cs index c617573..c6bf8d0 100644 --- a/src/Flurl.Http.Xml/HttpResponseMessageExtensions.cs +++ b/src/Flurl.Http.Xml/HttpResponseMessageExtensions.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; @@ -24,62 +26,91 @@ private static HttpCall GetHttpCall(HttpRequestMessage request) return null; } - /// - /// Deserializes XML-formatted HTTP response body to object of type T. Intended to chain off an async HTTP. - /// - /// A type whose structure matches the expected XML response. - /// A Task whose result is an object containing data in the response body. - /// x = await url.PosAsync(data).ReceiveXml<T>() - public static async Task ReceiveXml(this Task response) - { - var resp = await response.ConfigureAwait(false); - var call = GetHttpCall(resp.RequestMessage); - try - { - using (var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false)) - { - return call.Settings.XmlSerializer().Deserialize(stream); - } - } - catch (Exception ex) - { - var s = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); - call.Exception = ex; - throw new FlurlHttpException(call, s, ex); - } - } + private static string GetMediaType(HttpRequestMessage request) + { + if (request.Headers.Accept.Any()) + { + // return media type of first accepted media type containing "xml", else of first accepted media type + var acceptHeader = request.Headers.Accept.First(x => x.MediaType.IndexOf("xml", StringComparison.OrdinalIgnoreCase) >= 0) + ?? request.Headers.Accept.First(); - /// - /// Parses XML-formatted HTTP response body into an XDocument. Intended to chain off an async call. - /// - /// A Task whose result is an XDocument containing XML data from the response body. - /// d = await url.PostAsync(data).ReceiveXDocument() - public static async Task ReceiveXDocument(this Task response) - { - var resp = await response.ConfigureAwait(false); - var call = GetHttpCall(resp.RequestMessage); - try - { - using (var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false)) - using (var streamReader = new StreamReader(stream)) - { - return XDocument.Parse(streamReader.ReadToEnd()); - } - } - catch (Exception ex) - { - var s = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); - call.Exception = ex; - throw new FlurlHttpException(call, s, ex); - } - } + return acceptHeader.MediaType; + } - /// - /// Parses XML-formatted HTTP response body into a collection of XElements. Intended to chain off an async call. - /// - /// A Task whose result is a collection of XElements from an XDocument containing XML data from the response body. - /// d = await url.PostAsync(data).ReceiveXElementsFromXPath(xpathExpression) - public static async Task> ReceiveXElementsFromXPath(this Task response, string expression) + // no accepted media type present, return default + return "application/xml"; + } + + /// + /// Receives XML-formatted HTTP response body. Intended to chain off an async HTTP. + /// + /// The response. + /// A Task whose result is a response message containing data in the response body. + /// x = await url.PostAsync(data).ReceiveXmlResponseMessage() + public static async Task ReceiveXmlResponseMessage(this Task responseMessage) + { + var response = await responseMessage.ConfigureAwait(false); + response.Content.Headers.ContentType = new MediaTypeHeaderValue(GetMediaType(response.RequestMessage)); + + return response; + } + + private static async Task ReceiveFromXmlStream(this Task response, Func streamHandler) + { + var resp = await ReceiveXmlResponseMessage(response); + var call = GetHttpCall(resp.RequestMessage); + + try + { + using (var stream = await resp.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + return streamHandler(call, stream); + } + } + catch (Exception ex) + { + var s = await resp.Content.ReadAsStringAsync().ConfigureAwait(false); + call.Exception = ex; + throw new FlurlHttpException(call, s, ex); + } + } + + /// + /// Deserializes XML-formatted HTTP response body to object of type T. Intended to chain off an async HTTP. + /// + /// A type whose structure matches the expected XML response. + /// The response. + /// A Task whose result is an object containing data in the response body. + /// x = await url.PostAsync(data).ReceiveXml<T>() + public static async Task ReceiveXml(this Task response) + { + return await ReceiveFromXmlStream(response, (call, stm) => + call.Settings.XmlSerializer().Deserialize(stm)); + } + + /// + /// Parses XML-formatted HTTP response body into an XDocument. Intended to chain off an async call. + /// + /// The response. + /// A Task whose result is an XDocument containing XML data from the response body. + /// d = await url.PostAsync(data).ReceiveXDocument() + public static async Task ReceiveXDocument(this Task response) + { + return await ReceiveFromXmlStream(response, (call, stm) => + { + using (var streamReader = new StreamReader(stm)) + { + return XDocument.Parse(streamReader.ReadToEnd()); + } + }); + } + + /// + /// Parses XML-formatted HTTP response body into a collection of XElements. Intended to chain off an async call. + /// + /// A Task whose result is a collection of XElements from an XDocument containing XML data from the response body. + /// d = await url.PostAsync(data).ReceiveXElementsFromXPath(xpathExpression) + public static async Task> ReceiveXElementsFromXPath(this Task response, string expression) { var doc = await response.ReceiveXDocument().ConfigureAwait(false); return doc.XPathSelectElements(expression); diff --git a/src/Flurl.Http.Xml/UrlExtensions.cs b/src/Flurl.Http.Xml/UrlExtensions.cs index f84b7e5..1d71db7 100644 --- a/src/Flurl.Http.Xml/UrlExtensions.cs +++ b/src/Flurl.Http.Xml/UrlExtensions.cs @@ -12,39 +12,41 @@ namespace Flurl.Http.Xml /// public static class UrlExtensions { - /// - /// Sends an asynchronous GET request. - /// - /// - /// The URL. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// - /// A Task whose result is the XML response body deserialized to an object of type T. - /// - public static Task GetXmlAsync(this Url url, CancellationToken cancellationToken) + /// + /// Sends an asynchronous GET request. + /// + /// + /// The URL. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// + /// A Task whose result is the XML response body deserialized to an object of type T. + /// + public static Task GetXmlAsync(this Url url, CancellationToken cancellationToken) => new FlurlClient(url, true).GetXmlAsync(cancellationToken); - /// - /// Sends an asynchronous GET request. - /// - /// A Task whose result is the XML response body deserialized to an object of type T. - public static Task GetXmlAsync(this Url url) + /// + /// Sends an asynchronous GET request. + /// + /// The URL. + /// A Task whose result is the XML response body deserialized to an object of type T. + public static Task GetXmlAsync(this Url url) => new FlurlClient(url, true).GetXmlAsync(); - /// - /// Sends an asynchronous GET request. - /// - /// The URL. - /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. - /// A Task whose result is the XML response body parsed into an XDocument. - public static Task GetXDocumentAsync(this Url url, CancellationToken cancellationToken) + /// + /// Sends an asynchronous GET request. + /// + /// The URL. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A Task whose result is the XML response body parsed into an XDocument. + public static Task GetXDocumentAsync(this Url url, CancellationToken cancellationToken) => new FlurlClient(url, true).GetXDocumentAsync(cancellationToken); - /// - /// Sends an asynchronous GET request. - /// - /// A Task whose result is the XML response body parsed into an XDocument. - public static Task GetXDocumentAsync(this Url url) => new FlurlClient(url, true).GetXDocumentAsync(); + /// + /// Sends an asynchronous GET request. + /// + /// The URL. + /// A Task whose result is the XML response body parsed into an XDocument. + public static Task GetXDocumentAsync(this Url url) => new FlurlClient(url, true).GetXDocumentAsync(); /// /// Sends an asynchronous GET request. diff --git a/test/Flurl.Http.Xml.Tests/StringExtensionsShould.cs b/test/Flurl.Http.Xml.Tests/StringExtensionsShould.cs index 820551d..6dc9958 100644 --- a/test/Flurl.Http.Xml.Tests/StringExtensionsShould.cs +++ b/test/Flurl.Http.Xml.Tests/StringExtensionsShould.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; -using Flurl.Http.Configuration; using Flurl.Http.Xml.Tests.Factories; using Flurl.Http.Xml.Tests.Models; using Xunit; diff --git a/test/Flurl.Http.Xml.Tests/UrlExtensionsShould.cs b/test/Flurl.Http.Xml.Tests/UrlExtensionsShould.cs index 80a50a2..f14092e 100644 --- a/test/Flurl.Http.Xml.Tests/UrlExtensionsShould.cs +++ b/test/Flurl.Http.Xml.Tests/UrlExtensionsShould.cs @@ -166,5 +166,21 @@ public async Task PostXmlToXDocumentWithCancellationTokenAsync() AssertXDocument(result, 3, "Test"); } + + [Theory] + [InlineData("text/xml", "text/xml")] + [InlineData("text/something+xml", "text/something+xml")] + [InlineData(null, "application/xml")] + public async Task ReceiveCorrectMediaType(string acceptMediaType, string expectedContentType) + { + FlurlHttp.Configure(c => c.HttpClientFactory = new EchoHttpClientFactory()); + + var result = await new Url("https://some.url") + .WithHeader("Accept", acceptMediaType) + .PostXmlAsync(new TestModel { Number = 3, Text = "Test" }) + .ReceiveXmlResponseMessage(); + + Assert.Equal(expectedContentType, result?.Content?.Headers?.ContentType?.MediaType); + } } }