hi everyone i need help to optimize a web request with Unity.
I need to send two numbers to my inline php script. I use this code
IEnumerator request()
{
WWWForm form = new WWWForm();
form.AddField("id", "12345");
form.AddField("hp", "9999");
using (UnityWebRequest www = UnityWebRequest.Post("https://website.com/script.php", form))
{
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
}
else
{
mytext.text = www.downloadHandler.text;
}
}
}
But each time I call this request, I use 6.5kb of internet. My application calls this query every 10 seconds. This application is used for training outside, so the user is probably using their lte network to play.
I need to reduce this cost as much as possible, how can I do that?
Related
I am using a flask server and having Unity access it. Testing it in Postman (with platform:test) gives me ImmutableMultiDict([('platform', 'test')]) (result of request.form in Flask) which works. But when unity makes a post request (code provided), it gives me ImmutableMultiDict([]). I'm not completely sure if this is a Unity or a Flask problem. Help is much appreciated.
IEnumerator PostRequest(string url)
{
WWWForm form = new WWWForm();
form.AddField("platform", "test");
UnityWebRequest uwr = UnityWebRequest.Post(url, form);
uwr.uploadHandler.contentType = "multipart/form-data";
yield return uwr.SendWebRequest();
if (uwr.isNetworkError)
{
Debug.Log("Error While Sending: " + uwr.error);
}
else
{
Debug.Log("Received: " + uwr.downloadHandler.text);
}
}
I have fixed the issue.
Unity formats its data wildly different from PostMan, and my Flask server couldn't read it. Instead of using request.form[key] or request.data[key] in Flask, I found request.form.getlist(key) worked perfectly with both Postman's requests and Unity's.
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.
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);
}
I wanna send a post request from a unity game to a laravel 5.4 controller.. in html form, we use {{csrf_field}} and it handles creating token. but how can I do it in unity?
I came across this thread whilst trying to acheive what it set out to do - and I got it working so I am sharing this:
It is a combination of this tutorial on YouTube: How to Use UnityWebRequest - Replacement for WWW Class - Unity Tutorial and this answer on a similar question: UnityWebRequest POST to PHP not work
The technology I am using:
Laravel Framework 7.15.0 (PHP)
Unity: 2019.3.11f1 (script is C#)
Now I am just sending basic forms not images etc - so I haven't tried anything complex but this sends a get to one URL on Laravel which returns the CSRF token.
This is then added as an additional field to the post request.
I am clicking the 2 buttons pretty quickly - I would suggest (and will implement this myself later) that you don't ask for the token until your ready to submit the form - to avoid it expiring if your form is quite long etc.
The C# Script (Unity)
Both the functions are set as ON Click() on the buttons in a simple UI Canvas in Unity.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class BasicWebCall : MonoBehaviour
{
public Text messageText;
public InputField scoreToSend;
private string csrf_token;
readonly string getURL = "http://mazegames.test/leaderboard/1";
readonly string postURL = "http://mazegames.test/register/1";
private void Start()
{
messageText.text = "Press buttons to interact with web server";
}
public void OnButtonGetScore()
{
messageText.text = "Getting Token";
StartCoroutine(SimpleGetRequest());
}
IEnumerator SimpleGetRequest()
{
UnityWebRequest www = UnityWebRequest.Get(getURL);
yield return www.SendWebRequest();
if(www.isNetworkError || www.isHttpError)
{
Debug.LogError(www.error);
}
else
{
messageText.text = "Token Received: " + www.downloadHandler.text;
csrf_token = www.downloadHandler.text;
}
}
public void OnButtonSendScore()
{
if (scoreToSend.text == string.Empty)
{
messageText.text = "Error: No high score to send.\nEnter a value in the input field.";
}
else
{
messageText.text = "Sending Post Request";
StartCoroutine(SimplePostRequest(scoreToSend.text));
}
}
IEnumerator SimplePostRequest(string curScore)
{
Dictionary<string, string> wwwForm = new Dictionary<string, string>();
wwwForm.Add("_token", csrf_token);
UnityWebRequest www = UnityWebRequest.Post(postURL, wwwForm);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.LogError(www.error);
}
else
{
messageText.text = www.downloadHandler.text;
}
}
}
Laravel routes/web.php
Obviously you need to configure your end points to receive your data, validate and store it if necessary. In this example its just validating the token as part of the post request.
Route::post('register/{game_id}', function($game_id) {
return "Game On!";
});
Route::get('leaderboard/{game_id}', function($game_id) {
return csrf_token();
});
And that is pretty much it - hope this helps someone else.
#EDIT# - Request Token on Submission
To only get the token when your submitting the form, literally all you have to do is put this line:
StartCoroutine(SimpleGetRequest());
above the line:
StartCoroutine(SimplePostRequest(scoreToSend.text));
so that is looks like this:
StartCoroutine(SimpleGetRequest());
StartCoroutine(SimplePostRequest(scoreToSend.text));
Obviously you could then remove the function SimpleGetRequest altogether.
Laravel will generate a token each time a page is generated. The token has a lifetime and after that lifetime it cannot be used anymore (that's the whole point).
You need to get a valid token from Laravel pass it to Unity3D and then when from Unity create a WWWForm and pass it back.
How to do this it depends on the platform that Unity3D is deployed to.
If you are using WebPlayer or WebGL then you can get your hand on the Unity3D objected embedded in the browser and use SendMessage. WebGL link here.
If the game is deployed to another platform it probably makes sense to expose and API on the Laravel side and use that endpoint instead of doing a POST request.
You can use WWWForm to send in POST and call it from a coroutine:
// this will send it at start
// but you can just call SendToController in another function
string laravel_url = "http://somedomain.com/whatever";
IEnumerator Start () {
yield return StartCoroutine(SendToController());
}
IEnumerator SendToController()
{
WWWForm form = new WWWForm();
form.AddField( "csrf_field", "replace this with what you want!!!!" );
WWW download = new WWW( laravel_url, form );
yield return download;
if(!string.IsNullOrEmpty(download.error)) {
print( "Error downloading: " + download.error );
} else {
// if succesful, do what you want
Debug.Log(download.text);
}
}
"Coroutine" is your friend. It will make sending forms a lot easier. You might want to read about it in here:
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
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