Advanced HTTP Responses
JaxServer Response API
In the previous chapter, the jax.server.sendText and jax.server.sendHTML functions were used to send HTTP responses. Here is the complete list of built-in response functions:
| API | Response Type |
|---|---|
| jax.server.sendText( HttpRequest request, HttpResponse response, String text ) | text/plain |
| jax.server.sendText( HttpRequest request, HttpResponse response, String html, Object headers ) | text/plain |
| jax.server.sendHTML( HttpRequest request, HttpResponse response, String text ) | text/html |
| jax.server.sendHTML( HttpRequest request, HttpResponse response, String html, Object headers ) | text/html |
| jax.server.sendError( HttpRequest request, HttpResponse response, Number httpCode, String message ) | HTML error message |
| jax.server.sendError( HttpRequest request, HttpResponse response, Number httpCode, Error object ) | HTML object inspection (for debugging) |
| jax.server.sendJSON( HttpRequest request, HttpResponse response, Object json, Object headers, Boolean allowCallback ) | application/json |
| jax.server.sendNotModified( HttpRequest request, HttpResponse response ) | 304 Not Modified |
| jax.server.sendRedirect( HttpRequest request, HttpResponse response, String URL ) | HTTP 302 Redirect |
| jax.server.sendFile( HttpRequest request, HttpResponse response, String path, Object headers, Boolean cache ) | Buffered file |
| jax.server.streamFile( HttpRequest request, HttpResponse response, String path, Object headers ) | Streamed file |
| jax.server.sendResponse( Object view ) | custom response |
| jax.server.streamResponse( Object view ) | custom streamed response |
NodeJS HTTP Responses
In addition to the JaxServer function, the NodeJS HTTP API can be used directly:
function Helloworld() {
//...
this.get('/noderesponse','nodeResponse');
}
//...
Helloworld.prototype.nodeResponse = function(request, response) {
response.writeHead(200, {
'Content-Type' : 'text/plain'
});
response.write(request.method+' '+request.url+'\n\n');
response.end();
};
When visiting http://localhost:8000/noderesponse?mynumbers=123&mytext=abc&mybool=true the response should be:
GET /dataresponse?mynumbers=123&mytext=abc&mybool=true
JaxServer HTTP Responses
JaxServer's built-in jax.server.sendResponse function is a almost exactly the same as the NodeJS API, it wraps all the response information in a single JavaScript object literal:
function Helloworld() {
//...
this.get('/customresponse','customResponse');
}
Helloworld.prototype.customResponse = function(request, response) {
jax.server.sendResponse({
request : request,
response : response,
statusCode : 200,
header : {
'Content-Type' : 'text/plain'
},
body : request.method+' '+request.url+'\n\n'+
JSON.stringify(request.params,null,4)
});
};
View the result at: http://localhost:8000/customresponse?mynumbers=123&mytext=abc&mybool=true
The reason to use this alternate form rather the raw NodeJS API is additional handling is provided by JaxServer including logging, cookies and session management.
The statusCode refers to the HTTP code number. See the HTTP 1.1 Specification for a complete list of available code numbers.
It is important to know that the request.params object will be convert all numbers, true, and false parameters to their associated JavaScript types Number or Boolean. This is a JaxServer convention. If this data needs to remain a string, use .toString() method to convert them back to strings:
request.params.mynumbers.toString(); // 123 becomes "123" request.params.mybool.toString() // true becomes "true"
Custom Error Responses
By changing the statusCode sent to jax.server.sendError a customizable error page can be sent to the browser. The following example sends a custom HTTP 500 Internal Server Error message to the browser.
function Helloworld() {
//...
this.get('/oneplusone','oneplusone');
}
//...
Helloworld.prototype.oneplusone = function(request, response) {
if (request.params.equals == 2) jax.server.sendHTML(request,response,"Correct!: 1 + 1 = 2");
else {
jax.server.sendResponse({
request : request,
response : response,
statusCode : 500,
header : {
'Content-Type' : 'text/html'
},
body : "Error : 1 + 1 != "+request.params.equals,
});
}
}
The error status code can be confirmed using a curl command:
curl -I -X GET http://localhost:8000/oneplusone?equals=3
JaxServer Error Responses
JaxServer has a convenient built-in function called jax.server.sendError for quickly sending error responses. Using this function will reduce the amount of code required to send error messages.
function Helloworld() {
//...
this.get('/twoplustwo','twoplustwo');
}
//...
Helloworld.prototype.twoplustwo = function(request, response) {
if (request.params.equals == 4) jax.server.sendHTML(request,response,"Correct!: 2 + 2 = 4");
else jax.server.sendError(request, response, 500, {message:"Error : 2 + 2 != "+request.params.equals});
}
JXP Error Views
Another way to handle error messages is to delegate this functionality to a JXP view:
function Helloworld() {
//...
this.get('/threeplusthree','threeplusthree');
}
//...
Helloworld.prototype.threeplusthree = function(request, response) {
this.sendView(request,response,'threeplusthree');
}
The JXP API function statusCode() the HTTP response status can be changed from within a JXP template:
<%
if (request.params.equals == 6) {
%>
<b>Correct!</b>: 3 + 3 = 6
<%
}
else {
statusCode(500);
%>
Error : 3 + 3 != <%=request.params.equals%>
<%
}
%>
- Success: http://localhost:8000/threeplusthree?equals=6
- Error: http://localhost:8000/threeplusthree?equals=3
JSON Responses
Just as jax.server.sendHTML sends an text/html response, jax.server.sendJSON sends an application/json response. Although there is one additional parameter for specifying the HTTP status code:
function Helloworld() {
//...
this.get('/json','json');
}
//...
Helloworld.prototype.json = function(request, response) {
var json = {
array : [1,2,3],
number : 123,
letters : "abc",
yes : true,
no : false
};
jax.server.sendJSON(request, response, 200, json, null, true);
};
Test JSON: http://localhost:8000/json
sendJSON has some built-in power features such as auto-converting to XML and specifying a JSON-P callback:
- XML: http://localhost:8000/json?format=xml&wrap=response
- JSON-P: http://localhost:8000/json?callback=mycallback
Redirect Responses
HTTP redirection can be performed using the jax.server.sendRedirect function. This function is preferred only when redirecting to an external website, or an absolute URL:
function Helloworld() {
//...
this.get('/external','redirectExternal');
}
//...
Helloworld.prototype.redirectExternal = function(request, response) {
jax.server.sendRedirect(request,response,'http://jaxcore.com');
};
- Redirect: http://localhost:8000/external
The jax.server.Controller also contains a redirect method. This method is preferred when redirecting to a URL that is handled by the same controller:
function Helloworld() {
//...
this.get('/one','one');
this.get('/two','two');
}
//...
Helloworld.prototype.one = function(request, response) {
this.redirect(request,response,'/two');
};
Helloworld.prototype.two = function(request, response) {
jax.server.sendHTML(request, response, 'this is two');
};
Visiting http://localhost:8000/one will redirect the browser to http://localhost:8000/two
Streaming Responses
NodeJS includes flexible and easy-to-use file and network streaming API's. When a web application needs to send a large file, instead of reading the entire file into memory before sending the response it can stream the data to the client in real-time.
First task is to set up a streamable response in JaxServer. This pattern can be reused in many ways depending on where the data is being read from (file, network, or database):
function Helloworld() {
//...
this.get('/streamdata','streamResponse');
}
//...
Helloworld.prototype.streamResponse = function(request, response) {
// create a streamResponse
var responseStream = jax.server.streamResponse({
request : request,
response : response,
header : {
'Content-Type':'text/plain'
}
});
// write to the stream as many times as needed
responseStream.write("this is my data 1\n");
responseStream.write("this is my data 2\n");
responseStream.write("this is my data 3\n");
responseStream.write("this is my data 4\n");
responseStream.write("this is my data 5\n");
// end the stream to complete the response
responseStream.end();
};
- Test streaming: http://localhost:8000/streamdata
File Streaming
Instead of manually performing the responseStream.write() commands, the data can be saved to an external file:
this is my data 1 this is my data 2 this is my data 3 this is my data 4 this is my data 5
Then the file can then be streamed into memory and immediately written to the responseStream:
function Helloworld() {
//...
this.get('/streamfile','streamFile');
}
Helloworld.prototype.streamFile = function(request, response) {
// create response stream
var responseStream = jax.server.streamResponse({
request : request,
response : response,
header : {
'Content-Type':'text/plain'
}
});
// create NodeJS read stream to read the data file
var stream = system.fs.createReadStream(this.webRoot+'/mydata.txt', {
flags : 'r',
mode : 0444,
bufferSize : 4096
});
stream.setEncoding('utf8');
stream.addListener("data", function(data){
// perform response writes here
responseStream.write(data);
});
stream.addListener('end', function(e) {
// close the response stream to end the HTTP response
responseStream.end();
});
};
- Test file streaming: http://localhost:8000/streamfile
Binary Files
Binary files can be handled in a similar way to the streaming example above by modifying the streams to handle binary data instead of text data. But there is an even easier method built into JaxServer using the jax.server.sendFile function:
function Helloworld() {
//...
this.get('/sendimage','sendImage');
}
//...
Helloworld.prototype.sendImage = function(request, response) {
jax.server.sendFile(request, response, this.webRoot+'/hubble.jpg');
};
- Test binary file sending: http://localhost:8000/sendimage
However, jax.server.sendFile does not stream the file. Therefore it should only be used for sending small to medium sized files (less than 100KB). For larger files, use jax.server.streamFile:
function Helloworld() {
//...
this.get('/streamimage','streamImage');
}
//...
Helloworld.prototype.streamImage = function(request, response) {
jax.server.streamFile(request, response, this.webRoot+'/hubble.jpg');
};
- Test binary file streaming: http://localhost:8000/streamimage
jax.server.sendFile and jax.server.streamFile can be used for both text and binary data.
Gzipped Responses
Instead of sending responses in plain text, HTML, CSS, or JavaScript, gzip compression can be used to reduce the amount of data that needs to be sent to the web browser with no perceptible difference to the user. Gzip deflation is part of the HTTP 1.1 and is supported by all modern web browsers. In most cases this will reduce the amount of data that needs to be sent by 50% or more. Although broadband internet connections are commonplace today, the need to reduce file sizes is even more important now due to the number of mobile devices connected to the internet over slow wireless cell networks.
Consider the following text data with a list of countries:
countries.txt (2783 bytes)
That same file can be gzip-compressed using the following command line operation:
gzip -9 -c countries.txt > countries.txt.gz
The resulting file is 47% smaller:
countries.txt.gz (1502 bytes)
When an HTTP request is sent for the data files, the gzipped version of that data can be sent in place of the raw text data. To do this, the controller's handler method must check if the web browser has gzip support by reading the request's accept-encoding HTTP header flag and by sending the response with a corresponding content-encoding HTTP header flag:
function Helloworld() {
//...
this.get('/countries','countriesGzip');
}
//...
Helloworld.prototype.countriesGzip = function(request, response) {
// check request"s accept-encoding
if (request.headers['accept-encoding'] && request.headers['accept-encoding'].indexOf('gzip')>-1) {
// read gzip compressed file as binary data
system.fs.readFile(this.webRoot+'/countries.txt.gz', 'binary', function(err, data) {
jax.server.sendResponse({
request : request,
response : response,
header : {
'Content-Type' : 'text/plain', // tells the browser this file is a plain text file
'Content-Encoding' : 'gzip' // tells the browser this file is gzip encoded
},
body : data,
encoding : 'binary' // tells jaxserver to send the response as binary data
});
});
}
else {
// read text file as utf text data
system.fs.readFile(this.webRoot+'/countries.txt', 'utf8', function(err, data) {
// send response as plain text
jax.server.sendText(request, response, data);
});
}
};
Test plain text response:
curl -X GET http://localhost:8000/countries
Test gzip response:
curl -X GET http://localhost:8000/countries -H "accept-encoding: gzip" | gzip -d | cat
Dynamic Gzip Responses
Instead of gzipping a data file ahead of time, it can be performed dynamically, immediately before sending the response using a gzip property in the call to jax.server.sendResponse:
function Helloworld() {
//...
this.get('/countriesdynamic','countriesGzipDynamic');
}
//...
Helloworld.prototype.countriesGzipDynamic = function(request, response) {
system.fs.readFile(this.webRoot+'/countries.txt', 'utf8', function(err, data) {
// if gzip is supported dynamically gzip the response
if (request.headers['accept-encoding'] && request.headers['accept-encoding'].indexOf('gzip')>-1) {
jax.server.sendResponse({
request : request,
response : response,
header : {
'Content-Type' : 'text/plain',
},
body : data,
gzip : true // tells jaxserver to dynamically gzip this data
});
}
// otherwise send response as plain text
else {
jax.server.sendText(request, response, data);
}
});
};
Test plain text response:
curl -X GET http://localhost:8000/countriesdynamic
Test gzip response:
curl -X GET http://localhost:8000/countriesdynamic -H "accept-encoding: gzip" | gzip -d | cat
Dynamic Gzip Streaming
Almost identical to the streaming response example above, gzip compression can be enabled for streamed responses with the same gzip property but this time applied to the jax.server.sendResponse function:
function Helloworld() {
//...
this.get('/countriesstream','countriesGzipStream');
}
//...
Helloworld.prototype.countriesGzipStream = function(request, response) {
var responseStream = jax.server.streamResponse({
request : request,
response : response,
header : {
'Content-Type':'text/plain'
},
gzip : true // tells jaxserver to dynamically gzip this data stream
});
var stream = system.fs.createReadStream(this.webRoot+'/countries.txt', {
flags : 'r',
mode : 0444,
bufferSize : 4096
});
stream.setEncoding('utf8');
stream.addListener("data", function(data){
responseStream.write(data);
});
stream.addListener('end', function(e) {
responseStream.end();
});
}
Test plain text response:
curl -X GET http://localhost:8000/countriesstream
Test gzip response:
curl -X GET http://localhost:8000/countriesstream -H "accept-encoding: gzip" | gzip -d | cat