[Bug]: POST request sent as GET
FelixZY opened this issue · comments
Describe the bug 🐞
We have an azure app service deployment where refit is used to communicate between two app services.
The same code was used to build and deploy to development, stage and production. Everything worked fine in development and stage but in production, Refit would appear to have sent a http GET
rather than a http POST
. I tried to rebuild and redeploy to our production environment, but the problem remained.
IService2Api.cs
using Refit;
public interface IService2Api
{
[Post($"/my/endpoint/")]
Task<IApiResponse<PartialResultDto<CustomId, CustomId>>> MyEndpointAsync(
[Header("Authorization")] string authorization,
[Body] IEnumerable<string> strings,
CancellationToken cancellationToken
);
}
AddRefitClient<IService2Api>
services
.AddRefitClient<IService2Api>(new RefitSettings { CollectionFormat = CollectionFormat.Multi })
.ConfigureHttpClient(
(sp, client) =>
{
client.DefaultRequestHeaders.TryAddWithoutValidation(
"User-Agent",
$"Service1 (Service1/1.0.1.0)"
);
client.BaseAddress = new Uri("http://service2.azurewebsites.net");
client.Timeout = TimeSpan.FromSeconds(10);
}
);
Service1 logs
...
2023-11-23 20:25:54.929 +00:00 [Information] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.LogicalHandler: Start processing HTTP request POST http://service2.azurewebsites.net/my/endpoint/
2023-11-23 20:25:54.929 +00:00 [Trace] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.LogicalHandler: Request Headers:
Authorization: Bearer [Redacted]
User-Agent: Service1, (Service1/1.0.1.0)
Content-Type: application/json; charset=utf-8
2023-11-23 20:25:54.929 +00:00 [Information] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Sending HTTP request POST http://service2.azurewebsites.net/my/endpoint/
2023-11-23 20:25:54.929 +00:00 [Trace] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Request Headers:
Authorization: Bearer [Redacted]
User-Agent: Service1, (Service1/1.0.1.0)
Content-Type: application/json; charset=utf-8
2023-11-23 20:25:54.972 +00:00 [Information] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Received HTTP response headers after 43.1596ms - 405
2023-11-23 20:25:54.972 +00:00 [Trace] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Response Headers:
Date: Thu, 23 Nov 2023 20:25:54 GMT
Content-Length: 0
Allow: POST
2023-11-23 20:25:54.972 +00:00 [Information] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.LogicalHandler: End processing HTTP request after 43.5574ms - 405
2023-11-23 20:25:54.972 +00:00 [Trace] System.Net.Http.HttpClient.Refit.Implementation.Generated+[Redacted]IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.LogicalHandler: Response Headers:
Date: Thu, 23 Nov 2023 20:25:54 GMT
Content-Length: 0
Allow: POST
...
2023-11-23 20:25:55.030 +00:00 [Error] Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer: Connection ID "[Redacted]", Request ID "[Redacted]": An unhandled exception was thrown by the application.
Refit.ApiException: Response status code does not indicate success: 405 (Method Not Allowed).
...
Service2 logs
...
2023-11-23 20:25:54.959 +00:00 [Information] Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware: Request:
Protocol: HTTP/1.1
Method: GET
Scheme: https
PathBase:
Path: /my/endpoint/
Cookie: [Redacted]
Host: service2.azurewebsites.net
Max-Forwards: 10
User-Agent: Service1 (Service1/1.0.1.0)
...
Step to reproduce
var apiResult = await service2Api.MyEndpointAsync(
$"Bearer {accessToken}",
strings,
cancellationToken
);
if (!apiResult.IsSuccessStatusCode)
{
throw apiResult.Error;
}
Reproduction repository
https://github.com/reactiveui/refit
Expected behavior
Refit reporting POST
should result in a POST
call.
Screenshots 🖼️
No response
IDE
No response
Operating system
Windows
Version
Dotnet - v7.0
Device
Azure App Service
Refit Version
7.0.0
Additional information ℹ️
I'm frankly stumped for now. If you have any ideas what might cause Refit to report POST
but actually perform GET
, I would be most interested.
This is almost certainly an Azure problem, Refit does not detect development vs production or Debug vs Release, it does the same thing in every environment. I would try to make a DelegatingHttpHandler and log the kind of request being sent down, but it'll probably report that it's sending a POST
Thank you for the suggestion. I wrapped things like this:
AddRefitClient call
services
.AddRefitClient<IService2Api>(
(sp) =>
new RefitSettings
{
CollectionFormat = CollectionFormat.Multi,
HttpMessageHandlerFactory = () =>
new LoggingHandler(
sp.GetRequiredService<ILoggerFactory>()
.CreateLogger<LoggingHandler>(),
new HttpClientHandler() { AllowAutoRedirect = true }
)
}
)
.ConfigureHttpClient(
// ...
LoggingHandler
private class LoggingHandler : DelegatingHandler
{
private readonly ILogger<LoggingHandler> logger;
public LoggingHandler(ILogger<LoggingHandler> logger, HttpMessageHandler innerHandler)
: base(innerHandler)
{
this.logger = logger;
}
protected override HttpResponseMessage Send(
HttpRequestMessage request,
CancellationToken cancellationToken
)
{
logger.LogDebug("Before [{Method}] to {Url}", request.Method, request.RequestUri);
var response = base.Send(request, cancellationToken);
logger.LogDebug("After [{Method}] to {Url}", request.Method, request.RequestUri);
return response;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken
)
{
logger.LogDebug("Before [{Method}] to {Url}", request.Method, request.RequestUri);
var response = await base.SendAsync(request, cancellationToken);
logger.LogDebug("After [{Method}] to {Url}", request.Method, request.RequestUri);
return response;
}
}
Which resulted in this output:
2023-11-24 23:34:11.315 +00:00 [Information] System.Net.Http.HttpClient.Refit.Implementation.Generated+IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Sending HTTP request POST http://service2.azurewebsites.net/my/endpoint/
2023-11-24 23:34:11.315 +00:00 [Trace] System.Net.Http.HttpClient.Refit.Implementation.Generated+IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Request Headers:
Authorization: Bearer [Redacted]
User-Agent: Service1, (Service1/1.0.1.0)
Content-Type: application/json; charset=utf-8
2023-11-24 23:34:11.315 +00:00 [Debug] LoggingHandler: Before [POST] to http://service2.azurewebsites.net/my/endpoint/
2023-11-24 23:34:11.400 +00:00 [Debug] LoggingHandler: After [GET] to https://service2.azurewebsites.net/my/endpoint/
2023-11-24 23:34:11.401 +00:00 [Information] System.Net.Http.HttpClient.Refit.Implementation.Generated+IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Received HTTP response headers after 85.2956ms - 405
2023-11-24 23:34:11.401 +00:00 [Trace] System.Net.Http.HttpClient.Refit.Implementation.Generated+IService2Api, Common, Version=1.0.1.0, Culture=neutral, PublicKeyToken=null.ClientHandler: Response Headers:
Date: Fri, 24 Nov 2023 23:34:11 GMT
Set-Cookie: [Redacted]
Content-Length: 0
Allow: POST
From this we can see that the request was indeed altered. However, this seems to have happened inside the innermost HttpMessageHandler
which I assume is deeper than Refit is supposed to operate?
Any ideas on what may have gone wrong or what might be worth testing are still most welcome! I will continue my own debugging next week.
Aside: It would be nice if refit could include a trace print of the http method as it is listed after calling base.Send
, similar to what I did, to make similar issues easier to debug in the future:
var response = base.Send(request, cancellationToken);
logger.LogDebug("After [{Method}] to {Url}", request.Method, request.RequestUri);
Any ideas on what may have gone wrong or what might be worth testing are still most welcome! I will continue my own debugging next week.
I'm not super familiar with Azure app development, sorry :( There are several issues on the tracker similar to this which is why I brought it up, searching the closed issues might give you some hints
I looked over other issues but didn't really see anything similar - do you have examples @anaisbetts ?
Currently, I'm not willing to lock this down to Azure specifically - we do have an identical environment where this is working fine. Rather, I'm currently interested in "what circumstances would cause .NET to convert a POST request to a GET request internally?".
In ConfigureHttpClient
, try disabling redirects. (Sorry, I forget exactly how, but there's a property on the client handler you set to false. Maybe AllowAutoRedirect
?)
Then log the response status and headers.
I think you have a 301/302 redirect in the mix somehow and the default behaviour of the http client is to automatically follow it.
Maybe you've configured your base URL in one environment to start with http://
and the server is configured to redirect to https://
?
@FelixZY They weren't specifically similar in the "POST turns into GET" but more general like, "My app works locally but not on Azure" - they always figured out that something in the Azure environment was messing with them :-/
I have now had time to test and verify. Indeed, service1
would encounter a 301 redirect from service2
, likely related to upgrading from http to https. This would result in the POST
being converted into a GET
.
Relevant sections from RFC 2616, section 10.3.2 (credit to Poul Bak on Stack Overflow):
If the 301 status code is received in response to a request other
than GET or HEAD, the user agent MUST NOT automatically redirect the
request unless it can be confirmed by the user, since this might
change the conditions under which the request was issued.Note: When automatically redirecting a POST request after
receiving a 301 status code, some existing HTTP/1.0 user agents
will erroneously change it into a GET request.
https://datatracker.ietf.org/doc/html/rfc2616#section-10.3.2
HTTP has been in use by the World-Wide Web global information initiative since 1990. This specification defines the protocol referred to as "HTTP/1.1", and is an update to RFC 2068. [STANDARDS-TRACK]
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.