How i can change the User-Agent in request header with the app-name in first section of user-agent like this :
Myappname (Linux; U; Android 4.3; Galaxy Nexus Build/JWR66Y)
note that i don't want to change other parameters in user-agnet string
i used volley for requests.
any suggestion to do it dynamically?
I wanted to do EXACTLY the same thing in my Android app. Namely, modify the User-Agent header but only append/prepend something and leave the "original" content (or most of it).
The problem with #athor answer is that, if look carefully, volley only uses the userAgent string when it uses HttpClientStack, and that is for devices using Android API Level 8 lower (in other words, in the minority of cases). For API Levels 9 or higher, it uses HurlStack, or, a stack or your own, if you are passing it as parameter for newRequestQueue, which in my case, and probably in most cases, is a subclass of HurlStack, for example OkHttpStack.
These, make no use of the userAgent string you see there, and, as far as I know, don't offer a way to set the user agent.
What I ended up doing was, in one of my subclasses of Volley's Request<T>, I overrode the getHeaders method, and made the necessary adjustment to the User-Agent header. Here's the code snippet that prepends the app's name and version to the original User-Agent header.
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
Map<String, String> headers = super.getHeaders();
if (headers == null || headers.equals(Collections.emptyMap())) {
headers = new HashMap<String, String>();
}
if (context != null) {
StringBuilder label = new StringBuilder();
label.append(context.getApplicationInfo().loadLabel(context.getPackageManager()));
label.append("/");
try {
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
label.append(pInfo.versionName);
} catch (NameNotFoundException e) {
}
label.append(" ");
label.append(System.getProperty("http.agent"));
headers.put("User-Agent", label.toString());
}
return headers;
}
Note that I'm using System.getProperty("http.agent") to access the "original" User-Agent header, as I found no other way to that. Of course, you could try to modify this string to remove the Dalvik/1.6.0 but I don't know if that's 100% safe.
Also, note that I'm using Android Context to read app name and version.
Hope this helps!
Volley sets up the user agent in the newRequestQueue method.
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
Network network = new ManualProxyNetwork(new BasicNetwork(stack));
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();
return queue;
}
A simple solution is just to copy this method to your application, and modify the user agent string to use your app name instead of the package name. It would be a simple 1 line change.
Then just call your custom newRequestQueue method instead of the volley method.
Related
while making connection using HttpClient in android from HttpResponse able to get all possible "set-cookie" header value (JESSIONID and XSCRF-TOKEN).Check below screenshot.
Now working with android studio with volley api for connection , i am getting only single value of "set-cookie" header (JESSIONID only).See below :
I have also check https://groups.google.com/forum/#!topic/volley-users/rNTlV-LORzY.
For which have to make change in volley api jar project. But don't know how to edit volley api. If any other solution present kindly guide.
Kindly help to retrieve multiple value of "set-cookie" using volley api.
Problem:
The problem is inside the Volley unfortunately. I had this problem and after many searches i figured out that there is a method called convertHeaders in BasicNetwork class that handles headers like this:
protected static Map<String, String> convertHeaders(Header[] headers) {
Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
for (int i = 0; i < headers.length; i++) {
result.put(headers[i].getName(), headers[i].getValue());
}
return result;
}
You see the result is Map<String, String> which can't contain same keys with different values. so you always have only last cookie.
The standard of cookie setting tells us we should separate cookies with ; for example if you want to contain 2 key-value in a request cookie you should put them like this:
Cookies: k1=v1;k2=v2
Solution:
In your case you have two options.
1 - change your code in Server-Side so that the response contains only 1 Set-Cookie separated key-values by ;. example of your response:
Set-Cookie: JESSIONID=qZtQ...;Path=/;HttpOnly;XSRF-TOKEN=6c65...
2 - get Volley source code and change that buggy method and make a fixed .jar again! this option is my favorite cause you didn't touch the response of server
My implementation of this method is:
protected static Map<String, String> convertHeaders(Header[] headers) {
TreeMap result = new TreeMap(String.CASE_INSENSITIVE_ORDER);
for(int i = 0; i < headers.length; ++i) {
String headerName = headers[i].getName();
if(!result.containsKey(headerName)) {
result.put(headers[i].getName(), headers[i].getValue());
} else {
String value = (String)result.get(headerName);
String mergedValue = value + ";" + headers[i].getValue();
result.remove(headerName);
result.put(headerName, mergedValue);
}
}
return result;
}
There is workaround for this in:
implementation "com.android.volley:volley:1.1.0"
"NetworkResponse (and Cache.Entry) now includes an "allHeaders" field which is the raw list of all headers returned by the server and thus can include duplicates by name."
Source: https://github.com/google/volley/issues/21
Example:
private static final String COOKIE_KEY = "Set-Cookie";
private static final String COOKIE_NAME = "NameOfOneOfTheCookies";
#Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
handleCookies(response);
String parsed;
try {
parsed = new String(response.data, "utf-8");
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed,
HttpHeaderParser.parseCacheHeaders(response));
}
private void handleCookies(NetworkResponse response) {
for (Header header : response.allHeaders) {
if (header.getName().equals(COOKIE_KEY) && header.getValue().startsWith(COOKIE_NAME)) {
getCookies(response);
}
}
}
private void getCookies(NetworkResponse response) {
ArrayList<String> cookiesList = new ArrayList<>();
for (Header header : response.allHeaders) {
if (header.getName().equals(COOKIE_KEY)) {
cookiesList.add(header.getValue());
}
}
// TODO Do something with the cookiesList
}
i want to call 3 service methods (A B and C) back to back. The important point is B must be called after response received from A and same situation between B and C as well. I add all of requests to queue using RequestQueue.add(...). But now request B is called before receiving response from A. Is it possible to manage this using volley library.
I know i can do request B after receiving response from A but i want to know can volley does this work.
You can implement your own Response listener so you can call A to B and B to C in the response callback method.
There is a simple example here: https://stackoverflow.com/a/17278867/508126
Volley can't do it itself but it can do it if you implement Response.Listener and add your logic in it
You can't give request an order, but you can make them run one after another. For this you need to implement your own RequestQueue.
Here is sample which demonstrates how to make all your requests execute in the same order, in which you added them to queue, since it uses single thread execution.
// Copied from Volley.newRequestQueue(..); source code
File cacheDir = new File(context.getCacheDir(), "def_cahce_dir");
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
}
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
int threadPoolSize = 1; // means only one request at a time
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network, threadPoolSize);
queue.start();
I recently started to use Volley lib from Google for my network requests. One of my requests get error 301 for redirect, so my question is that can volley handle redirect somehow automatically or do I have to handle it manually in parseNetworkError or use some kind of RetryPolicyhere?
Thanks.
Replace your url like that url.replace("http", "https");
for example:
if your url looking like that : "http://graph.facebook......." than
it should be like : "https://graph.facebook......."
it works for me
I fixed it catching the http status 301 or 302, reading redirect url and setting it to request then throwing expection which triggers retry.
Edit: Here are the main keys in volley lib which i modified:
Added method public void setUrl(final String url) for class Request
In class BasicNetwork is added check for redirection after // Handle cache validation, if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY) || statusCode == HttpStatus.SC_MOVED_TEMPORARILY), there I read the redirect url with responseHeaders.get("location"), call setUrl with request object and throw error
Error get's catched and it calls attemptRetryOnException
You also need to have RetryPolicy set for the Request (see DefaultRetryPolicy for this)
If you dont want to modify the Volley lib you can catch the 301 and manually re-send the request.
In your GsonRequest class implement deliverError and create a new Request object with the new Location url from the header and insert that to the request queue.
Something like this:
#Override
public void deliverError(final VolleyError error) {
Log.d(TAG, "deliverError");
final int status = error.networkResponse.statusCode;
// Handle 30x
if(HttpURLConnection.HTTP_MOVED_PERM == status || status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
Log.d(TAG, "Location: " + location);
final GsonRequest<T> request = new GsonRequest<T>(method, location, jsonRequest, this.requestContentType, this.clazz, this.ttl, this.listener, this.errorListener);
// Construct a request clone and change the url to redirect location.
RequestManager.getRequestQueue().add(request);
}
}
This way you can keep updating Volley and not have to worry about things breaking.
Like many others, I was simply confused about why Volley wasn't following redirects automatically. By looking at the source code I found that while Volley will set the redirect URL correctly on its own, it won't actually follow it unless the request's retry policy specifies to "retry" at least once. Inexplicably, the default retry policy sets maxNumRetries to 0. So the fix is to set a retry policy with 1 retry (10s timeout and 1x back-off copied from default):
request.setRetryPolicy(new DefaultRetryPolicy(10000, 1, 1.0f))
For reference, here is the source code:
/**
* Constructs a new retry policy.
* #param initialTimeoutMs The initial timeout for the policy.
* #param maxNumRetries The maximum number of retries.
* #param backoffMultiplier Backoff multiplier for the policy.
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
Alternatively, you can create a custom implementation of RetryPolicy that only "retries" in the event of a 301 or 302.
Hope this helps someone!
End up doing a merge of what most #niko and #slott answered:
// Request impl class
// ...
#Override
public void deliverError(VolleyError error) {
super.deliverError(error);
Log.e(TAG, error.getMessage(), error);
final int status = error.networkResponse.statusCode;
// Handle 30x
if (status == HttpURLConnection.HTTP_MOVED_PERM ||
status == HttpURLConnection.HTTP_MOVED_TEMP ||
status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
if (BuildConfig.DEBUG) {
Log.d(TAG, "Location: " + location);
}
// TODO: create new request with new location
// TODO: enqueue new request
}
}
#Override
public String getUrl() {
String url = super.getUrl();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url; // use http by default
}
return url;
}
It worked well overriding StringRequest methods.
Hope it can help someone.
Volley supports redirection without any patches, no need for a separate fork
Explanation:
Volley internally uses HttpClient which by default follows 301/302 unless specified otherwise
From: http://hc.apache.org/httpcomponents-client-4.2.x/tutorial/html/httpagent.html
ClientPNames.HANDLE_REDIRECTS='http.protocol.handle-redirects': defines whether redirects should be handled automatically. This parameter expects a value of type java.lang.Boolean. If this parameter is not set HttpClient will handle redirects automatically.
ok, im a bit late to the game here, but i've recently been trying to achieve this same aspect, so https://stackoverflow.com/a/17483037/2423312 is the best one, given that you are willing to fork volley and maintain it and the answer here : https://stackoverflow.com/a/27566737/2423312 - I'm not sure how this even worked.This one is spot on though : https://stackoverflow.com/a/28454312/2423312. But its actually adding a new request object to the NetworkDipatcher's queue, so you'll have to notify the caller as well somehow, there is one dirty way where you can do this by not modifying the request object + changing the field "mURL", PLEASE NOTE THAT THIS IS DEPENDENT ON YOUR IMPLEMENTATION OF VOLLEY'S RetryPolicy.java INTERFACE AND HOW YOUR CLASSES EXTENDING Request.java CLASS ARE, here you go : welcome REFLECTION
Class volleyRequestClass = request.getClass().getSuperclass();
Field urlField = volleyRequestClass.getDeclaredField("mUrl");
urlField.setAccessible(true);
urlField.set(request, newRedirectURL);
Personally I'd prefer cloning volley though. Plus looks like volley's example BasicNetwork class was designed to fail at redirects : https://github.com/google/volley/blob/ddfb86659df59e7293df9277da216d73c34aa800/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java#L156 so i guess they arent leaning too much on redirects, feel free to suggest/edit. Always looking for good way..
I am using volley:1.1.1 with https url though the request was having some issue. On digging deeper i found that my request method was getting changed from POST to GET due to redirect (permanent redirect 301). I am using using nginx and in server block i was having a rewrite rule that was causing the issue.
So in short everything seems good with latest version of volley. My utility function here-
public void makePostRequest(String url, JSONObject body, final AjaxCallback ajaxCallback) {
try {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST,
url, body, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.d(LOG, response.toString());
ajaxCallback.onSuccess(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(LOG, error.toString());
ajaxCallback.onError(error);
}
});
singleton.getRequestQueue().add(jsonObjectRequest);
} catch(Exception e) {
Log.d(LOG, "Exception makePostRequest");
e.printStackTrace();
}
}
// separate file
public interface AjaxCallback {
void onSuccess(JSONObject response);
void onError(VolleyError error);
}
While integrating Weibo's Android SDK into an app, I discovered the HttpClient class it contains uses an obsolete library that Android dislikes intensely (in fact I think they just pasted a Java SDK into an Android Eclipse project and shipped it). This library only seems to perform a single function within Weibo, which is to assemble POST requests (using the PostMethod class) and send them to the Weibo server. I blithely assumed it would be relatively straightforward to replace this with the standard Apache HttpPost, which is included in Android.
Unfortunately the Part class seems to have no straightforward equivalent. At least some of the Parts could be replaced by BasicNameValuePair classes, but there is a custom Part defined by Weibo that looks more like a ByteArrayEntity.
Examining the two methods called multPartUrl (not a typo) in
Weibo's source for HttpClient
the first of which is reproduced here (the second is very similar but pastes in a different type of content):
public Response multPartURL(String url, PostParameter[] params,ImageItem item,boolean authenticated) throws WeiboException{
PostMethod post = new PostMethod(url);
try {
org.apache.commons.httpclient.HttpClient client = new org.apache.commons.httpclient.HttpClient();
long t = System.currentTimeMillis();
Part[] parts=null;
if(params==null){
parts=new Part[1];
}else{
parts=new Part[params.length+1];
}
if (params != null ) {
int i=0;
for (PostParameter entry : params) {
parts[i++]=new StringPart( entry.getName(),(String)entry.getValue());
}
parts[parts.length-1]=new ByteArrayPart(item.getContent(), item.getName(), item.getImageType());
}
post.setRequestEntity( new MultipartRequestEntity(parts, post.getParams()) );
List<Header> headers = new ArrayList<Header>();
if (authenticated) {
if (basic == null && oauth == null) {
}
String authorization = null;
if (null != oauth) {
// use OAuth
authorization = oauth.generateAuthorizationHeader( "POST" , url, params, oauthToken);
} else if (null != basic) {
// use Basic Auth
authorization = this.basic;
} else {
throw new IllegalStateException(
"Neither user ID/password combination nor OAuth consumer key/secret combination supplied");
}
headers.add(new Header("Authorization", authorization));
log("Authorization: " + authorization);
}
client.getHostConfiguration().getParams().setParameter("http.default-headers", headers);
client.executeMethod(post);
Response response=new Response();
response.setResponseAsString(post.getResponseBodyAsString());
response.setStatusCode(post.getStatusCode());
log("multPartURL URL:" + url + ", result:" + response + ", time:" + (System.currentTimeMillis() - t));
return response;
} catch (Exception ex) {
throw new WeiboException(ex.getMessage(), ex, -1);
} finally {
post.releaseConnection();
}
}
it can be seen that a number of Parts are added to a MultiPartRequestEntity, the last of which is a byte array or a file.
What (if anything) is the equivalent to MultiPartRequestEntity in the more up to date Apache libraries?
Is there a way to add a byte array to a UrlEncodedFormEntity?
Is there, alternatively, a way to add name-value pairs to a ByteArrayEntity?
Is there something else I am missing completely?
The best answer I've found so far seems to be based on the answer to this question:
Post multipart request with Android SDK
This shows where to get the non-obsolete replacements for the libraries used by Weibo.
I've then replaced
Part[] parts=null;
if(params==null){
parts=new Part[1];
}else{
parts=new Part[params.length+1];
}
if (params != null ) {
int i=0;
for (PostParameter entry : params) {
parts[i++]=new StringPart( entry.getName(),(String)entry.getValue());
}
parts[parts.length-1]=new ByteArrayPart(item.getContent(), item.getName(), item.getImageType());
}
post.setRequestEntity( new MultipartRequestEntity(parts, post.getParams()) );
with
MultipartEntity entity = new MultipartEntity();
if(params!=null) {
for (PostParameter entry : params) {
entity.addPart(entry.getName(), new StringBody(entry.getValue()));
}
}
entity.addPart("filename", new ByteArrayBody(item.getContent(), item.getImageType(), item.getName()));
I'm not 100% certain about the last line as I've based that on Weibo's own class, which sets a filename parameter; however, it could well be called something else, though what is not clear.
I've then replaced the HttpClient with an AndroidHttpClient and used standard AndroidHttpClient methods to get the status code and an InputStream with the content from the entity; this stream, I am passing into the Weibo Response class and reading into the string it has as its main member. These assumptions may be wrong but I will continue to look into it.
I'm trying to get the user's cover photo and show it at the top of a layout. I'm using AsyncTask to run the API call to Facebook. The code I'm using to get the Facebook data is
JSONObject json = null;
response = Utility.facebook.request("me?fields=cover");
json = Util.parseJson(response);
The exception that stops the thread comes from a json error on the next step because the returned json is empty, even though the request clears through. I can get a proper json using just "me" or "me/albums" or anything other than "me?fields=cover". When I comment out the last line, 'try' process finishes with no exceptions/errors.
Is there something wrong with the Facebook API or am I doing something wrong?
I personally prefer using FQL when dealing with User Profile. If you would like to give FQL a try, check the following piece of code. If you would like to stick to Graph API, see this answer: https://stackoverflow.com/a/12434640/450534
try {
String query = "SELECT pic_cover FROM user where uid = " + PUT_THE_USER_ID_HERE;
Bundle param = new Bundle();
param.putString("method", "fql.query");
param.putString("query", query);
String response = Utility.mFacebook.request(param);
JSONArray JAUser = new JSONArray(response);
for (int i = 0; i < JAUser.length(); i++) {
JSONObject JOUser = JAUser.getJSONObject(i);
// COVER PHOTO
if (JOUser.has("pic_cover")) {
String getCover = JOUser.getString("pic_cover");
if (getCover.equals("null")) {
String finalCover = null;
} else {
JSONObject JOCoverSource = JOUser.optJSONObject("pic_cover");
if (JOCoverSource.has("source")) {
String finalCover = JOCoverSource.getString("source");
} else {
String finalCover = null;
}
}
} else {
String finalCover = null;
}
}
} catch (Exception e) {
// TODO: handle exception
}
The above code already accounts for User's who do not have a Cover Photo set in their profiles and checks for its availability. With this code, you will have the URL to the Cover Photo and can then process it as you prefer.
NOTE: If you are fetching the logged in users cover photo, this piece of code SELECT pic_cover FROM user where uid = " + PUT_THE_USER_ID_HERE; can also be written as: SELECT pic_cover FROM user where uid = me()"; For the non-logged in user's cover photo, the above can be used as is.
Couple of things as a side note.
I use Fedor's Lazy Loading technique to load images in almost exclusively.
I recommend running the code block, mine or any other solution you choose, in an AsyncTask.
The reason for not getting any result can be found in the javadoc of request(String graphPath) method:
(...) this method blocks waiting for a network response, so do not
call it in a UI thread.
In your case, you should probably do the following synchronous call:
Bundle params = new Bundle();
params.putString("fields", "cover");
String result = Utility.facebook.request("me/", params);
Siddharth Lele is very correct in his answer, but I wanted to specify the actual reason for not getting any response in this case.
Note: Fetching Cover Photo using Facebook API and endpoint https://graph.facebook.com/me?fields=cover no longer works as on 20th Dec 2014.
It was supposed to give following response:
{
"cover": {
"cover_id": "10151008748223553",
"source": "http://sphotos-a.ak.fbcdn.net/hphotos-ak-ash4/s720x720/391237_10151008748223553_422785532_n.jpg",
"offset_y": 0
},
"id": "19292868552"
}
But now it just gives User's id:
{
"id": "19292868552"
}
Verified this using Graph Tool explorer v2.2 using me?fields=cover.