SPA: how to know if user's alive - java

I have: SPA application, which means it can even do offline-processing. It means my app ask server only if it needs some additional info or wants to save something.
I holds user connections in Guava Cache with expired policy. It means I shouldn't worry about session destruction after timeout.
Crucial point: Each time user do some request I reset timeout to avoid session destruction. When user is inactive during some specified period, Guava Cache just throw his session away.
Problem: The problem is linked with SPA. With SPA, if user don't send any requests it doesn't mean that he's inactive.
I want: Automatically close user session an log out him after timeout.
Question: How can I know if user is active or not in SPA?

The first what comes in mind - is to use something like expired cache on server-side, and send each e.g. 5 minutes keep_alive request from the client to indicate that client is active. But it seems to be overengineering. Plus we clog the server with hundreds of unnecessary requests.
So I found (no mean to be pioneer) the better solution: client calculate an inactive period by his own, and send the appropriate request if inactive_period is greater than timeout.
activityListener = {
timeout: null,
activityHandler: function () {
$.cookie('last_activity', new Date().getTime());
},
initialize: function (timeout) {
this.timeout = timeout;
$.cookie('last_activity', new Date().getTime());
if ($.cookie('do_activity_check') != true) {
$.cookie('do_activity_check', true);
setInterval(this.activityCheck, timeout / 2);
}
addEventListener('click', this.activityHandler, false);
addEventListener('scroll', this.activityHandler, false);
},
handleTimeout: function () {
if (oLoginPage.authorities != null) {
le.send({
"#class": "UserRequest$Logout",
"id": "UserRequest.Logout"
});
}
},
activityCheck: function () {
var after_last_activity_ms = new Date().getTime() - $.cookie('last_activity');
if (after_last_activity_ms > activityListener.timeout) activityListener.handleTimeout();
}
}

Related

Retry HTTP Request using Spring's Webclient

I'm using springframework's reactive WebClient to make a client HTTP request to another service.
I currently have:
PredictionClientService.java
var response = externalServiceClient.sendPostRequest(predictionDto);
if (response.getStatusCode() == HttpStatusCode.OK) {
predictionService.updateStatus(predictionDto, Status.OK);
} else {
listOfErrors.add(response.getPayload());
predictionService.updateStatus(predictionDto, Stage.FAIL);
//Perhaps change above line to Stage.PENDING and then
//Poll the DB every 30, 60, 120 mins
//if exhausted, then call
// predictionService.updateStatus(predictionDto, Stage.FAILED);??
}
}
ExternalServiceClient.java
public PredictionResponseDto sendPostRequest(PredictionDto predictionDto) {
var response = webClient.post()
.uri(url)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(predictionDto.getPayload()))
.exchange()
.retryWhen(Retry.backoff(3, Duration.ofMinutes(30)))
//Maybe I can remove the retry logic here
//and handle retrying in PredictionClientService?
.onErrorResume(throwable ->
Mono.just(ClientResponse.create(TIMEOUT_HTTP_CODE,
ExchangeStrategies.empty().build()).build()))
.blockOptional();
return response.map(clientResponse ->
new PredictionResponseDto(
clientResponse.rawStatusCode(),
clientResponse.bodyToMono(String.class).block()))
.orElse(PredictionResponseDto.builder().build());
}
This will retry a maximum of 3 times on intervals 30, 60, 120 mins. The issue is, I don't want to keep a processing for running upwards of 30 mins.
The top code block is probably where I need to add the retry logic (poll from database if status = pending and retries < 3)?
Is there any sensible solution here? I was thinking if I could save the failed request to a DB with columns 'Request Body', "Retry attempt", "Status" and poll from this? Although not sure if cron is the way to go here.
How would I retry sending the HTTP request every 30, 60, 120 mins to avoid these issues? Would appreciate any code samples or links!

Refreshing an Access Token for Client Credentials Flow

I was wondering what the best way is for me to refresh an access token that is obtained through the client credentials flow within OAuth 2.0. I've read over the spec, but I can't seem to be able to find an answer for my particular situation.
For my specific case, I am using the Spotify Web API for my Android app in order to search for content (tracks, albums, and/or artists). In order to perform a search, I need an access token. Since I'm not interested in a Spotify user's data, I can use the client credentials flow to obtain the access token, which is explain in Spotify's terms here.
Because the access token can eventually expire, I had to figure out a way to refresh it once expiration occurred. What I'm ultimately wondering is if my approach is valid and if there's any concern with how I've approached this.
First and foremost, I stored my access token within SharedPreferences. Within onCreate(), I have the following:
#Override
protected void onCreate(Bundle savedInstanceState) {
// A bunch of other stuff, views being initialized, etc.
mAccessToken = getAccessToken();
// If the access token is expired, then we will attempt to retrieve a new one
if (accessTokenExpired()) {
retrieveAccessToken();
}
}
I've defined accessTokenExpired() and retrieveAccessToken() as follows:
private boolean accessTokenExpired() {
// If mAccessToken hasn't yet been initialized, that means that we need to try to retrieve
// an access token. In this case, we will return true;
if (mAccessToken == null) {
return true;
}
SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);
long timeSaved = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_TIME_SAVED, 0L);
long expiration = preferences.getLong(PREFERENCES_KEY_TOKEN_RESPONSE_EXPIRATION, 0L);
long now = System.currentTimeMillis()/1000;
long timePassed = Math.abs(now - timeSaved);
if (timePassed >= expiration) {
return true;
} else {
return false;
}
}
One thing worth noting about retrieveAccessToken() is that I'm using Retrofit for my HTTP request:
private void retrieveAccessToken() {
// First, we obtain an instance of SearchClient through our ClientGenerator class
mClient = ClientGenerator.createClient(SearchClient.class);
// We then obtain the client ID and client secret encoded in Base64.
String encodedString = encodeClientIDAndSecret();
// Finally, we initiate the HTTP request and hope to get the access token as a response
Call<TokenResponse> tokenResponseCall = mClient.getAccessToken(encodedString, "client_credentials");
tokenResponseCall.enqueue(new Callback<TokenResponse>() {
#Override
public void onResponse(Call<TokenResponse> call, Response<TokenResponse> response) {
Log.d(TAG, "on Response: response toString(): " + response.toString());
TokenResponse tokenResponse = null;
if (response.isSuccessful()) {
tokenResponse = response.body();
Log.d(TAG, tokenResponse.toString());
mAccessToken = tokenResponse.getAccessToken();
saveAccessToken(tokenResponse);
}
}
#Override
public void onFailure(Call<TokenResponse> call, Throwable t) {
Log.d(TAG, "onFailure: request toString():" + call.request().toString());
mAccessToken = "";
}
});
}
Finally, saveAccessToken(tokenResponse) is sort of the complement of accessTokenExpired(), where I'm saving the values from the token response into SharedPreferences rather than retrieving them.
Are there any concerns with how I'm doing this? I got the idea from this SO post and slightly modified it. Spotify doesn't provide a refresh token in their access token response. Therefore, I can't make use of it here to reduce the number of access token requests I make.
Any input on this would be greatly appreciated!
Two considerations are:
you probably want some error handling around the requests you make using the access token that can handle the token expiring and do retries. The two situations where this will help are
when the token expires between checking if it's valid and your usage of it
when in the cycle of check the token is valid -> make some requests with the token -> repeat, you spend over an hour using the token. Another way you can do it is to calculate now + expected_api_request_time > token_expiration_time where expected_api_request_time would be a constant you set, but I think handling token expiry as an exception is better practice (you probably want to be able to make retries anyway in cases of network instability).
you can perform the calculations to work out when the token expires either when you retrieve the timeSaved and expiration from your local storage, or just calculate the time the token will expire initially and save that. This is relatively minor, both this and the way you've done it are fine I think.

Direct messaging communication failure over BLE between Android and Garmin FR230 (SDK 1.3.x)

Hi fellow Garmin developers,
I have been trying to develop a direct messaging communication setup over BLE between my Android App and my connectIQ app (on Garmin Forerunner 230, SDK version 1.3.x). The goal here is that the Android app is collecting some data, and then pushing it to the watch app.
Following the details on the developer site, I have managed to get this to work, but there are a lot of dropped messages that don't get sent, and the watch receives fewer values than what is being sent.
On Android, I get this status (ConnectIQ.IQMessageStatus) = FAILURE_DURING_TRANSFER in my debug statements. '240' is the data being sent.
D/GarminMessenger: onMessageStatus: Message: 240, device: Forerunner 230, FAILURE_DURING_TRANSFER
This is my app code on the garmin:
SampleApp.mc
using Toybox.Application as App;
using Toybox.Communications as Comm;
using Toybox.WatchUi as Ui;
using Toybox.System as Sys;
var mailMethod;
var crashOnMessage = false;
var msg;
class SampleApp extends App.AppBase {
function initialize() {
AppBase.initialize();
Sys.println("app-initialize()");
msg = "0";
mailMethod = method(:onMail);
Comm.setMailboxListener(mailMethod);
Sys.println("app-initialize(): mail box listener has been set");
}
// onStart() is called on application start up
function onStart(state) {
System.println("app-onStart()");
}
// Return the initial view of your application here
function getInitialView() {
Sys.println("app-getInitialView()");
return [ new SampleAppView() ];
}
function onMail(mailIter) {
var mail = mailIter.next();
while(mail!=null) {
Sys.println("app-onMail: received - "+mail);
message = mail.toString();
Ui.requestUpdate();
mail = mailIter.next();
}
Comm.emptyMailbox();
}
// onStop() is called when your application is exiting
function onStop(state) {
System.println("app-onStop()");
}
}
class CommListener extends Comm.ConnectionListener {
function initialize() {
Comm.ConnectionListener.initialize();
sys.println("commlistener-initialize");
}
function onComplete() {
Sys.println("commlistener-onComplete: Transmit Complete");
}
function onError() {
Sys.println("commlistener-onError: Transmit Failed");
}
}
Any ideas on what could be causing this issue? I am performing all the necessary checks on the Android side to verify if the Garmin watch is paired and connected (&the app is open).
One reason this could be happening is that I am trying to send 1-2 data values (each with a ConnectIQ.sendMessage()) every second, so perhaps the Garmin device/BLE module does not support communication at that rate?
Thanks in advance for solutions and suggestions.
I think that the Connect messaging system just gets into some broken state and then no messages will go through.
What you could try is to set up the Mailbox listener in onStart method instead of initialize.
Also there is a new method to make the message reading a lot easier. It is still largely undocumented, but I got a word it will be documented with the next SDK release. However, it is already working on every ConnectIQ watch.
The method is:
Comm.registerForPhoneAppMessages(method(:onMsg));
where in your callback method you do:
function onMsg(msg) {
handleIncomingMessage(msg.data.toString());
}
or something similar. The input object msg is of class
Toybox::Communications::Message
probably (this is not documented yet).
So I posted a similar question on the Garmin developer forum here, and got a partial answer to my problem. Posting a summary from there.
What I was hoping to implement was something life the following:
Assuming the messages from Android are 1, 2, 3, 4, 5: I would like the
app to do update the UI as the messages are received, in real-time like this:
app-onMail: received - 1
//update the UI
app-onMail: received - 2
//update the UI
app-onMail: received - 3
//update the UI
app-onMail: received - 4
//update the UI
app-onMail: received - 5
//update the UI
Instead, this happens
app-onMail: received - 1
app-onMail: received - 2
app-onMail: received - 3
app-onMail: received - 4
app-onMail: received - 5
//update the UI
//update the UI
//update the UI
//update the UI
//update the UI
THE ANSWER
The framework polls to see if there are new, unread mail messages. If there are any, it invokes the application onMail() callback which consumes each message from the queue, and repeatedly sets a flag that indicates the UI needs to update. After the call returns, the framework checks the flag to see if the UI needs to be updated, and if so it calls onUpdate() for the active view.
As such, I could only display every message if I send messages from Android at 5sec intervals. I could not find a way to receive and display data at higher rates due to its message polling frequency.
My responder suggested maintaining a queue of mail items (or just a counter) and then handling the mail items between draws, like this:
class MyApp extends App.AppBase
{
hidden var _M_messages;
hidden var _M_count;
function initialize() {
AppBase.initialize();
_M_messages = new [10];
_M_count = 0;
}
function getInitialView() {
return [ new MyView() ];
}
function onStart(params) {
Comm.setMailboxListener(self.method(:onMail));
}
function onStop(params) {
Comm.setMailboxListener(null);
}
function onMail(mailIter) {
var mail = mailIter.next();
while (mail != null) {
// only track up to 10 messages
if (_M_count < 10) {
_M_messages[_M_count] = mail;
++_M_count;
}
else {
break;
}
mail = mailIter.next();
}
Comm.emptyMailbox();
startProcessingMessages();
}
hidden function startProcessingMessages() {
if (_M_timer == null) {
_M_timer = new Timer.Timer();
_M_timer.start(self.method(:processOneMessage), 250, true);
}
}
hidden function stopProcessingMessages() {
if (_M_timer != null) {
_M_timer.stop();
_M_timer = null;
}
}
function getMessageCount() {
return _M_messages;
}
function processOneMessage() {
if (_M_count != 0) {
--_M_count;
var mail = _M_messages[_M_count];
_M_messages[_M_count] = null;
// process the message here
Ui.requestUpdate();
if (_M_count == 0) {
stopProcessingMessages();
}
}
}
}
class MyView extends Ui.View
{
hidden var _M_app;
function initialize(app) {
View.initialize();
_M_app = app;
}
function onUpdate(dc) {
var mailMessages = _M_app.getMessageCount();
// draw the number of mail messages
}
}

Prevent continuous F5 on a web application

This is related with handling the scenario when some crazy user is holding down the F5 key to send unlimited requests to our server.
Our application is very much database and cache intensive and when such consecutive requests come in; our web application is crashing after some time. I know we need to fix the application cache handling and need to add some check at the web server but I am asked to take care of this issue in our code.
I am handling this on both Javascript and server side, but looks like still it is failing, so would like to know if you have any better solution.
My code is as follows:
Javascript Code:
function checkPageRefresh(e) {
e = e || window.event;
ar isPageRefreshed = false;
// detect if user tries to refresh
if ((e.keyCode == 116) /* F5 */ ||
(e.ctrlKey && (e.keyCode == 116)) /* Ctrl-F5 */ ||
(e.ctrlKey && (e.keyCode == 82)) /* Ctrl-R */) {
isPageRefreshed = true;
}
// only trigger special handling for page refresh
if (isPageRefreshed){
var lastRefreshTimeMillis= readCookie("last_refresh");
var currentTimeMillis = new Date().getTime();
// set cookie with now as last refresh time
createCookie(lastRefreshCookieName, currentTimeMillis);
var lastRefreshParsed = parseFloat(lastRefreshTimeMillis, 10);
var timeDiff = currentTimeMillis - lastRefreshParsed;
var F5RefreshTimeLimitMillis = <%=request.getAttribute("F5RefreshTimeLimitMillis")%>;
// if detected last refresh was within 1 second, abort refresh
if (timeDiff < F5RefreshTimeLimitMillis) {
if (e.preventDefault) {
e.preventDefault();
return;
}
}
} // end if (isPageRefreshed)
}
Java Code:
Queue<VisitsInfoHolder> recentlyVisitedUrls = (LinkedList<VisitsInfoHolder>)session.getAttribute(SupportWebKeys.RECENTLY_VISITED_URLS);
String urlBeingCalled = PageUrlUtils.getFullURL(request);
int maxCountOfRecentURLs = 3;
if(null != recentlyVisitedUrls){
//verify if last visit count is matching with the count provided
if(recentlyVisitedUrls.size() >= maxCountOfRecentURLs ) {
int noOfMatchingVisits = 0;
Long firstAccessedTime = 0l;
int count = 0;
for(VisitsInfoHolder urlIno : recentlyVisitedUrls) {
//Store the time stamp of the first record
if(count == 0 && null != urlIno) {
firstAccessedTime = urlIno.getTimeOfTheVisit();
}
count++;
//count how many visits to the current page
if(null != urlIno && null != urlIno.getUrl() && urlIno.getUrl().equalsIgnoreCase(urlBeingCalled)) {
noOfMatchingVisits++;
}
}
if (noOfMatchingVisits >= maxCountOfRecentURLs && (new Date().getTime() - firstAccessedTime) <= 1000){
LOGGER.error(">>>>> Redirecting the client to the warning page.");
VisitsInfoHolder currentVisitInfo = new VisitsInfoHolder(urlBeingCalled,new Date().getTime());
recentlyVisitedUrls.remove();
recentlyVisitedUrls.add(currentVisitInfo);
response.sendRedirect((String)request.getAttribute("F5IssueRedirectPage"));
LOGGER.error(">>>>> Redirected successfully.");
return;
}
else{
VisitsInfoHolder currentVisitInfo = new VisitsInfoHolder(urlBeingCalled,new Date().getTime());
recentlyVisitedUrls.remove();
recentlyVisitedUrls.add(currentVisitInfo);
session.setAttribute(SupportWebKeys.RECENTLY_VISITED_URLS, recentlyVisitedUrls);
}
}
else if (recentlyVisitedUrls.size() < maxCountOfRecentURLs) {
VisitsInfoHolder currentVisitInfo = new VisitsInfoHolder(urlBeingCalled,new Date().getTime());
recentlyVisitedUrls.add(currentVisitInfo);
session.setAttribute(SupportWebKeys.RECENTLY_VISITED_URLS, recentlyVisitedUrls);
}
}
else{
recentlyVisitedUrls = new LinkedList<VisitsInfoHolder>();
VisitsInfoHolder currentVisitInfo = new VisitsInfoHolder(urlBeingCalled,new Date().getTime());
recentlyVisitedUrls.add(currentVisitInfo);
session.setAttribute(SupportWebKeys.RECENTLY_VISITED_URLS, recentlyVisitedUrls);
}
Now I keep holding the F5 button then my Javascript is not understanding that the same key is held for longer time and server side code prints the following 2 loggers
Redirecting the client to the warning page.
Redirected successfully.
But in reality it is not redirecting any single time. I tried adding Thread.sleep(1000) before and after redirect, but still no luck.
Please let me know if you see any issue with my code or let me know if there is any better solution.
When you reproduce this problem are you the only person on your server? Can you reproduce this problem on your local dev instance? If so you really need to fix your server code such that it doesn't crash. You are doing something on your server that is too intensive and needs to be optimized.
Simply intercepting the F5 key on someone's browser is treating the symptoms not the disease. If you are having problems handling a single user hitting F5 really quickly it simply means you'll never be able to scale up to many simultaneous users because that's the exact same request/response pattern as a single user round tripping you with F5.
It's time to break out the profiler and check the timings on how long it takes to process a single request through the system. Then look for hotspots and optimize it. Also watch your memory usage see if you are cleaning things up or if they are growing off into infinity.

Frontend Instance Hours - How can I decrease the usage?

I'm trying to use the free Google App Engine as a backend for Google Cloud Messages for my next Android app but when I have "finished" writing the server it already uses almost 100% of the free frontend instance hours. The question I have is if and how I can improve this?
The application is a servlet that is called every 15 minutes from a cron job, the servlet downloads and parses 3 RSS feeds and checks if anything has changed since the last call, saves the dates to the database (JDO and memcache, 3 calls) to know when the last running was and if any changes have happend since the last call sends that information out the the connected phones, right now 3 phones are connected, it's just one call to Googles servers. No data is returned from the servlet.
Here is the code
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException
{
boolean sendMessage = false;
String eventsFeedUrl = "http://rss.com";
String newsFeedUrl = "http://rss2.com";
String trafficFeedUrl = "http://rss3.com";
response.setContentType("text/plain");
Message.Builder messageBuilder = new Message.Builder();
String messageData = getFeedMessageData(eventsFeedUrl);
if (!messageData.equals(StringUtils.EMPTY))
{
messageBuilder.addData("event", messageData);
sendMessage = true;
}
messageData = getFeedMessageData(newsFeedUrl);
if (!messageData.equals(StringUtils.EMPTY))
{
messageBuilder.addData("news", messageData);
sendMessage = true;
}
messageData = getFeedMessageData(trafficFeedUrl);
if (!messageData.equals(StringUtils.EMPTY))
{
messageBuilder.addData("traffic", messageData);
sendMessage = true;
}
if (sendMessage)
{
sendMessage(messageBuilder.build(), response, debug);
}
}
private void sendMessage(Message message, HttpServletResponse response, boolean debug)
throws IOException
{
SendResult sendResult = GCMService.send(message, Device.list());
int deleteCount = 0;
for (MessageResult errorResult : sendResult.getErrorResults())
{
if (deleteCount < 200 && (errorResult.getErrorName().equals(Constants.ERROR_NOT_REGISTERED) || errorResult.getErrorName().equals(Constants.ERROR_INVALID_REGISTRATION)))
{
Device.delete(errorResult.getDeviceId());
deleteCount++;
}
}
}
private String getFeedMessageData(String feedUrl)
{
String messageData = StringUtils.EMPTY;
FeedHistory history = FeedHistory.getFeedHistoryItem(feedUrl);
Feed feedContent = RssParser.parse(feedUrl);
if (feedContent != null && feedContent.getFeedItems().size() > 0)
{
if (history == null)
{
history = new FeedHistory(feedUrl);
history.setLastDate(new Date(0));
history.save();
}
for (FeedItem item : feedContent.getFeedItems())
{
if (item.getDate().after(history.getLastDate()))
{
messageData += "|" + item.getCountyId();
}
}
if (!messageData.equals(StringUtils.EMPTY))
{
messageData = new SimpleDateFormat("yyyyMMddHHmmssZ").format(history.getLastDate()) + messageData;
}
history.setLastDate(feedContent.getFeedItem(0).getDate());
history.save();
}
return messageData;
}
The call Device.list() uses memcache so after one call it will be cached, the RSS parser is a simple parser that uses org.w3c.dom.NodeList and javax.xml.parsers.DocumentBuilder. According to the log file I use the same instance for days so there are no problems with instances starting up and taking resources. A normal call to the servlet looks like this in the log,
ms=1480 cpu_ms=653 api_cpu_ms=0 cpm_usd=0.019673
I have some ideas of what to try next, try to do the RSS download calls async to minimize the request time. Move the RSS parsing to a backgroud job. What else can be done? It feels like I have done some fundamental errors with my code here because how can a normal web app work if this servlet can't be called 100 times during 24 hours without consuming 100% of the frontend hours.
/Viktor
Your idle instances hang around for a little while before shutting themselves down. I don't know how long this is, but I'm guessing it's somewhere in the 5-15 minute range. If it is in fact 15 minutes, then your cron job hitting it every 15 minutes will keep it alive indefinitely, so you'll end up using 24 instance hours a day.
You can test this theory by setting your cron job to run every 30 minutes, and see if it halves your instance hour usage.

Categories

Resources