Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Commit

Permalink
Merge pull request #155 from microsoft/feature/trimming
Browse files Browse the repository at this point in the history
feature/trimming
  • Loading branch information
baywet authored Oct 23, 2023
2 parents 8d2556c + dd89d43 commit 0699301
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 153 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.2.0] - 2023-10-23

### Added

- Added support for dotnet trimming.

## [1.1.1] - 2023-08-28

- Fixes a bug where the `ParametersNameDecodingHandler` would also decode query parameter values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ namespace Microsoft.Kiota.Http.HttpClientLibrary.Tests.Extensions
public class HttpRequestMessageExtensionsTests
{
private readonly HttpClientRequestAdapter requestAdapter;
public HttpRequestMessageExtensionsTests () {
public HttpRequestMessageExtensionsTests()
{
requestAdapter = new HttpClientRequestAdapter(new AnonymousAuthenticationProvider());
}
[Fact]
Expand Down Expand Up @@ -66,7 +67,7 @@ public async Task CloneAsyncWithHttpContent()
HttpMethod = Method.GET,
URI = new Uri("http://localhost")
};
requestInfo.SetStreamContent(new MemoryStream(Encoding.UTF8.GetBytes("contents")));
requestInfo.SetStreamContent(new MemoryStream(Encoding.UTF8.GetBytes("contents")), "application/octet-stream");
var originalRequest = await requestAdapter.ConvertToNativeRequestAsync<HttpRequestMessage>(requestInfo);
originalRequest.Content = new StringContent("contents");

Expand Down Expand Up @@ -101,10 +102,10 @@ public async Task CloneAsyncWithRequestOption()
Assert.NotNull(clonedRequest);
Assert.Equal(originalRequest.Method, clonedRequest.Method);
Assert.Equal(originalRequest.RequestUri, clonedRequest.RequestUri);
#if NET6_0_OR_GREATER
#if NET5_0_OR_GREATER
Assert.NotEmpty(clonedRequest.Options);
Assert.Equal(redirectHandlerOption, clonedRequest.Options.First().Value);
#elif NET462_OR_GREATER
#else
Assert.NotEmpty(clonedRequest.Properties);
Assert.Equal(redirectHandlerOption, clonedRequest.Properties.First().Value);
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ private async Task DelayTestWithMessage(HttpResponseMessage response, int count,
Message = message;
await Task.Run(async () =>
{
await this._retryHandler.Delay(response, count, delay, out _, new CancellationToken());
await RetryHandler.Delay(response, count, delay, out _, new CancellationToken());
Message += " Work " + count;
});
}
Expand Down
82 changes: 51 additions & 31 deletions Microsoft.Kiota.Http.HttpClientLibrary.Tests/RequestAdapterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,33 +155,35 @@ public async Task GetRequestMessageFromRequestInformationSetsContentHeaders()
};
requestInfo.Headers.Add("Content-Length", "26");
requestInfo.Headers.Add("Content-Range", "bytes 0-25/128");
requestInfo.SetStreamContent(new MemoryStream(Encoding.UTF8.GetBytes("contents")));
requestInfo.SetStreamContent(new MemoryStream(Encoding.UTF8.GetBytes("contents")), "application/octet-stream");

// Act
var requestMessage = await requestAdapter.ConvertToNativeRequestAsync<HttpRequestMessage>(requestInfo);

// Assert
Assert.NotNull(requestMessage.Content);
// Content length set correctly
Assert.Equal(26,requestMessage.Content.Headers.ContentLength);
Assert.Equal(26, requestMessage.Content.Headers.ContentLength);
// Content range set correctly
Assert.Equal("bytes", requestMessage.Content.Headers.ContentRange.Unit);
Assert.Equal(0, requestMessage.Content.Headers.ContentRange.From);
Assert.Equal(25, requestMessage.Content.Headers.ContentRange.To);
Assert.Equal(128,requestMessage.Content.Headers.ContentRange.Length);
Assert.Equal(128, requestMessage.Content.Headers.ContentRange.Length);
Assert.True(requestMessage.Content.Headers.ContentRange.HasRange);
Assert.True(requestMessage.Content.Headers.ContentRange.HasLength);
// Content type set correctly
Assert.Equal("application/octet-stream", requestMessage.Content.Headers.ContentType.MediaType);
}

[Fact]
public async void SendMethodDoesNotThrowWithoutUrlTemplate() {
public async void SendMethodDoesNotThrowWithoutUrlTemplate()
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage {
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Test")))
});
Expand All @@ -204,12 +206,14 @@ public async void SendMethodDoesNotThrowWithoutUrlTemplate() {
[InlineData(HttpStatusCode.NonAuthoritativeInformation)]
[InlineData(HttpStatusCode.PartialContent)]
[Theory]
public async void SendStreamReturnsUsableStream(HttpStatusCode statusCode) {
public async void SendStreamReturnsUsableStream(HttpStatusCode statusCode)
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage {
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = statusCode,
Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Test")))
});
Expand All @@ -226,20 +230,22 @@ public async void SendStreamReturnsUsableStream(HttpStatusCode statusCode) {
Assert.Equal(4, response.Length);
var streamReader = new StreamReader(response);
var responseString = await streamReader.ReadToEndAsync();
Assert.Equal("Test",responseString);
Assert.Equal("Test", responseString);
}
[InlineData(HttpStatusCode.OK)]
[InlineData(HttpStatusCode.Created)]
[InlineData(HttpStatusCode.Accepted)]
[InlineData(HttpStatusCode.NonAuthoritativeInformation)]
[InlineData(HttpStatusCode.NoContent)]
[Theory]
public async void SendStreamReturnsNullForNoContent(HttpStatusCode statusCode) {
public async void SendStreamReturnsNullForNoContent(HttpStatusCode statusCode)
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage {
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = statusCode,
});
var adapter = new HttpClientRequestAdapter(_authenticationProvider, httpClient: client);
Expand All @@ -260,12 +266,14 @@ public async void SendStreamReturnsNullForNoContent(HttpStatusCode statusCode) {
[InlineData(HttpStatusCode.NoContent)]
[InlineData(HttpStatusCode.PartialContent)]
[Theory]
public async void SendSNoContentDoesntFailOnOtherStatusCodes(HttpStatusCode statusCode) {
public async void SendSNoContentDoesntFailOnOtherStatusCodes(HttpStatusCode statusCode)
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage {
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = statusCode,
});
var adapter = new HttpClientRequestAdapter(_authenticationProvider, httpClient: client);
Expand All @@ -284,12 +292,14 @@ public async void SendSNoContentDoesntFailOnOtherStatusCodes(HttpStatusCode stat
[InlineData(HttpStatusCode.NoContent)]
[InlineData(HttpStatusCode.ResetContent)]
[Theory]
public async void SendReturnsNullOnNoContent(HttpStatusCode statusCode) {
public async void SendReturnsNullOnNoContent(HttpStatusCode statusCode)
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage {
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = statusCode
});
var adapter = new HttpClientRequestAdapter(_authenticationProvider, httpClient: client);
Expand Down Expand Up @@ -338,14 +348,16 @@ public async void SendReturnsNullOnNoContentWithContentHeaderPresent(HttpStatusC
[InlineData(HttpStatusCode.Accepted)]
[InlineData(HttpStatusCode.NonAuthoritativeInformation)]
[Theory]
public async void SendReturnsObjectOnContent(HttpStatusCode statusCode) {
public async void SendReturnsObjectOnContent(HttpStatusCode statusCode)
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
using var mockContent = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Test")));
mockContent.Headers.ContentType = new("application/json");
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage {
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = statusCode,
Content = mockContent,
});
Expand All @@ -367,18 +379,21 @@ public async void SendReturnsObjectOnContent(HttpStatusCode statusCode) {
Assert.NotNull(response);
}
[Fact]
public async void RetriesOnCAEResponse() {
public async void RetriesOnCAEResponse()
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
var methodCalled = false;
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.Returns<HttpRequestMessage, CancellationToken>((mess, token) => {
var response = new HttpResponseMessage {
.Returns<HttpRequestMessage, CancellationToken>((mess, token) =>
{
var response = new HttpResponseMessage
{
StatusCode = methodCalled ? HttpStatusCode.OK : HttpStatusCode.Unauthorized,
Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Test")))
};
if (!methodCalled)
if(!methodCalled)
response.Headers.WwwAuthenticate.Add(new("Bearer", "realm=\"\", authorization_uri=\"https://login.microsoftonline.com/common/oauth2/authorize\", client_id=\"00000003-0000-0000-c000-000000000000\", error=\"insufficient_claims\", claims=\"eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwgInZhbHVlIjoiMTY1MjgxMzUwOCJ9fX0=\""));
methodCalled = true;
return Task.FromResult(response);
Expand All @@ -399,29 +414,34 @@ public async void RetriesOnCAEResponse() {
[InlineData(HttpStatusCode.NotFound)]
[InlineData(HttpStatusCode.BadGateway)]
[Theory]
public async void SetsTheApiExceptionStatusCode(HttpStatusCode statusCode) {
public async void SetsTheApiExceptionStatusCode(HttpStatusCode statusCode)
{
var mockHandler = new Mock<HttpMessageHandler>();
var client = new HttpClient(mockHandler.Object);
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() => {
var responseMessage = new HttpResponseMessage
{
StatusCode = statusCode
};
responseMessage.Headers.Add("request-id", "guid-value");
return responseMessage;
});
.ReturnsAsync(() =>
{
var responseMessage = new HttpResponseMessage
{
StatusCode = statusCode
};
responseMessage.Headers.Add("request-id", "guid-value");
return responseMessage;
});
var adapter = new HttpClientRequestAdapter(_authenticationProvider, httpClient: client);
var requestInfo = new RequestInformation
{
HttpMethod = Method.GET,
UrlTemplate = "https://example.com"
};
try {
try
{
var response = await adapter.SendPrimitiveAsync<Stream>(requestInfo);
Assert.Fail("Expected an ApiException to be thrown");
} catch (ApiException e) {
}
catch(ApiException e)
{
Assert.Equal((int)statusCode, e.ResponseStatusCode);
Assert.True(e.ResponseHeaders.ContainsKey("request-id"));
}
Expand Down
20 changes: 19 additions & 1 deletion src/Extensions/HttpRequestMessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Extensions;
Expand All @@ -25,7 +26,11 @@ public static class HttpRequestMessageExtensions
/// <returns>A request option</returns>
public static T? GetRequestOption<T>(this HttpRequestMessage httpRequestMessage) where T : IRequestOption
{
#if NET5_0_OR_GREATER
if(httpRequestMessage.Options.TryGetValue<T>(new HttpRequestOptionsKey<T>(typeof(T).FullName!), out var requestOption))
#else
if(httpRequestMessage.Properties.TryGetValue(typeof(T).FullName!, out var requestOption))
#endif
{
return (T)requestOption!;
}
Expand All @@ -36,11 +41,12 @@ public static class HttpRequestMessageExtensions
/// Create a new HTTP request by copying previous HTTP request's headers and properties from response's request message.
/// </summary>
/// <param name="originalRequest">The previous <see cref="HttpRequestMessage"/> needs to be copy.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the request.</param>
/// <returns>The <see cref="HttpRequestMessage"/>.</returns>
/// <remarks>
/// Re-issue a new HTTP request with the previous request's headers and properties
/// </remarks>
internal static async Task<HttpRequestMessage> CloneAsync(this HttpRequestMessage originalRequest)
internal static async Task<HttpRequestMessage> CloneAsync(this HttpRequestMessage originalRequest, CancellationToken cancellationToken = default)
{
var newRequest = new HttpRequestMessage(originalRequest.Method, originalRequest.RequestUri);

Expand All @@ -49,14 +55,26 @@ internal static async Task<HttpRequestMessage> CloneAsync(this HttpRequestMessag
newRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);

// Copy request properties.
#if NET5_0_OR_GREATER
foreach(var property in originalRequest.Options)
if(property.Value is IRequestOption requestOption)
newRequest.Options.Set(new HttpRequestOptionsKey<IRequestOption>(property.Key), requestOption);
else
newRequest.Options.Set(new HttpRequestOptionsKey<object?>(property.Key), property.Value);
#else
foreach(var property in originalRequest.Properties)
IDictionaryExtensions.TryAdd(newRequest.Properties, property.Key, property.Value);
#endif

// Set Content if previous request had one.
if(originalRequest.Content != null)
{
// HttpClient doesn't rewind streams and we have to explicitly do so.
#if NET5_0_OR_GREATER
var contentStream = await originalRequest.Content.ReadAsStreamAsync(cancellationToken);
#else
var contentStream = await originalRequest.Content.ReadAsStreamAsync();
#endif

if(contentStream.CanSeek)
contentStream.Seek(0, SeekOrigin.Begin);
Expand Down
Loading

0 comments on commit 0699301

Please sign in to comment.