How do I handle/fix "Error getting response stream (ReadDone2): ReceiveFailure" when using MonoTouch? - iphone

I am using MonoTouch to build an iPhone app. In the app I am making Web Requests to pull back information from the web services running on our server.
This is my method to build the request:
public static HttpWebRequest CreateRequest(string serviceUrl, string methodName, JsonObject methodArgs)
{
string body = "";
body = methodArgs.ToString();
HttpWebRequest request = WebRequest.Create(serviceUrl) as HttpWebRequest;
request.ContentLength = body.Length; // Set type to POST
request.Method = "POST";
request.ContentType = "text/json";
request.Headers.Add("X-JSON-RPC", methodName);
StreamWriter strm = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
strm.Write(body);
strm.Close();
return request;
}
Then I call it like this:
var request = CreateRequest(URL, METHOD_NAME, args);
request.BeginGetResponse (new AsyncCallback(ProcessResponse), request);
And ProcessResponse looks like this:
private void ProcessResponse(IAsyncResult result)
{
try
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
using (HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result)) // this is where the exception gets thrown
{
using (StreamReader strm = new System.IO.StreamReader(response.GetResponseStream()))
{
JsonValue value = JsonObject.Load(strm);
// do stuff...
strm.Close();
} // using
response.Close();
} // using
Busy = false;
}
catch(Exception e)
{
Console.Error.WriteLine (e.Message);
}
}
There is another question about this issue for Monodroid and the answer there suggested explicitly closing the output stream. I tried this but it doesn't solve the problem. I am still getting a lot of ReadDone2 errors occurring.
My workaround at the moment involves just re-submitting the Web Request if an error occurs and the second attempt seems to work in most cases. These errors only happen when I am testing on the phone itself and never occur when using the Simulator.

Whenever possible try to use WebClient since it will deal automatically with a lot of details (including streams). It also makes it easier to make your request async which is often helpful for not blocking the UI.
E.g. WebClient.UploadDataAsync looks like a good replacement for the above. You will get the data, when received from the UploadDataCompleted event (sample here).
Also are you sure your request is always and only using System.Text.Encoding.ASCII ? using System.Text.Encoding.UTF8 is often usedm, by default, since it will represent more characters.
UPDATE: If you send or receive large amount to byte[] (or string) then you should look at using OpenWriteAsync method and OpenWriteCompleted event.

This is a bug in Mono, please see https://bugzilla.xamarin.com/show_bug.cgi?id=19673

Related

Why does the Facebook Graph API respond with the same "next" URL over and over?

I am using Volley to submit POST requests to the Facebook Graph API in order to retrieve information about photos and videos from a user account using their BATCH facility so I get it all in one go (rather than making one call for photos, one for videos). The first call works perfectly:
request = new StringRequest(Request.Method.POST,
"https://graph.facebook.com",
future,
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Timber.e("Got VolleyError: %s", error.getMessage());
}
}) {
#Override
public String getBodyContentType() {
return "application/x-www-form-urlencoded; charset=" + getParamsEncoding();
}
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> params = new HashMap<>();
JSONArray batchRequest = new JSONArray();
JSONObject photoRequest = new JSONObject();
JSONObject videoRequest = new JSONObject();
try {
photoRequest.put("method", "GET");
photoRequest.put("relative_url",facebookUserID + String.format("?fields=photos.limit(%1$s){id,created_time,images{source},picture}",batchSize));
videoRequest.put("method", "GET");
videoRequest.put("relative_url",facebookUserID + String.format("?fields=videos.limit(%1$s){id,created_time,source,picture}",batchSize));
batchRequest.put(photoRequest);
batchRequest.put(videoRequest);
} catch (JSONException e) {
Timber.d("Lifecycle: Exception constructing batch request: %s", e.getMessage());
return null;
}
//try {
// Timber.d("Lifecycle: batchRequest: %s", batchRequest.toString(2));
//} catch (JSONException e) {
// e.printStackTrace();
//}
params.put("batch", batchRequest.toString());
params.put("include_headers", "false");
params.put(FB_BASE_ACCESSTOKEN_KEY, facebookToken);
return params;
}
};
InTouchUtils.addToRequestQueue(request);
// Using a blocking volley request, this chain has been called on a separate async task
// See SO: https://stackoverflow.com/questions/16904741/can-i-do-a-synchronous-request-with-volley
facebookRetval = future.get(VOLLEY_REQUEST_DEFAULT_TIMEOUT, TimeUnit.SECONDS);
returnResult = parseBatchRequest(facebookRetval);
The returned JSON has all the fields I've requested, as well as the pagination block with cursors, and a "next" and/or "previous" url, per the Facebook documentation.
A "next" URL looks something like:
https://graph.facebook.com/v7.0/FACEBOOK_ID_HERE/photos?access_token=ACCESS_TOKEN_HERE&fields=id,created_time,images{source},picture&limit=5&after=AFTER_TOKEN_HERE
There is one of these that gets passed back from the batch operation for each of the original GET operations (assuming both photos and videos have greater than LIMIT items).
Again, this part works fine.
But when I try and use that "next" URL to create another BATCH call, it fails with an "unsupported GET operation" error. This is true even though I can use a standard Volley GET using that exact same URL and it works perfectly.
I have tried using the "https://graph.facebook.com" portion of the above URL as the root of the POST (like what worked in the initial call), and everything after that as the "relative_url" parameter. No go.
Then I tried parsing out just the "after" portion of the "next" url, and constructing a new relative_url that was exactly like the first one, but tacking on a "&after=" + AFTER_VALUE to it as the relative_url. No go - in fact, while this succeeded in making the call, I keep getting the initial batch over and over and over. It is like it is ignoring the "&after=" parameter.
For now I am back to making two GET calls (one for photos, one for videos) just using the NEXT url as long as it keeps being passed back to me. This works fine, but obviously I'm making two network calls instead of the single batch one.
Ideas?
A little more examination revealed that I had made a string parsing error on the subsequent batch operation, and was inadvertently including a forward slash when I should not have been.
For those new to using the batch API, the lesson is that you need "https://graph.facebook.com" as the POST url (no trailing forward slash), then your relative url should NOT start with a forward slash. So the URL I was trying to utilize on calls 2..N like this:
https://graph.facebook.com/v7.0/FACEBOOK_ID_HERE/photos?access_token=ACCESS_TOKEN_HERE&fields=id,created_time,images{source},picture&limit=5&after=AFTER_TOKEN_HERE
should be broken out as:
photoRequest.put("relative_url", "v7.0/FACEBOOK_ID_HERE/photos?access_token=ACCESS_TOKEN_HERE&fields=id,created_time,images{source},picture&limit=5&after=AFTER_TOKEN_HERE");
The API handles putting in the forward slash between the root and the relative url.

HttpResponseException: Internal Server Error

Weirdest thing I have seen in a while. I run my API call through Postman and have no problems at all making a GET request. However, the groovy code below pulls groovyx.net.http.HttpResponseException: Internal Server Error. I am not able to pull even debug to understand if I am actually getting a 5xx error or my code is legitimately broken.
Additionally I have had code like this work in the past, I re-pulled that working code and have the same error. Curious if my Maven config settings would be causing the issue as well (Not sure where I would have to debug). I have also tried messing with the URIbuilder line to see if changing the endpoints would help.
Thanks for helping
abstract class HTTTPClient {
protected runGetRequest(String endpointPassedIn, RESTClient Client){
URIBuilder myEndpoint = new URIBuilder(new URI(Client.uri.toString() + endpointPassedIn))
//Error happens at the next Line
Client.get(uri: myEndpoint, contentType: ContentType.JSON)
LazyMap Response = unprocessedResponse.getData() as LazyMap
return Response
}
}
#Singleton(lazy = true)
class RequestService extends HTTTPClient {
private String auth = "myAuth"
private String baseURL = 'https://api.endpoint.net/'
private RESTClient client = setClient(baseURL, auth)
public buildResponseList(int pagesToPull) {
String endpoint = 'site/address.json?page='
ArrayList responseList = []
for (int i = 1; i <= pagesToPull; i++) {
LazyMap Response = runGetRequest(endpoint + i, client)
for (row in Response) {
responseList.add(row)
//TODO Add in items call here
}
}
return conversationList
}
The error was due to encoding in the Authorization, was on the server side, not the code side

How to consume REST api in Xamarin.iOS?

I have made a REST API and I want to use it using my Xamarin.iOS application.
Basically I want to call the API from my Xamarin application by sending some arguments to one of my API's function.
I tried the resources available at Xamarin's official website, but I a newbie so I cannot understand how it was done.
The REST API is hosted locally by the network I am using. It is not hosted at a static IP.
Kindly guide me.
You don't really need a fancy plugin if you just want to hit Web Endpoints. I simply use the basic WebRequest API.
var request = WebRequest.CreateHttp(YOUR_URL_HERE);
request.Method = "GET";
request.ContentType = "application/JSON";
request.BeginGetResponse(ResponseComplete, request);
... and then your response method can be something along the lines of...
protected void ResponseComplete(IAsyncResult result)
{
try
{
var request = result.AsyncState as HttpWebRequest;
if (request != null)
{
Debug.WriteLine("Completed query: " + request.RequestUri);
using (var streamReader = new StreamReader(response.GetResponseStream()))
{
var result = streamReader.ReadToEnd();
Debug.WriteLine("Query Result: " + result);
}
}
}
}
... and if you need to post data you can add request.BeginGetRequestStream(PostData, request); before request.BeginGetResponse(ResponseComplete, request); and make your GetRequestStream handling method something along the lines of...
protected void PostData(IAsyncResult result)
{
var request = result.AsyncState as HttpWebRequest;
if (request != null)
{
using (var postStream = request.EndGetRequestStream(result))
{
var json = JsonConvert.SerializeObject(DATA_TO_POST);
Debug.WriteLine("Posting data: " + json);
var byteArray = Encoding.UTF8.GetBytes(json);
postStream.Write(byteArray, 0, byteArray.Length);
}
}
}
I would recommend Refit, you can install it as a NuGet package. Its pritty simple to use.
Refit allows us to define an interface that describes the API that we're calling, and the Refit framework handles making the call to the service and deserializing the return.
Have a look at this great blog post on how to set it up and other packages that might help you out. http://arteksoftware.com/resilient-network-services-with-xamarin/
I have used RestSharp before but Refit is alot easier to get running.

Threading in MonoTouch with webrequest and uialertview

I access a website and retrieve some data using HttpWebRequest and I need a UIAlertView with a UIActivityIndicatorView to display while it retrieves said data. However, it just dims the screen and doesn't show the alert until it's too late. I've read other threading related questions here and can't figure out how to resolve this. I tried using ThreadPool, InvokeOnMainThread, Tasks, all of which have not worked.
EDIT:
HttpWebRequest url = (HttpWebRequest)WebRequest.Create ("maps.googleapis.com/maps/api/place/search/..);
HttpWebResponse webResp = (HttpWebResponse)url.GetResponse (); JsonValue jVal =
JsonObject.Load (webResp.GetResponseStream ());
JsonObject jObj = (JsonObject)jVal;
I have the above code, if it will properly format for me, how would I convert that to use WebClient? Can you still get JSON and such from it?
It's often a lot easier (and less error prone) to use WebClient and one of it's *Async methods than using HttpWebRequest.
Here's an example to download and show a small image.
WebClient client = new WebClient ();
var wait = new UIActivityIndicatorView (View.Bounds);
client.DownloadDataCompleted += (object sender, DownloadDataCompletedEventArgs e) => {
byte[] result = e.Result;
if (result != null) {
NSData data = NSData.FromArray (e.Result);
UIImage img = UIImage.LoadFromData (data);
InvokeOnMainThread (delegate {
ShowImage (img);
wait.StopAnimating ();
});
}
};
wait.StartAnimating ();
client.DownloadDataAsync (new Uri (url));
EDIT (for updated question)
For streams you can use WebClient.OpenReadAsync and the OpenReadCompleted event.

multiple pages with C# Web browser control

I am trying to download HTML content from any URL through webbrowser control in C#.net.
I choose webrowser to handle Javascript issues. I am using webbrowser control without placing
it on the form. It works great for one url, but when I call it for multiple urls I am unable
to download the page.
Here is the code
GetWebpage()
{
System.Windows.Forms.WebBrowser wb = new System.Windows.Forms.WebBrowser();
wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(wb_DocumentCompleted);
wb.Navigate(sURI, false);
bDocumentLoaded = false;
while (!bDocumentLoaded)
{
Application.DoEvents();
Thread.Sleep(100);
}
sHTML = wb.DocumentText;
bDocumentLoaded = false;
}
Event:
private void wb_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
System.Windows.Forms.WebBrowser webBrowser1;
webBrowser1 = sender as WebBrowser;
string strTit = webBrowser1.DocumentTitle;
string str = webBrowser1.DocumentText;
bDocumentLoaded = true;
}
Cheers,
Karthik
You can use webclient object to fetch data from some url.
Try using Downloading String
public static void DownloadString (string address)
{
WebClient client = new WebClient ();
string reply = client.DownloadString (address);
Console.WriteLine (reply);
}
You can also use ASYC method of same downloading string.
I think your problem is that some sites are detecting specific browsertype and then they are returning HTML
Try setting the HeaderProperty of WebClient Object this is a list of HttpWebRequest Object
For Example
myWebClient.Headers.Add("Content-Type","application/x-www-form-urlencoded");
Modify the useragent of HTTPWEBRequest then add to headers.
HTTPWEBRequest.UserAgent=".NET Framework Test Client";
You can check more information about this in MSDN Link
I might recommend using the mshtml and SHDocVW libraries and using approach found in the answer here:
Unable to to locate and click a submit button using mshtml.HTMLInputElement