I'm trying to implement the Reddit oAuth2 (every app that utilizes Reddit content has to have this implemented) in Android based 'userless' application and I'm following the guidelines.
I registered an app and get the respective client_id.
I'm following this for API guidelines and this for Retrofit in order to properly write the Android code.
Hence, I've coded two approaches to the issue and it seems that neither works. The call in the appropriate Fragment is the same for the two options and it goes as follows:
public void oAuth(){
String bodyString = "grant_type=" + "https://oauth.reddit.com/grants/installed_client"
+ "&device_id=" + UUID.randomUUID().toString();
TypedInput requestBody = new TypedByteArray("application/x-www-form-urlencoded", bodyString.getBytes(Charset.forName("UTF-8")));
RedditAPI.sRedditAuth().redditAuth(requestBody, new Callback<TokenResponse>() {
#Override
public void success(TokenResponse tokenResponse, Response response) {
Log.d("OATH_TAG", "oAuth() | YAY! :)");
}
#Override
public void failure(RetrofitError error) {
Log.d("OATH_TAG", "oAuth() | NOOOOOoooooo.... :(");
}
});
}
OPTION 1:
the Retrofit interface:
public interface RedditAuthInterface {
#POST(Urlz.REDDIT_OATH2_PATH)
void redditAuth(#Body TypedInput body, Callback<TokenResponse> result);
}
//the adapter
public static RedditAuthInterface sRedditAuth() {
if (sRedditAuthInterface == null) {
RestAdapter restAdapter = new RestAdapter
.Builder()
.setClient(getAuthClient())
.setEndpoint(Urlz.BASE_REDDIT_URL)
.build();
sRedditAuthInterface = restAdapter.create(RedditAuthInterface.class);
}
return sRedditAuthInterface;
}
/* support methods */
private static OkClient getAuthClient() {
final OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setReadTimeout(Static.READ_TIMEOUT, TimeUnit.SECONDS);
okHttpClient.setConnectTimeout(Static.CONNECT_TIMEOUT, TimeUnit.SECONDS);
/*okHttpClient.setAuthenticator(new Authenticator() {
#Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
String credential = Credentials.basic(BldCnfg.REDDIT_CLIENT_ID, BldCnfg.REDDIT_PASS);
return response.request().newBuilder().header("Authorization", credential).build();
}
#Override
public Request authenticateProxy(Proxy proxy, Response response) throws IOException {
return null;
}
});*/
okHttpClient.networkInterceptors().add(OAUTH_INTERCEPTOR);
return new OkClient(okHttpClient);
}
private static final Interceptor OAUTH_INTERCEPTOR = new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
String credentials = BldCnfg.REDDIT_CLIENT_ID + ":" + BldCnfg.REDDIT_PASS; // REDDIT_PASS = "" as by API guides
String string = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
originalResponse.header("Authorization", string);
originalResponse.header("Accept", "application/json");
return originalResponse;
}
};
result:
RetrofitError: 401 Unauthorized
OPTION 2:
the Retrofit interface:
public interface RedditAuthInterface {
#POST(Urlz.REDDIT_OATH2_PATH)
void redditAuth(#Body TypedInput body, Callback<TokenResponse> result);
}
//the adapter
public static RedditAuthInterface sRedditAuth() {
if (sRedditAuthInterface == null) {
RestAdapter restAdapter = new RestAdapter
.Builder()
.setClient(getConfuguredClient())
.setRequestInterceptor(getRequestInerceptorPass())
.setEndpoint(Urlz.BASE_REDDIT_URL)
.build();
sRedditAuthInterface = restAdapter.create(RedditAuthInterface.class);
}
return sRedditAuthInterface;
}
/* support methods */
public static RequestInterceptor getRequestInerceptorPass() {
RequestInterceptor rqInter = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
String credentials = BldCnfg.REDDIT_CLIENT_ID + ":" + BldCnfg.REDDIT_PASS; // REDDIT_PASS = "" as by API guides
String string = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
request.addHeader("Authorization", string);
request.addHeader("Accept", "application/json");
}
};
return rqInter;
}
private static OkClient getConfuguredClient() {
final OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.setReadTimeout(Static.READ_TIMEOUT, TimeUnit.SECONDS);
okHttpClient.setConnectTimeout(Static.CONNECT_TIMEOUT, TimeUnit.SECONDS);
return new OkClient(okHttpClient);
}
result:
It seems that I'm getting empty response (I only get "*" for scope). The successful response looks like this:
and header like this:
Do you have any ideas what am I doing wrong?
Has anybody done this?
The official Reddit github wiki lacks Android examples (has in almost every other language, though).
I was going through the same problem before and make this library to handel OAuth2 in Android. and the library is an extension for Retrofit that simplifies the process of authenticating against an OAuth 2 provider.
Based on your image with the "empty" response, showing that you got * back as a scope, I suspect that your definition for the access token response is using camel case instead of snake case, so the JSON is not getting loaded properly into the Java object.
Related
I am using Retrofit with the OkHttp Client and Jackson for Json Serialization and want to get the header of the response.
I know that i can extend the OkClient
and intercept it. But this comes before the deserialization process starts.
What i basically needs is to get the header alongside with the deserialized Json Object.
With Retrofit 1.9.0, if you use the Callback asynchronous version of the interface,
#GET("/user")
void getUser(Callback<User> callback)
Then your callback will receive a Response object
Callback<User> user = new Callback<User>() {
#Override
public void success(User user, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
}
Which has a method called getHeaders()
Callback<User> user = new Callback<User>() {
#Override
public void success(User user, Response response) {
List<Header> headerList = response.getHeaders();
for(Header header : headerList) {
Log.d(TAG, header.getName() + " " + header.getValue());
}
}
For Retrofit 2.0's interface, you can do this with Call<T>.
For Retrofit 2.0's Rx support, you can do this with Observable<Result<T>>
In Retrofit 2.0.0, you can get header like this:
public interface Api {
#GET("user")
Call<User> getUser();
}
Call<User> call = api.getUser();
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
// get headers
Headers headers = response.headers();
// get header value
String cookie = response.headers().get("Set-Cookie");
// TODO
}
#Override
public void onFailure(Call<User> call, Throwable t) {
// TODO
}
});
Much like you I wanted the headers along side of the payload. I needed access to the Etag. It takes some retro-foo, but you can do it. here's what I did. It's a dirty sample so dont take this as a best practices sample.
public static RestAdapter.Builder getRestBuilder(Context context) {
GsonBuilder gsonBuilder = GsonBuilderUtils.getBuilder();
Gson gson = gsonBuilder.create();
// **
// 1. create our own custom deserializer here
// **
final MyGsonConverter gsonConverter = new MyGsonConverter(gson);
OkHttpClient httpClient = MyPersonalOkHttpFactory.getInstance().getAuthHttpClient(context);
httpClient.networkInterceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Response response = chain.proceed(originalRequest);
// **
// 2. add the headers from the Interceptor to our deserializer instance
// **
gsonConverter.headers = response.headers();
return response;
}
});
RestAdapter.Builder builder = new RestAdapter.Builder()
.setClient(new OkClient(httpClient))
.setEndpoint(Common.getApiOriginUrl())
.setConverter(gsonConverter);
return builder;
}
private static class MyGsonConverter extends GsonConverter {
private Headers headers;
public MyGsonConverter(Gson gson) {
super(gson);
}
#Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
Object obj = super.fromBody(body, type);
// **
// 3. at this point, gson is called and you have access to headers
// do whatever you want here. I just set it on the return object.
// **
if (obj instanceof HeadersArrayList) {
((HeadersArrayList)obj).setHeaders(headers);
}
return obj;
}
}
public class HeadersArrayList<K> extends ArrayList<K>{
private Headers headers;
public Headers getHeaders() {
return headers;
}
public void setHeaders(Headers headers) {
this.headers = headers;
}
}
// the retrofit api for reference
#GET("/api/of/my/backend/{stuff}")
HeadersArrayList<String> getSomething(#Path("stuff") String stuff);
First print the entire response, body, code, message, header(by logging or something else) and try to find a clue from there.
I would recommend you to read the API docs and see the type of request it is asking for.
Use Postman to check which one of the following is working:
1.form-data
2.x-www-form-Urlencoded
3.raw
4.binary
And then accordingly set the annotations in the method declarations in the interface.
eg: in my case, it was taking x-www-form-Urlencoded so I had to mention it using
#FormUrlEncoded
#Headers("Content-Type: application/x-www-form-urlencoded")
in the method declaration.
Then used #Field annotations for individual value I was sending
like
Call<'ReturnObj'> Signup(#Field("name") String name, #Field("phoneNumber") long phoneNumber, #Field("password") String password, #Field("counter") int counter);
I am plugging Retrofit into my android app.
Here is how I build retrofit, notice the interceptor for the logging and headers.
public void buildRetrofit(String token){
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addNetworkInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.header("api-version", "1")
.method(chain.request().method(), chain.request().body())
.build();
return chain.proceed(newRequest);
}
});
httpClient.addInterceptor(logging);
Retrofit.Builder buidler = new Retrofit.Builder()
.baseUrl("XXX_HIDDEN_FORSTACKOVERFLOW")
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build());
retroFit = buidler.build();
}
I make the call like so
OrderApi orderApi = mainActivity.retroFit.create(OrderApi.class);
Call<Order> call = orderApi.getOpenOrder();
call.enqueue(new Callback<Order>() {
#Override
public void onResponse(Call<Order> call, Response<Order> response) {
Order a = response.body();
int b = 1;
}
#Override
public void onFailure(Call<Order> call, Throwable t) {
}
});
And here is how the actual request tag
public interface OrderApi {
#POST("/HIDDEN")
Call<Order> getOpenOrder();
}
Lastly, here is the order class
public class Order {
private String orderId;
private OrderStatus orderStatus;
public String getOrderId(){
return orderId;
}
public OrderStatus getOrderStatus() {
return orderStatus;
}
}
I get a response of 400. I have no idea why, and It works in postman etc. Something to note is that the response contains a lot more properties than just the ones in the class. I just want a proof on concept, but that shouldn't break things right?
.................
Managed to fix it. Had to send an empty body request as it was a post but I wasn't posting anything. API is dumb.
See here to send empty request Send empty body in POST request in Retrofit
I was trying to develop a simple POST API call in Android so I made one thinking that the request content-type was a json. Turns out it is expecting a multipart/form-data format and I'm struggling changing my function.
I'd like to know if there is any library to manage this. If not, I'd like to know how to pass my arguments in a multipart format.
#Override
public boolean post(String poiId, String description, ArrayList<String> tags, Resource resource) {
RequestQueue queue = mRequestQueue;
poiId = "1";
description = "Test post";
final HashMap<String, Object> params = new HashMap<>();
params.put("poiID", poiId);
params.put("description", description);
System.out.println("POI ID " + description);
params.put("tags", tags);
params.put("resource", resource);
RequestFuture<JSONObject> future = RequestFuture.newFuture();
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.POST,
API_POST_URL,
new JSONObject(params),
future, future) {
#Override
public HashMap<String, String> getHeaders() {
System.out.println(PostRepositoryImpl.this.getHeaders());
return PostRepositoryImpl.this.getHeaders();
}
};
queue.add(request);
try {
future.get(TIMEOUT, TIMEOUT_TIME_UNIT); // this will block
}catch (InterruptedException | ExecutionException | TimeoutException e){
e.printStackTrace();
return false;
}
return true;
}
I hardcoded some of the values because I wanted to test with poiID and description
So I want to send these kind of values in my multipart/form-date:
- poiID : String
- description : String
- resource : image
- tags
Is there any way to do this similar to the way I made my json request?
Kind regards
EDIT:
#Override
public boolean post(String poiId, String description, ArrayList<String> tags, Resource resource) {
RequestQueue queue = mRequestQueue;
StringRequest postRequest = new StringRequest(Request.Method.POST, API_POST_URL,
new Response.Listener<String>()
{
#Override
public void onResponse(String response) {
// response
Log.d("Response", response);
}
},
new Response.ErrorListener()
{
#Override
public void onErrorResponse(VolleyError error) {
// error
Log.d("Error.Response", "400");
}
}
) {
#Override
protected HashMap<String, String> getParams()
{
HashMap<String, String> params = new HashMap<String, String>();
params.put("poiID", "Alif");
params.put("description", "http://itsalif.info");
return params;
}
};
queue.add(postRequest);
return true;
}
How do I add the headers?
If it isn't JSON, simply use a StringRequest.
Not sure how to use Future with Volley, so change that accordingly
Then, params are added in an overridden method
Request request = new StringRequest(
Request.Method.POST,
API_POST_URL,
future, future) {
#Override
public HashMap<String, String> getHeaders() {
HashMap<String, String> headers = PostRepositoryImpl.this.getHeaders();
System.out.println(headers);
return headers;
}
#Override
public HashMap<String, String> getParams() {
// TODO: Put your params here
}
};
And for Multipart, see Working POST Multipart Request with Volley and without HttpEntity
Using Retrofit 2, you could do this:
//Lets Suppose this you have this postman or you want to make some request like this
//ServiceCreator (In my case i am using oauth2 so have AccessToken). This is a working and production sample, so you have to make your own changes, but i attach to example all components.
public class APIRestClient {
public static String API_BASE_URL = "http://186.151.238.14/";
private static OkHttpClient.Builder httpClient;
private static Retrofit.Builder builder;
public static Retrofit retrofit;
private static Activity mActivity;
private static AccessToken mToken;
/**
* setupBase URL
* #param _baseActivity
*/
public static void setupBaseUrl(Context _baseActivity){
String tmpBase = SharedPreferenceUtilities.getDomain(_baseActivity);
if (tmpBase != null && tmpBase.length() > 0){
if (tmpBase != API_BASE_URL) {
APIRestClient.API_BASE_URL = tmpBase;
}
}
}
/**
* auth2 Authorization Bearer...token create Service instance
* #param _serviceClass
* #param _baseActivity
* #param <S>
* #return
*/
public static <S> S createService(Class<S> _serviceClass, final Activity _baseActivity) {
AccessToken accessToken = TaskManagementApplication.getInstance().getAccessToken();
if (_baseActivity != null) {
setupBaseUrl(_baseActivity);
}
httpClient = new OkHttpClient.Builder();
httpClient.connectTimeout(30000, TimeUnit.SECONDS)
.readTimeout(30000,TimeUnit.SECONDS);
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(logging);
httpClient.addNetworkInterceptor(new StethoInterceptor());
}
builder = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create());
if (accessToken == null){
accessToken = new AccessToken();
accessToken.setAccessToken("");
accessToken.setTokenType("Bearer");
accessToken.setScope("");
accessToken.setRefreshToken("");
accessToken.setClientID("");
accessToken.setClientSecret("");
accessToken.setExpiry(0);
}
if(accessToken != null) {
mActivity = _baseActivity;
mToken = accessToken;
final AccessToken token = accessToken;
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Content-type", "application/json")
.header("Authorization",
token.getTokenType() + " " + token.getAccessToken())
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
});
httpClient.authenticator(new Authenticator() {
#Override
public Request authenticate(Route route, Response response) throws IOException {
if(responseCount(response) >= 2) {
// If both the original call and the call with refreshed token failed,
// it will probably keep failing, so don't try again.
LoginUtilities.initLogin(_baseActivity,LoginActivity.LOGININTENTRESULT,null);
return null;
}
// We need a new client, since we don't want to make another call using our client with access token
OAuthInterface tokenClient = createAuthService(OAuthInterface.class,mActivity);
Call<AccessToken> call = tokenClient.getRefreshAccessToken(
Grant_type.REFRESH_TOKEN.toString(),
token.getRefreshToken(),
StringUtilities.API_OAUTH_CLIENTID(_baseActivity),
StringUtilities.API_OAUTH_SECRET(_baseActivity),
"");
try {
retrofit2.Response<AccessToken> tokenResponse = call.execute();
if(tokenResponse.code() == 200) {
AccessToken newToken = tokenResponse.body();
mToken = newToken;
SharedPreferenceUtilities.setAccessToken(mActivity,mToken);
TaskManagementApplication.getInstance().setupToken(mToken);
return response.request().newBuilder()
.header("Authorization", newToken.getTokenType() + " " + newToken.getAccessToken())
.build();
} else {
LoginUtilities.initLogin(_baseActivity,LoginActivity.LOGININTENTRESULT,null);
return null;
}
} catch(IOException e) {
LoginUtilities.initLogin(_baseActivity,LoginActivity.LOGININTENTRESULT,null);
return null;
}
}
});
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = builder.client(client).build();
return retrofit.create(_serviceClass);
}
/**
* not auth create Service instance
* #param _serviceClass
* #param _context
* #param <S>
* #return
*/
private static int responseCount(Response response) {
int result = 1;
while ((response = response.priorResponse()) != null) {
result++;
}
return result;
}
}
//ApiInterface
public interface StudentInterface
{
public static final String ENVIARTAREAAPI = "api/estudiante/entregatarea";
#Multipart
#POST(ENVIARTAREAAPI)
Call<TareaCalificacion> entregatarea(#Part("Descripcion") RequestBody Descripcion,
#Part("IdTarea") RequestBody IdTarea,
#Part("IdEstudiante") RequestBody IdEstudiante);
}
//ApiCall (in your activity, fragment or wetheaver) this should be used when you execute your api call
RequestBody descripcionRequestBody = RequestBody.create(
okhttp3.MediaType.parse("text/plain; charset=utf-8"),
mensageEntregaTmp);
RequestBody idTareaRequestBody = RequestBody.create(
okhttp3.MediaType.parse("text/plain; charset=utf-8"),
String.valueOf(mTarea.getIdTarea()));
RequestBody idEstudianteRequestBody = RequestBody.create(
okhttp3.MediaType.parse("text/plain; charset=utf-8"),
String.valueOf(currUser.getPerfil().getSisId()));
StudentInterface studentInterface = APIRestClient.createService(StudentInterface.class,DetalleTareaActivity.this);
Call<TareaCalificacion> call = studentInterface.entregatarea(
descripcionRequestBody,
idTareaRequestBody,
idEstudianteRequestBody);
call.enqueue(new Callback<TareaCalificacion>() {
#Override
public void onResponse(Call<TareaCalificacion> call, Response<TareaCalificacion> response) {
int statusCode = response.code();
if(statusCode == 200) {
Toast.makeText(getApplicationContext, "Success Request", Toast.LENGTH_SHORT).show();
} else {
//todo some kind of error
}
}
#Override
public void onFailure(Call<TareaCalificacion> call, Throwable t) {
//todo some kind of error
}
});
I have used this to upload photos, so i have to use this sample to do that, thats the reason i did not use Content Type application/json.
Hope that helps how to do.
Some class (pojo) like TareaCalificacion (that is what i expect from the response are just class, that i use with GSON), so TareaCalificacion.java is like:
public class TareaCalificacion {
#SerializedName("sisId")
#Expose
private long sisId;
#SerializedName("sisDescripcion")
#Expose
private String sisDescripcion;
#SerializedName("sisEstado")
#Expose
private String sisEstado;
#SerializedName("sis")
#Expose
private int sis;
#SerializedName("sisUsuario")
#Expose
private String sisUsuario;
#SerializedName("CalificacionObtenida")
#Expose
private double CalificacionObtenida;
#SerializedName("IdEstudiante")
#Expose
private long IdEstudiante;
#SerializedName("IdTarea")
#Expose
private long IdTarea;
#SerializedName("Adjunto")
#Expose
private int Adjunto;
#SerializedName("ObservacionCalificacion")
#Expose
private String ObservacionCalificacion;
#SerializedName("IdCatedratico")
#Expose
private long IdCatedratico;
public TareaCalificacion() {
}
}
Attach some links that could help you if you have doubts:
Retrofit Documentation
Another example using this
Lets me know if that works or if is not clear how to do
Regards.
Using retrofit I want to make POST request to http://milzinas.lt/oauthsilent/authorize. This URL is special because it redirects you to http://milzinas.e-bros.lt/oauthsilent/authorize. My retrofit setup uses OkHttpClient. If I make request using OkHttpClient only then redirecting works fine, i.e. 401 status code is received. However, when I use the same OkHttpClient with retrofit then response is status code 307. I think this has something to do with OkClient implementation which wraps the OkHttpClient but I'm not sure. Below is the code I used to test this scenario. I'm using these libraries:
com.squareup.retrofit:retrofit:1.9.0
com.squareup.okhttp:okhttp:2.2.0
I understand that when URL is redirecting you to another URL the http client has to make two requests. In my case the first request returns 307 (Temporary Redirect) and the second one returns 401 (Unauthorized). However, retrofit always returns response of the first request. Do you know how to make redirecting work properly with retrofit? Maybe I could achieve this by using some other HTTP client? Any suggestions will be appreciated.
So when I execute code below console prints
Retrofit failure. Status: 307
OkHttp. Status: 401
I want it to be
Retrofit failure. Status: 401
OkHttp. Status: 401
public class MainActivity extends AppCompatActivity {
interface Api {
#POST(URL)
#Headers("Accept: application/json")
void test(#Body Object dummy, Callback<Object> callback);
}
static final String BASE_URL = "http://milzinas.lt";
static final String URL = "/oauthsilent/authorize";
final OkHttpClient okHttpClient = new OkHttpClient();
Api api;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RestAdapter retrofit = new RestAdapter.Builder()
.setEndpoint(BASE_URL)
.setClient(new OkClient(okHttpClient))
.setConverter(new Converter() {
#Override
public Object fromBody(TypedInput body, Type type) throws ConversionException {
return null;
}
#Override
public TypedOutput toBody(Object object) {
return null;
}
})
.build();
api = retrofit.create(Api.class);
makeRequestOkHttp();
makeRequestRetrofit();
}
void makeRequestOkHttp() {
new AsyncTask<Object, Object, Object>() {
#Override
protected Object doInBackground(Object... objects) {
try {
Request request = new Request.Builder().url(BASE_URL + URL).build();
com.squareup.okhttp.Response response = okHttpClient.newCall(request).execute();
android.util.Log.d("matka", "OkHttp. Status: " + response.code());
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
}.execute();
}
void makeRequestRetrofit() {
api.test("", new Callback<Object>() {
#Override
public void success(Object o, Response response) {
android.util.Log.d("matka", "Retrofit success. Status: " + response.getStatus());
}
#Override
public void failure(RetrofitError error) {
android.util.Log.d("matka", "Retrofit failure. Status: " + error.getResponse().getStatus());
}
});
}
}
The problem persist even in the latest v3.5.0
The only workaround that works is
https://github.com/square/okhttp/issues/936#issuecomment-266430151
Is there a way to set a custom cookie on retrofit requests?
Either by using the RequestInterceptor or any other means?
Through the retrofit.RequestInterceptor:
#Override
public void intercept(RequestFacade request) {
request.addHeader("Cookie", "cookiename=cookievalue");
}
You can set a custom RequestInterceptor as follows:
String cookieKey = ...
String cookieValue = ...
RestAdapter adapter = new RestAdapter.Builder()
.setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
// assuming `cookieKey` and `cookieValue` are not null
request.addHeader("Cookie", cookieKey + "=" + cookieValue);
}
})
.setServer("http://...")
.build();
YourService service = adapter.create(YourService.class);
And to read any cookies set by the server, attach a custom cookie manager like this:
OkHttpClient client = new OkHttpClient();
CustomCookieManager manager = new CustomCookieManager();
client.setCookieHandler(manager);
RestAdapter adapter = new RestAdapter.Builder()
.setClient(new OkClient(client))
...
.build();
where CustomCookieManager could look like this:
public class CustomCookieManager extends CookieManager {
// The cookie key we're interested in.
private final String SESSION_KEY = "session-key";
/**
* Creates a new instance of this cookie manager accepting all cookies.
*/
public CustomCookieManager() {
super.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
}
#Override
public void put(URI uri, Map<String, List<String>> responseHeaders) throws IOException {
super.put(uri, responseHeaders);
if (responseHeaders == null || responseHeaders.get(Constants.SET_COOKIE_KEY) == null) {
// No cookies in this response, simply return from this method.
return;
}
// Yes, we've found cookies, inspect them for the key we're looking for.
for (String possibleSessionCookieValues : responseHeaders.get(Constants.SET_COOKIE_KEY)) {
if (possibleSessionCookieValues != null) {
for (String possibleSessionCookie : possibleSessionCookieValues.split(";")) {
if (possibleSessionCookie.startsWith(SESSION_KEY) && possibleSessionCookie.contains("=")) {
// We can safely get the index 1 of the array: we know it contains
// a '=' meaning it has at least 2 values after splitting.
String session = possibleSessionCookie.split("=")[1];
// store `session` somewhere
return;
}
}
}
}
}
}
This is how it's done for retrofit2
Gradle:
compile 'com.squareup.retrofit2:retrofit:2.1.0'
The code:
static final class CookieInterceptor implements Interceptor {
private volatile String cookie;
public void setSessionCookie(String cookie) {
this.cookie = cookie;
}
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (this.cookie != null) {
request = request.newBuilder()
.header("Cookie", this.cookie)
.build();
}
return chain.proceed(request);
}
}
class Creator {
public static MyApi newApi() {
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
.create();
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new CookieInterceptor())
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(MyApi.URL)
.callFactory(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return retrofit.create(MyApi.class);
}
}
Another way to set a cookie is this way:
#Headers("Cookie: cookiename=cookievalue")
#GET("widget/list")
Call<List<Widget>> widgetList();
And here is a dynamic way:
#GET("user")
Call<User> getUser(#Header("Cookie") String cookie)
I've only just started with RetroFit, but the way it handles cookies does not seem to be on par with the rest of the library. I wound up doing something like this:
// Set up system-wide CookieHandler to capture all cookies sent from server.
final CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
CookieHandler.setDefault(cookieManager);
// Set up interceptor to include cookie value in the header.
RequestInterceptor interceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
for (HttpCookie cookie : cookieManager.getCookieStore().getCookies()) {
// Set up expiration in format desired by cookies
// (arbitrarily one hour from now).
Date expiration = new Date(System.currentTimeMillis() + 60 * 60 * 1000);
String expires = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz")
.format(expiration);
String cookieValue = cookie.getName() + "=" + cookie.getValue() + "; " +
"path=" + cookie.getPath() + "; " +
"domain=" + cookie.getDomain() + ";" +
"expires=" + expires;
request.addHeader("Cookie", cookieValue);
}
}
};
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.setRequestInterceptor(interceptor) // Set the interceptor
.build();
GitHubService service = restAdapter.create(GitHubService.class);