Grails Asynchronous Request with HttpSession behaves strangely - grails

I have a simple controller with an action for a long running task and an action for checking a status of the long task:
class AsyncController {
def index() { }
def longTerm() {
session.longTerm = 0
session.longTermDone = false
task {
for (int i; i < 10; i++ ) {
try {
sleep(3000) //NOT WORKING
println " TASK: sessionID ${session.id} value ${session.longTerm++}"
//sleep(3000) //WORKING
} catch (e) {
println(e.class.name)
}
}
session.longTermDone = true
}
render(text: [] as JSON, status: 200)
}
def longTermStatus() {
println "STATUS: sessionID ${session.id} value ${session.longTerm}"
render(text: [successCount: session.longTerm, done: session.longTermDone] as JSON, status: 200)
}
}
In the longTerm action there is a problem with HttpSession.
If the first line of code in the task closure is doing something with HttpSession the rest of the code is working fine. But if the first line is doing something else I get NullPointerException when I try to access session.id
Working code example is at https://github.com/machacekjan/StackOverflowAsyncRequest
Does anyone know why Grails behaves this way?

The issue here appears to be that you're performing the render() outside of the tasks block. If you move render() inside the tasks block, the NullPointerException disappears.
I think this is because the render() finishes the request and you bypass the Servlet 3 Async support. You need to return a promise from the action, which task() does .
Unfortunately, render() doesn't seem to work with the async stuff in Grails 2.3.7 or 2.3.10. But that's a separate issue.

Related

Jenkins timeout/abort exception

We have a Jenkins pipeline script that requests approval from the user after all the preparatory steps are complete, before it actually applies the changes.
We want to add a timeout to this step, so that if there is no input from the user then the build is aborted, and are currently working on using this kind of method:
try {
timeout(time: 30, unit: 'SECONDS') {
userInput = input("Apply changes?")
}
} catch(err) {
def user = err.getCauses()[0].getUser()
if (user.toString == 'SYSTEM') { // if it's system it's a timeout
didTimeout = true
echo "Build timed out at approval step"
} else if (userInput == false) { // if not and input is false it's the user
echo "Build aborted by: [${user}]"
}
}
This code is based on examples found here: https://support.cloudbees.com/hc/en-us/articles/226554067-Pipeline-How-to-add-an-input-step-with-timeout-that-continues-if-timeout-is-reached-using-a-default-value and other places online, but I really dislike catching all errors then working out what's caused the exception using err.getCauses()[0].getUser(). I'd rather explicitly catch(TimeoutException) or something like that.
So my question is, what are the actual exceptions that would be thrown by either the approval step timing out or the userInput being false? I haven't been able to find anything in the docs or Jenkins codebase so far about this.
The exception class they are referring to is org.jenkinsci.plugins.workflow.steps.FlowInterruptedException.
Cannot believe that this is an example provided by CloudBeeds.
Most (or probably all?) other exceptions won't even have the getCauses() method which of course would throw another exception then from within the catch block.
Furthermore as you already mentioned it is not a good idea to just catch all exceptions.
Edit:
By the way: Scrolling further down that post - in the comments - there you'll find an example catching a FlowInterruptedException.
Rather old topic, but it helped me, and I've done some more research on it.
As I figured out, FlowInterruptedException's getCauses()[0] has .getUser() only when class of getCauses()[0] is org.jenkinsci.plugins.workflow.support.steps.input.Rejection. It is so only when timeout occured while input was active. But, if timeout occured not in input, getCause()[0] will contain object of another class: org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution$ExceededTimeout (directly mentioning timeout).
So, I end up with this:
def is_interrupted_by_timeout(org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e, Boolean throw_again=true) {
// if cause is not determined, re-throw exception
try {
def cause = e.getCauses()[0]
def cause_class = cause.getClass()
//echo("cause ${cause} class: ${cause_class}")
if( cause_class == org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution$ExceededTimeout ) {
// strong detection
return true
} else if( cause_class == org.jenkinsci.plugins.workflow.support.steps.input.Rejection ) {
// indirect detection
def user = cause.getUser()
if( user.toString().equals('SYSTEM') ) {
return true
} else {
return false
}
}
} catch(org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException e_access) {
// here, we may deal with situation when restricted methods are not approved:
// show message and Jengins' admin will copy/paste and execute them only once per Jenkins installation.
error('''
To run this job, Jenkins admin needs to approve some Java methods.
There are two possible ways to do this:
1. (better) run this code in Jenkins Console (URL: /script):
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
def scriptApproval = ScriptApproval.get()
scriptApproval.approveSignature('method org.jenkinsci.plugins.workflow.steps.FlowInterruptedException getCauses')
scriptApproval.approveSignature('method org.jenkinsci.plugins.workflow.support.steps.input.Rejection getUser')
scriptApproval.save()
'''.stripIndent())
return null
}
if( throw_again ) {
throw e
} else {
return null
}
}
And now, you may catch it with something like this:
try {
...
} catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException err) {
if( is_interrupted_by_timeout(err) ) {
echo('It is timeout!')
}
}
P.S. I agree, this is bad Jenkins design.

run something async in Grails 2.3

In My Grails service, there is a part of a method I wish to run asynchronously.
Following, the doc for 2.3.x http://grails.org/doc/2.3.0.M1/guide/async.html
I do
public class MyService {
public void myMethod() {
Promise p = task {
// Long running task
}
p.onError { Throwable err ->
println "An error occured ${err.message}"
}
p.onComplete { result ->
println "Promise returned $result"
}
// block until result is called
def result = p.get()
}
}
However, I want to execute mine without any blocking. The p.get() method blocks. How do I execute the promise without any sort of blocking. I don't care if myMethod() returns, it is a kinda of fire and forget method.
So, according to the documentation if you don't call .get() or .waitAll() but rather just make use of onComplete you can run your task without blocking the current thread.
Here is a very silly example that I worked up in the console to as a proof of concept.
import static grails.async.Promises.*
def p = task {
// Long running task
println 'Off to do something now ...'
Thread.sleep(5000)
println '... that took 5 seconds'
return 'the result'
}
p.onError { Throwable err ->
println "An error occured ${err.message}"
}
p.onComplete { result ->
println "Promise returned $result"
}
println 'Just to show some output, and prove the task is running in the background.'
Running the above example gives you the following output:
Off to do something now ...
Just to show some output, and prove the task is running in the background.
... that took 5 seconds
Promise returned the result

Grails sImple chat - Chat messages not displaying on screen

Following is a code fragment obtained from Grails website.
<script>
function messageKeyPress(field,event) {
var theCode = event.keyCode ? event.keyCode : event.which ? event.which : event.charCode;
var message = $('#messageBox').val();
if (theCode == 13){
<g:remoteFunction action="submitMessage" params="\'message=\'+message" update="temp"/>
$('#messageBox').val('');
return false;
} else {
return true;
}
}
function retrieveLatestMessages() {
<g:remoteFunction action="retrieveLatestMessages" update="chatMessages"/>
}
function pollMessages() {
retrieveLatestMessages();
setTimeout('pollMessages()', 5000);
}
pollMessages();
</script>
The above code worked but when i added the Controller it stopped working. I meant that the records gets saved in the DB, but i am not able to retrieve the data and display on screen.
This is what i did
<g:remoteFunction controller="message" action="retrieveLatestMessages" update="chatMessages"/>
The MessageController function is as follows:
#Secured([ 'ROLE_USER'])
def retrieveLatestMessages() {
println "test"
def messages = Message.listOrderByDate(order: 'desc', max:1000)
[messages:messages.reverse()]
println messages
}
The above controller function gets executed (I see the println statements on console), but the data isn't getting populating on the screen.
Can someone help me out here
UPDATE
[{"class":"myPro.Message","id":3,"date":"2014-07-23T17:31:58Z","message":"dfdf","name":"hi"},{"class":"myPro.Message","id":2,"date":"2014-07-23T17:31:56Z","message":"dfdfdf","name":"dd"},{"class":"myPro.Message","id":1,"date":"2014-07-23T17:31:18Z","message":"xxxx","name":"fie"}]
Your method - retrieveLatestMessages() action in your case - must return a model, but it returns the output of println instead.
To make your code work, you must place the model in the last line, or explicitly return it by using return statement:
def retrieveLatestMessages() {
println "test"
def messages = Message.listOrderByDate(order: 'desc', max:1000)
println messages
[messages:messages.reverse()]
}
Try this
import grails.converters.JSON
#Secured([ 'ROLE_USER'])
def retrieveLatestMessages() {
println "test"
def messages = Message.listOrderByDate(order: 'asc', max:1000)
render messages as JSON
}
Enjoy.
I had this sample app working on mine with no issues but here is the thing, this process requires you to poll the page consistently and it is resource intensive:
I ended up writing a domainClass that was bound to a Datasource that was using the HQL db and was outside of my own app, the process requires a DB table to stream chat....
Alternative is to move away from polling and use websockets:
check out this video
https://www.youtube.com/watch?v=8QBdUcFqRkU
Then check out this video
https://www.youtube.com/watch?v=BikL52HYaZg
Finally look at this :
https://github.com/vahidhedayati/grails-websocket-example
This has been updated and includes the 2nd method of using winsocket to make simple chat ....

Using Dispatch Event to check authorization

I am trying to setup a simple API. I have a Controller all other API controllers are extending, and I have attached a dispatch listener like so. I created a test that will always fail. Set the status code to 401, and return a message. However, it's still calling the main Controller method and not abandoning the request from the preDispatch method. Can I build a proper response here and force ZF2 not to continue executing the requested route? I tried just adding an exit() statement, but the client side receives an incomplete response.
protected function attachDefaultListeners()
{
parent::attachDefaultListeners();
$events = $this->getEventManager();
$this->events->attach('dispatch', array($this, 'preDispatch'), 100);
$this->events->attach('dispatch', array($this, 'postDispatch'), -100);
}
public function preDispatch (MvcEvent $e)
{
$this->dm = $this->getServiceLocator()->get('doctrine.documentmanager.odm_default');
// TODO: Check user and token from DB
if (Cookie::isCookieSet())
{
$cookie = Cookie::readCookie();
if (empty($cookie->user))
{
$this->getResponse()->setStatusCode(401);
return new JsonModel(array('auth' => false, 'msg' => 'Try again'));
}
// Cookie not set, if we are authenticating, continue; otherwise return a failure
} else {
}
}
You need to return a response object to short circuit the process, not a ViewModel:
Try something like this:
$response = $this->getResponse();
$response->setStatusCode(401);
$response->setContent(array('auth' => false, 'msg' => 'Try again'));
return $response;

grails webflow error

i have this grails webflow
def currencyDescriptionFlow = {
start {
action {
flow.messageCommand = new MessageCommand()
flow.currencyId = params.id
flow.languageList = Language.findAll([sort: 'codeDescription', order: 'asc'])
}
on('success').to('description')
}
description{
action{
flow.DescriptionMessageList = Message.findAllByKey(flow.currencyId + '_Currency')
}
on('add').to('addSubmit')
on('finish').to('currencyShow')
}
addSubmit{
action {
MessageCommand cmd ->
flow.messageCommand = cmd
if (!flow.messageCommand.validate()){
error()
}
}
on('error').to('description')
on('success').to('description')
}
saveMessage{
action{
def str = flow.currencyId + '_Currency'
messageSevice.descriptionMultiLanguage(str, params.description, params.Language)
}
on('error').to('description')
on('success').to('description')
}
currencyShow{
redirect(controller:'currency', action: "show", id: flow.currencyId)
}
}
when i click the link to redirect to this page, ann error occurs
Error 500: No transition was matched on the event(s) signaled by the [1] action(s)
that executed in this action state 'description' of flow 'message/currencyDescription';
transitions must be defined to handle action result outcomes -- possible flow
configuration error? Note: the eventIds signaled were: 'array<String>
['success']', while the supported set of transitional criteria for this action
state is 'array<TransitionCriteria>[add, finish]'
Servlet: grails
URI: /adm-appserver-manager/grails/message/currencyDescription.dispatch
Exception Message: No transition was matched on the event(s) signaled by the [1]
action(s) that executed in this action state 'description' of flow 'message/currencyDescription';
transitions must be defined to handle action result outcomes -- possible flow
configuration error? Note: the eventIds signaled were: 'array<String>
['success']', while the supported set of transitional criteria for this action state
is 'array<TransitionCriteria>[add, finish]'
Caused by: No transition was matched on the event(s) signaled by the [1] action(s)
that executed in this action state 'description' of flow 'message/currencyDescription';
transitions must be defined to handle action result outcomes -- possible flow
configuration error? Note: the eventIds signaled were: 'array<String>['success']',
while the supported set of transitional criteria for this action state is
'array<TransitionCriteria>[add, finish]'
Class: Unknown
At Line: [-1]
Code Snippet:
Note:
where language is a table in the database
MessageCommand is command object
messageService is a service
descriptionMultiLanguage method in message service for saving message
i try this way of writing code in another action and controoler and it works without any error.
by debugging this code i found the error on (
on('add').to('addSubmit')
on('finish').to('currencyShow')
)
when i remove these 2 lines , no problem found
def currencyDescriptionFlow = {
start {
action {
flow.messageCommand = new MessageCommand()
flow.currencyId = params.id
flow.languageList = Language.findAll([sort: 'codeDescription', order: 'asc'])
flow.DescriptionMessageList = Message.findAllByKey(flow.currencyId + '_Currency')
}
on('success').to('description')
}
description{
on('add').to('addSubmit')
on('finish').to('currencyShow')
}
addSubmit{
action {
MessageCommand cmd ->
flow.messageCommand = cmd
if (!flow.messageCommand.validate()){
error()
}
flow.message = null
}
on('error').to('description')
on('success').to('saveMessage')
}
saveMessage{
action{
Message messageInstance = new Message(language:flow.messageCommand.language,value:flow.messageCommand.value,key:flow.currencyId + '_Currency')
if (!messageInstance.save(flush:true)){
flow.message = "${message(code: 'error.adding.description')}"
error()
}
flow.DescriptionMessageList = Message.findAllByKey(flow.currencyId + '_Currency')
}
on('error').to('description')
on('success').to('description')
}
currencyShow{
redirect(controller:'currency', action: "show", id: flow.currencyId)
}
}

Resources