/ Node.js

<tutorial>RESTFul requests to JasperServer from Node.js. My gruelingly wordy approach.</tutorial>

Note: I ramble a bit at the beginning just to give some insight into my mindset. Skip down to the next title for some actuall how-to.

In a previous post I discussed the difficulties with node-gyp in windows. The reason that I had stumbled upon this issue was because I went to extraordinary efforts to run a piece of Java software from within Node.js to execute a report. It Turns out that the methodologies in there, while neat, were extremely cumbersome and locked up the entire Node.js system. I first thought that I should maybe delegate this service to an independent Node.js server but then realised quickly that it would just be simpler to install JasperServer and host reports from in there.

This has more than one benefit. JasperServer provides a rest api to make calls for specific reports and you can hand parameters over through the call. It integrates well with their report designer called Jaspersoft Studio. You can find the software here. They are free and have been hugely helpful in my work to date.

Step one is going to be install JasperServer. Once it's up and running try to access it from the following url: http://localhost:8080/jasperserver. The default username and password is jasperadmin. (Yes, both username and password)
I'm going to go ahead and assume that you're all logged in and the default system is up and running. Next we're going to want to get a feel for their REST api before we start losing our minds in Node.js with calls not working properly. I say this because I pulled every last hair out in confusion when starting.

Curl

This is an amazing tool that is useful for debugging REST requests. There is a bit of a learning curve involved but it'll definitely help us make sure that we are forming our REST calls properly and will let us test the API from the command line without actually needing to write any code. This saves us debugging later.

Firstly we need to authenticate against the server. Open a command prompt and try the following command:

curl -X POST --user "user:pass" -d "j_username=jasperadmin&j_password=jasperadmin" -c bCookies http://localhost:8080/jasperserver/rest/login

This command is posting (-X POST) a x-www-form-encoded request (--user "user:pass") to the server with the parameters j_username and j_password. (denoted by the -d flag) the -c flag tells curl to store the cookie into a file called bCookies. You can examine the bCookies file and you will see that our JSESSIONID. If the variable is set then congratulations! Your server is responding properly.

Next we're going to try to save one of the default reports using this cookie and the REST api. It's a little weird to look at because we need to use /reports/reports in our URL. This confused me immensely at first.

curl -X GET -o safeFile.pdf -b bCookies http://localhost:8080/jasperserver/rest_v2/reports/reports/samples/Cascading_multi_select_report.pdf

This command is getting (-X GET) a file and is saving it as saveFile.pdf (-o saveFile.pdf) and is using the cookie we created earlier (-b bCookies). You should see a little download run and if you open up your saveFile.pdf you will see a small pdf report that was returned to us. If you've got this far then you're well on your way to implementing a rest call in Node!

Node and Express

I'm going to assume that you all know how to set up a Node + Express application.

express myNewApp
cd myNewApp && npm install

and all that jazz.

We're going to set up exactly 1 route to handle 1 report. This will show that we can connect to our JasperServer and request a report properly. From there I would definitely suggest architecting your routes and code / configuration files to effectively handle reports for your application. This is outside the scope of our discussion though.

In the routes/index.js file, add another route to your router.
I used something grossly obvious in :

...
router.get('/api/reports/Cascading_multi_select', function(req, res){

});

We expect a parameter to be passed as well with the format that we want the file back in. We will add support for both pdf and xlsx.

...
router.get('/api/reports/Cascading_multi_select', function(req, res){
	var format = req.param('format');
    if(!format){
    	res.send(400, 'BAD REQUEST');
        return;
    }
    else{
    	if(!(format === 'pdf') && !(format === 'xlsx')){
        	res.send(415, 'Unsupported Media Type');
            return;
        }
    }
    
});

This will ensure that we get the variables that we require and that we are capable of returning the requested format. Next we're going to want to prepare and send a http call. We will be using Node's http request and querystring. You'll need to install querystring in order to load it in. npm install querystring

var express = require('express');
var router = require('router');
var http = require('http');
var querystring = require('querystring');
...

router.get('/api/reports/Cascading_multi_select', function(req, res){
	var format = req.param('format');
    if(!format){
    	res.send(400, 'BAD REQUEST');
        return;
    }
    else{
    	if(!(format === 'pdf') && !(format === 'xlsx')){
        	res.send(415, 'Unsupported Media Type');
            return;
        }
    }
    //the post data to get the cookie for the report
    var data = querystring.stringify({
    	j_username:'jasperadmin',
        j_password:'jasperadmin'
    });
    //options for the request.
    var options = {
    	host:'localhost',
        port:8080,
        path:'/jasperserver/rest/login',
        method:'POST',
        headers:{
        	'Content-Type':'application/x-www-form-urlencoded',
            'Content-Length':Buffer.byteLength(data)
        }
    };
    
    var req = http.request(options, function(resp){
    	resp.setEncoding('utf8');
        cookie = resp.headers['set-cookie'];
        console.log(cookie);
    });
    req.write(data);
    req.end();
});
...

If you did this properly, you will be logging out the value of the cookie that came from the authentication request to Jasper. We are going to use this cookie in the next section to make a request for one of the sample reports. We will do this by nesting a second request in the first one and passing the cookie back to Jasper again.

...
router.get('/api/reports/Cascading_multi_select', function(req, res){
	var format = req.param('format');
	if(!format){
		res.send(400, 'BAD REQUEST');
		return;
	}
	else{
		if(!(format === 'pdf') && !(format === 'xlsx')){
			res.send(415, 'Unsupported Media Type');
			return;
		}
	}
	var data = querystring.stringify({
		j_username:'jasperadmin',
		j_password:'jasperadmin'
	});
	var options = {
		host:'localhost',
		port:8080,
		path:'/jasperserver/rest/login',
		method:'POST',
		headers:{
			'Content-Type':'application/x-www-form-urlencoded',
			'Content-Length':Buffer.byteLength(data)
		}
	};

	var req = http.request(options, function(resp){
		resp.setEncoding('utf8');
		cookie = resp.headers['set-cookie'];
		console.log(cookie);		
		var newOptions = {
			host:'localhost',
			port:8080,
			'path':'/jasperserver/rest_v2/reports/reports/samples/Cascading_multi_select_report'+'.'+format,
			'headers':{
				'Cookie':cookie
			}
		};

		var newReq = http.request(newOptions, function(resp){
			var chunks = [];
			resp.on('data', function(chunk){
				chunks.push(chunk);
			})
			resp.on('end', function(){
				if(format=='pdf')
					res.setHeader('Content-Type','application/pdf');
				if(format == 'xlsx')
					res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
				var buffer = Buffer.concat(chunks);
                res.setHeader('Content-Length', buffer.length);
				res.send(buffer);
			});
		});

		newReq.write('');
		newReq.end();

	});

req.write(data);
req.end();

});
...

This should work just fine for what we're trying to accomplish for now!
It's not the most elegant way to run reports and I'm sure you can see that there are a lot of areas where code may be duplicated when it doesn't need to be.
There is quite the possibility here to write some middleware! I may get on that myself.

Happy coding!

P.S. if you're super new to express, you probably need to tell the app to listen still. in the app.js file in your root, just before module.exports = app; add

app.listen(3000, function(){
	console.log('listening on port 3000');
};