Nodejs Book: Chapter 11

With our Nodejs server handling forms, we now have two methods of sending data
from a client to our server. We have both raw JSON data, and multipart form
data. When we send Ajax requests from the clients, we can add a header to
our request to tell Nodejs how to handle the information being sent to it.

var ajax = new XMLHttpRequest();
ajax.open("POST", "/api/test", true);
ajax.setRequestHeader("Content-Type", "application/json");
ajax.send(JSON.stringify({"msg":"hello, world!"}));

ajax.onload = function() {

    console.log(ajax.responseText);

}

We can then use this content-type header to create a generic module for Nodejs.
If the content-header is of type “application/json”, then we know to treat the
included data as JSON, otherwise if the type is “multipart/form-data”, then we
know to treat the incoming body data as a form. We can then make a module that
takes an incoming request and a callback and returns form data and files.

File: handle-body.js

"use strict";

module.exports = function( req, callback ) {

    if(!req || !req.headers) {

        callback( "Empty request or null header", null, []);

    } else if(req.headers["content-type"].indexOf("application/json") !== -1) {

        handleJSON( req, callback );

    } else if(req.headers["content-type"].indexOf("multipart/form-data") !== -1) {

        handleForm( req, callback);

    } else {

        callback("Unknown content-type: " + req.header["content-type"], null, []);

    }

}

function handleJSON( req, callback ) {

    var json_str, json;

    json_str = "";

    req.on("data", function(data) {

        json_str += data;

    });

    req.on("end", function() {

        try {
            json = JSON.parse(json_str);
        } catch(err) {
            return callback("Uploaded data is not JSON encoded:\n" + json_str, null, []);
        }

        callback(null, json, []);

    });

}

function handleForm( req, callback ) {

    var raw_data = [];
    var raw_length = 0;

    req.on("data", function(data) {

        raw_data.push(data);
        raw_length += data.length;

    });

    req.on("end", function() {

        var boundary, i, buf, file, line;
        var buffer = Buffer.concat(raw_data, raw_length);

        var ofs = [];

        let form = {};
        let files = [];

        for (i = 0; i < buffer.length; i++) {

            if (buffer[i] !== 10) {
                continue;
            }

            boundary = buffer.toString("ascii", 0, i - 1);
            break;

        }

        i = 0;
        while (i !== -1) {
            ofs.push(i);
            i = buffer.indexOf(boundary, i + 1, "ascii");
        }

        for (i = 0; i < ofs.length - 1; i++) {

            buf = buffer.slice(ofs[i], ofs[i + 1]);
            boundary = buf.indexOf("\r\n\r\n", 0, "ascii");

            let header = buf.slice(0, boundary).toString("ascii");
            let key = header.match(/name="(.*?)"/)[1];
            let filename = header.match(/filename="(.*?)"/);

            file = buf.slice(boundary + 4);

            if (!filename) {

                form[key] = file.toString("utf8");

            } else {

                files.push({
                    "name": filename[1],
                    "key": key,
                    "data": file
                });

            }

        }

        callback(null, form, files);

    });
}

While means that using this module, we can greatly simplify our code from the
previous chapter.

File: callback.js

"use strict";

const fs = require("fs");
const http = require("http");
const async = require("async");
const uniqid= require("uniqid");
const handleFile = require("handle-file");
const handleBody = require("handle-body");

const server = http.createServer();
server.on("request", handleRequest);
server.listen(8080, handleListen);

function handleRequest(req, res) {

    console.log(req.method);

    if(req.method === "GET") {

        handleFile(req, res);

    } else if(req.method === "POST") {

        handleBody(req, function( err, form, files ) {
            if(err) {
                res.writeHead(500, {"Content-Type" : "text/plain"});
                return res.end("Unable to handle body");
            }

            switch(req.url) {
                case "/api/form":

                    handleFiles( res, files );

                break;
                default:
                    res.writeHead( 204, { "Content-Type" : "text/plain" });
                    res.end( "Empty Response" );
                break;
            }
        });

    } else {

        res.writeHead( 204, { "Content-Type" : "text/plain" });
        res.end( "Empty Response" );

    }

}

function handleListen( ) {

    console.log("Server is listening on port 8080");

}

function handleFiles( res, files ) {

    var array = [];

    async.eachSeries(files, function(file, nextFile) {

        array.push("/img/" + file.name);
        fs.writeFile("public/img/" + file.name, file.data, function(err) {
            if(err) {
                throw err;
            }

            nextFile();
        });

    }, function () {

        res.writeHead(200, { "Content-Type" : "text/plain" });
        res.end(JSON.stringify(array));

    });

}

Now that we are able to handle incoming data from the client, we need a way to
store and manage it. The most common way to accomplish this is to use a readily
available Database server. In the next chapter we will learn how to connect,
write to, and read from a MariaDB database.