Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why does HttpClient in Core allow GET requests with bodies, while Framework version does not? #25485

Closed
joshfree opened this issue Mar 16, 2018 · 10 comments
Labels
area-System.Net.Http question Answer questions and provide assistance, not an issue with source code or documentation.
Milestone

Comments

@joshfree
Copy link
Member

Opened on behalf of @IanKemp from dotnet/core#1333


@IanKemp writes -
As per the title, the HttpClient implementation between Core and Framework differs in this regard. Consider the following example:

using (var client = new HttpClient())
{
    var request = new HttpRequestMessage
    {
        RequestUri = new Uri("some url"),
        Method = HttpMethod.Get,
    };

    request.Content = new ByteArrayContent(Encoding.UTF8.GetBytes("some json"));

    request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

    var result = client.SendAsync(request).Result;
    result.EnsureSuccessStatusCode();

    var responseBody = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
}
  • In .NET Core (tested with 1.0 and 2.0), the above executes successfully.
  • In .NET framework (tested with 4.7.1, 4.6.1, 4.5), the above throws a ProtocolViolationException with the message Cannot send a content-body with this verb-type on the SendAsync call.

While I am very happy that Core allows this (technically correct, but unusual) request type, I am less happy that the Framework does not support it. Why does Core allow this why Framework does not? Is this intentional or an oversight? Is there somewhere where these differences/idiosyncrasies are documented?

(For another example of differing HTTP behaviour in Core vs Framework, see this issue.)


@AppBeat writes -
Interesting. According to this post: https://stackoverflow.com/questions/978061/http-get-with-request-body
standard does not explicitly forbid this. GET body should be ignored by server.


@IanKemp writes -
@AppBeat The server is free to ignore or accept the body; that should not prevent the client from sending a request that the server may ignore. It is ultimately up to the client to determine whether the server accepts requests like this.

A popular example of a product (server) that supports GET requests with bodies is Elasticsearch, specifically their REST query API. In particular, the section on that page "A GET Request with a Body?" explains their rationale (but note that they do also allow POST as a fallback option for clients that do not support this).


@AppBeat writes -
I didn't say that this should be removed from .NET Core :) Although this is not common (bad?) practice I think .NET Core version in this case is more correct than .NET Framework implementation.

I will try to test this on new managed implementation of SocketsHttpHandler which will probably be prefered HttpHandler in future for more consistent behaviour across all different platforms.
https://github.com/dotnet/corefx/tree/master/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler

I created new functional test for SocketsHttpHandler and it works as it should:

[Fact]
public async Task SendAsync_HttpGetWithPayload_Success()
{
    await LoopbackServer.CreateServerAsync(async (server, url) =>
    {
        string responseBody =
            "HTTP/1.1 200 OK\r\n" +
            $"Date: {DateTimeOffset.UtcNow:R}\r\n" +
            "Content-Length: 0\r\n" +
            "Connection: close\r\n" +
            "\r\n";

        using (HttpClient client = CreateHttpClient())
        {
            var request = new HttpRequestMessage
            {
                RequestUri = url,
                Method = HttpMethod.Get,
            };

            request.Content = new StringContent("{}", Encoding.UTF8, "application/json");
            Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
            await server.AcceptConnectionAsync(async connection =>
            {
                Task<List<string>> serverTask = connection.ReadRequestHeaderAndSendCustomResponseAsync(responseBody);
                await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
            });

            Assert.True(getResponseTask.IsCompletedSuccessfully);
            var result = getResponseTask.Result;
            Assert.True(result.IsSuccessStatusCode);
        }
    });
}
@ghost
Copy link

ghost commented Mar 16, 2018

This maybe a breaking change with older versions of HttpClient, but it is useful. curl supports GET with body curl -d "stuff" -XGET http://site/path.
GET is idempotent, that means the contents of request shouldn't change the response: https://tools.ietf.org/html/rfc7231#section-4.2.2 suggests PUT, DELETE and safe methods are idempotent, and 4.2.1 says GET and HEAD are defined as safe methods. Which means it is upto the server to make sure RFC is respected and RFC does not impose any restriction on client to allow sending GET's body.

@karelz
Copy link
Member

karelz commented Mar 16, 2018

It would be good to know if SocketsHttpHandler allows it or not (it should for compat with curl/winhttp). If it does not, please file a new bug.

.NET Framework is separate (older) implementation based on Sockets as well. If it doesn't allow these request, it is likely a more strict RFC interpretation. Given that GET bodies are useless (they are ignored by servers) and that it is not a regression on .NET Framework, I don't see any reason to change .NET Framework -- there is easy workaround and the scenario is very corner-case.

Closing as question answered. If you disagree feel free to reopen / ping me. Thanks!

@karelz karelz closed this as completed Mar 16, 2018
@stephentoub
Copy link
Member

It would be good to know if SocketsHttpHandler allows it or not (it should for compat with curl/winhttp).

It does.

@davidsh
Copy link
Contributor

davidsh commented Mar 16, 2018

From RFC7231:
https://tools.ietf.org/html/rfc7231#page-24

A payload within a GET request message has no defined semantics;
sending a payload body on a GET request might cause some existing
implementations to reject the request.

So, either way is ok. Servers might reject a GET request with a body. The design picked in .NET Core was to be flexible and allow this since the RFC doesn't explicitly forbid it.

@LucidObscurity
Copy link

It would be good to know if SocketsHttpHandler allows it or not (it should for compat with curl/winhttp). If it does not, please file a new bug.

.NET Framework is separate (older) implementation based on Sockets as well. If it doesn't allow these request, it is likely a more strict RFC interpretation. Given that GET bodies are useless (they are ignored by servers) and that it is not a regression on .NET Framework, I don't see any reason to change .NET Framework -- there is easy workaround and the scenario is very corner-case.

Closing as question answered. If you disagree feel free to reopen / ping me. Thanks!

Would you mind expanding on the "easy workaround"? I need to implement an API which requires GET requests with a body (idealy using a ITypeHttpClientFactory). The particular API is deprecating all GET request w/out body support.

Thanks.

@davidsh
Copy link
Contributor

davidsh commented Feb 26, 2019

This particular issue was about questions regarding why .NET Core behavior is different from .NET Framework. That question was answered and this issue closed.

Would you mind expanding on the "easy workaround"? I need to implement an API which requires GET requests with a body (idealy using a ITypeHttpClientFactory). The particular API is deprecating all GET request w/out body support.

In terms of the "easy workaround" that @karelz mentioned, I don't think one exists. The only possibility is to use a different handler with the HttpClient API. For example, WinHttpHandler is available as a separate NuGet package (System.Net.Http.WinHttpHandler) for .NET Framework. If someone used that underneath HttpClient, then they could probably be able to use GET requests with a request body.

@LucidObscurity
Copy link

LucidObscurity commented Feb 27, 2019

Thanks davidsh. That worked.

services.AddHttpClient<ApiService>()
                .ConfigureHttpMessageHandlerBuilder(c => {
                    c.PrimaryHandler = new WinHttpHandler(); //Windows only. Required to send body with GET requests.
                })

@LeroyPakade
Copy link

I am using WinHttpHandler but now im loosing UseDefaultCredentials = true that i had with HttpClientHandler, and now i have too use NetworkCredential is there a way too use WinHttpHandler with UseDefaultCredentials

@davidsh
Copy link
Contributor

davidsh commented Apr 25, 2019

@LeroyPakade

HttpClientHandler.UseDefaultCredentials is really a duplicate property. You can use HttpClientHandler.Credentials property instead. Just set HttpClientHandler.Credentials = CredentialCache.DefaultCredentials and it is the same thing as setting UseDefaultCredentials=true.

So, for WinHttpHandler, just set the WinHttpHandler.ServerCredentials property to CredentialCache.DefaultCredentials.

Also, in the future, please don't comment on closed issues. They are rarely monitored. If you have a question, please open a new issue. Thanks.

@LeroyPakade
Copy link

Thanks for the help its working now with WinHttpHandler

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Http question Answer questions and provide assistance, not an issue with source code or documentation.
Projects
None yet
Development

No branches or pull requests

7 participants