Index Next: Advanced HTTP Routing

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:

APIResponse 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:

Source of ~/jaxserver/app/helloworld/views/threeplusthree.jxp:
<%
if (request.params.equals == 6) {
	%>
	<b>Correct!</b>: 3 + 3 = 6
	<%
}
else {
	statusCode(500);
	%>
	Error : 3 + 3 != <%=request.params.equals%>
	<%
}
%>

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:

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');
};

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();
};

File Streaming

Instead of manually performing the responseStream.write() commands, the data can be saved to an external file:

Source of ~/jaxserver/app/helloworld/web/mydata.txt:
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();
	});
};

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');
};

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');
};

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
Next: Advanced HTTP Routing