- Node Cookbook
- David Mark Clements
- 1340字
- 2021-08-13 18:13:55
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>
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:\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 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:\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.
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:\n', postData); response.end('You Posted:\n' + 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.
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:\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 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.
- Mastering macOS Programming
- 零基礎(chǔ)入門學(xué)習(xí)Python
- QGIS By Example
- C語言程序設(shè)計(jì)教程
- Integrating Facebook iOS SDK with Your Application
- 51單片機(jī)C語言開發(fā)教程
- Mastering C++ Multithreading
- Getting Started with Nano Server
- Building Serverless Web Applications
- C陷阱與缺陷
- Learning Python Data Visualization
- HTML并不簡單:Web前端開發(fā)精進(jìn)秘籍
- Java 7 Concurrency Cookbook
- Pandas入門與實(shí)戰(zhàn)應(yīng)用:基于Python的數(shù)據(jù)分析與處理
- INSTANT Apache Maven Starter