Processing POST data

If we want to be able to receive POST data, we have to instruct our server on how to accept and handle a POST request. In PHP we could access our POST values seamlessly with $_POST['fieldname'], because it would block until an array value was filled. By contrast, Node provides low-level interaction with the flow of HTTP data allowing us to interface with the incoming message body as a stream, leaving it entirely up to the developer to turn that stream into usable data.

Getting ready

Let's create a server.js file ready for our code, and an HTML file called form.html, containing the following code:

<form method=post>
  <input type=text name=userinput1><br>
  <input type=text name=userinput2><br>
  <input type=submit>
</form>

Tip

For our purposes, we'll place form.html in the same folder as server.js, though this is not generally a recommended practice. Usually, we should place our public code in a separate folder from our server code.

How to do it...

We'll provision our server for both GET and POST requests. Let's start with GET by requiring the http module and loading form.html for serving through createServer:

var http = require('http'),
var form = require('fs').readFileSync('form.html'),
http.createServer(function (request, response) {
  if (request.method === "GET") {
    response.writeHead(200, {'Content-Type': 'text/html'});
    response.end(form);
  }
}).listen(8080);

We are synchronously loading form.html at initialization time instead of accessing the disk on each request. If we navigate to localhost:8080, we'll be presented with a form. However, if we fill out our form nothing happens because we need to handle POST requests:

  if (request.method === "POST") {
  	var postData = '';
request.on('data', function (chunk) {
    		postData += chunk;
 	}).on('end', function() {
 	   console.log('User Posted:
' + postData);
  	   response.end('You Posted:
' + postData);
});
  }

Once the form is completed and submitted, the browser and console will output the raw query string sent from the client. Converting postData into an object provides an easy way to interact with and manipulate the submitted information. The querystring module has a parse method which transforms query strings into objects, and since form submission arrives in query string format, we can use it to objectify our data as follows:

var http = require('http'),
var querystring = require('querystring'),
var util = require('util'),
var form = require('fs').readFileSync('form.html'),

http.createServer(function (request, response) {
  if (request.method === "POST") {
    var postData = '';
    request.on('data', function (chunk) {
      postData += chunk;
    }).on('end', function () {
      var postDataObject = querystring.parse(postData);
      console.log('User Posted:
', postData);
      response.end('You Posted:
' + util.inspect(postDataObject));
    });

  }
  if (request.method === "GET") {
    response.writeHead(200, {'Content-Type': 'text/html'});
    response.end(form);
  }
}).listen(8080);

Notice the util module. We require it to use its inspect method for a simple way to output our postDataObject to the browser.

Finally, we're going to protect our server from memory overload exploits.

Tip

Protecting a POST server

V8 (and therefore Node) has virtual memory limitations, based upon the processor architecture and operating system constraints. These limitations far exceed the demands of most use cases. Nevertheless, if we don't restrict the amount of data our POST server will accept, we could leave ourselves open for a type of Denial of Service attack. Without protection, an extremely large POST request could cause our server to slow down significantly or even crash.

To achieve this, we'll set a variable for the maximum acceptable data size and check it against the growing length of our postData variable.

var http = require('http'),
var querystring = require('querystring'),
var util = require('util'),
var form = require('fs').readFileSync('form.html'),
var maxData = 2 * 1024 * 1024; //2mb
http.createServer(function (request, response) {
  if (request.method === "POST") {
    var postData = '';
    request.on('data', function (chunk) {
      postData += chunk;
      if (postData.length > maxData) {
        postData = '';
        this.pause();
        response.writeHead(413); // Request Entity Too Large
        response.end('Too large'),
      }
    }).on('end', function () {
      if (!postData) { response.end(); return; } //prevents empty post requests from crashing the server
      var postDataObject = querystring.parse(postData);

      console.log('User Posted:
', postData);

      response.end('You Posted:
' + util.inspect(postDataObject));

    });
//rest of our code....

How it works...

Once we know a POST request has been made of our server (by checking request.method), we aggregate our incoming data into our postData variable via the data event listener on the request object. However, if we find that the submitted data exceeds our maxData limit, we will clear our postData variable and pause the incoming stream preventing any further data arriving from the client. Using stream.destroy instead of stream.pause seems to interfere with our response mechanism. Once a stream has been paused for a while it is automatically removed from memory by v8's garbage collector.

Then we send a 413 Request Entity Too Large HTTP header. In the end event listener, as long as postData hasn't been cleared for exceeding maxData (or wasn't blank in the first place), we use querystring.parse to turn our POST message body into an object. From this point, we could perform any number of interesting activities: manipulate, analyze, pass it to a database, and so on. However, for the example, we simply output postDataObject to the browser and postData to the console.

There's more...

If we want our code to look a little more elegant, and we're not so concerned about handling POST data as a stream, we can employ a user land (non-core) module to get a little sugar on our syntax.

Accessing POST data with connect.bodyParser

Connect is an excellent middleware framework for Node providing a method framework that assimilates a higher level of abstraction for common server tasks. Connect is actually the basis of the Express web framework, which will be discussed In Chapter 6, Accelerating Development with Express

One piece of middleware that comes bundled with Connect is bodyParser. By chaining connect.bodyParser to a normal callback function, we suddenly have access to the POST data via request.body (when data is sent by the POST request it is held in the message body). request.body turns out to be exactly the same object as postDataObject we generated in our recipe.

First, let's make sure we have Connect installed:

npm install connect

We require connect in place of http since it provides us with the createServer capabilities. To access the createServer method, we can use connect.createServer, or the shorthand version, which is simply connect. Connect allows us to combine multiple pieces of middleware together by passing them in as parameters to the createServer method. Here's how to implement similar behavior, as in the recipe using Connect:

var connect = require('connect'),
var util = require('util'),
var form = require('fs').readFileSync('form.html'),
connect(connect.limit('64kb'), connect.bodyParser(),
  function (request, response) {
    if (request.method === "POST") {
      console.log('User Posted:
', request.body);
      response.end('You Posted:
' + util.inspect(request.body));
    }
    if (request.method === "GET") {
      response.writeHead(200, {'Content-Type': 'text/html'});
      response.end(form);
    }
  }).listen(8080);

Notice we are no longer using the http module directly. We pass connect.limit in as our first parameter to achieve the same maxData restriction implemented in the main example.

Next, we pass in bodyParser, allowing connect to retrieve our POST data for us, objectifying the data into request.body. Finally, there's our callback function, with all the former POST functionality stripped out except the code to echo our data object (which is now request.body) to console and browser. This is where we deviate slightly from our original recipe.

In the recipe we return the raw postData to the console, though we return the request.body object here. To output raw data with Connect would either take pointless deconstruction of our object to reassemble the raw query string or an extension of the bodyParser function. This is the tradeoff with using third-party modules: we can only easily interact with information the module author expects us to interact with.

Let's look under the hood for a moment. If we fire up an instance of node without any arguments, we can access the REPL (Read-Eval-Print-Loop) which is the Node command-line environment. In the REPL, we can write:

console.log(require('connect').bodyParser.toString());

If we look at the output, we'll see its connect.bodyParser function code and should be able to easily identify the essential elements from our recipe at work in the connect.bodyParser code.

See also

  • Handling file uploads discussed in this chapter
  • Browser-server transmission via AJAX discussed In Chapter 3, Working with Data Serialization
  • Initializing and using a session discussed In Chapter 6, Accelerating Development with Express
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.105.137