I am trying to unit test a Server Sent Event resource with an additional cookie. I am already using Jersey for the EventSource and JavaX for the client. The following code works fine:
WebTarget target = ClientBuilder.newBuilder()
.register(SseFeature.class)
.build()
.target("http://localhost:8080/sse");
EventSource eventSource = EventSource.target(target).build();
EventListener listener = new EventListener() {
#Override
public void onEvent(InboundEvent inboundEvent) {
LOG.info(inboundEvent.readData(String.class));
}
};
eventSource.register(listener);
eventSource.open();
serverEventManager.send("/sse", "foo");
eventSource.close();
Hoewever, for an additional unit test I need to add an additional cookie to the request. I have already tryed the following
target.(...).request.cookie("foo", "bar");
But this returns a builder from which I can't create the required WebTarget for the EventSource.
Here's what's happens within EventSource to establish a conection to the given WebTarget:
private Invocation.Builder prepareHandshakeRequest() {
Invocation.Builder request = EventSource.this.target
.request(new MediaType[] { SseFeature.SERVER_SENT_EVENTS_TYPE });
if ((this.lastEventId != null) && (!(this.lastEventId.isEmpty()))) {
request.header("Last-Event-ID", this.lastEventId);
}
if (EventSource.this.disableKeepAlive) {
request.header("Connection", "close");
}
return request;
}
As we can see - no chance to add a cookie here.
So WebTarget.request(new MediaType[] { SseFeature.SERVER_SENT_EVENTS_TYPE }) needs to return a Builder that
already has that desired cookie added.
Consider this delegating class adding the desired cookie to all request* type methods:
public class CookieAddedWebTarget implements WebTarget {
private WebTarget base;
private Cookie cookie;
public CookieAddedWebTarget(WebTarget base, Cookie cookie) {
this.base = base;
this.cookie = cookie;
}
// Inject that cookie whenever someone requests a Builder (like EventSource does):
public Builder request() {
return base.request().cookie(cookie);
}
public Builder request(String... paramArrayOfString) {
return base.request(paramArrayOfString).cookie(cookie);
}
public Builder request(MediaType... paramArrayOfMediaType) {
return base.request(paramArrayOfMediaType).cookie(cookie);
}
public Configuration getConfiguration() {
return base.getConfiguration();
}
//All other methods from WebTarget are delegated as-is:
public URI getUri() {
return base.getUri();
}
public UriBuilder getUriBuilder() {
return base.getUriBuilder();
}
public WebTarget path(String paramString) {
return base.path(paramString);
}
public WebTarget matrixParam(String paramString, Object... paramArrayOfObject) {
return base.matrixParam(paramString, paramArrayOfObject);
}
public WebTarget property(String paramString, Object paramObject) {
return base.property(paramString, paramObject);
}
public WebTarget queryParam(String paramString, Object... paramArrayOfObject) {
return base.queryParam(paramString, paramArrayOfObject);
}
public WebTarget register(Class<?> paramClass, Class<?>... paramArrayOfClass) {
return base.register(paramClass, paramArrayOfClass);
}
public WebTarget register(Class<?> paramClass, int paramInt) {
return base.register(paramClass, paramInt);
}
public WebTarget register(Class<?> paramClass, Map<Class<?>, Integer> paramMap) {
return base.register(paramClass, paramMap);
}
public WebTarget register(Class<?> paramClass) {
return base.register(paramClass);
}
public WebTarget register(Object paramObject, Class<?>... paramArrayOfClass) {
return base.register(paramObject, paramArrayOfClass);
}
public WebTarget register(Object paramObject, int paramInt) {
return base.register(paramObject, paramInt);
}
public WebTarget register(Object paramObject, Map<Class<?>, Integer> paramMap) {
return base.register(paramObject, paramMap);
}
public WebTarget register(Object paramObject) {
return base.register(paramObject);
}
public WebTarget resolveTemplate(String paramString, Object paramObject) {
return base.resolveTemplate(paramString, paramObject);
}
public WebTarget resolveTemplate(String paramString, Object paramObject, boolean paramBoolean) {
return base.resolveTemplate(paramString, paramObject, paramBoolean);
}
public WebTarget resolveTemplateFromEncoded(String paramString, Object paramObject) {
return base.resolveTemplateFromEncoded(paramString, paramObject);
}
public WebTarget resolveTemplates(Map<String, Object> paramMap) {
return base.resolveTemplates(paramMap);
}
public WebTarget resolveTemplates(Map<String, Object> paramMap, boolean paramBoolean) {
return base.resolveTemplates(paramMap, paramBoolean);
}
public WebTarget resolveTemplatesFromEncoded(Map<String, Object> paramMap) {
return base.resolveTemplatesFromEncoded(paramMap);
}
}
Now you should be able to reerite your test to:
EventSource eventSource = EventSource.target(new CookieAddedWebTarget(target,
new Cookie("name", "value"))).build();
And the cookie should be inserted.
Caveat: I have no way of testing this. Solution based only on reading source-code of jersey-media-sse-2.22.1.
Good Luck.
You could set the cookie in a ClientRequestFilter. Though the getCookies() on the ClientRequestContext is immutable, you should remember that a cookie is technically nothing more than a header. And the headers map on the request context is mutable. So you could do something like
public static class SseCookieFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
Cookie cookie = new Cookie("foo", "bar");
requestContext.getHeaders().add("Cookie", cookie.toString());
}
}
Just register the filter with the client (client.register(new SseCookieFilter())). It would be the same result as if you were to do
target.(...).request().cookie("foo", "bar");
Here is a complete example using Jersey Test Framework
public class SseCookieFilterTest extends JerseyTest {
#Path("events")
public static class SseResource {
#GET
#Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents(#CookieParam("foo") String foo) {
final EventOutput eventOutput = new EventOutput();
new Thread(() -> {
try {
final OutboundEvent.Builder eventBuilder
= new OutboundEvent.Builder();
eventBuilder.name("message");
eventBuilder.data(String.class, "Blah " + foo + "!!!");
final OutboundEvent event = eventBuilder.build();
eventOutput.write(event);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
eventOutput.close();
} catch (IOException ioClose) {
throw new RuntimeException(ioClose);
}
}
}).start();
return eventOutput;
}
}
public static class SseCookieFilter implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
Cookie cookie = new Cookie("foo", "bar");
requestContext.getHeaders().add("Cookie", cookie.toString());
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig(SseResource.class)
.register(new LoggingFilter());
}
#Test
public void doit() throws Exception {
Client client = ClientBuilder.newBuilder()
.register(SseFeature.class).build();
client.register(new SseCookieFilter());
WebTarget target = client.target("http://localhost:9998/events");
EventSource eventSource = EventSource.target(target).build();
EventListener listener = (InboundEvent inboundEvent) -> {
System.out.println("From server ---====++++> "
+ inboundEvent.readData(String.class));
};
eventSource.register(listener, "message");
eventSource.open();
Thread.sleep(100);
eventSource.close();
}
}
These are the only dependencies needed to test
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
Here is the server side result from the LoggingFilter I registered on the server in the test
INFO: 1 * Server has received a request on thread grizzly-http-server-2
1 > GET http://localhost:9998/events
1 > accept: text/event-stream
1 > connection: close
1 > cookie: $Version=1;foo=bar
1 > host: localhost:9998
1 > user-agent: Jersey/2.19 (HttpUrlConnection 1.8.0_31)
INFO: 1 * Server responded with a response on thread grizzly-http-server-2
1 < 200
1 < Content-Type: text/event-stream
From server ---====++++> Blah bar!!!
Related
I want to use this webflux client code to send POST requests with reply and without reply. I tried this code implementation:
public class RestClientBuilder {
private String token;
private String username;
private String password;
private URL gatewayUrl;
private SslContextBuilder sslContextBuilder;
public static RestClientBuilder builder() {
return new RestClientBuilder();
}
public RestClientBuilder token(String token) {
this.token = validateAndTrim(token, "Token");
return this;
}
public RestClientBuilder usernamePassword(String username, String password) {
this.username = validateAndTrim(username, "Username");
this.password = validateAndTrim(password, "Password");
return this;
}
private String validateAndTrim(String value, final String parameter) {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException(parameter + " is empty");
}
return value.trim();
}
public RestClientBuilder gatewayUrl(String gatewayUrl) {
String urlSt = validateAndTrim(gatewayUrl, "Gateway URL");
try {
this.gatewayUrl = new URL(urlSt);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Malformed URL: " + urlSt, e);
}
return this;
}
public RestClientBuilder truststore(File truststoreFile) {
getSslContextBuilder().trustManager(truststoreFile);
return this;
}
public RestClientBuilder sslCertificate(File keyCertChainFile, File keyFile, String keyPassword) {
getSslContextBuilder().keyManager(keyCertChainFile, keyFile, keyPassword);
return this;
}
public RestClient build() throws SSLException {
SslContext sslContext = sslContextBuilder != null ? sslContextBuilder.build() : null;
return new RestClient(gatewayUrl.toString(), token, username, password, sslContext);
}
private SslContextBuilder getSslContextBuilder() {
if (sslContextBuilder == null) {
sslContextBuilder = SslContextBuilder.forClient();
}
return sslContextBuilder;
}
}
Implementation of the rest client:
public class RestClient {
private WebClient client;
private String gatewayUrl;
public RestClient(String gatewayUrl, String token, String username, String password, SslContext sslContext) {
this.gatewayUrl = gatewayUrl;
WebClient.Builder builder = WebClient.builder().baseUrl(gatewayUrl);
if (sslContext != null) {
HttpClient httpClient = HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
ClientHttpConnector httpConnector = new ReactorClientHttpConnector(httpClient);
builder.clientConnector(httpConnector);
}
if (username != null && password != null) {
builder.filter(basicAuthentication(username, password));
}
client = builder.build();
}
public Mono<Void> executeOnly(ReportRequest transaction) {
Mono<ReportRequest> transactionMono = Mono.just(transaction);
return client.post().uri(gatewayUrl)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, ReportRequest.class)
.retrieve()
.bodyToMono(Void.class);
}
}
Make remote calls:
public class ReportingProcessor {
private String URL2 = "......";
public void collectEnvironmentData() throws JAXBException {
ReportRequest report = new ReportRequest();
report.setVersion("1.0");
RestClient client = null;
try {
client = RestClientBuilder.builder()
.gatewayUrl(URL2)
// .token(contract.getTerminal_token())
// .usernamePassword("user", "password")
// .truststore(new File("server.pem"))
// .sslCertificate(new File("client.pem"), new File("clientKey.p8"), "secret")
.build();
} catch (SSLException e) {
e.printStackTrace();
}
Mono<Void> result = client.executeOnly(report);
Void response = result.block();
}
When I remove Void response = result.block(); the request is not send. I Can't find why. Can you give me some advice how to make the client code working without using block().
Whenever you work with Spring-webflux you have to keep one thing in mind. i.e You don't have to break your chain. because it is necessary to, someone should call subscribe on your chain. as it works on RXJava specification.
if you break the chain then you have to call block() which not recommended.
you have to modify your code in the below manner.
Let's Consider you have a handler which is making a call to your collectEnvironmentData() method and your method is making a call to remote service.
public Mono<ServerResponse> handelerMethod(ServerRequest request){
return collectEnvironmentData().flatMap(aVoid -> ServerResponse.ok().build());
}
your method should be modified to
public Mono<Void> collectEnvironmentData() throws JAXBException {
ReportRequest report = new ReportRequest();
report.setVersion("1.0");
RestClient client = null;
try {
client = RestClientBuilder.builder()
.gatewayUrl(URL2)
// .token(contract.getTerminal_token())
// .usernamePassword("user", "password")
// .truststore(new File("server.pem"))
// .sslCertificate(new File("client.pem"), new File("clientKey.p8"),
//"secret").build();
} catch (SSLException e) {
e.printStackTrace();
}
return client.executeOnly(report);
}
Change your implementation in the above manner, hope it will work.
How I would implement your method is:
public Mono<Void> executeOnly(ReportRequest transaction) {
Mono<ReportRequest> transactionMono = Mono.just(transaction);
return client.post().uri(gatewayUrl)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transaction, ReportRequest.class)
.exchange()
.then();
}
And then I would use it as follows:
client.executeOnly(report).subscribe()
Change the method return type to Mono<Void> for end to end streaming.
public void collectEnvironmentData() throws JAXBException {
ReportRequest report = new ReportRequest();
report.setVersion("1.0");
RestClient client = null;
try {
client = RestClientBuilder.builder()
.gatewayUrl(URL2)
.build();
} catch (SSLException e) {
e.printStackTrace();
}
return client.executeOnly(report);
}
Or you can also subscribe the Mono
client.executeOnly(report).subscribe();
This question already has an answer here:
How to fix Expected BEGIN_OBJECT in Retrofit?
(1 answer)
Closed 4 years ago.
In my application i want use Retrofit for get some data from server.
I write below codes but when run application and call api show me below error :
E/socketLogResponse: Err : com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $
Please see my above codes and help me
API response from server :
{
"status": "ok",
"time": 0.014972925186157227
}
ApiService interface :
#POST("api/log")
Call<SocketPingResponse> getSocketPingLog(#Header("jwt") String jwt, #Body SocketPingBodySendData socketPingBodySendData);
SocketPingResponse class :
public class SocketPingResponse {
#SerializedName("status")
#Expose
private String status;
#SerializedName("time")
#Expose
private Double time;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Double getTime() {
return time;
}
public void setTime(Double time) {
this.time = time;
}
}
SocketPingBodySendData class :
public class SocketPingBodySendData {
#SerializedName("auction_id")
#Expose
int auction_id;
#SerializedName("data")
#Expose
List<SocketPingEntity> data;
public int getAuction_id() {
return auction_id;
}
public void setAuction_id(int auction_id) {
this.auction_id = auction_id;
}
public List<SocketPingEntity> getData() {
return data;
}
public void setData(List<SocketPingEntity> data) {
this.data = data;
}
}
Api call codes in activity :
pingEntityList.addAll(socketPingDatabase.socketPingDao().getSocketPingEntityList());
SocketPingBodySendData pingBodySendData = new SocketPingBodySendData();
pingBodySendData.setAuction_id(auctionID);
pingBodySendData.setData(pingEntityList);
Toast.makeText(context, ""+pingEntityList.size(), Toast.LENGTH_SHORT).show();
Call<SocketPingResponse> pingResponseCall = apis.getSocketPingLog(jwtToken, pingBodySendData);
pingResponseCall.enqueue(new Callback<SocketPingResponse>() {
#Override
public void onResponse(Call<SocketPingResponse> call, Response<SocketPingResponse> response) {
if (response.body() != null) {
Toast.makeText(context, response.body().getStatus(), Toast.LENGTH_SHORT).show();
if (response.body().getStatus().equals("ok")) {
pingEntityList.clear();
socketPingDatabase.socketPingDao().deleteAll();
}
}
}
#Override
public void onFailure(Call<SocketPingResponse> call, Throwable t) {
Log.e("socketLogResponse", "Err : " + t.toString());
}
});
ApiClient class :
public class ApiClient {
private static final String BASE_URL = Constants.SERVER;
private static Retrofit retrofit = null;
private static Context context;
public static Retrofit getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.interceptors().add(interceptor);
client.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("X-Client-Version", Constants.getAppVersionName()).build();
return chain.proceed(request);
}
});
client.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("uuid", Constants.getUUID(Constants.currentActivity)).build();
return chain.proceed(request);
}
});
client.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("agent", Constants.getAgent()).build();
return chain.proceed(request);
}
});
OkHttpClient client2 = client
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client2)
.build();
}
return retrofit;
}
}
How can i fix this issue?
I think the problem returns string when no data is returned. Backend can usually do such errors. this error had happened to me before. you should check the response json when no data is available
Retrofit is typesafe library. It means that it waits only particular (predefined) types of objects. If server sends something else - it crashes with error. This is your case. Just check raw server response and you'll see what's wrong.
Try changing your API call
from
#POST("api/log")
Call<SocketPingResponse> getSocketPingLog(#Header("jwt") String jwt, #Body SocketPingBodySendData socketPingBodySendData);
to
#POST("api/log")
Call<String> getSocketPingLog(#Header("jwt") String jwt, #Body SocketPingBodySendData socketPingBodySendData);
pingEntityList.addAll(socketPingDatabase.socketPingDao().getSocketPingEntityList());
SocketPingBodySendData pingBodySendData = new SocketPingBodySendData();
pingBodySendData.setAuction_id(auctionID);
pingBodySendData.setData(pingEntityList);
Toast.makeText(context, ""+pingEntityList.size(), Toast.LENGTH_SHORT).show();
Call<String> pingResponseCall = apis.getSocketPingLog(jwtToken, pingBodySendData);
pingResponseCall.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.body() != null) {
//Convert here your string response to Other POJO format
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
Log.e("socketLogResponse", "Err : " + t.toString());
}
});
How to start LoginActivity from the interceptor(non-activity class)? I have tried the code (Interceptor) below but not working for me.
Interceptor
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + auth_token_string)
.build();
Response response = chain.proceed(newRequest);
Log.d("MyApp", "Code : "+response.code());
if (response.code() == 401){
Intent intent = new Intent(SplashActivity.getContextOfApplication(), LoginActivity.class);
startActivity(intent);
finish(); //Not working
return response;
}
return chain.proceed(newRequest);
}
}).build();
This is the current solution I'm using, is there any better solution than this? This solution has to keep repeat on every api call.
MainActivity
call.enqueue(new Callback<Token>() {
#Override
public void onResponse(Call<Token> call, Response<Token> response) {
if(response.isSuccessful())
{
//success
}
else
{
Intent intent = new Intent(MainActivity.this.getApplicationContext(), LoginActivity.class);
startActivity(intent);
finish();
}
}
#Override
public void onFailure(Call<Token> call, Throwable t) {
}
});
Personally, I would like to suggest using event bus pattern here. You can use greenrobot implementation or whatever you want, since it's more about an architecture approach rather than concrete implementation.
Create event model
public class UnauthorizedEvent {
private static final UnauthorizedEvent INSTANCE = new UnauthorizedEvent();
public static UnauthorizedEvent instance() {
return INSTANCE;
}
private UnauthorizedEvent() {
}
}
Implement custom Interceptor which disptaches event about unauthorized reqeusts
class UnauthorizedInterceptor implements Interceptor {
#Override
public Response intercept(#NonNull Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if (response.code() == 401) {
EventBus.getDefault().post(UnauthorizedEvent.instance());
}
return response;
}
}
Create BaseActivity class which handles UnauthorizedEvent
public class BaseActivity extends Activity {
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
#Subscribe
public final void onUnauthorizedEvent(UnauthorizedEvent e) {
handleUnauthorizedEvent();
}
protected void handleUnauthorizedEvent() {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
}
Prevent launching LoginActivity from LoginActivity
public class LoginActivty extends BaseActivity {
#Override
protected void handleUnauthorizedEvent() {
//Don't handle unauthorized event
}
}
Another approach is to not extending BaseActivity here.
Register your interceptor
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new UnauthorizedInterceptor())
.build();
Pros:
Loose coupling between components
Easaly extending the logic by overriding handleUnauthorizedEvent
No need to rewrite code to use new types of callbacks
Reduce human factor about making mistakes (using Callback instead of BaseCallback)
Cons:
EventBus pattern makes debugging more complicated
One more dependency or own implementation which brings new code to the project
Also, please take into account, that this example doesn't cover multithreading issues. It solves your problem of handling unauthorized requests. Thus, if two requests receive 401 than it is possible that 2 instances of LoginActivity is started.
Consider introducing a custom implementation of retrofit2.Callback interface, e.g. BaseCallback:
public abstract class BaseCallback<T> implements Callback<T> {
private final Context context;
public BaseCallback(Context context) {
this.context = context;
}
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (response.code() == 401) {
// launch login activity using `this.context`
} else {
onSuccess(response.body());
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
}
abstract void onSuccess(T response);
}
Now, from the caller site you should change new Callback<Token> with new BaseCallback<Token>:
call.enqueue(new BaseCallback<Token>(context) {
#Override
void onSuccess(Token response) {
// do something with token
}
});
Although, this approach doesn't fulfil your following statement:
so I don't have to keep repeat the same code over again for each api call
nevertheless, I cannot come up with a better approach.
The simplest way is to inject activity context in Interceptor instance.
If you are using some DI tools, like Dagger2 or Toothpick it will be very simple. I recommend to use toothpick)
https://github.com/stephanenicolas/toothpick
The most code near by will be in kotlin, because it's my boilerplate code. Those thinks, that you are need to solve your problem i will write in Java.
The solution will be like this:
#Qualifier
annotation class BackendUrl
class ActivityModule(activity: BaseActivity): Module() {
init {
bind(Activity::class.java).toInstance(activity)
}
}
class NetworkModule: Module() {
init {
bind(String::class.java).withName(BackendUrl::class.java).toInstance(Constants.URL)
bind(Gson::class.java).toInstance(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create())
bind(CacheHolder::class.java).toProvider(CacheProvider::class.java).singletonInScope()
bind(OkHttpClient::class.java).toProvider(OkHttpProvider::class.java).instancesInScope()
bind(BackendApi::class.java).toProvider(BackendApiProvider::class.java).instancesInScope()
bind(RedirectInterceptor::class.java).to(RedirectInterceptor::class.java)
}
}
Than you need to create Providers for injection dependency
class BackendApiProvider #Inject constructor(
private val okHttpClient: OkHttpClient,
private val gson: Gson,
#BackendUrl private val serverPath: String
) : Provider<BackendApi> {
override fun get() =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.baseUrl(serverPath)
.build()
.create(BackendApi::class.java)
}
And your redirect interceptor:
public class RedirectInterceptor implements Interceptor {
private final Context context;
#Inject
public RedirectInterceptor(Activity context) {
this.context = context;
}
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.build();
Response response = chain.proceed(newRequest);
Log.d("MyApp", "Code : "+response.code());
if (response.code() == 401){
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
((Activity) context).finish();
return response;
}
return chain.proceed(newRequest);
}
}
Oh, yes. For Authorization header will be better to create new instance of another interceptor
class HeaderInterceptor(private val token: String?) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val newRequest = request.newBuilder()
Log.d(TAG, "token: $token")
if (token != null && token.isNotBlank()) {
newRequest.addHeader("Authorization", "Bearer $token")
}
return chain.proceed(newRequest.build())
}
companion object {
private val TAG = HeaderInterceptor::class.java.toString()
}
}
And your OkhttpProvder
class OkHttpProvider #Inject constructor(cacheHolder: CacheHolder, prefs: IPreferences, redirectInterceptor: RedirectInterceptor) : Provider<OkHttpClient> {
private val client: OkHttpClient
init {
val builder = OkHttpClient.Builder()
builder
.addNetworkInterceptor(redirectInterceptor)
.addNetworkInterceptor(HeaderInterceptor(prefs.getAuthToken()))
.readTimeout(30, TimeUnit.SECONDS)
.cache(cacheHolder.okHttpCache)
client = builder.build()
}
override fun get() = client
}
Thats all! Now, you just only need to place you modules in right scopes.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.init_view)
Toothpick.openScopes("activity scope").apply {
installModules(ActivityModule(this#YourActivity))
Toothpick.inject(this#YourActivity, this)
}
Toothpick.openScopes("activity scope", "network scope").apply {
installModules(NetworkModule())
}
// your activity code
}
This is how interceptor worked for handling 401 globally
public class ResponseHeaderInterceptor implements Interceptor {
private final Context context;
public ResponseHeaderInterceptor(Context context) {
this.context = context;
}
#NotNull
#Override
public Response intercept(#NotNull Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
if(response.code() == 401){
SharedPreferences pref = context.getSharedPreferences(Constants.PREFERENCES, 0);
String userName = pref.getString("key_user_email", "");
//clear shared preferences
pref.edit().clear().apply();
Bundle params = new Bundle();
params.putString("user", userName);
FirebaseAnalytics.getInstance(context).logEvent(Constants.USER_UNAUTHORIZED_EVENT, params);
Intent intent = new Intent(this.context, IntroActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.context.startActivity(intent);
}
return response;
}
}
adding to okhttp client of retrofit
var okHttpClient: OkHttpClient = OkHttpClient()
.newBuilder()
.addInterceptor(ResponseHeaderInterceptor(MyApplication.getMyApplicationContext()))//Header interceptor for logging network responses
.build()
private var retrofit: Retrofit? = null
val client: Retrofit?
get() {
if (retrofit == null) {
retrofit = Retrofit.Builder()
.client(okHttpClient)
.baseUrl(BuildConfig.SERVER)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
Generalized Solution:
You can solve it by generalizing the error handling. You can use custom CallAdapterFactory to the Retrofit builder. Please refer below classes :
RxErrorHandlingCallAdapterFactory :
public class RxErrorHandlingCallAdapterFactory extends CallAdapter.Factory {
private static Context mContext = null;
private final RxJava2CallAdapterFactory original;
private RxErrorHandlingCallAdapterFactory() {
original = RxJava2CallAdapterFactory.create();
}
public static CallAdapter.Factory create(Context context) {
mContext = context;
return new RxErrorHandlingCallAdapterFactory();
}
#Override
public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
return new RxCallAdapterWrapper(retrofit, original.get(returnType, annotations, retrofit));
}
private static class RxCallAdapterWrapper<R> implements CallAdapter<R, Object> {
private final Retrofit retrofit;
private final CallAdapter<R,
Object> wrapped;
public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<R, Object> wrapped) {
this.retrofit = retrofit;
this.wrapped = wrapped;
}
#Override
public Type responseType() {
return wrapped.responseType();
}
#Override
public Object adapt(Call<R> call) {
Object result = wrapped.adapt(call);
if (result instanceof Single) {
return ((Single) result).onErrorResumeNext(new Function<Throwable, SingleSource>() {
#Override
public SingleSource apply(#NonNull Throwable throwable) throws Exception {
return Single.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Observable) {
return ((Observable) result).onErrorResumeNext(new Function<Throwable, ObservableSource>() {
#Override
public ObservableSource apply(#NonNull Throwable throwable) throws Exception {
return Observable.error(asRetrofitException(throwable));
}
});
}
if (result instanceof Completable) {
return ((Completable) result).onErrorResumeNext(new Function<Throwable, CompletableSource>() {
#Override
public CompletableSource apply(#NonNull Throwable throwable) throws Exception {
return Completable.error(asRetrofitException(throwable));
}
});
}
return result;
}
private RetrofitException asRetrofitException(Throwable throwable) {
// We had non-200 http error
Log.v("log", "eror");
throwable.printStackTrace();
if (throwable instanceof HttpException) {
HttpException httpException = (HttpException) throwable;
final Response response = httpException.response();
//if ((mContext instanceof Activity)) {
String s = "Something went wrong."; //mContext.getString(R.string.something_went_wrong);
try {
s = new JSONObject(response.errorBody().string()).getString("message");
if (response.code() == 401) { // 401 Unauthorized
Intent intent = new Intent(mContext, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mContext.startActivity(intent);
}
} catch (JSONException | IOException e) {
e.printStackTrace();
}
return RetrofitException.unexpectedError(s, response, retrofit);
//showErrorDialog(mContext, response);
//}
// return RetrofitException.httpError(response.errorBody().toString(), response, retrofit);
}
// A network error happened
if (throwable instanceof IOException) {
return RetrofitException.networkError((IOException) throwable);
}
// We don't know what happened. We need to simply convert to an unknown error
return RetrofitException.unexpectedError(throwable);
}
}
}
RetrofitException :
public class RetrofitException extends RuntimeException {
private final String url;
private final Response response;
private final Kind kind;
private final Retrofit retrofit;
RetrofitException(String message, String url, Response response, Kind kind, Throwable exception, Retrofit retrofit) {
super(message, exception);
this.url = url;
this.response = response;
this.kind = kind;
this.retrofit = retrofit;
}
public static RetrofitException httpError(String url, Response response, Retrofit retrofit) {
String message = response.code() + " " + response.message();
return new RetrofitException(message, url, response, Kind.HTTP, null, retrofit);
}
public static RetrofitException networkError(IOException exception) {
return new RetrofitException(exception.getMessage(), null, null, Kind.NETWORK, exception, null);
}
public static RetrofitException unexpectedError(Throwable exception) {
return new RetrofitException(exception.getMessage(), null, null, Kind.UNEXPECTED, exception, null);
}
public static RetrofitException unexpectedError(String s, Response response, Retrofit retrofit) {
return new RetrofitException(s, null, null, Kind.UNEXPECTED, null, null);
}
/**
* The request URL which produced the error.
*/
public String getUrl() {
return url;
}
/**
* Response object containing status code, headers, body, etc.
*/
public Response getResponse() {
return response;
}
/**
* The event kind which triggered this error.
*/
public Kind getKind() {
return kind;
}
/**
* The Retrofit this request was executed on
*/
public Retrofit getRetrofit() {
return retrofit;
}
/**
* HTTP response body converted to specified {#code type}. {#code null} if there is no
* response.
*
* #throws IOException if unable to convert the body to the specified {#code type}.
*/
public <T> T getErrorBodyAs(Class<T> type) throws IOException {
if (response == null || response.errorBody() == null) {
return null;
}
Converter<ResponseBody, T> converter = retrofit.responseBodyConverter(type, new Annotation[0]);
return converter.convert(response.errorBody());
}
/**
* Identifies the event kind which triggered a {#link RetrofitException}.
*/
public enum Kind {
/**
* An {#link IOException} occurred while communicating to the server.
*/
NETWORK,
/**
* A non-200 HTTP status code was received from the server.
*/
HTTP,
/**
* An internal error occurred while attempting to execute a request. It is best practice to
* re-throw this exception so your application crashes.
*/
UNEXPECTED
}
}
Retrofit Builder :
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxErrorHandlingCallAdapterFactory.create(context))
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(API_URL)
.client(client)
.build();
You can handle 401 in RxErrorHandlingCallAdapterFactory and other errors through Throwable.
Hi From last two I'm stuck with this retrofit any one please help me.I have tried so many method to pass header in retrofit could but i couldn't im using Retrofit 2.0.1
build.gradle
compile 'com.google.code.gson:gson:2.6.2'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
compile 'com.squareup.okhttp:okhttp:2.7.2'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
And in
ApiClientHeader.jav
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class ApiClientHeader {
public static final String BASE_URL = "URL";
private static Retrofit retrofit = null;
public static Retrofit getClient(final String token) {
OkHttpClient okClient = new OkHttpClient.Builder()
.addInterceptor(
new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "Bearer " + token)
.method(original.method(), original.body());
Request request = requestBuilder.build();
return chain.proceed(request);
}
})
.build();
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okClient)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
And In my
ApiInterface.java
#GET("profile")
Call<JsonObject> getProfile();
And In My Activity i am just calling function
private void getProfileData()
{
Singleton single = new Singleton();
String auth = single.getAuthorization();
Log.d("===========>>>>>>",auth);
ApiInterface apiService =
ApiClientHeader.getClient(auth).create(ApiInterface.class);
//showProgress(true);
Call<JsonObject> profileResponse = apiService.getProfile();
profileResponse.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
//showProgress(false);
Log.d("============>"," Call Request " +String.valueOf(call.request().toString()));
Log.d("============>", " Response code " + String.valueOf(response.code()));
// Log.d("============>", " Response Body " + String.valueOf(response.body().toString()));
if(response.code() == HttpURLConnection.HTTP_OK)
{
}
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
//showProgress(false);
Log.d("============>"," Call Request " +String.valueOf(call.request().toString()));
Log.d("============>"," Call Request " +String.valueOf(call.request().headers()));
Log.d("=======>", "Failure" + t.getMessage());
}
});
}
Still I am getting 403 Invalid acces.
Here i have not used any POJO class to send or receive data. Please help me.
You have to pass your authentication string like this
#GET("profile")
Call<JsonObject> getProfile((#Header("Authorization") String authorization))
#Headers({
"Accept: application/vnd.github.v3.full+json",
"User-Agent: Retrofit-Sample-App"
})
#GET("users/{username}")
Call<User> getUser(#Path("username") String username);
more info on documentation
I found one of the best link for Token based authentication using Retrofit 1.9 + OkHttp 2.4
You can add NetworkInterceptor, I have used it in my demo like:
httpClient.addNetworkInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
// Add authorization header with updated authorization value to intercepted request
Request authorisedRequest = originalRequest.newBuilder()
.header("Authorization", AccountAuthenticator.getAccessTokenWithTokenType(mContext))
.build();
return chain.proceed(authorisedRequest);
}
});
Dependencies:
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
Thank you.
Try below code with your class, It is working for me:
final RestAdapter restAdapter = new RestAdapter.Builder().setClient(client).setLogLevel(RestAdapter.LogLevel.FULL).setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade requestFacade) {
requestFacade.addHeader("key", "value");
requestFacade.addHeader("X-Requested-With", "XMLHttpRequest");
}
}).setConverter(new GsonConverter(gson)).setEndpoint(context.getString(R.string.base_url)).build();
Edit Post :
public class RestClient {
// private static final String BASE_URL = "http://api.plumperfect.com";
private WebServicesInterface apiService;
private static RestClient instance;
public RestClient(Context context) {
instance = this;
final Gson gson = new GsonBuilder().registerTypeAdapterFactory(new ItemTypeAdapterFactory()).setDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSS'Z'").create();
// final int cacheSize = 10 * 1024 * 1024; // 10 MiB
// final File cacheDirectory = new File(context.getCacheDir().getAbsolutePath(), "HttpCache");
// final OkHttpClient client = new OkHttpClient();
// Cache cache;
// try {
// cache = new Cache(cacheDirectory, cacheSize);
// client.setCache(cache);
// } catch (IOException e) {
// e.printStackTrace();
// }
final OkHttpClient okHttpClient = new OkHttpClient();
final Client client = new OkClient(okHttpClient);
final RestAdapter restAdapter = new RestAdapter.Builder().setClient(client).setLogLevel(RestAdapter.LogLevel.FULL).setRequestInterceptor(new RequestInterceptor() {
#Override
public void intercept(RequestFacade requestFacade) {
requestFacade.addHeader("key", "value");
requestFacade.addHeader("X-Requested-With", "XMLHttpRequest");
}
}).setConverter(new GsonConverter(gson)).setEndpoint(context.getString(R.string.base_url)).build();
apiService = restAdapter.create(WebServicesInterface.class);
}
public WebServicesInterface getApiService() {
return apiService;
}
public static RestClient getInstance() {
return instance;
}
}
Secound Class :
public class ItemTypeAdapterFactory implements TypeAdapterFactory {
#Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
final TypeAdapter<JsonElement> elementAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader in) throws IOException {
JsonElement jsonElement = elementAdapter.read(in);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has(Constants.DATA) && jsonObject.get(Constants.DATA).isJsonObject()) {
jsonElement = jsonObject.get(Constants.DATA);
}
}
return delegate.fromJsonTree(jsonElement);
}
}.nullSafe();
}
}
I have a minified URL and I want to have the final URL
With Retrofit 1.9 I used to do this :
#HEAD("/XXXXXXXXX")
void fetchFinalUrl(Callback<String> cb);
public void getUrl() {
mMinifyService.fetchFinalUrl(new Callback<String>() {
#Override
public void success(String s, Response response) {
response.getUrl();
}
[...]
}
But now with Retrofit 2 .getUrl() not exist any ideas how to do this?
Thanks in advance.
EDIT
Finally got it!
public class ApiProvider<T> {
private Retrofit retrofit;
private static final String END_POINT_MINIFY = "XXXXXXX";
public ApiProvider() {
initAdapter();
}
public T getService(Class<T> service) {
return retrofit.create(service);
}
private void initAdapter() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.followRedirects(false)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(END_POINT_MINIFY)
.addConverterFactory(new ToStringConverterFactory())
.client(client)
.build();
}
}
public interface IMinifyService {
#HEAD("/XXXXXXXXX")
Call<Void> fetchFinalUrl(Callback<String> cb);
}
public class MinifyServiceImpl {
private ApiProvider<IMinifyService> mApiProvider = new ApiProvider<>();
private IMinifyService mMinifyService = mApiProvider.getService(IMinifyService.class);
public Promiser<String, Integer> fetchMinifyUrl() {
return new Promiser<>((resolve, reject) -> mMinifyService.fetchMinifyUrl().enqueue(new Callback<Void>() {
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
if (response.code() >= 300 && response.code() < 400){
resole.run(response.headers().get("Location"));
} else {
reject.run(response.code());
}
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
reject.run(t.hashCode());
}
}));
}
}
if you want to use Promizer --> Click here
response.raw().request().url()