Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public class ClientProxyBase<TService> : ITransientDependency

protected virtual async Task RequestAsync(string methodName, ClientProxyRequestTypeValue? arguments = null)
{
await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments));
using (await RequestAsync(BuildHttpProxyClientProxyContext(methodName, arguments)))
{
}
}

protected virtual async Task<T> RequestAsync<T>(string methodName, ClientProxyRequestTypeValue? arguments = null)
Expand Down Expand Up @@ -120,18 +122,21 @@ await responseContent.ReadAsStreamAsync(),
responseContent.Headers?.ContentLength);
}

var stringContent = await responseContent.ReadAsStringAsync();
if (typeof(T) == typeof(string))
using (responseContent)
{
return (T)(object)stringContent;
}
var stringContent = await responseContent.ReadAsStringAsync();
if (typeof(T) == typeof(string))
{
return (T)(object)stringContent;
}

if (stringContent.IsNullOrWhiteSpace())
{
return default!;
}
if (stringContent.IsNullOrWhiteSpace())
{
return default!;
}

return JsonSerializer.Deserialize<T>(stringContent);
return JsonSerializer.Deserialize<T>(stringContent);
}
}

protected virtual async Task<HttpContent> RequestAsync(ClientProxyRequestContext requestContext)
Expand Down Expand Up @@ -184,7 +189,15 @@ await ClientAuthenticator.Authenticate(

if (!response.IsSuccessStatusCode)
{
await ThrowExceptionForResponseAsync(response);
try
{
await ThrowExceptionForResponseAsync(response);
}
catch
{
response.Dispose();
throw;
}
}

return response.Content;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ await GetActionApiDescriptionModel(invocation),

if (invocation.Method.ReturnType.GenericTypeArguments.IsNullOrEmpty())
{
await InterceptorClientProxy.CallRequestAsync(context);
using (await InterceptorClientProxy.CallRequestAsync(context))
{
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Shouldly;
using Volo.Abp.AspNetCore.TestBase;
using Volo.Abp.Http.Client;
using Volo.Abp.Http.Client.Proxying;
using Xunit;

namespace Volo.Abp.Http.DynamicProxying;

public class ClientProxyResponseDisposal_Tests : AbpHttpClientTestBase
{
private readonly IRegularTestController _controller;
private readonly StubProxyHttpClientFactory _factory;

public ClientProxyResponseDisposal_Tests()
{
_controller = ServiceProvider.GetRequiredService<IRegularTestController>();
_factory = (StubProxyHttpClientFactory)ServiceProvider.GetRequiredService<IProxyHttpClientFactory>();
}

protected override void ConfigureServices(IServiceCollection services)
{
services.Replace(ServiceDescriptor.Singleton<IProxyHttpClientFactory, StubProxyHttpClientFactory>());
}

[Fact]
public async Task Non_Abp_Error_Response_Should_Dispose_HttpContent()
{
var content = new TrackingHttpContent("Bad Gateway from upstream");
_factory.SetStubResponse(() => new HttpResponseMessage(HttpStatusCode.BadGateway)
{
Content = content
});

var exception = await Assert.ThrowsAsync<AbpRemoteCallException>(
() => _controller.IncrementValueAsync(1));

exception.HttpStatusCode.ShouldBe((int)HttpStatusCode.BadGateway);
content.Disposed.ShouldBeTrue();
}

[Fact]
public async Task Successful_Response_With_Json_Return_Should_Dispose_HttpContent()
{
var content = new TrackingHttpContent("42");
_factory.SetStubResponse(() => new HttpResponseMessage(HttpStatusCode.OK)
{
Content = content
});

var result = await _controller.IncrementValueAsync(0);

result.ShouldBe(42);
content.Disposed.ShouldBeTrue();
}

[Fact]
public async Task Successful_Response_With_Void_Return_Should_Dispose_HttpContent()
{
var content = new TrackingHttpContent(string.Empty);
_factory.SetStubResponse(() => new HttpResponseMessage(HttpStatusCode.NoContent)
{
Content = content
});

await _controller.GetException1Async();

content.Disposed.ShouldBeTrue();
}

class StubProxyHttpClientFactory : IProxyHttpClientFactory
{
private readonly ITestServerAccessor _testServerAccessor;

private int _count;
private Func<HttpResponseMessage> _responseFactory;

public StubProxyHttpClientFactory(ITestServerAccessor testServerAccessor)
{
_testServerAccessor = testServerAccessor;
}

public void SetStubResponse(Func<HttpResponseMessage> responseFactory)
{
_responseFactory = responseFactory;
}

public HttpClient Create(string name) => Create();

public HttpClient Create()
{
if (_count++ == 0)
{
return _testServerAccessor.Server.CreateClient();
}

return new HttpClient(new StubHttpMessageHandler(() =>
_responseFactory?.Invoke()
?? throw new InvalidOperationException("Stub response is not configured.")))
{
BaseAddress = new Uri("http://localhost/")
};
}
}

class StubHttpMessageHandler : HttpMessageHandler
{
private readonly Func<HttpResponseMessage> _responseFactory;

public StubHttpMessageHandler(Func<HttpResponseMessage> responseFactory)
{
_responseFactory = responseFactory;
}

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(_responseFactory());
}
}

class TrackingHttpContent : HttpContent
{
private readonly byte[] _data;

public bool Disposed { get; private set; }

public TrackingHttpContent(string text)
{
_data = Encoding.UTF8.GetBytes(text);
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
return stream.WriteAsync(_data, 0, _data.Length);
}

protected override bool TryComputeLength(out long length)
{
length = _data.Length;
return true;
}

protected override void Dispose(bool disposing)
{
Disposed = true;
base.Dispose(disposing);
}
}
}
Loading