Xamarin iOS add Content-Length header to HttpClient throws error - ios

I am attempting to use HttpClient to upload a file to Microsoft Azure Blob Storage via their REST api in Xamarin.iOS. It's been going alright until now. Every time I try to add Content-Length header to the client I get this error:
System.InvalidOperationException: Content-Length\n at System.Net.Http.Headers.HttpHeaders.CheckName (System.String name) [0x0005f] in /Developer/MonoTouch/Source/mono/mcs/class/System.Net.Http/System.Net.Http.Headers/HttpHeaders.cs:253 \n at System.Net.Http.Headers.HttpHeaders.Add (System.String name, IEnumerable`1 values) [0x00011] in /Developer/MonoTouch/Source/mono/mcs/class/System.Net.Http/System.Net.Http.Headers/HttpHeaders.cs:171 \n at System.Net.Http.Headers.HttpHeaders.Add (System.String name, System.String value) [0x00000] in /Developer/MonoTouch/Source/mono/mcs/class/System.Net.Http/System.Net.Http.Headers/HttpHeaders.cs:163
This is my code for creating the HttpClient
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Content-Length", blobLength.ToString()); // Error here
client.DefaultRequestHeaders.Add("x-ms-date", dateInRfc1123Format);
client.DefaultRequestHeaders.Add("x-ms-version", msVersion);
Debug.WriteLine("Added all headers except Authorization");
client.DefaultRequestHeaders.Add("Authorization", authorizationHeader);
Debug.WriteLine("Added Authorization header");
//logRequest(requestContent, uri);
Debug.WriteLine("created new http client");
HttpContent requestContent = new ByteArrayContent(blobckContent);
HttpResponseMessage response = await client.PutAsync(uri, requestContent);
I tried using TryAddWithoutValidation instead of Add:
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Length", blobLength.ToString());
The error doesn't get thrown but the header still doesn't get added.
Any help would be great.

Here's the inner workings of CheckName(), which is throwing the exception. You can find the source here: https://github.com/mono/mono/blob/master/mcs/class/System.Net.Http/System.Net.Http.Headers/HttpHeaders.cs
HeaderInfo CheckName (string name)
{
if (string.IsNullOrEmpty (name))
throw new ArgumentException ("name");
Parser.Token.Check (name);
HeaderInfo headerInfo;
if (known_headers.TryGetValue (name, out headerInfo) && (headerInfo.HeaderKind & HeaderKind) == 0) {
if (HeaderKind != HttpHeaderKind.None && ((HeaderKind | headerInfo.HeaderKind) & HttpHeaderKind.Content) != 0)
throw new InvalidOperationException (name);
return null;
}
return headerInfo;
}
After looking at the full source file:
It looks like Content-Length is in the collection of known_headers.
Also looks like the internal type of the Content-Length header value is a long. But the Add() method only take a string for the value, which get's parsed to a long. Is the string value that you're passing for the Content-Length value a valid long?

Related

Facing Exception of MessageBodyWriter while sending JSONObject to Rest web service

I am newbie to web service. Due to requirement I have to send a file[most probably in txt format] to server through REST web service.
I am getting the exception like below.
MessageBodyWriter not found for media type=application/json, type=class gvjava.org.json.JSONObject, genericType=class gvjava.org.json.JSONObject.
Here is my web service method.
#Path("{c}")
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String convert(#PathParam("c") JSONObject object) throws JSONException {
String result = "";
return "<ctofservice>" + "<ctofoutput>" + result + "</ctofoutput>" + "</ctofservice>";
}
Now client code is like below
JSONObject data_file = new JSONObject();
data_file.put("file_name", uploadFile.getName());
data_file.put("description", "Something about my file....");
data_file.put("file", uploadFile);
Client client = ClientBuilder.newClient();
webTarget = client.target(uploadURL).path("ctofservice").path("convert");
Response value = webTarget.request(MediaType.APPLICATION_JSON_TYPE)
.post(Entity.entity(data_file,MediaType.APPLICATION_JSON_TYPE),
Response.class);
Please help me with this.
Thanks in advance.
------------------------------------------------------------------------
As suggested by peeskillet in the answer below, I tried to send file through multipart. Still I am facing exception of no octet stream found.
Below is my rest api
#Path("{c}")
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response convert(#FormDataParam("file") FormDataContentDisposition file) {
String result = "";
Some operation with attached parameter ...
return Response.status(200).entity(result).build();
}
Here is my test client
FormDataMultiPart multiPart = new FormDataMultiPart();
multiPart.setMediaType(MediaType.MULTIPART_FORM_DATA_TYPE);
FileDataBodyPart fileDataBodyPart = new FileDataBodyPart("file",
uploadFile,MediaType.APPLICATION_OCTET_STREAM_TYPE);
multiPart.bodyPart(fileDataBodyPart);
Client client = Client.create();
WebResource webResource = client
.resource(uploadURL).path("ctofservice");
ClientResponse response = webResource.accept("application/json")
.post(ClientResponse.class,multiPart);
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : "
+ response.getStatus());
}
And I am getting the exception below
I am not able to understand why I need to send data as MediaType.APPLICATION_OCTET_STREAM_TYPE ? As I have used multipart as media type before ...
I appreciate your help..
Without needing to configuring anything else, the easiest way to get around this is to just use a String instead of the actual JSONObject (i.e. just passing toString())
.post(Entity.json(data_file.toString()))
The problem with using JSONObject is that there is no provider that knows how to handle the conversion. You will have the same problem on the server side, where there is no provider to handle the conversion to JSONObject. So you will need to just do
#POST
public Response post(String json) {
JSONObject jsonObject = new JSONObject(json);
}
If you really want to be able to just use JSONObject without needing to use a String, then you should check out this post.
As an aside, this is not valid JSON (it's XML)
"<ctofservice>" + "<ctofoutput>" + result + "</ctofoutput>" + "</ctofservice>"
but you are saying that the endpoint returns JSON

Setting ContentType causing multiple requests in MVC Web API

I have a simple GetFile Method in a Web API that returns a file stored as binary in a database. The file can be either image, video or a recording, so I set the content type based on the extension:
[System.Web.Mvc.HttpGet]
public HttpResponseMessage GetFile(Guid Id)
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
var file = db.Files.Where<NonConformityFile>(item => item.Id == Id).FirstOrDefault<NonConformityFile>();
byte[] imgData = file.FileContents;
MemoryStream ms = new MemoryStream(imgData);
response.Content = new StreamContent(ms);
if (file.FileExtension == "png")
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");
else if (file.FileExtension == "mp4")
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("video/mp4");
else if (file.FileExtension == "caf")
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("audio/x-caf");
else
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, file.FileExtension + " is not a supported file format"));
return response;
}
This Works fine, but when adding some Security to the Method (by providing a token in the http header) it started crashing. When setting a breakpoint, I discovered that the breakpoint was actually hit twice. And the second time the token was gone from the header, causing authentication failure.
Further debugging made me find out that setting the response.Content.Header.ContentType causes the GetFile Method to be called a second time.
Is this designed behaviour, or some crazy side effects from something in my Development environment?
To reproduce, try this:
[System.Web.Mvc.HttpGet]
public HttpResponseMessage GetFile()
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new StreamContent(new MemoryStream());
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/png");
return response;
}
And see if the Method is run twice. I cannot see the Logic in this behaviour, and it is causing problems in my authentication.

Storing Value in HttpCookie

Using MVC.
Function Login(ByVal token As String) As ActionResult
Using client As New WebClient
Try
Dim jsonResponse As String = client.DownloadString(URL & "/Getuser&token=" & token)
Dim obj As UserInfo = Newtonsoft.Json.JsonConvert.DeserializeObject(Of UserInfo)(jsonResponse)
Response.Cookies.Add(New HttpCookie("token", token))
Response.Cookies.Add(New HttpCookie("user_id", obj.id))
Return Json(obj)
Catch ex As WebException
Return Content("ERROR")
Catch ex As Exception
Return Content("ERROR")
End Try
End Using
End Function
I am sending a token to this function.
Then Using this token to get the User Info from a certain API
Then Storing this token in a HttpCookie
All this has been working fine for almost a month,
Until it stopped working.
When I debugged, token had a value, and it stored it in the HttpCookie, but when I called Request.Cookies("token").Value it returned ''
Any help would be appreciated.
I did a trace on the Token..
I am writing the parameter "token" in a file before storing it in the cookie.
then I am writing the cookie Request.Cookies("token").Value in a file,
Function Login(ByVal token As String) As ActionResult
WriteToFile("TOKEN RECEIVED = ", token)
Using client As New WebClient
Try
Dim jsonResponse As String = client.DownloadString(URL & "/Getuser&token=" & token)
Dim obj As UserInfo = Newtonsoft.Json.JsonConvert.DeserializeObject(Of UserInfo)(jsonResponse)
Response.Cookies.Add(New HttpCookie("token", token))
Response.Cookies.Add(New HttpCookie("user_id", obj.id))
WriteToFile("TOKEN COOKIE = ", Request.Cookies("token").Value)
Return Json(obj)
Catch ex As WebException
Return Content("ERROR")
Catch ex As Exception
Return Content("ERROR")
End Try
End Using
End Function
it returns the following:
TOKEN RECEIVED = X132WEeRT3AASDV
TOKEN COOKIE =
When I try to write both Request and Response Cookies:
WriteToFile("TOKEN COOKIE = ", Request.Cookies("token").Value)
WriteToFile("TOKEN COOKIE = ", Response.Cookies("token").Value)
Request.Cookies("token").Value Returns Empty String
Response.Cookies("token").Value Returns Actual Value
Maybe your cookie just expire after one month? When using cookies don't forget to set expiration date, and check web browser settings (example: if you are using "tor browser bundle" this can be an issue).
HttpCookie myCookie = new HttpCookie("UserSettings");
myCookie["Font"] = "Arial";
myCookie["Color"] = "Blue";
myCookie.Expires = DateTime.Now.AddDays(1d);
Response.Cookies.Add(myCookie);
https://msdn.microsoft.com/en-us/library/78c837bd%28v=vs.140%29.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1
Make sure you access the cookies only during the actual Http request. Maybe you have changed the way (or place where) you call this function.
This is an old question on SO but for those interested, .NET team has created a new method to properly handle cookies.
This is available in .NET 4.7.1 and not earlier versions:
See under the section ASP.NET HttpCookie parsing here
https://blogs.msdn.microsoft.com/dotnet/2017/09/13/net-framework-4-7-1-asp-net-and-configuration-features/

'identity.api.rackspacecloud.com' randomly throws 'The remote name could not be resolved' exception

I am accessing Rackspace Cloud APIs.
I have one api call which authenticates me on the rackspace cloud.
The method works perfectly, however, from time to time, i get this exception, randomly :
The remote name could not be resolved: 'identity.api.rackspacecloud.com'
When i am not getting this exception, the method returns the expected result, as it should be.
Is there any specific reason why it does this?
Here is my .net code:
private async Task<XDocument> AuthenticateAsync()
{
XNamespace ns = "http://docs.rackspace.com/identity/api/ext/RAX-KSKEY/v1.0";
XDocument doc =
new XDocument(
new XDeclaration("1.0", "UTF-8", "Yes"),
new XElement("auth",
new XElement(ns + "apiKeyCredentials",
new XAttribute("username", "the userName"),
new XAttribute("apiKey", "the apiKey")
)
)
);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
StringContent content = new StringContent(doc.ToString(), Encoding.UTF8, "application/xml");
// i randomly get "The remote name could not be resolved" exception
HttpResponseMessage response = await client.PostAsync("https://identity.api.rackspacecloud.com/v2.0/tokens", content);
response.EnsureSuccessStatusCode();
string stringResponse = await response.Content.ReadAsStringAsync();
return XDocument.Parse(stringResponse);
}
}
This certainly sounds like a DNS failure. Can you configure your machine to use the Google DNS servers and try again?

HttpResponseMessage ReasonPhrase max length?

I have this code:
public void Put(int id, DistributionRuleModelListItem model)
{
CommonResultModel pre = new BLL.DistributionRules().Save(id, model, true);
if(!pre.success){
DAL.DBManager.DestroyContext();
var resp = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(string.Format("Internal server error for distruleId: {0}", id)),
ReasonPhrase = pre.message.Replace(Environment.NewLine, " ")//.Substring(0,400)
};
throw new HttpResponseException(resp);
}
}
There is logic that can set the value of pre.message to be an exception.ToString() and if it is too long i receive the following application exception:
Specified argument was out of the range of valid values. Parameter
name: value
But if I uncomment .Substring(0,400) everything works fine and on client side I receive the correct response and it is possible to show it to the user.
What is the max length of ReasonPhrase? I can't find any documentation that specifies this value.
I couldn't find the max value documented anywhere, however through trial and error, I found it to have a maximum length of 512 bytes.

Resources