Xamarin WebClient for POST Requests extremely slow - c#

When I try to make POST requests in Xamarin the time the function GetRequestStream() takes is 10-20 seconds. The answer time on my server is below 1 second and so is the POST request I made from a website.
I already tried:
Setting proxy server to null so it won't look it up
Using blocks around the requests so it get's flushed
Increased the maximum connections
Did everything with async multithreading
Even tried another class called RestSharp - same result.
Nothing I did was actually helping to reduce the runtime even by 100 milliseconds. I just cannot imagine that this is Xamarins fault because I can't be the only one who decided to do some HTTP requests in his cross platform app. I already lost UWP since the ServiceManager, which I use to connect to TLS sites isn't supported in UWP - thank you Xamarin.
I really need solutions so please help :)
this is the code i used and optimized:
byte[] byteArray = Encoding.UTF8.GetBytes(query);
HttpWebRequest webRequest = new HttpWebRequest(uri);
webRequest.Method = "POST";
webRequest.Proxy = null;
webRequest.ContentType = "application/x-www-form-urlencoded";
webRequest.Timeout = 1000;
webRequest.ReadWriteTimeout = 1000;
Stream dataStream = await webRequest.GetRequestStreamAsync();
await dataStream.WriteAsync(byteArray, 0, byteArray.Length);
dataStream.Close();
WebResponse webResponse = await webRequest.GetResponseAsync();
using (StreamReader reader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8))
{
string ret = await reader.ReadToEndAsync();
return ret;
}
and this is the code i tried with the ModernHttpClient
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient(new NativeMessageHandler());
Dictionary<string, string> values = new Dictionary<string, string>();
string[] q = query.Split('&');
for (int i = 0; i < q.Length; i++)
values.Add(q[i].Split('=')[0], q[i].Split('=')[1]);
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
HttpResponseMessage response;
try
{
response = await client.PostAsync(uri, content);
string answer = await response.Content.ReadAsStringAsync();
return answer;
}
catch (Exception e)
{
return "";
}
and of course i added these lines before calling the whole network stuff
ServicePointManager.ServerCertificateValidationCallback += (a, b, c, d) => { return true; };
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.Expect100Continue = false;
ServicePointManager.DefaultConnectionLimit = 100;
It's definitely a Xamarin or an Android Problem. I don't have other devices to test it,but... what can i do now? I tested it on a .Net console application, so it's definitely not .Net related.

I just found the answer!
it speeds up when i set the Protocol Version. It's mentione basically nowhere but i googled for android post slowdowns and there i found it, finally.
setting the protocol version to HTTP 1.1 speeds things up very fast!
now i just have to fix the boot delay with xamarin >.<
Thank you so much for you help!

Try implement ModernHttpClientPlugin into your code.
Just add this line when initializing new HttpClient, and everything should run smoother.
var httpClient = new HttpClient(new NativeMessageHandler());
Another idea is to try your implementation in new blank console aplication. Then you will see if this is problem with your server/client or Xamarin itself. It should look something like this.
class Program
{
static void Main(string[]args)
{
MainAsync(null);
Console.ReadKey();
}
static async System.Threading.Tasks.Task MainAsync(string[] args)
{
System.Net.Http.HttpClient client = new System.Net.Http.HttpClient();
Dictionary<string, string> values = new Dictionary<string, string>();
var query = "user=mickey&passwd=mini";
string[] q = query.Split('&');
for (int i = 0; i < q.Length; i++)
values.Add(q[i].Split('=')[0], q[i].Split('=')[1]);
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
HttpResponseMessage response;
try
{
response = await client.PostAsync(new Uri("https://www.example.com/login.php"), content);
string answer = await response.Content.ReadAsStringAsync();
}
catch (Exception e)
{
}
}
}
Please, give us feedback how fast that goes.

In my case 'WebClient' and 'HttpWebReuest' cause first-time delays about 20 secs (possible, during resolve of new hosts) on some specific Android 7 devices, but works good on others.
I tried different ways to solve this problem but for me only replacement to 'HttpClient' solves it.

Related

Get Data of online API

I want to download data of this website into a json file but as I am quite new to coding with C# I cant manage to get the data. I want to get Data of https://discosweb.esoc.esa.int/api/objects the authorization via token works but I dont know how I can send a request so the server gives me a json back and I cant find a solution online. I cant give you a screenshot of the API because you have to be logged in to see it. Plz ask me for detailed information if you can help me. Thank you realy for trying.
The code I want to run is here.
class Program
{
static HttpClient client = new HttpClient();
static void Main(string[] args)
{
client.BaseAddress = new Uri("https://discosweb.esoc.esa.int");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/vnd.api+json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("my_token");
var httpRequest = (HttpWebRequest)WebRequest.Create(client.BaseAddress);
var httpResponse = (HttpWebResponse)httpRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var streamReaderResult = streamReader.ReadToEnd();
}
Console.WriteLine("Status https://discosweb.esoc.esa.int : " + httpResponse.StatusCode);
}
}
Try this
var url = "https://discosweb.esoc.esa.int/api/objects";
var httpRequest = (HttpWebRequest)WebRequest.Create(url);
httpRequest.Method = "POST";
httpRequest.Headers["Authorization"] = "Basic XXXx";
httpRequest.ContentType = "";
httpRequest.Headers["Content-Length"] = "0";
var httpResponse = (HttpWebResponse)httpRequest.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
}
Console.WriteLine(httpResponse.StatusCode);
Where XXXx is user:password in base64.
Here is a basic implementation for making that API call to get the JSON result. You will need to parse that JSON into something other than a string but I'll assume you can handle that part.
This uses System.Net.HttpClient which is the modern HTTP api provided by .NET. Its operations are async so hopefully your code is or can be written to properly await async operations.
//Someplace convenient, create a shared HttpClient to avoid
//creating and disposing for each request.
HttpClient client = new HttpClient();
string data = await GetObjects(client);
//Example implementation
public async Task<string> GetObjects(HttpClient client)
{
string url = "https://discosweb.esoc.esa.int/api/objects";
using (HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, url))
{
msg.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "your personal access token here");
using (var result = await client.SendAsync(msg))
{
string content = await result.Content.ReadAsStringAsync();
return content;
}
}
}
While I may be a month late, I've actually developed an SDK for this particular API.
So, if you use this SDK it's pretty simple to do what you want. You can essentially forget about handling anything HTTP related, my SDK abstracts all of that away.
For example, to fetch Sputnik's data (which has an ID of 1) you'd run.
HttpClient innerClient = new();
innerClient.BaseAddress = "https://discosweb.esoc.esa.int/api/"
innerClient.DefaultRequestHeaders.Authorization = new("bearer", yourApiKey);
DiscosClient client = new();
DiscosObject sputnik = await client.GetSingle<DiscosObject>("1");
If you're using ASP.NET, there's a set of DI extensions that can actually set it all up for you, so you can skip the first three lines.
If you do choose to use it, please let me know, as it would be nice knowing my SDK is getting some use. If you have any issues, please just reach out through the GitHub issues page and I'll try to help!

Microsoft Project Online OData asynchronous query

I am trying to use Microsoft Project OData by querying data in C#. I am having performances issues with delays around 1s for each query. I am trying to query 2 information at once using that method :
public static async Task<string> ReadXml(string url)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.Credentials = Credentials; // SharePointOnlineCredentials
request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
using (var response = (HttpWebResponse)await request.GetResponseAsync())
using (var stream = response.GetResponseStream())
using (var reader = new System.IO.StreamReader(stream))
{
var xml = await reader.ReadToEndAsync();
return xml;
}
}
It works fine if I call it and always wait for it to end before calling it again, but I never receive any response from the WebRequest if I call it multiple times at once :
// just an example. I usually put a condition to filter for the tasks of a single project
var query1 = ReadXml(#"https://something.sharepoint.com/sites/pwa/_api/ProjectData/Projects");
var query2 = ReadXml(#"https://something.sharepoint.com/sites/pwa/_api/ProjectData/Tasks");
Task.WaitAll(query1, query2);
If I "await" the first one and then do the second one it works fine, but not with the code above. And this is assuming there is < 300 tasks in the project, if more than that I have to query them in chunk of 300 leading to 4 or 5 seconds for the entire query since I can't to them all at once!
Is there a way to send multiple request at the same time ?
I am able to do it by simply entering the url in multiple chrome tabs really fast / have faster responses. I don't understand why it doesn't work with my code!
Thanks,
According to the following post Webrequest.Create could be the problem, it uses an internally blocking method C# Thread UI is getting blocked | Possible reason WebRequest.Create?.
The code below uses the newer HttpClient and shouldn't have this issue.
public static HttpClient _HttpClient { get; } = new HttpClient(new HttpClientHandler { Credentials=new NetworkCredential("","")});
public static async Task<string> ReadXml(string url)
{
using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, url))
{
requestMessage.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
using (var response = await _HttpClient.SendAsync(requestMessage))
{
return await response.Content.ReadAsStringAsync();
}
}
}

HttpClient won't work

So I'm trying to send a multiform POST to API with http client but it's just hang there indefinetly. I test this code in console and it worked as it should, but then I try to run it like this for the UI
private static async Task<string> ApiTask(...)
{
var SourceStream = File.Open(imgpath,FileMode.Open);
var FileStreamContent = new StreamContent(SourceStream);
FileStreamContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg");
var client = new HttpClient();
using (var formData = new MultipartFormDataContent())
{
formData.Add(new StringContent("this is a test"),"comment");
formData.Add(new StringContent("Command: detect"),"message");
formData.Add(fileStreamContent, "image","image.jpg");
var response = await client.PostAsync(url,formData).ConfigureAwait(false);
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return responseString
}
}
And I'm calling it from the EventHandler
public async void buttnclck(object sender, EventArgs e)
{
var task = await ApiTask(...);
lblresult.Text = task;
}
but as I said the code just stay in de .PostAsync line indefinetly or when a System.Threading.Task.TaskCanceledException is thrown.
So what I missing here? I thing I was handeling the async/await methods just fine but it's clear I'm not. I tried also with .Result but it won't work even and would throw System.AggregateException. So please help, been trying to make it work modifying the code as other suggested responses but still not working
EDIT:
after couple of hours debugging and searching I find out my problem relies in formData.Add(FileStreamContent, "image","image.jpg"); maybe I'm not serializing the image correctly? How can I fix this??

401 Unauthorized on SECOND HttpClient/HttpWebRequest call

I have a application that uses the SharePoint 2010 REST API.
In the process of creating an Item there are multiple request done after each other:
1 Call: Getting Items from List: Succes
2 Call: Create Item: 401 Unauthorized
This is the same if I do it like this:
1 Call: Create Item: Succes
2 Call: Delete Item: 401 Unauthorized
What I know is that my functions work separately they DON'T work when they are called after each other.
When I close the application (Windows Phone 8.1 app) after creating a item and when restarted try to delete the item it works.
First I thought it had to do with the way I handle my fields so I changed them to NULL in a finally statement but that didn't work.
public async Task<bool> CreateNewItem(NewItem myNewItem)
{
try
{
StatusBar statusBar = await MyStatusBar.ShowStatusBar("Creating new List Item.");
//Retrieving Settings from Saved file
mySettings = await MyCredentials.GetMySettings();
myCred = new NetworkCredential(mySettings.UserName, mySettings.Password, mySettings.Domain);
using (var handler = new HttpClientHandler { Credentials = myCred })
{
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
NewItem newItem = myNewItem;
var jsonObject = JsonConvert.SerializeObject(newItem);
HttpResponseMessage response = await client.PostAsync(new Uri(baseUrl + listNameHourRegistration), new StringContent(jsonObject.ToString(), Encoding.Unicode, "application/json"));
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
response.EnsureSuccessStatusCode();
string responseMessage = await response.Content.ReadAsStringAsync();
client.Dispose();
if (responseMessage.Length > 0)
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return false;
}
finally
{
request = null;
response = null;
myCred = null;
mySettings = null;
}
return false;
}
Just run into the same problem.
Anyway, the 2nd request does not follow the same authentication procedure. Even if you initialize a new HttpClient object. I sniffed the HTTP traffic.
After the 1st request I am doing another with different credentials. This is also ending in a 401. I am really confused...
Seems the NTLM Handshake stucks at the 2nd of 6 steps
http://www.innovation.ch/personal/ronald/ntlm.html
Edit:
You may want to use the CSOM.
http://social.msdn.microsoft.com/Forums/office/en-US/efd12f11-cdb3-4b28-a9e0-32bfab71a419/windows-phone-81-sdk-for-sharepoint-csom?forum=sharepointdevelopment
While I still don't know what the actual problem is, at least I found a workaround: Use the WebRequest class instead of HttpClient.
I was running into this same error when I realized I was adding the headers each time I was calling the endpoint. Hopefully this will help someone.
Instead I initialized the HttpClient instance in my class constructor and set the headers there. Also I learned it is better practice to only use 1 instance instead of recreating with "using" (See this article https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/)
I'm invoking CallApiAsync from another class in a loop.
Here's my final solution:
class ApiShared
{
private HttpClient client;
public ApiShared() {
client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", ConfigurationManager.AppSettings["ApiKey"]);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
public async Task<ApiResponse_Root> CallApiAsync(string endpoint)
{
// Make API call
Uri endpointUri = new Uri(endpoint);
var stringTask = client.GetStringAsync(endpointUri);
var data = JsonConvert.DeserializeObject<ApiResponse_Root>(await stringTask);
return data;
}
}
On a windows machine you can resolve this with this registry setting change:
Go to the following Registry entry:
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
Now add a new DWORD to the Lsa folder called: DisableLoopBackCheck and set this to 1
I see that this question has been posted long back. But I don't see a correctly working solution posted yet to this thread.
I faced exactly the same issue where the next requests kept on failing returning me 401 UnAuthorized.
I figured out using fiddler that from SECOND request onwards, there was a Cookie added to the request which was possibly a result of Set-Cookie response sent by the server along with first response.
So here's how I tackled the situation - Make UseCookies false:
new HttpClientHandler { Credentials = myCred, UseCookies = false }
This should resolve your issue. Hope this helps someone who's looking for a solution to a similar issue.

HttpWebRequest.GetResponse() keeps getting timed out

i wrote a simple C# function to retrieve trade history from MtGox with following API call:
https://data.mtgox.com/api/1/BTCUSD/trades?since=<trade_id>
documented here: https://en.bitcoin.it/wiki/MtGox/API/HTTP/v1#Multi_currency_trades
here's the function:
string GetTradesOnline(Int64 tid)
{
Thread.Sleep(30000);
// communicate
string url = "https://data.mtgox.com/api/1/BTCUSD/trades?since=" + tid.ToString();
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
StreamReader reader = new StreamReader(response.GetResponseStream());
string json = reader.ReadToEnd();
reader.Close();
reader.Dispose();
response.Close();
return json;
}
i'm starting at tid=0 (trade id) to get the data (from the very beginning). for each request, i receive a response containing 1000 trade details. i always send the trade id from the previous response for the next request. it works fine for exactly 4 requests & responses. but after that, the following line throws a "System.Net.WebException", saying that "The operation has timed out":
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
here are the facts:
catching the exception and retying keeps causing the same exception
the default HttpWebRequest .Timeout and .ReadWriteTimeout are already high enough (over a minute)
changing HttpWebRequest.KeepAlive to false didn't solve anything either
it seems to always work in the browser even while the function is failing
it has no problems retrieveing the response from https://www.google.com
the amount of successful responses before the exceptions varies from day to day (but browser always works)
starting at the trade id that failed last time causes the exception immediately
calling this function from the main thread instead still caused the exception
running it on a different machine didn't work
running it from a different IP didn't work
increasing Thread.Sleep inbetween requests does not help
any ideas of what could be wrong?
I had the very same issue.
For me the fix was as simple as wrapping the HttpWebResponse code in using block.
using (HttpWebResponse response = (HttpWebResponse) request.GetResponse())
{
// Do your processings here....
}
Details: This issue usually happens when several requests are made to the same host, and WebResponse is not disposed properly. That is where using block will properly dispose the WebResponse object properly and thus solving the issue.
There are two kind of timeouts. Client timeout and server timeout. Have you tried doing something like this:
request.Timeout = Timeout.Infinite;
request.KeepAlive = true;
Try something like this...
I just had similar troubles calling a REST Service on a LINUX Server thru ssl. After trying many different configuration scenarios I found out that I had to send a UserAgent in the http head.
Here is my final method for calling the REST API.
private static string RunWebRequest(string url, string json)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
// Header
request.ContentType = "application/json";
request.Method = "POST";
request.AllowAutoRedirect = false;
request.KeepAlive = false;
request.Timeout = 30000;
request.ReadWriteTimeout = 30000;
request.UserAgent = "test.net";
request.Accept = "application/json";
request.ProtocolVersion = HttpVersion.Version11;
request.Headers.Add("Accept-Language","de_DE");
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
byte[] bytes = Encoding.UTF8.GetBytes(json);
request.ContentLength = bytes.Length;
using (var writer = request.GetRequestStream())
{
writer.Write(bytes, 0, bytes.Length);
writer.Flush();
writer.Close();
}
var httpResponse = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
var jsonReturn = streamReader.ReadToEnd();
return jsonReturn;
}
}
This is not a solution, but just an alternative:
These days i almost only use WebClient instead of HttpWebRequest. Especially WebClient.UploadString for POST and PUT and WebClient.DownloadString. These simply take and return strings. This way i don't have to deal with streams objects, except when i get a WebException. i can also set the content type with WebClient.Headers["Content-type"] if necessary. The using statement also makes life easier by calling Dispose for me.
Rarely for performance, i set System.Net.ServicePointManager.DefaultConnectionLimit high and instead use HttpClient with it's Async methods for simultaneous calls.
This is how i would do it now
string GetTradesOnline(Int64 tid)
{
using (var wc = new WebClient())
{
return wc.DownloadString("https://data.mtgox.com/api/1/BTCUSD/trades?since=" + tid.ToString());
}
}
2 more POST examples
// POST
string SubmitData(string data)
{
string response;
using (var wc = new WebClient())
{
wc.Headers["Content-type"] = "text/plain";
response = wc.UploadString("https://data.mtgox.com/api/1/BTCUSD/trades", "POST", data);
}
return response;
}
// POST: easily url encode multiple parameters
string SubmitForm(string project, string subject, string sender, string message)
{
// url encoded query
NameValueCollection query = HttpUtility.ParseQueryString(string.Empty);
query.Add("project", project);
query.Add("subject", subject);
// url encoded data
NameValueCollection data = HttpUtility.ParseQueryString(string.Empty);
data.Add("sender", sender);
data.Add("message", message);
string response;
using (var wc = new WebClient())
{
wc.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
response = wc.UploadString( "https://data.mtgox.com/api/1/BTCUSD/trades?"+query.ToString()
, WebRequestMethods.Http.Post
, data.ToString()
);
}
return response;
}
Error handling
try
{
Console.WriteLine(GetTradesOnline(0));
string data = File.ReadAllText(#"C:\mydata.txt");
Console.WriteLine(SubmitData(data));
Console.WriteLine(SubmitForm("The Big Project", "Progress", "John Smith", "almost done"));
}
catch (WebException ex)
{
string msg;
if (ex.Response != null)
{
// read response HTTP body
using (var sr = new StreamReader(ex.Response.GetResponseStream())) msg = sr.ReadToEnd();
}
else
{
msg = ex.Message;
}
Log(msg);
}
For what it's worth, I was experiencing the same issues with timeouts every time I used it, even though calls went through to the server I was calling. The problem in my case was that I had Expect set to application/json, when that wasn't what the server was returning.

Categories