- Node Cookbook
- David Mark Clements
- 1335字
- 2021-08-13 18:13:54
Setting up a router
In order to deliver web content we need to make a URI available. This recipe walks us through the creation of an HTTP server that exposes routes to the user.
Getting ready
First, let's create our server file. If our main purpose is to expose server functionality, it's general practice to call the file server.js
, which we could put in a new folder. It's also a good idea to install and use hotnode:
sudo npm -g install hotnode hotnode server.js
Hotnode
will conveniently auto-restart the server when we save changes.
How to do it...
In order to create the server we need the http
module, so let's load it and use the http.createServer
method:
var http = require('http'); http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/html'}); response.end('Woohoo!'); }).listen(8080);
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files emailed directly to you.
Now, if we save our file and access localhost:8080
on a web browser or using curl, our browser (or curl) will exclaim:'Woohoo!'
. However, the same will occur at localhost:8080/foo
. Indeed, any path will render the same behavior, so let's build in some routing. We can use the path
module to extract basename
of the path (the final part of the path), and reverse any URI encoding from the client with decodeURI:
var http = require('http'); var path = require('path'); http.createServer(function (request, response) { var lookup = path.basename(decodeURI(request.url));
We now need a way to define our routes. One option is to use an array of objects:
var pages = [ {route: '', output: 'Woohoo!'}, {route: 'about', output: 'A simple routing with Node example'}, {route: 'another page', output: function() {return 'Here\'s '+this.route;}}, ];
Our pages
array should be placed above the http.createServer
call.
Within our server, we need to loop through our array and see if the lookup variable matches any of our routes. If it does we can supply the output. We'll also implement some 404
handling:
http.createServer(function (request, response) {
var lookup=path.basename(decodeURI(request.url));
pages.forEach(function(page) { if (page.route === lookup) { response.writeHead(200, {'Content-Type': 'text/html'}); response.end(typeof page.output === 'function' ? page.output() : page.output); } }); if (!response.finished) { response.writeHead(404); response.end('Page Not Found!'); }
}).listen(8080);
How it works...
The callback function we provide to http.createServer
gives us all the functionality we need to interact with our server through the request
and response
objects. We use request
to obtain the requested URL and then we acquire its basename
with path
. We also use decodeURI
which our another page
route would fail without as our code would try to match another%20page
against our pages
array and return false
.
Once we have our basename
, we can match it in any way we want. We could send it in a database query to retrieve content, use regular expressions to effectuate partial matches, or we could match it to a file name and load its contents.
We could have used a switch
statement to handle routing but our pages
array has several advantages. It's easier to read and extend, and it can be seamlessly converted to JSON. We loop through our pages
array using forEach
.
Node is built on Google's V8 engine, which provides us with a number of ECMAScript 5 features. These features can't be used in all browsers as they're not yet universally implemented, but using them in Node is no problem! forEach
is an ES5 implementation, but the ES3 way is to use the less convenient for
loop.
While looping through each object, we check its route
property. If we get a match, we write the 200 OK
status and content-type
headers. We then end the response with the object's output property.
response.end
allows us to pass a parameter to it, which it writes just before finishing the response. In response.end
, we used a ternary operator (?:) to conditionally call page.output
as a function or simply pass it as a string. Notice that the another page
route contains a function instead of a string. The function has access to its parent object through the this
variable, and allows for greater flexibility in assembling the output we want to provide. In the event that there is no match in our forEach
loop, response.end
would never be called. Therefore, the client would continue to wait for a response until it times out. To avoid this, we check the response.finished
property and if it's false, we write a 404
header and end the response.
response.finished
depends on the forEach
callback, yet it's not nested within the callback. Callback functions are mostly used for asynchronous operations. So on the surface this looks like a potential race condition, however forEach
does not operate asynchronously. It continues to block until all loops are complete.
There's more...
There are many ways to extend and alter this example. There's also some great non-core modules available that do the leg work for us.
So far, our routing only deals with a single-level path. A multilevel path (for example, /about/node)
will simply return a 404
. We can alter our object to reflect a subdirectory-like structure, remove path
, and use request.url
for our routes instead of path.basename:
var http=require('http'); var pages = [ {route: '/', output: 'Woohoo!'}, {route: '/about/this', output: 'Multilevel routing with Node'}, {route: '/about/node', output: 'Evented I/O for V8 JavaScript.'}, {route: '/another page', output: function () {return 'Here\'s ' + this.route; }} ]; http.createServer(function (request, response) { var lookup = decodeURI(request.url);
Multilevel routing could be taken further, allowing us to build and then traverse a more complex object.
{route: 'about', childRoutes: [ {route: 'node', output: 'Evented I/O for V8 Javascript'}, {route: 'this', output: 'Complex Multilevel Example'} ]}
After the third or fourth level, this object would become a leviathan to look at. We could instead create a helper function to define our routes that essentially pieces our object together for us. Alternatively, we could use one of the excellent non-core routing modules provided by the open source Node community. Excellent solutions already exist which provide helper methods to handle the increasing complexity of scalable multilevel routing (see Routing modules discussed in this chapter andChapter 6, Accelerating Development with Express).
Two other useful core modules are url
and querystring
. The url.parse
method allows two parameters. First the URL string (in our case, this will be request.url)
and second a Boolean parameter named parseQueryString
. If set to true
, it lazy loads the querystring
module, saving us the need to require it, to parse the query into an object. This makes it easy for us to interact with the query portion of a URL.
var http = require('http'); var url = require('url'); var pages = [ {id: '1', route: '', output: 'Woohoo!'}, {id: '2', route: 'about', output: 'A simple routing with Node example'}, {id: '3', route: 'another page', output: function () {return 'Here\'s ' + this.route; }}, ]; http.createServer(function (request, response) { var id = url.parse(decodeURI(request.url), true).query.id; if (id) { pages.forEach(function (page) { if (page.id === id) { response.writeHead(200, {'Content-Type': 'text/html'}); response.end(typeof page.output === 'function' ? page.output() : page.output); } }); } if (!response.finished) { response.writeHead(404); response.end('Page Not Found'); } }).listen(8080);
With the added id
properties we can access our object data by, for instance, localhost:8080?id=2.
There's an up-to-date list of various routing modules for Node at https://www.github.com/joyent/node/wiki/modules#wiki-web-frameworks-routers. These community-made routers cater to various scenarios. It's important to research the activity and maturity of a module before taking it into a production environment. In Chapter 6, Accelerating Development with Express, we will go into greater detail on using the built-in Express/Connect router for more comprehensive routing solutions.
See also
- Serving static files and Securing against filesystem hacking exploits discussed in this chapter
- Dynamic Routing discussed In Chapter 6, Accelerating Development with Express.
- 在最好的年紀學Python:小學生趣味編程
- Apache Spark 2.x Machine Learning Cookbook
- PyTorch Artificial Intelligence Fundamentals
- x86匯編語言:從實模式到保護模式(第2版)
- Java Web開發技術教程
- Java 11 Cookbook
- Oracle 18c 必須掌握的新特性:管理與實戰
- Learning Vaadin 7(Second Edition)
- Python大規模機器學習
- Java EE實用教程
- JBoss AS 7 Development
- 青少年Python趣味編程
- Spring Boot 2+Thymeleaf企業應用實戰
- Eclipse開發(學習筆記)
- Java基礎案例教程(第2版)