Working on a backend with Spring (Java) and Firebase. We are using the Firebase tokens (appended as an authentication header) to identify the user, using the built in UID.
Unfortunately, extracting this UID from the token must be done asynchronously, so I can only get the token from the onSuccess callback.
To serve a response, I must return an object from the below deleteUser method, however I cannot know what the response will be until I get a success/failure callback!
I can imagine a way to do this by waiting on a flag which is set my the callback, or with some messy timing, but I'm wondering if there is a clean way of handling this without introducing race conditions or lots of extra code. Can anyone help?
Request Mapping (handles request, serves response)
#RequestMapping(value = "/users", method = RequestMethod.DELETE)
public #ResponseBody String deleteUser(#RequestHeader("Authentication") String token) {
FirebaseUtil.getUid(token, new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
//RETURN SUCCESSFUL HERE
}
}, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
//RETURN FAILURE HERE
}
});
//MUST RETURN SOMETHING HERE?
User userToDelete = userDao.get(uid); //DONT HAVE THE uid HERE
userDao.delete(uid);
clearUserAccounts(userToDelete);
return uid + " was deleted";
}
FirebaseUtil.getUid()
public static void getUid(String token, OnSuccessListener<FirebaseToken> successListener, OnFailureListener failureListener) {
FirebaseAuth.getInstance()
.verifyIdToken(token)
.addOnSuccessListener(successListener)
.addOnFailureListener(failureListener);
}
While there are ways to block the thread until the asynchronous request finishes, there is a simple and more resource-effective solution since Spring 3.2.
You can use DeferredResult<T> as your return type to enable asynchronous processing. This allows the servlet container to reuse the HTTP worker thread right away, while sparing you the headache of forcefully serializing a chain of asynchronous requests.
By filling out the comments, your code would look like this:
#RequestMapping(value = "/users", method = RequestMethod.DELETE)
public DeferredResult<String> deleteUser(#RequestHeader("Authentication") String token) {
final DeferredResult<String> result = new DeferredResult<>();
FirebaseUtil.getUid(token, new OnSuccessListener<FirebaseToken>() {
#Override
public void onSuccess(FirebaseToken decodedToken) {
String uid = decodedToken.getUid();
User userToDelete = userDao.get(uid);
userDao.delete(uid);
clearUserAccounts(userToDelete);
result.setResult(uid + " was deleted");
}
}, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
result.setErrorResult(e);
}
});
return result;
}
Related
I make a request (any, authorization, registration, etc.) and only then I find out that I need to update the ACCESS-TOKEN, that is, I get the error 401.
Here is the authorization request:
BaseApplication.getApiClient()
.signIn(accessToken, body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<UserProfile>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("-- SignInOnSubscribe", "Subscribed!");
}
#Override
public void onSuccess(UserProfile userProfile) {
if (userProfile.getErrorDetails() != null) {
onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
Log.d("-- SignInOnError", userProfile.getErrorDetails());
} else {
onSignInFinishedCallback.onLoginSuccess(userProfile);
profileRepository.updateUserProfile(userProfile);
Log.d("-- SignInOnSuccess", userProfile.getName());
}
}
#Override
public void onError(Throwable e) {
Log.d("-- SignInOnError", e.getMessage());
if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)){
// Action on error 401
}
onSignInFinishedCallback.onLoginFailure(e.getMessage());
}
});
The API requests:
#POST("/api/login")
Single<UserProfile> getAccessToken(#Body Map<String, String> requestBody);
#POST("/api/abonent/login")
Single<UserProfile> signIn(#Header("X-ACCESS-TOKEN") String accessToken,
#Body Map<String, String> requestBody);
For example, the request for authorization is request 1, the request to receive TOKEN is query 2.
Question: How can I update TOKEN if I get an error in query 1 and after query 2 succeeds, back to do query 1?
I'm not sure how you receive the new token, since the return type of getAccessToken() is Single<UserProfile>. I suppose it should be Single<String> instead. Maybe this is not the case and you receive the token in a header or as a field of UserProfile. In either case, you can get an idea from the below solution and adjust it to your case.
The approach is that we create a new observable from your original one that uses a token store, which holds the most up-to-date token. We handle the 401 error using compose and onErrorResumeNext so that a token refresh request is made, the new token is saved to the token store, and the original request is retried with the new token this time.
For a more detailed explanation, see the comments in the code below:
public void signIn(final Map<String, String> body) {
Single
// Wrap the original request with a "defer" so that the access token is
// evaluated each time it is called. This is important because the refreshed
// access token should be used the second time around.
.defer(new Callable<SingleSource<UserProfile>>() {
#Override
public SingleSource<UserProfile> call() throws Exception {
return BaseApplication.getApiClient()
.signIn(accessTokenStore.getAccessToken(), body);
}
})
// Compose it with a transformer that refreshes the token in the token store and
// retries the original request, this time with the refreshed token.
.compose(retryOnNotAuthorized(body))
// The code remains the same from here.
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<UserProfile>() {
#Override
public void onSubscribe(Disposable d) {
Log.d("-- SignInOnSubscribe", "Subscribed!");
}
#Override
public void onSuccess(UserProfile userProfile) {
if (userProfile.getErrorDetails() != null) {
onSignInFinishedCallback.onLoginFailure(userProfile.getErrorDetails());
Log.d("-- SignInOnError", userProfile.getErrorDetails());
} else {
onSignInFinishedCallback.onLoginSuccess(userProfile);
profileRepository.updateUserProfile(userProfile);
Log.d("-- SignInOnSuccess", userProfile.getName());
}
}
#Override
public void onError(Throwable e) {
Log.d("-- SignInOnError", e.getMessage());
if (e.getMessage().equals(Constants.CODE_UNAUTHORIZED)) {
// Action on error 401
}
onSignInFinishedCallback.onLoginFailure(e.getMessage());
}
});
}
#NonNull
private SingleTransformer<UserProfile, UserProfile> retryOnNotAuthorized(final Map<String, String> body) {
return new SingleTransformer<UserProfile, UserProfile>() {
#Override
public SingleSource<UserProfile> apply(final Single<UserProfile> upstream) {
// We use onErrorResumeNext to continue our Single stream with the token refresh
// and the retrial of the request.
return upstream.onErrorResumeNext(new Function<Throwable, SingleSource<? extends UserProfile>>() {
#Override
public SingleSource<UserProfile> apply(Throwable throwable) throws Exception {
if (throwable instanceof HttpException
&& ((HttpException) throwable).code() == 401) {
return BaseApplication.getApiClient().getAccessToken(body)
// I always use doOnSuccess() for non-Rx side effects, such as caching the token.
// I think it's clearer than doing the caching in a map() or flatMap().
.doOnSuccess(new Consumer<String>() {
#Override
public void accept(String accessToken) throws Exception {
// Save the access token to the store for later use.
accessTokenStore.storeAccessToken(accessToken);
}
})
// We don't need the result of getAccessToken() any more, so I
// think it's cleaner to convert the stream to a Completable.
.toCompletable()
// After the token is refreshed and stored, the original request
// should be repeated.
.andThen(upstream);
}
// If the error was not 401, pass through the original error
return Single.error(throwable);
}
});
}
};
}
Update: The token store is just a regular interface with a get and a store method. You should implement it either as a POJO (storing the token in a field) or you could store the token in a shared preference so that the token survives app restarts.
I have a situation where I need to return an "accepted" response for every request received and publish the actual response later to a separate endpoint outside the service.
To implement the 'accepted' Response I implemented a filter.
public class AcknowledgementFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext containerRequestContext) throws IOException {
containerRequestContext.abortWith(Response.accepted().build());
// call Resource method in new Thread() . <------ ?
}
}
Implementation of service endpoints:
#Path("/vendor")
public class VendorHandler {
#POST
public void addVendor(VendorRequest addVendorRequest)){
vendor = new Vendor();
Client.publish(vendor); // publish request to an endpoint
return null;
}
How do I call the addVendor of VendorHandler(or any method depends on request) from the acknowledgement filter?
Is there any other way to implement an accepted response for every request then process the request separately?
You can use AsyncResponse,
#GET
#ManagedAsync
#Produces(MediaType.APPLICATION_JSON)
public void getLives(#Suspended final AsyncResponse asyncResponse,
#DefaultValue("0") #QueryParam("newestid") final int newestId,
#QueryParam("oldestid") final Integer oldestId) {
asyncResponse.setTimeoutHandler(asyncResponse1 -> {
logger.info("reached timeout");
asyncResponse1.resume(Response.ok().build());
});
asyncResponse.setTimeout(5, TimeUnit.MINUTES);
try {
List<Life> lives = oldestId == null ?
Lifes.getLastLives(newestId) : Lifes.getOlderLives(oldestId);
if (lives.size() > 0) {
final GenericEntity<List<Life>> entity = new GenericEntity<List<Life>>(lives) {
};
asyncResponse.resume(entity);
} else LifeProvider.suspend(asyncResponse);
} catch (SQLException e) {
logger.error(e, e);
asyncResponse.resume(new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR));
}
}
Check this Link for more details.
Just want to know where do I insert the API key for the server in my code below:
public class GetCurrentJob extends Job {
Context context;
GetFeedback feedback;
protected GetCurrentJob(Context context, GetFeedback fb) {
super(new Params(PRIORITY.HIGH).requireNetwork());
feedback = fb;
this.context = context;
}
#Override
public void onAdded() {
}
#Override
public void onRun() throws Throwable {
//POST feedback to server... require API key. How?
Response<String> response = Ion.with(context)
.load("POST", URLbuilder.getURL())
.setStringBody(feedback.toJson())
.asString()
.withResponse()
.get();
//Toast.makeText(context, "post", Toast.LENGTH_SHORT).show();
if (response.getHeaders().code() != HttpURLConnection.HTTP_OK) {
Log.d("test", "error in request " + String.valueOf(response.getResult()));
return;
}
else
{
Log.d("test", "success" + String.valueOf(response.getResult()));
}
}
#Override
protected void onCancel(int cancelReason, #Nullable Throwable throwable) {
}
#Override
protected RetryConstraint shouldReRunOnThrowable(#NonNull Throwable throwable, int runCount, int maxRunCount) {
return null;
}
}
My URLbuilder class:
public class URLbuilder {
private static final String SERVER = "http://jxapp-s-ticket.cloudapp.net/jxapp_ticket/upload/api/http.php/tickets.json";
public static String getURL(){
return Uri.parse(SERVER).buildUpon().toString();
}
}
Just a little information:
My app takes user feedback and returns the feedback to the server, then the server will generate a ticket to the user.
I am able to generate a response from the server, namely from the log. But the log generates: "error in request Valid API key required".
I need a way to insert the API key but I do not know how (am quite new to Android Studio as well as POST and GET operations)!
Do help if possible! Thanks!
Response<String> response = Ion.with(context)
.load("POST", URLbuilder.getURL())
.setHeader("x-api"," API KEY HERE ")
.setStringBody(feedback.toJson())
.asString()
.withResponse()
.get();
Should anyone encounter the same problem this is how i solved this.
Modified the ion formatting by adding a header with the API key.
All credits to a senior of mine.
Hi folks I'm creating an android application's login/register part using the Android Volley Library. My application was working well, but the UI and logic were at the same class. So, I have separated them into two classes. My app makes requests to my NodeJS server using POST methods and gets JSON response. So I have tried to keep the POST request function in another class.
After separating the classes, I have a problem while waiting for response. Here is the function;
public String doWebRequestLogin(Context context, boolean checkLoginForm, final Map<String,String> json){
result[0] = "FREE";
this.context = context;
if(checkLoginForm){
StringRequest post = new StringRequest(Request.Method.POST, loginUrl, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
try {
Log.d("Login Response: ",response);
data = response;
res = new JSONObject(data);
if (res.getString(KEY_SUCCESS) != null) {
int success = Integer.parseInt(res.getString(KEY_SUCCESS));
if (success == 1) {
result[0] = "LOGGED";
} else if (success == 0) {
result[0] = "LOGIN ERROR";
} else {
result[0] = "INVALID POST";
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.d("Response Error", error.toString());
result[0] = "INVALID POST";
}
}){
#Override
protected Map<String, String> getParams() throws AuthFailureError {
Map<String,String> map = json;
return map;
}
};
VolleyController.getInstance(this.context).getRequestQueue().add(post);
}
return result[0];
}
This function returns result[0] as "FREE" at every time due to response time. How could it wait for the response and set result[0] according to the response? I need to know what happened while making requests.
I'm calling doWebRequestLogin() on the UI within an onclick function
Then you do NOT want to "wait for the response". That will freeze your UI for however long the network I/O takes, and your users will... be unimpressed.
Instead, update your UI in the onResponse() and onErrorResponse() methods.
This sort of asynchronous call, handling the results via callbacks, is core to the event-driven programming model at the heart of Android.
The request is asynchronous and you must not block the main thread waiting for a response. Make the method void and use a callback to handle the response once it's received.
public void doWebRequestLogin(SomeCallback callback, Context context, boolean checkLoginForm, final Map<String,String> json){
[...]
if (res.getString(KEY_SUCCESS) != null) {
int success = Integer.parseInt(res.getString(KEY_SUCCESS));
callback.someMethod(success);
}
}
For the callback:
public interface SomeCallback{
void someMethod(int result); // response received, handle it
}
Callback may also have a return type or be generic, this depends solely on your needs...
I am using deferredResult on Spring MVC, but using this code, the timeout still are sending back the HTTP code 503 to the client.
future.onCompletion(new Runnable() {
#Override
public void run() {
if(future.isSetOrExpired()){
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
}
});
Any idea what else to try?
I ran into the same issue. My Spring MVC Controller method originally returned DeferredResult<Object>, but then I realised I wanted to control the HTTP status code. I found the answer here:
https://www.jayway.com/2014/09/09/asynchronous-spring-service/
#RequestMapping("/async")
DeferredResult<ResponseEntity<?>> async(#RequestParam("q") String query) {
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>();
ListenableFuture<RepoListDto> repositoryListDto = repoListService.search(query);
repositoryListDto.addCallback(
new ListenableFutureCallback<RepoListDto>() {
#Override
public void onSuccess(RepoListDto result) {
ResponseEntity<RepoListDto> responseEntity =
new ResponseEntity<>(result, HttpStatus.OK);
deferredResult.setResult(responseEntity);
}
#Override
public void onFailure(Throwable t) {
log.error("Failed to fetch result from remote service", t);
ResponseEntity<Void> responseEntity =
new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
deferredResult.setResult(responseEntity);
}
}
);
return deferredResult;
}
Just use DeferredResult<ResponseEntity> and you can set both the response and the Http response code in the ResponseEntity.