HttpClient.SendAsync not sending request body - c#

I am using the ASP.NET Web API Client Libraries for .NET 4.0 (Microsoft.AspNet.WebApi.Client version 4.0.30506.0).
I need to send an HTTP DELETE with a request body. I have coded it as follows:
using (var client = new HttpClient())
{
client.BaseAddress = Uri;
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// I would normally use httpClient.DeleteAsync but I can't because I need to set content on the request.
// For this reason I use httpClient.SendAsync where I can both specify the HTTP DELETE with a request body.
var request = new HttpRequestMessage(HttpMethod.Delete, string.Format("myresource/{0}", sessionId))
{
var data = new Dictionary<string, object> {{"some-key", "some-value"}};
Content = new ObjectContent<IDictionary<string, object>>(data, new JsonMediaTypeFormatter())
};
var response = await client.SendAsync(request);
// code elided
}
Per Fiddler, the request body is never serialized:
DELETE http://localhost:8888/myApp/sessions/blabla123 HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: localhost:8888
Content-Length: 38
Expect: 100-continue
The response from the server:
HTTP/1.1 408 Request body incomplete
Date: Sun, 10 Aug 2014 17:55:17 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Cache-Control: no-cache, must-revalidate
Timestamp: 13:55:17.256
The request body did not contain the specified number of bytes. Got 0, expected 38
I have tried a number of workarounds, including changing the type being serialized to something else, doing the serialization myself with JsonSerialize, changing the HTTP DELETE to PUT, etc...
Nothing worked. Any help would be much appreciated.

I resolved the issue, though it does not make sense. I noticed that if I changed my call to HTTP PUT or POST, it still failed to serialize the Content as a request body. That was rather strange as previous PUTs and POSTs were successful. After doing a ton of debugging into framework libraries (using Reflector), I finally got to the only thing left that was "different."
I am using NUnit 2.6.2. The structure of my test is:
[Test]
async public void Test()
{
// successful HTTP POST and PUT calls here
// successful HTTP DELETE with request body here (after
// moving it from the TearDown below)
}
[TearDown]
async public void TerminateSession()
{
// failed HTTP DELETE with request body here
}
Why does this fail in the TearDown but not in the Test itself? I have no idea. Is something going on with the TearDown attribute or with the use of the async keyword (since I await async calls)?
I am not sure what it is causing this behavior, but I do know now that I can submit an HTTP DELETE with a request body (as outlined in my code sample in the question).
Another solution that worked is as follows:
[Test]
async public void Test()
{
// create and use an HttpClient here, doing POSTs, PUTs, and GETs
}
// Notice the removal of the async keyword since now using Wait() in method body
[TearDown]
public void TerminateSession()
{
// create and use an HttpClient here and use Wait().
httpClient.SendAsync(httpRequestMessage).Wait();
}

I know it's never quite that helpful to say, "don't do it that way", but in this case I think it makes sense to split the calls into a DELETE followed or preceeded by a POST or PUT.
The HTTP RFC doesn't explicitly opine on the matter, so technically it means that we can. The other question, however, is should we do it.
In cases such as this I would look for other implementations to see what is the de facto standard. As you've found in the .net implementation, it appears that the designers did not expect to send a body with the DELETE call. So, let's look at another popular (and very different impl) Python Requests:
>>> r = requests.delete(url=url, auth=auth)
>>> r.status_code
204
>>> r.headers['status']
'204 No Content'
No body here other. So, if the spec authors didn't mention it, and popular implementations assume that there's no body, then the principle of least surprise means we shouldn't do it either.
So, if you can change the API, it will be easier on clients of the API to split into two calls. Otherwise, you'll likely have to resort to custom hackery to cram the body into a DELETE call.
The good news is that you've likely found a bug in the .net framework, which is an achievement in and of itself. Clients advertising a non-zero Content-Length without actually sending it are broken.

In case anybody else runs into this, one thing I've noticed that can cause this is if you set a header with a newline in it.
We had an encrypted OAuth token, which gets decrypted at runtime and set as the OAuth header on the app. The newline was encrypted into the token, so it was not obvious from looking at the configs or anything that it was there, but if you do:
var message = new HttpRequestMessage(HttpMethod.Post, "https://example.com");
message.Headers.ContentType = new MediaTypeHeaderValue("application/json");
message.Content = new StringContent("{ \"someKey\": \"someValue\" }", Encoding.UTF8);
// note the trailing newline
message.Headers.Authorization = new AuthenticationHeaderValue("OAuth", "my auth token\n");
var response = await httpClient.SendAsync(request);
The HTTP request will be sent, but the content will not be sent with it. There are no exceptions thrown when this happens and if you inspect the HttpRequestMessage, the content will appear to be there, but it does not actually get sent over the wire.
This happens in .NET 5 on Windows and Linux, I haven't tested it on other framework versions/platforms.

Related

How to send unencoded form data with C# HttpClient

I'm trying to "repurpose" a third-party API used by a desktop application. I've found that the below code gets me very close to matching the packets sent by the app:
var formData = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>(JsonConvert.SerializeObject(myPayload), "")
});
var response = Client.PostAsync(myURL, formData).Result;
var json = response.Content.ReadAsStringAsync().Result;
This gets me almost exactly the same payload sent by the application, except it encodes the data (I know, "encoded" is right there in the name). I need to get the exact same request but without the data being encoded, but I can't quite find the right object(s) to pull it off. How do I keep this payload from being URL encoded?
Edit:
This is a login request I pulled from Wireshark emanating from the application:
POST /Login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: 1.1.1.1
Content-Length: 161
Expect: 100-continue
Connection: Close
{"username":"myuser","auth-id":"0a0a140f81a2ce0c303386e93cec41bf04660c22a881be9a"}
This is what the above will generate:
POST /Login HTTP/1.1
Expect: 100-continue
Connection: Close
Content-Type: application/x-www-form-urlencoded
Content-Length: 221
Host: 1.1.1.1
%7B%22user-name%22%3A%22myuser%22%2C%22auth-id%22%3A%220a0a140f81a2ce0c303386e93cec41bf04660c22a881be9a%22%7D=
I've edited them for brevity so the Content-Length is wrong. I realize it might not be the best way to send this data, but I have no control over how it's consumed.
Since you're actually trying to send JSON, I think you need to wrap the JSON in a StringContent object rather than a FormUrlEncoded object. Form-encoded data and JSON data are two different ways of formatting a payload (another commonly used format would be XML, for example). Using them both together doesn't make any sense.
I think something like this should work:
var content = new StringContent(JsonConvert.SerializeObject(myPayload), Encoding.UTF8, "application/json");
var response = Client.PostAsync(myURL, content).Result;
var json = response.Content.ReadAsStringAsync().Result;
(P.S. the Content-Type: application/x-www-form-urlencoded header sent by the application appears to be misleading, since the request body clearly contains JSON. Presumably the receiving server is tolerant of this nonsense, or just ignores it because it's always expecting JSON.)

Why is the entire HttpResponseMessage serialized?

Why does this Web API implementation
[HttpGet("hello")]
public HttpResponseMessage Hello()
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
res.Content = new StringContent("hello", Encoding.UTF8, "text/plain");
return res;
}
return
{
"Version":{
"Major":1,
"Minor":1,
"Build":-1,
"Revision":-1,
"MajorRevision":-1,
"MinorRevision":-1
},
"Content":{
"Headers":[{
"Key":"Content-Type",
"Value":["text/plain; charset=utf-8"]
}]
},
"StatusCode":200,
"ReasonPhrase":"OK",
"Headers":[],
"RequestMessage":null,
"IsSuccessStatusCode":true
}
instead of
hello
?
How can I make the Web API return an HTTP response like below?
200 OK
Content-Type: text/plain
hello
What I want to do finally is return JSON and other formats with various status codes, so the following code wouldn't help me as an answer.
[HttpGet("hello")]
public string Hello()
{
return "hello";
}
(I'm new to ASP.NET and other Microsoft technologies.)
I had the same happen to me recently. The reason was that, for some reason, there was two references to the System.Net.Http.dll assembly: one from the GAC and one local copy from my project.
It results in an interesting case where the type of HttpResponseMessage you send isn't the same type of HttpResponseMessage ASP.NET expects, and that's why instead on processing the message, it just serializes it as JSON.
The solution I found for this is to install the System.Net.Http package from NuGet, and ensure that the binding redirect is generated correctly on the Web.config, so that only one copy of the dependency is used.
Intriguing, if I try your code on ASP.NET 4
public HttpResponseMessage Hello()
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
res.Content = new StringContent("hello", Encoding.UTF8, "text/plain");
return res;
}
I get response, what I expect.
Header:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 5
Content-Type: text/plain; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
...
X-Powered-By: ASP.NET
Date: ...
Body
hello
Depending on the situation, you can either rely on client to specify what it can accept or you can specify server to always throw json.
I usually have custom request/response objects which I throw back to client. For instance
public CustomResponse Get()
{
CustomResponse response = new CustomResponse();
// some work
response.TestProperty1 = "Test Value 1";
response.TestProperty2 = "Test value 2";
return response;
}
Now if your API would respect what your client accepts. So if client set request header "Accept: application/xml" then it would return xml or json if its json. Refer to screenshot of fiddler request below.
From memory, I think you can also specify on server as well say to always send json.
Hope this helps!
ASP.net Web Api uses something called Content Negotiation where the client specifies that content to be returned.
In your case, you could specify that you want text/plain returned and the built-in content negotiation should provide what you want.
You've not specified how you are making the request - manually in a browser (which browser) / via jquery $.ajax / .net client code / etc - and how you make the request determines how you add the accept header to that request, eg:
GET http://[url] HTTP/1.1
Accept: text/plain
It's possible to override the content negotiation and always return a specific type (eg JSON) and there are many questions on SO on how to do this - mostly because they want JSON but get xml and don't know how to request JSON. If you're creating a reusable Web API then you should leave this up to the client to request what they want, which is why it's built that way into the framework.
(Self-answering) Hmm... The reason might be that I had chosen
ASP.NET 5 Preview Templates
instead of
ASP.NET 4.5.2 Templates
when I tried to create a Web API.
I guess it might be a bug of ASP.NET 5 Preview or ASP.NET 5 has introduced another new mechanism that supersedes HttpResponseMessage.

(partial) HTTP Request isn't recognized by C# HTTP Server

In my visual studio 13 console application, I receive following byte stream on a TCP-Socket(receiving from a embedded device):
POST /setup HTTP/1.1
Content-Length: 6
Content-Type: application/setup+tlv8
TLV8-data
Although it seem to be a valid http request, none of my following attempts have successful been recognize it as a http request: (on regular HTTP Requests they work perfectly)
.NET HttpListener class (does not even inform me that any request has been invoked)
Grapevine (same thing, with any routes given on POST or GET) https://github.com/scottoffen/Grapevine
Alchemy (OnConnect method has been invoked, but in the according UserContext i was just seeing request path: / . Similar to this reported issue: https://github.com/Olivine-Labs/Alchemy-Websockets/issues/70
So far, I'm actually only interested in the requested path from the POST or GET as well as the attached content (tlv formatted) from the body.
Am I wrong in configuring? such as: I need to tell the proper content-type ?
Is there any way to get rid of writing a own simple text parser ?
Code sample in case of grapevine:
private void init()
{
s = new PairServer();
s.Host = "172.28.22.78";
s.Port = "52025";
s.Start();
}
providing following server class:
public class PairServer : RestServer
{
[RestRoute(Method = HttpMethod.POST, PathInfo = #"^/setup")]
[RestRoute(Method = HttpMethod.GET, PathInfo = #"^/setup")]
public void PairSetup(HttpListenerContext context)
{
// will not reach here
}
[RestRoute(Method = HttpMethod.POST)]
public void AnyRoute(HttpListenerContext context)
{
// Not even here
}
Although it seem to be a valid http request
No, that's not a valid HTTP request. A valid HTTP request, as the specification states, must include a Host request header:
A client MUST include a Host header field in all HTTP/1.1 request
messages . If the requested URI does not include an Internet host name
for the service being requested, then the Host header field MUST be
given with an empty value. An HTTP/1.1 proxy MUST ensure that any
request message it forwards does contain an appropriate Host header
field that identifies the service being requested by the proxy. All
Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request)
status code to any HTTP/1.1 request message which lacks a Host header
field.
So make sure that your client is following the specification:
POST /setup HTTP/1.1
Host: example.com
Content-Length: 6
Content-Type: application/setup+tlv8
TLV8-data

Is it possible or necessary to set the content type of a ServiceStack client delete request?

I have the following code.
public T SendUpdateRequest(string url)
{
using (JsonServiceClient client = new JsonServiceClient())
{
T response = client.Put<T>(url);
return response;
}
}
I have similar methods for create and delete requests, calling the JsonServiceClient Post and Delete methods respectively.
When calling my update or create methods, the call to the external API works fine. Delete does not. I can see that the API's delete method does indeed work if I fire a request to it via REST console.
When I compare my non-working delete with the working one's request/response in Fiddler, I can see the main difference is my request is not setting content-type to application/json (all these methods return JSON).
My question is, is it possible (or even necessary) to explicitly set the content-type of my delete request to application/json in order to successfully call my API method?
The ServiceStack clients do not set the content-type header on requests where there is no request body, as the content-type only applies to the body, and is therefore redundant.
This can be seen here in the code that prepares the client request.
if (httpMethod.HasRequestBody())
{
client.ContentType = ContentType;
...
A correctly implemented RESTful service should be happy with a DELETE request without content-type being specified where there is no body.
DELETE /User/123 HTTP/1.1
If the service you are calling is not happy with your request without this type being specified (which is unusual), then you can manually enforce the sending of the type using this filter:
var client = new JsonServiceClient("https://service/");
client.RequestFilter += (httpReq) => {
// Force content type to be sent on all requests
httpReq.ContentType = "application/json";
};
I hope that helps.

Fetching AccessToken returns "400 - Bad Request" for authenticating cloud service with WNS [duplicate]

PLEASE HELP!! Can't figure out why this simple code given by MSDN doesn't work....
I am using the following code in GetAccessToken() as given in the this MSDN article to get the access token to be used in windows notifications, but it returns "Bad Request 400"
PACKAGE_SECURITY_IDENTIFIER, CLIENT_SECRET are the values obtained when the app was registered with the Windows Store Dashboard
string urlEncodedSid = HttpUtility.UrlEncode(PACKAGE_SECURITY_IDENTIFIER);
string urlEncodedSecret = HttpUtility.UrlEncode(CLIENT_SECRET);
string body = String.Format("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=notify.windows.com", urlEncodedSid, urlEncodedSecret);
string response;
using (WebClient client = new WebClient())
{
client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
response = client.UploadString("https://login.live.com/accesstoken.srf", body);
}
Any help would be highly appreciated.......
I suspect the problem has to do with either an incorrect package identifier, and / or incorrect client secret.
From the MSDN page Push notification service request and response headers:
RESPONSE DESCRIPTION
--------------- --------------------------
200 OK The request was successful.
400 Bad Request The authentication failed.
Update - I ran the code from the question, using FAKE credentials.
Here is the RAW HTTP request:
POST https://login.live.com/accesstoken.srf HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: login.live.com
Content-Length: 88
Expect: 100-continue
Connection: Keep-Alive
grant_type=client_credentials&client_id=test&client_secret=test&scope=notify.windows.com
Here is the server's RAW response:
HTTP/1.1 400 Bad Request
Cache-Control: no-store
Content-Length: 66
Content-Type: application/json
Server: Microsoft-IIS/7.5
X-WLID-Error: 0x80045A78
PPServer: PPV: 30 H: BAYIDSLGN2A055 V: 0
Date: Thu, 21 Mar 2013 12:34:19 GMT
Connection: close
{"error":"invalid_client","error_description":"Invalid client id"}
You will note that the response is a 400. There is also some json that indicates the type of error. In my case, the error is Invalid client id. You probably want to take a look at your response - it will give you an indication of what happened.
I used Fiddler to debug the request/ response.
I found the reason for the error response. In fact it is the wrong PACKAGE_SECURITY_IDENTIFIER and CLIENT_SECRET.
DO NOT type the values. Because associated ASCII values differ. Therefore it is always better to copy and paste directly.
You will probably will get the access token with the simple code snippet.
Cheers
If you're using the new HttpClient API and you're sure you've copied and pasted the SID/secret values correct, you might be experiencing this issue because of encoding, provided you're using the FormUrlEncodedContent class as the content of your POST operation.
Contrary to the examples in the MSDN documentation, you don't want to URL encode the SID and secret values before adding them to the KeyValuePair collection. This is because encoding is implied by the FormUrlEncodedContent class, though I'm not seeing any documentation for this behavior. Hopefully this saves someone some time because I've been wrestling with this all night...

Categories