My previous step towards implementing a minimal WebGL viewer node server showed how to render 3D geometric POST data using Swig.
Due to various self-created difficulties, I switched the rendering engine to Swig after struggling with initial attempts using Express Handlebars.
Now that those difficulties have been solved, I thought I would show how to switch back to the handlebars support and prove that I use either one.
They cannot be used in parallel, though, since the rendering engine is defined and set up when starting the server.
I implemented code to switch between the two by just toggling a single Boolean variable and then restarting the server.
Before getting to that, I also added more functionality to my interactive testing form below that immediately brought up a new little problem to resolve.
Finally, when I thought everything was wrapped up and set to go, I discovered and fixed an unexpected problem with the version number I was trying to use:
Before starting to add support for using the handlebars rendering engine in parallel with swig and defining a new REST API version to drive it, I added support for the different hard-wired wall geometry data definitions that I set up by exporting 3D element geometry from Revit, an architectural desktop app, to the WebGL viewer.
It worked fine for the simple wall, but the straight wall with a more complex opening generated an error saying 'request entity too large'.
Happily, I am not the first person to encounter that, and the solution was easy to find, e.g. from the Stack Overflow thread on large body for node.js express bodyParser.
Just as suggested there, I set and thereby raised the limit on the size of the POST request data to 1MB like this:
var bodyParser = require('body-parser'); // ... app.use(bodyParser.json({limit: '1mb'})); app.use(bodyParser.urlencoded({limit: '1mb'}));
That fixed it.
As explained in the discussion on synchronising a Heroku repository with GitHub, I can commit the changes to the source code and update the running Heroku-hasted server with these couple of command-line commands:
$ git add server.js $ git commit -m "raised body parser limit to 1mb to fix error: request entity too large" $ git push git master $ git push heroku master
Comfy and effective.
The updated node server version with a 1 MB POST data limit is release 1.0.0.5.
I already mentioned how to install Express Handlebars, and now repeated that step again:
$ npm install express-handlebars --save
I also added the same lines as before to the node server mainline to install it as a renderer for files with a handlebars
filename extension:
var handlebars = require('express-handlebars'); app.engine('handlebars', handlebars()); app.set('view engine', 'handlebars');
Miraculously enough, that is the only code change required.
I also have to set up a handlebars view template for it to render from.
Again, miraculously, I can simply use my existing swig template, completely unchanged:
$ cp views/viewer.swig views/viewer.handlebars
The entire exploration on how to handle JavaScript in the view template file and transfer data to it remains valid and unchanged for the new engine.
Therefore, I could actually also set up the system to use the same extension for both engines and avoid copying the file at all, or create a link. I simply copied it, though, to highlight the fact that these are two different engines, even if they have a lot in common.
Wow. I was not expecting this to be so simple.
The entire node.js server still consists of three main pieces:
The mainline implementation in server.js now includes a hard-coded switch to toggle between the two rendering engines:
var express = require('express'); var app = express(); app.set('port', (process.env.PORT || 5000)); app.use(express.static(__dirname + '/public')); app.get('/', function(req, res) {}); // leads to public/index.html var bodyParser = require('body-parser'); app.use( bodyParser.json({ limit: '1mb' }) ); app.use( bodyParser.urlencoded({ extended: true, limit: '1mb' }) ); app.set('views', __dirname + '/views'); var use_swig_render = false; if( use_swig_render ) { var swig = require('swig'); app.engine('swig', swig.renderFile); app.set('view engine', 'swig'); swig.setDefaults({ cache: false }); app.set('view cache', false); } else { var handlebars = require('express-handlebars'); app.engine('handlebars', handlebars()); app.set('view engine', 'handlebars'); } var api = require('./routes/api'); app.get('/api/v1', api.v1); app.post('/api/v1', api.v1); app.get('/api/v2', api.v2); app.post('/api/v2', api.v2); app.listen(app.get('port'), function() { console.log('Node WebGL app with ' + (use_swig_render ? 'swig' : 'handlebars') + ' is running at localhost:' + app.get('port')); });
Calling the server with no API route appended is handled and leads to the static index.html provided in the public folder.
The REST API is accessed through the routes api/v1
and api/v2
, which both respond to both GET and POST requests.
The trivial v1 API remains unchanged from the initial implementation, so the previous original test client and blog post describing it continue to run unmodified and unaffected.
A GET request to the REST API v2 again just replies with an explanatory message.
The POST request to the v2 API finally brings up the data driven WebGL viewer by rendering the view template file, which is either views/viewer.swig
or views/viewer.handlebars
, depending on which engine the server was started up with:
exports.v1 = function(req, res) { if (req.method == 'GET') { res.send('API v1 GET: Hello World!'); } else if (req.method == 'POST') { res.send('API v1 POST: ' + JSON.stringify(req.body)); } }; exports.v2 = function(req, res) { if (req.method == 'GET') { res.send('API v2 GET: Here belongs a succinct ' + 'explanation how to use The Building Coder ' + 'WebGL Viewer REST API v2...'); } else if (req.method == 'POST') { console.log('API v2 POST: ' + JSON.stringify(req.body)); res.render('viewer', req.body); } };
Note that the only line of code that has anything whatsoever to do with the rendering process in the call to res.render
.
Both it and the viewer template file are completely identical and unchanged for either of the two engines, swig or handlebars.
Cool, huh?
I updated the interactive online testing framework yet a bit more again for this new step.
Below is one single HTML form for testing the current version v2
of the API using both GET and POST, depending on which button you click.
I updated and restarted the Heroku node server to run the handlebars rendering engine instead of swig.
The form needs a couple of JavaScript helper functions to:
Here they are:
// to switch between heroku and localhost, // toggle the Boolean 'local' variable below function get_base_url_0017() { var local = false; return local ? 'http://localhost:5000' : 'https://nameless-harbor-7576.herokuapp.com'; } function get_api_url_0017() { var api_route = '/api/v2'; return get_base_url_0017() + api_route; } function open_node_server_window_0017() { var w = 400; var h = 400; var features = 'width=' + w.toString() + ',height=' + h.toString(); window.open(get_api_url_0017(), 'node_server_0017', features); } window.onload = function(e) { var baseurl = get_base_url_0017(); var apiurl = get_api_url_0017(); document.getElementById('form_0017').action = apiurl; document.getElementById('iframe_0017').src = baseurl; } function set_wall_data_0017(geometry_data_option) { var arrays_cube = { position: [ 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1, -1, 1, -1, 1, 1, 1, 1, 1, 1, 1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1], normal: [ 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1], texcoord: [1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1], indices: [0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23] }; var arrays_wall_simple = { position: [-0.04, -0.8, 1, -0.04, 0.8, 1, -0.04, ], // ... normal: [-1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, ], // ... indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ] // ... }; var arrays_wall_straight = { position: [-0.4, 0.02, -0.19, -0.13, -0.37, -0.05, ], // ... normal: [-0.47, 0, 0.88, -0.47, 0, 0.88, -0.47, 0, ], // ... indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, ] // ... }; var arrays_wall_curved_facets = { position: [-0.49, 0.95, 0.05, -0.57, 0.95, 0.07, ], // ... normal: [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, ], // ... indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ] // ... }; var arrays_wall_curved_smooth = { position: [-0.49, 0.95, 0.05, -0.57, 0.95, 0.07, ], // ... normal: [0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, ], // ... indices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ] // ... }; var arrays_nil = { position: [], normal: [], indices: [] }; var arrays_list = [ arrays_cube, arrays_wall_simple, arrays_wall_straight, arrays_wall_curved_facets, arrays_wall_curved_smooth, arrays_nil]; var arrays = arrays_list[parseInt(geometry_data_option)]; document.getElementById('position').value = arrays['position'].join(', '); document.getElementById('normal').value = arrays['normal'].join(', '); document.getElementById('indices').value = arrays['indices'].join(', '); }
The open_node_server_window_0017
function is called from the submit button onclick event before the form routes the node server response to it through the form action
– set by the JavaScript code above on window load – and target
attributes.
The HTML form presents a choice between the hard-coded test geometry data sets and also enables you to manually specify your own.
Here is the form source:
<form method="post" id="form_0017" target="node_server_0017"> <p>Populate with hard-wired wall geometry data: <select name="walldata" onchange="set_wall_data_0017(this.form.walldata.value)"> <option value="0">Cube</option> <option value="1">Simple</option> <option value="2">Straight</option> <option value="3">Curved Facets</option> <option value="4">Curved Smooth</option> <option value="5">Nil – Clear All</option> </select></p> <table> <tr><td colspan="2">... or enter your own data:</td></tr> <tr><td>position:</td><td><input type="text" id="position" name="position" value="1, 1, -1, 1, 1, 1, . . ."></td></tr> <tr><td>normal:</td><td><input type="text" id="normal" name="normal" value="1, 0, 0, 1, 0, 0, . . ."></td></tr> <tr><td>indices:</td><td><input type="text" id="indices" name="indices" value="0, 1, 2, 0, 2, 3, . . ."></td></tr> <tr><td></td><td> <input value="Get" type="submit" onclick="this.form.method='get';open_node_server_window_0017()"> <input value="Post" type="submit" onclick="this.form.method='post';open_node_server_window_0017('post')"> </td></tr> </table> </form>
Here is the form submitting the REST API HTTP POST request to the Node.js server using the Swig or Handlebars engine, respectively, to drive the 3D WebGL geometry viewer for you to try out live:
You can use the same form to test the result of calling the same API with the HTTP GET verb instead of POST – as in the past, it just responds with a simple explanatory text message.
As always, the original non-API-driven and the preceding API version v1 still work exactly as before; I am continuing to enhance the REST API step by step while preserving complete upward compatibility and keeping the original server running continuously at the same time.
The non-API version looks like this:
I had finished all my local testing and updated the GitHub repo.
Final step: update on Heroku and relaunch.
That failed.
Snooping around in the command line messages generated by the command git push heroku master
turned up the following error:
remote: npm ERR! Invalid version: "1.0.0.5"
I had changed the version number in my package.json file to match what I was using for my GitHub releases, "1.0.0.5".
Previously, it was "0.1.3".
I am used to using the four-number major.minor.build.revision or major.minor.maintenance.build convention common for desktop products.
Apparently, npm requires a three-position version string and throws and error on a four-position one.
I changed the NodeWebGL version number to "0.2.6" and had better luck.
Phew.
Up and running both locally and on Heroku again.
Obviously, changed my GitHub version numbering system to match.
The complete node server implementation is available from the NodeWebGL GitHub repo, and the version discussed here is release 0.2.6.