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

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.

Related

UnityWebRequest.downloadHandler.text is empty, eventhough the POST method returns a response

I am making a POST api call to my server using UnityWebRequest (Unity version that i have is 2017.4.0f1)
I am sending some data elements in the request body to the server, which
inserts into my DB and returns a response body, which is a json string
I am using
UnityWebRequest.downloadhandler.text to read the response message,
but it is empty, eventhough the data elements
are getting inserted into my DB. request.downloadHandler.data.Length also gives me 0
Making the same call through
Postman returns me the appropriate response (so does using
HTTPWebRequest and reading the response via a stream reader)
This is the code snippet that i have for it:
UnityWebRequest request=new UnityWebRequest(endpoint,"POST");
request.SetRequestHeader("Content-Type","application/json");
request.SetRequestHeader("host",host);
request.SetRequestHeader("X-Amz-Date",dateTime);
request.SetRequestHeader("Authorization",authorizationHeader);
request.uploadHandler=(UploadHandler)new
UploadHandlerRaw(Encoding.UTF8.GetBytes(requestParameter));
request.chunkedTransfer=false;
request.downloadHandler=new DownloadHandlerBuffer();
request.SendWebRequest();
print(request.downloadHandler.text);
Please advise as to what i am doing wrong here.....
You need to wait for the results to be downloaded before you can do anything with it. Webrequests are asynchronous!
Typically, you would use a Coroutine to do this, like
public IEnumerator LoadData()
{
// ......
// all your code goes here, up to the SendWebRequest line
// then you yield to wait for the request to return
yield return request.SendWebRequest();
// after this, you will have a result
print(request.downloadHandler.text);
}
Start this coroutine like this:
StartCoroutine(LoadData());
More examples in the answer to this question: Sending http requests in C# with Unity
As frankhermes says you need wait for the results to be downloaded before you do anything
yield return request.SendWebRequest();
But the above line alone didn't work for me.
public IEnumerator LoadData()
{
// ......
// all your code goes here, up to the SendWebRequest line
var asyncOperation = request.SendWebRequest();
while (!asyncOperation.isDone)
{
// wherever you want to show the progress:
float progress = request.downloadProgress;
Debug.Log("Loading " + progress);
yield return null;
}
while (!request.isDone)
yield return null; // this worked for me
// after this, you will have a result
print(request.downloadHandler.text);
}

Callback function issues in FB.API for Unity

I am using Unity 5.5.2f1 pro and facebook's SDK v 7.9.4
I have a script which after login (managed in a previous scene) sends an API request to FB asking for friends, name and email and sends that info as a POST to a php website.
code:
[Serializable]
public struct FBData {
public string first_name;
public string email;
public string friends;
public string id;}
public class UserManagement : MonoBehaviour {
string urlSaveUserData="some php website";
public Text testTxt;
FBData parsedData;
// Use this for initialization
void Start () {
//Check if it's the first time the user is opening the app.
if (UserInfo.FIRST_TIME) {
//update text (only used for testing, should be removed in production.)
testTxt.text = "Your user id is: " + UserInfo.ID;
//Perform FB.API call to get User Data.
getUserData ();
//Save in SQL table. (won't get here if line in getUserData() is active)
StartCoroutine ("saveUserData");
} else {
//do something else.
}
note: Since this is meant for iOS I have to test it on a device so I'm using text in the screen to display info (think of it as a badly implemented print statement).
The problem: In my callback function for FB.API I write in the text Gameobject (aka testTxt) the parsed information from the response which is saved in the Custom UserInfo clss. It display's correctly but the code gets stuck there. It doesn't continue to the next function. HOWEVER, if I delete/comment that line and don't display anything in the text field. The codes does continue to the POST function BUT the information from the API call is not passed, i.e my custom class is empty (leading me to believe the callback function is not called at all).
public void getUserData(){
string query = "me?fields=first_name,email,friends";
FB.API (query, HttpMethod.GET, Apicallback, new Dictionary<string, string> ());
}
private void Apicallback(IGraphResult result){
//Parse Graph response into a specific class created for this result.
parsedData = JsonUtility.FromJson<FBData>(result.RawResult);
//Pass each field into UserInfo class.
UserInfo.EMAIL = parsedData.email;
UserInfo.FRIENDS = parsedData.friends;
UserInfo.NAME = parsedData.first_name;
UserInfo.FACEBOOKID = parsedData.id;
/*problem area, if I comment line below, then previous information is apparently not stored. If left as is then testTxt displays correct information but code gets stuck there. */
testTxt.text = "This is the info from USerInfoInside the APICallback: " + UserInfo.EMAIL + UserInfo.FRIENDS + UserInfo.FACEBOOKID;
}
The function below is to send info to php website, is there for illustrative purposes:
code:
public IEnumerator saveUserData() {
//get user info (this information is EMPTY if line in getUserData() is commented.
parsedData.id = UserInfo.FACEBOOKID;
parsedData.friends = UserInfo.FRIENDS;
parsedData.first_name = UserInfo.NAME;
parsedData.email = UserInfo.EMAIL;
//translate data into json
string JsonBodyData = JsonUtility.ToJson (parsedData);
//Custom web request (POST method doesnt seem to work very well, documentation example sends empty form)
var w = new UnityWebRequest(urlSaveUserData, "POST");
byte[] bodyRaw = new System.Text.UTF8Encoding().GetBytes(JsonBodyData);
w.uploadHandler = (UploadHandler) new UploadHandlerRaw(bodyRaw);
w.downloadHandler = (DownloadHandler) new DownloadHandlerBuffer();
w.SetRequestHeader("Content-Type", "application/json");
yield return w.Send();
//work with received data...}
Im stuck here any help is appreciated. Thanks!
Be sure to use EscapeURL when using strings directly for JSON or HTTP POST and GET methods. The lack of this treatment tends to screw things over, particulary in iOS platforms.
From what I can see, this code
string query = "me?fields=first_name,email,friends";
should instead be escaped as
string query = WWW.EscapeURL("me?fields=first_name,email,friends");
so characters like "?" won't get encoded as an URL symbol.
I'm assuming you don't need to do that for your illustrative example, because UnityWebRequest already escapes your POST request strings internally, but I can't fully confirm that.

Web Api returns garbage for text files unless run from the browser bar

I am writing a file service using Asp.Net’s Web Api. The service retrieves files (Css, Excel, Csv, etc.) from SQL Server and serves them up in response to Get requests.
My first test case is for Css files. The issue is that, while I can see the correct data on the server side, when the browser retrieves/decodes it, the results are mangled. The issue appears to be related to the encodings.
Here are the request/response headers in FireFox:
When I click on the response tab in FireBug, here’s what it looks like:
The results look like ascii being displayed as utf8. This is the html view in FireBug:
The above example is an iFrame inside a Facebook application which is running ssl.
If I take the url and open it directly in the browser, it works and correctly displays my Css:
In summary, when I retrieve my Css file from a tag inside my Facebook app, I get garbage (encoding issue?). If I retrieve it straight from the browser, it works.
My CssFormatter MediaTypeFormatter code:
public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
var taskSource = new TaskCompletionSource<object>();
try
{
var incomingFile = value as FileRestService.Entity.IFile;
var ms = new MemoryStream(incomingFile.DataBuffer);
ms.CopyTo(writeStream);
ms.Flush();
taskSource.SetResult(writeStream);
}
catch (Exception e)
{
taskSource.SetException(e);
}
return taskSource.Task;
}
Am I creating the response stream incorrectly? I noticed that the response headers do not specify the encoding. Is this an issue?
I find the easiest way to handle this is to write something along the lines of (here's the important details):
public class Formatter : MediaTypeFormatter {
// TODO override the constructor to add some mappings or some other way for this formatter to be picked up
// TODO override CanReadType and CanWriteType according to your rules
public override void SetDefaultContentHeaders(Type t, HttpContentHeaders headers, string mediaType) {
base.SetDefaultContentHeaders(t, headers, mediaType);
headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") {
FileName = "SomeName.ext"
};
}
public override Task WriteToStreamAsync(Type t, object value, Stream s, HttpContentHeaders headers, TransportContext context) {
return Task.Factory.StartNew(() => {
// TODO code to write to the output stream, flush it but don't explicitly close it
});
}
}

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

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

Trouble posting comments with the LWUIT Facebook API

I'm working on a mobile project and decided to try out the LWUIT framework for development. So far it has been quite interesting although I wish the documentation was a bit better.
I run into a problem trying to post content to facebook using the recently released Facebook API. I'm able to authenticate without issues. However, when I try to post comments to the user's wall, I get a http 404 error.
Has anyone else had this sort of challenge. Below is an excerpt from my code;
protected boolean onShareScreenPost() {
// If the resource file changes the names of components this call will break notifying you that you should fix the code
//boolean val = super.onShareScreenPost();
Form shareForm = Display.getInstance().getCurrent();
final TextField shareField = findShareField(shareForm);
String postText = shareField.getText();
try {
makeFacebookAuthenticationRequest();
FaceBookAccess.getInstance().postOnWall(me.getId(), postText);
} catch (IOException ex) {
ex.printStackTrace();
//Include a dialog saying unable to post or connect to the internet or whatever
}
return true;
}
private void makeFacebookAuthenticationRequest() throws IOException {
FaceBookAccess.getInstance().authenticate("125527160846284", "http://a.b.c/", new String[]{ "publish_stream"});
me = new User();
FaceBookAccess.getInstance().getUser("me", me, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("returned user");
}
});
}
Seeing this question 24 hours later makes me feel a bit silly.
The answer was quite simple and staring me in the face all along. I needed to wait for the Facebook API to return a User object before making additional calls to the API. Failure to do this resulted in a null reference for my user object and this was being used in the wall post request causing the facebook api to return a http 404.
Hope this helps someone...