I try to use volley for http request. I tried a request on postman and the response headers are below :
Content-Encoding →gzip
Content-Length →153
Content-Type →application/json; charset=utf-8
Date →Fri, 30 Jun 2017 13:36:10 GMT
ETag →W/"23-E742p6pP4kakmVh3lok1ww"
Server →Microsoft-IIS/8.0
Vary→X-HTTP-Method-Override, Accept-Encoding,Accept-Encoding
X-Powered-By →Express, ASP.NET
Volley adds some extra keys and removes Content-Length and Content-Encoding key. I write keys and values on parseNetworkResponse function. Response headers are below :
Content-Type : application/json; charset=utf-8
Date : Fri, 30 Jun 2017 09:05:23 GMT
ETag : W/"23-E742p6pP4kakmVh3lok1ww"
Server : Microsoft-IIS/8.0
Set-Cookie : ARRAffinity=16d81073e15abb17d2faba962adb6504734210ff2b9ff1ddfa770750ac7752e2;Path=/;xxx.net
Vary : X-HTTP-Method-Override, Accept-Encoding,Accept-Encoding
X-Android-Received-Millis : 1498813523832
X-Android-Response-Source : NETWORK 200
X-Android-Selected-Protocol : http/1.1
X-Android-Sent-Millis : 1498813523632
X-Powered-By : Express
Why does it change content? How can I get Content-Length and Content-Encoding?
if you are using the java.net.HttpURLConnection and the getHeaderFieldKey method, then do not forget to check the 0th element from the header according to the API reference:
Some implementations may treat the 0th header field as special, i.e. as the status line returned by the HTTP server. In this case, getHeaderField(0) returns the status line, but getHeaderFieldKey(0) returns null.
Related
BRIEFING BEFORE 'technical stuff'
Not new to working with Retrofit but came across this strange behaviour which I am having very hard time to understand and fix,
I have two web service, both work fine as expected in Postman and iOS but only one works in Retrofit and not the other,
In my defence I can say I am getting (Unauthorized) response,which means I was able to hit the server and get a result
In API developer's defence he says it works in Postman and other devices so not a service issue
If any Retrofit expert out there tell me what retrofit may be doing behind my back in order to get this error?
TECHNICAL STUFF
Talking about the type of service , It contains Authorization Bearer token as header which expires every 6 hours and contains no params at all (so it should be easy, right ?) and a simple url
http://hashchuna.nn-assets.com/api/locations
Unfortunately the header token cant be shared with valid key, cos it'l be expired before anyone can try it, but here it's anyway Authorization Bearer 3d44626a55dbb024725984e0d37868336fd7e48a
WHAT I'VE TRIED
I am using okhttp intercept to add Authorization Header to request using both addHeader/header method, no spaces in the url cos there r no params
Getting 401 unauthorized error in retrofit?
Java: Android: Retrofit - using Call but, Response{code = 401,message=unauthorized}
https://github.com/square/retrofit/issues/1290
But non of them helped
WARNING
Now the tricky part to keep in mind, the token when expired must give 401 error which is expected, but the problem is even for freshly created token I get 401 , which is my core problem
LOG
D/OkHttp: --> GET http://hashchuna.nn-assets.com/api/locations http/1.1
D/OkHttp: Authorization: Bearer 7c0d53de006b6de931f7d8747b22442354cecef9
D/OkHttp: --> END GET
D/OkHttp: <-- 401 Unauthorized http://hashchuna.nn-assets.com/api/locations (773ms)
D/OkHttp: Date: Mon, 20 Feb 2017 10:44:11 GMT
D/OkHttp: Server: Apache
D/OkHttp: X-Powered-By: PHP/7.0.15
D/OkHttp: Access-Control-Allow-Origin: *
D/OkHttp: Access-Control-Allow-Credentials: true
D/OkHttp: Access-Control-Max-Age: 1000
D/OkHttp: Access-Control-Allow-Headers: X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding
D/OkHttp: Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
D/OkHttp: Expires: Thu, 19 Nov 1981 08:52:00 GMT
D/OkHttp: Cache-Control: no-store, no-cache, must-revalidate
D/OkHttp: Pragma: no-cache
D/OkHttp: Set-Cookie: PHPSESSID=u477o8g0q387t92hms4nhc14n1; path=/
D/OkHttp: Vary: Authorization
D/OkHttp: X-Powered-By: PleskLin
D/OkHttp: Keep-Alive: timeout=5
D/OkHttp: Connection: Keep-Alive
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Content-Type: application/json;charset=utf-8
D/OkHttp: <-- END HTTP
CODE
Intercept
Request request = chain
.request()
.newBuilder()
//.header("Authorization","Bearer "+ SharedPrefsUtils.getSPinstance().getAccessToken(context))
.addHeader("Authorization","Bearer 1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e")
//.addHeader("cache-control", "no-cache")
//.cacheControl(CacheControl.FORCE_NETWORK)
.build();
Retrofit Instance
private Api getApiInstance(Context context) {
HttpLoggingInterceptor logInter = new HttpLoggingInterceptor();
logInter.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient mIntercepter = new OkHttpClient.Builder()
.addInterceptor(new RequestResponseInterseptor(context))
.addInterceptor(logInter)
.build();
Retrofit retrofitInstance = new Retrofit.Builder()
//.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(mIntercepter)
.build();
return retrofitInstance.create(Api.class);
}
SOLUTION (its the COOKIE)
Thanks to some of the tips, the actual reason for Incompatibility of service is , Supposedly POSTMAN and iOS client store and reuse COOKIE all by itself when requests are made without any need for explicit handling, Cookie in Postman can be tested using Postman Intercepter ,but cant be edited because chrome does not allow editing cookie by plugins
However Retrofit/OkHttp unless specified will consider it disabled(for security reason maybe) ,
Cookie is added either inside Interseptor as one of the header addHeader("Cookie","KEY-VALUE")
or
Use cookieJar to add into
OkHttpClient mIntercepter = new OkHttpClient.Builder()
.cookieJar(mCookieJar)
.addInterceptor(new RequestResponseInterseptor(context))
.addInterceptor(logInter)
.build();
based on your need and cookie type
I think you are overriding other headers Retrofit is adding for you, causing your API to not care about your Authorization header. Code below will add a header to your existing headers instead of overriding them.
OkHttpClient mIntercepter = new OkHttpClient.Builder()
...
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("Authorization", "Bearer " + "1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e").build();
return chain.proceed(request);
})
...
.build();
Format of those headers is correct here, key should be Authorization and value should be Bearer 1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e (in your case).
In my case, unfortunately, none of advices listed in #Ujju 's solution worked (i.e. neither "Cookie" header nor CookieJar applied). The only thing that helped me is just replacing addInterceptor with addNetworkInterceptor, and everything began to work.
401 Unauthorized http://www.stackoverflow.com/api/login?email=test#test.com&password=123456
Date: Fri, 07 Apr 2017 11:23:28 GMT
Server: Apache/2.4.25 (Amazon) PHP/5.6.29
X-Powered-By: PHP/5.6.29
Cache-Control: no-cache, private
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
Content-Length: 41
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
{"msg":"Invalid Credentials"}
I was facing issue like that not able fetch error message.
When server throwing error like 401 or other error we are getting null body from server.But you can get error message from server in errorBody
String response = response.errorBody().string()
I faced with this problem as well.
Requests work fine not only in POSTMAN, but also in CURl.
Having spent a lot of time I found a solution.
Service example for login:
#FormUrlEncoded
#POST("authentication/login")
fun login(
#Field("login") login: String,
#Field("password") password: String
): Single<Void>
Provide okhttp client:
private fun provideOkHttpClient(): OkHttpClient {
val cookieManager = CookieManager()
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL)
val client = OkHttpClient.Builder()
.cookieJar(JavaNetCookieJar(cookieManager))
.addNetworkInterceptor(provideRequestInterceptor())
.addNetworkInterceptor(provideLoggingInterceptor())
.protocols(Arrays.asList(Protocol.HTTP_1_1))
.build()
return client
}
Make enable JavaNetCookieJar:
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okHttpVersion"
Provide authorization:
private fun provideRequestInterceptor() = Interceptor {
val builder = it.request().newBuilder().url(it.request().url())
val tokenStr = BuildConfig.SONAR_TOKEN
builder.addHeader("Authorization", "Basic "+ getBase64String(tokenStr+":"))
it.proceed(builder.build())
}
private fun getBase64String(value: String): String {
return Base64.encodeToString(value.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
}
I have two services to generate and download PDF files. First there is POST (for hiding data) which save data in session, generate unique id and return it.
Second service is GET (param is unique id from POST) which remove id from session, generate PDF and returns it as stream. It looks like:
#RequestMapping(value = "/get", method = RequestMethod.GET)
#ResponseBody
public HttpEntity<byte[]> getData(
#ApiParam(name="hash", value="hash", required=true)
#RequestParam(value="hash", required = true) String hash,
#Context HttpServletResponse response) throws IOException {
Map reportData = reportsContext.getReportData(hash);
/*generate PDF here*/
return new HttpEntity<>(report.getContent(), getHeaders(report));
}
and getHeaders() is:
private HttpHeaders getHeaders(ReportData report) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/pdf"));
headers.add("Content-Disposition", "attachment; filename=".concat(report.getTitle()).concat(".pdf"));
return headers;
}
It generally works fine on all browsers and systems but Android Chrome. First, I found out that Chrome on Android send two GETs (one from browser, second from download manager) - because hash was deleted, second GET thowed exception. Next step was saving generated stream in session (>.<) and returned it on second GET - despite returned streams was the same (when returning from getData()), second response is bad formated. I guess this is some kind of Spring issue, somehow it changes formatting.
There are initials of responses:
first GET:
HTTP/1.1 200 OK X-Powered-By: Express server: Apache-Coyote/1.1
content-disposition: attachment;
filename=operation_20052016.pdf content-type:
application/pdf content-length: 28626 date: Fri, 20 May 2016 07:51:08
GMT connection: close
%PDF-1.4 %âăĎÓ
second GET:
HTTP/1.1 200 OK X-Powered-By: Express server: Apache-Coyote/1.1
content-disposition: attachment;
filename=operation_20052016.pdf content-type:
application/pdf transfer-encoding: chunked date: Fri, 20 May 2016
07:51:13 GMT connection: close
2000 "JVBERi0xLjQKJeLj
------------ANSWER------------
Finally I put manually producible attribute, like:
request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Sets.newHashSet(MediaType.valueOf("application/pdf")));
just before returning correct PDF. In case of error I dont set produces attribute so it take default value.
ANSWER:
Finally I put manually producible attribute, like:
request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Sets.newHashSet(MediaType.valueOf("application/pdf")));
just before returning correct PDF. In case of error I dont set produces attribute so it take default value.
I have got an interesting exception when trying to upload data from Android client to AWS:
com.amazonaws.services.s3.model.AmazonS3Exception: x-amz-website-redirect-location header is not supported for this operation. (Service: Amazon S3; Status Code: 400; Error Code: InvalidArgument; Request ID: E3900749ACF1D979), S3 Extended Request ID: kFjMM7JVFSOxvaKlHgM0bVM5zKZAR/0K8qeMyt44vjvtMFcGk8CxY9gDBDs0sqWmr8r2jcCyENo=
The user located in China region and data is uploaded it under VPN (HongKong) to western bucket http://currentidmedia.s3-ap-southeast-1.amazonaws.com
More details about request below:
PUT http://currentidmedia.s3-ap-southeast-1.amazonaws.com / Headers: (Expect: 100-continue, Content-Type: application/octet-stream, Date: Fri, 05 Jun 2015 10:13:01 GMT+00:00, Content-Length: 0, User-Agent: aws-sdk-android/2.2.1 Linux/3.4.0-g8a80a0e Dalvik/2.1.0/0 en_US com.amazonaws.mobileconnectors.s3.transfermanager.TransferManager/2.2.1, x-amz-website-redirect-location: /storage/emulated/0/DCIM/Camera/IMG_20150601_153423.jpg, Accept-Encoding: identity, Authorization: AWS AKIAIYL3TJHYVMB4SFRQ:dAkmOJxaIe5viO5kNjz74I/UKSc=, )
Does it bug in SDk or I don't use it in proper way?
UPDATE:
// set up credentials
AWSCredentials awsCredentials = new BasicAWSCredentials(
awsMetadata.getAccountId(),
awsMetadata.getSecretKey()
);
// set up request
PutObjectRequest request = new PutObjectRequest(
awsMetadata.getBucketName(),
FileUtil.extractNameFromPath(mediaItem.getContentUri()),
mediaItem.getContentUri()
);
// perform request
TransferManager transferManager = new TransferManager(awsCredentials);
transferManager.getAmazonS3Client().setRegion(getRegionForMedia(awsMetadata));
transferManager.upload(request, listener);
The answer is I used PutObjectRequest in not correct way. It contains several constructors and if you pass String to content instead of File you setup redirection url instead of path to the data.
However after fix I got the same SSLException error for non-China bucket. I used it under VPN and without it. More details about request you can find below.
PUT https://currentidmedia.s3-ap-southeast-1.amazonaws.com / Headers: (Content-MD5: GI1M/hfrwwQHvHMBlmh/lA==, Expect: 100-continue, Content-Type: image/jpeg, Date: Sat, 06 Jun 2015 08:56:31 GMT+00:00, Content-Length: 2140572, User-Agent: aws-sdk-android/2.2.1 Linux/3.4.0-g8a80a0e Dalvik/2.1.0/0 en_US com.amazonaws.mobileconnectors.s3.transfermanager.TransferManager/2.2.1, Accept-Encoding: identity, Authorization: AWS AKIAIYL3TJHYVMB4SFRQ:fqWbHO6lBPsbI6lkcaHKhms8Hkw=, )
The x-amz-website-redirect-location metadata is for static website only. Static website has a different endpoint <bucket-name>.s3-website-<AWS-region>.amazonaws.com. See http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html for more information. Do you mind explaining the purpose of setting it? I'll see if there is an alternative.
As the title says, I'm trying to run a job on parse.com from javascript (specifically an Appcelerator Android app) using the REST API. I'm using REST because this is just for diagnostics and I don't want to deal with trying to get the parse.com javascript API working in Appcelerator. The problem is I cannot get authenticated. If I don't pass in the authentication headers, I get the appropriate 401 authentication error, but if I do set them, I get "BAD REQUEST". I have gotten it working via cURL, so the URL is right, and parse responds to the call as expected. Here's my code:
var url = "https://api.parse.com/1/jobs/sendMail";
var client = Ti.Network.createHTTPClient({
// function called when the response data is available
onload : function(e) {
Ti.API.info("Received text: " + this.responseText);
alert("Received text: " + this.responseText);
},
// function called when an error occurs, including a timeout
onerror : function(e) {
Ti.API.debug(e.error);
alert(e.error);
},
timeout : 5000 // in milliseconds
});
var param = {"text":msg};
// Prepare the connection.
var auth = {"app":"sTnsthoeunotreallymyappIDbutabunchofcharactersESnecu","key":"8ll5thisisntreallymykeyeitherhMKqkYG"};
client.open("POST", url);
client.setRequestHeader("X-Parse-Application-Id",auth.app);
client.setRequestHeader("X-Parse-REST-API-Key",auth.key);
client.setRequestHeader("Content-Type","application/json");
// Send the request.
client.send(param);
Here is the request and response:
POST https://api.parse.com/1/jobs/sendMail HTTP/1.1
X-Parse-Application-Id: myappid
User-Agent:
X-Parse-REST-API-Key: myrestapikey
Content-Type: application/json
X-Requested-With: XMLHttpRequest
Content-Length: 0
Host: api.parse.com
Connection: Keep-Alive
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Methods: *
Access-Control-Allow-Origin: *
Cache-Control: no-cache
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Feb 2015 04:36:11 GMT
Server: nginx/1.6.0
Set-Cookie: _parse_session=BAh7BkkiD3Nlc3Npb25faWQGOgZFRiIlMTY4MzY0NTZlOWQ3ZGRjZDJkOWQwMjA4MWZjNWViMTY%3D--ffc760efbe32aa80a5e6d369606361413433fa72; domain=.parse.com; path=/; expires=Tue, 24-Mar-2015 04:36:11 GMT; secure; HttpOnly
Status: 401 Unauthorized
WWW-Authenticate: Basic realm="Parse"
X-Content-Type-Options: nosniff
X-Runtime: 0.018320
X-UA-Compatible: IE=Edge,chrome=1
Content-Length: 24
Connection: keep-alive
{"error":"unauthorized"}
Triggering background jobs via the REST-API requires using the Master-Key, not the REST-API key.
https://parse.com/docs/rest#backgroundjobs
I have an issue with caching using OkHttpClient 2.0. It seems that the Response is ignoring the Cache-Control header completely. This is how I am setting up the client and the cache.
OkHttpClient client = new OkHttpClient();
cache = new Cache(new File(Session.getInstance().getContext().getCacheDir(),"http"), 10 * 1024 * 1024);
client.setCache(cache);
client.setCookieHandler(CookieHandler.getDefault());
client.setConnectTimeout(CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS);
client.setReadTimeout(SOCKET_TIMEOUT, TimeUnit.MILLISECONDS);
I believe that the cache directory is created correctly. This is what I see in journal in the /cache/http directory of my application.
libcore.io.DiskLruCache
1
201105
2
This is how I am creating the Request.
Request mRequest = new Request.Builder().url(mUrl).get().build();
Getting the response :
Response response = client.newCall(mRequest).execute();
When using curl, the headers as as follows.
< HTTP/1.1 200 OK
< Date: Fri, 27 Jun 2014 19:39:40 GMT
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Cache-Control: no-transform, max-age=1800
< Content-Type: application/json
< Transfer-Encoding: chunked
The OKHttp response headers are as follows.
Connection:Keep-Alive
Content-Type:application/json
Date:Fri, 27 Jun 2014 18:58:30 GMT
Keep-Alive:timeout=5, max=100
OkHttp-Received-Millis:1403895511337
OkHttp-Selected-Protocol:http/1.1
OkHttp-Sent-Millis:1403895511140
Server:Apache-Coyote/1.1
Transfer-Encoding:chunked
The responses never get cached and the call client.getCache().getHitCount() always gives 0. Can someone please suggest what changes might be required here to make the cache work? Thanks.
Okay, the problem was all my get and post requests were using the Authorization Bearer xxxx header and http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html section 14.8 states that these requests can't be cached. The solution was to use s-maxage on the server instead of just max age according to this :
When a shared cache (see section 13.7) receives a request
containing an Authorization field, it MUST NOT return the
corresponding response as a reply to any other request, unless one
of the following specific exceptions holds:
If the response includes the "s-maxage" cache-control
directive, the cache MAY use that response in replying to a
subsequent request.
Are you reading the entire response body? OkHttp won't cache unless you consume the entire response.
I realize you solved your specific problem, but the symptom you describe has another cause.
When using the okhttp-urlconnection, caching doesn't kick in by default, unless we do this:
connection.setUseCaches(true)
(It should be on by default, but some library I was using was setting it to off)