- Node Cookbook(Second Edition)
- David Mark Clements
- 1404字
- 2021-07-16 12:04:29
Processing POST data
If we want to be able to receive POST data, we have to instruct our server 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. Contrariwise, 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
, that contains the following:
<form method=post> <input type=text name=userinput1><br> <input type=text name=userinput2><br> <input type=submit> </form>
For our purposes, we'll place form.html
in the same folder as server.js
. Though this is not generally a recommended practice, we should usually 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
to serve through createServer
, as follows:
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. But if we fill out our form, nothing happens because we need to handle POST
requests, as follows:
if (request.method === "POST") { var postData = ''; request.on('data', function (chunk) { postData += chunk; }).on('end', function() { console.log('User Posted:\n' + postData); response.end('You Posted:\n' + postData); }); }
Once the form is completed and submitted, the browser and console will output the raw query string sent from the client. Converting our postData
variable 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:\n', postData); response.end('You Posted:\n' + 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.
Note
Protecting a POST server
V8 (and therefore Node) has virtual memory limitations based upon the processor architecture and operating system constraints. If we don't restrict the amount of data our POST server accepts, 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 as follows:
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.destroy(); 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:\n', postData); response.end('You Posted:\n' + util.inspect(postDataObject)); }); //rest of our code....
How it works...
Once we know that a POST
request has been made for 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 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).
We then 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 on, we can perform any number of interesting activities, such as manipulate, analyze, and pass it to a database. However, for the example, we simply output postDataObject
to the browser and postData
to the console.
In Chapter 5, Employing Streams, we'll look into ways to constrain the actual stream input to a maximum size and abort if that size is exceeded by using the post-Node 0.8 extra stream API's.
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.
Connect is an excellent middleware framework for Node, providing a method framework that assimilates a higher level of abstraction for common server tasks. The body-parser module is actually part of the Express web framework (which was inspired by, and originally built on top of Connect). We will be discussing Express in detail in Chapter 7, Accelerating Development with Express.
By chaining body-parser
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). The request.body
object turns out to be exactly the same object as the postDataObject
we generated in our recipe.
First, let's make sure we have Connect and body-parser installed by executing the following commands:
npm install connect npm install body-parser
We can require connect
in place of http
, since it provides us with 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 as parameters to the createServer
method. The following code shows how to implement similar behavior to what is as seen in the recipe, using Connect:
var connect = require('connect'); var bodyParser = require('body-parser'); var util = require('util'); var form = require('fs').readFileSync('form.html'); connect(connect.limit('2mb'), bodyParser(),function (request, response) { if (request.method === "POST") { console.log('User Posted:\n', request.body); response.end('You Posted:\n' + util.inspect(request.body)); } if (request.method === "GET") { response.writeHead(200, {'Content-Type': 'text/html'}); response.end(form); } }).listen(8080);
Notice that we are no longer using the http
module directly. We pass connect.limit
as our first parameter to achieve the same maxData
restriction implemented in the main example.
Next, we pass in the bodyParser
method, 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 the console and browser. This is where we deviate slightly from our original recipe.
In the recipe, we return the raw postData
variable to the console, but, here we return the request.body
object. 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 trade off 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 Read-Eval-Print-Loop (REPL), which is the Node command-line environment. In REPL, we can write the following:
console.log(require('body-parser').toString());
If we look at the output, we'll see its connect.bodyParser
function code and should be able to easily identify the similarities between the connect.bodyParser
code and our main recipe's code.
- Advanced Quantitative Finance with C++
- Intel Galileo Essentials
- Unity 2020 Mobile Game Development
- NativeScript for Angular Mobile Development
- Mastering Unity Shaders and Effects
- Learning Network Forensics
- C語(yǔ)言程序設(shè)計(jì)
- Highcharts Cookbook
- Everyday Data Structures
- 多媒體技術(shù)及應(yīng)用
- Java EE實(shí)用教程
- 產(chǎn)品架構(gòu)評(píng)估原理與方法
- Raspberry Pi開(kāi)發(fā)實(shí)戰(zhàn)
- JSP程序設(shè)計(jì)與案例教程
- Python輕松學(xué):爬蟲(chóng)、游戲與架站