Difference between revisions of "Node.JS"

From CSE330 Wiki
Jump to navigationJump to search
(Adding npm stuff)
 
(38 intermediate revisions by 7 users not shown)
Line 1: Line 1:
AJAX is a useful technology for web applications that need to pull periodic updates from your server.  But what do you do when you have thousands of clients all requesting data that changes real-time?  A PHP+AJAX approach would choke.  It is situations like these that Node.JS comes to the rescue.
+
AJAX is a useful technology for web applications that need to pull periodic updates from your server.  But what do you do when you have thousands of clients all requesting data that changes real-time?  A PHP+AJAX approach could choke without a lot of effort on your part.  It is situations like these that Node.js comes to the rescue. Node.js is a web framework, made in JavaScript, that's become extremely popular over the last few years.
 +
 
 +
In general, JavaScript frameworks are very trendy right now; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/, that "tracks" how long it's been since a new framework has come out.
  
 
== Non-Blocking I/O ==
 
== Non-Blocking I/O ==
  
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module.  Node.JS uses this execution model at its core.
+
You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module.  Node.js uses this execution model at its core.
  
 
The core idea is the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations.  Consider the pseudo-code:
 
The core idea is the use of '''''anonymous functions''''' as ''callbacks'' for processing the results of intensive operations.  Consider the pseudo-code:
Line 26: Line 28:
 
</source>
 
</source>
  
The revised pseudo-code achieves the same end result as the first version.  However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''  The code occurs in two parts: first, we start the query.  Second, ''when the query is complete'', our '''anonymous callback function''' is executed.  In Node.JS, callback functions are usually passed one or more parameters.
+
The revised pseudo-code achieves the same end result as the first version.  However, ''it allows other clients to perform tasks while this process is waiting for the database to be queried!''  The code occurs in two parts: first, we start the query.  Second, ''when the query is complete'', our '''anonymous callback function''' is executed.  In Node.js, callback functions are usually passed one or more parameters.
  
 
Better yet, with non-blocking I/O, you have no concurrency issues, because everything runs in just one thread.  Nifty!
 
Better yet, with non-blocking I/O, you have no concurrency issues, because everything runs in just one thread.  Nifty!
  
== Installing Node.JS ==
+
== Installing Node.js ==
  
For a super easy way to install Node.JS on Linux, execute the following one-liner on your server.
+
Run the following commands to install Node and NPM:
  
 
<source lang="bash">
 
<source lang="bash">
sudo env PATH=$PATH HOME=/root sh -c "curl https://raw.github.com/isaacs/nave/master/nave.sh | bash -s usemain stable && curl https://npmjs.org/install.sh | sh && npm install -g nave"; source ~/.bashrc
+
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
 +
-
 +
$ source ~/.bashrc
 +
-
 +
$ nvm install 16
 
</source>
 
</source>
  
To make sure it worked, open up a JavaScript prompt by typing `node`.  A Hello World JavaScript could be: <code>console.log("Hello World");</code>
 
  
For more information on the above one-liner, see the blog post at http://codrspace.com/vote539/installing-node-on-a-clean-os/
+
<!-- The unofficial instructions for installing Node.js can be found here: https://gist.github.com/isaacs/579814
 +
 
 +
There are several choices, but my favorite is probably ''use-nave-no-shell.sh''.
 +
 
 +
The instructions at the above link have a couple bugs.  If you run this sequence of commands, Node and NPM will install correctly:
 +
 
 +
<source lang="bash">
 +
$ mkdir ~/.nave
 +
$ cd ~/.nave
 +
$ wget http://github.com/isaacs/nave/raw/master/nave.sh
 +
$ sudo ln -s $PWD/nave.sh /usr/local/bin/nave
 +
$ chmod a+x nave.sh
 +
$ sudo chown -R $USER:$USER /usr/local
 +
$ nave usemain stable
 +
$ curl -L https://www.npmjs.org/install.sh | sh
 +
</source>
  
To install Node.JS on other operating systems, see this page: http://howtonode.org/how-to-install-nodejs
+
-->
  
== Your First Node.JS Application: Hello World ==
+
== Your First Node.js Application: Hello World ==
  
 
Let's try running the example application given on the front page of nodejs.org:
 
Let's try running the example application given on the front page of nodejs.org:
  
 
<source lang="javascript">
 
<source lang="javascript">
var http = require('http');
+
const http = require('http');
 
http.createServer(function (req, res) {
 
http.createServer(function (req, res) {
 
res.writeHead(200, {
 
res.writeHead(200, {
Line 55: Line 75:
 
});
 
});
 
res.end('Hello World\n');
 
res.end('Hello World\n');
}).listen(1337);
+
}).listen(3456);
console.log('Server running at http://localhost:1337/');
+
console.log('Server running at http://localhost:3456/');
 
</source>
 
</source>
  
Paste the example into a file named `hello.js`, and then run it by calling `node hello.js`.  You should now be able to see your Hello World by visiting your server at port 1337!  (Press ^C to stop the application.)
+
Paste the example into a file named `hello.js`, and then run it by calling `node hello.js`.  You should now be able to see your Hello World by visiting your server at port 3456!  (Press ^C to stop the application.) '''Note:''' If you're running the code from your EC2 instance, you will need to open port 3456.
  
 
Let's walk through each line in the above example.
 
Let's walk through each line in the above example.
  
* <code>var http = require('http');</code> enables us to use Node's built-in HTTP server.  We've been spoiled that languages like PHP automatically load all of the libraries that we need.  However, '''''most functionality in Node.JS is NOT loaded by default.'''''  When you want to use any non-fundamental feature of Node, you need to ''require'' it into a variable, as this example does with the HTTP package.  You should be familiar with this concept when you needed to use ArrayList and other specialty packages in Java in CSE 132.
+
* <code>var http = require('http');</code> enables us to use Node's built-in HTTP server.  We've been spoiled that languages like PHP automatically load all of the libraries that we need.  However, '''''most functionality in Node.js is NOT loaded by default.'''''  When you want to use any non-fundamental feature of Node, you need to ''require'' it into a variable, as this example does with the HTTP package.  You should be familiar with this concept when you needed to use ArrayList and other specialty packages in Java in CSE 132.
* <code>http.createServer(function (req, res) { ... }).listen(1337);</code> uses the HTTP server API to listen for HTTP connections on port 1337.  The anonymous callback function will be called whenever a connection is made.
+
* <code>http.createServer(function (req, res) { ... }).listen(3456);</code> uses the HTTP server API to listen for HTTP connections on port 3456.  The anonymous callback function will be called whenever a connection is made.
 
* <code>res.writeHead(200, { 'Content-Type': 'text/plain' });</code> specifies the Content-Type header to be "text/plain".
 
* <code>res.writeHead(200, { 'Content-Type': 'text/plain' });</code> specifies the Content-Type header to be "text/plain".
 
* <code>res.end('Hello World\n');</code> writes "Hello World" to the response.
 
* <code>res.end('Hello World\n');</code> writes "Hello World" to the response.
Line 70: Line 90:
 
Note the use of non-blocking I/O, even in this one little example.  ''Whenever a connection is made, the callback function will be called and passed parameters associated with that connection.''
 
Note the use of non-blocking I/O, even in this one little example.  ''Whenever a connection is made, the callback function will be called and passed parameters associated with that connection.''
  
== A Static Fileserver Using Node.JS ==
+
== A Static File Server Using Node.js ==
  
Node.JS does not automatically serve up static files in the way that an Apache/PHP does.  We need to either use an extension that handles this for us, like Express, or we need to write our own code to make a simple static fileserver.  The latter option is really not all that difficult, and it gives good insight into how a static fileserver works.
+
Node.js does not automatically serve up static files in the way that an Apache/PHP does.  We need to either use an extension that handles this for us, like Express, or we need to write our own code to make a simple static file server.  The latter option is really not all that difficult, and it gives good insight into how a static file server works.
  
 
=== Installing Packages ===
 
=== Installing Packages ===
  
In order for the example code below to work, you need to install the ''mime'' package.  To do this, '''cd to the working directory where you will save your Node.JS JavaScript file''' and run:
+
In order for the example code below to work, you need to install the ''mime'' package.  To do this, '''cd to the working directory where you will save your Node.js JavaScript file''' and run:
  
<source lang="bash">$ npm install mime</source>
+
<source lang="bash">$ npm install mime@3.0.0 --save</source>
  
'''npm''' is the package manager for Node.JS.  Unlike all of the package managers we've used up to this point, ''npm'' installs dependencies locally, ''not'' globally.  This means that you can have two different projects using different versions of the same package.  ''npm'' will create a ''node_modules'' directory in the CWD and will install the packages into it.
+
''npm'' is the package manager for Node.js.  Unlike all of the package managers we've used up to this point, ''npm'' installs dependencies locally, ''not'' globally.  This means that you can have two different projects using different versions of the same package.  ''npm'' will create a ''node_modules'' directory in the CWD and will install the packages into it.
 +
 
 +
'''Note:''' When using npm to install local dependencies, '''''do not use sudo'''''.  It will screw up your file permissions.
  
 
=== Static Fileserver Code ===
 
=== Static Fileserver Code ===
  
 
The following code will make an HTTP server on port 3456 that serves up all static files in <STATIC DIRECTORY NAME> relative to app.js.
 
The following code will make an HTTP server on port 3456 that serves up all static files in <STATIC DIRECTORY NAME> relative to app.js.
 +
 +
'''Make an attempt to understand this code.'''  It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.
  
 
<source lang="javascript">
 
<source lang="javascript">
Line 90: Line 114:
 
var http = require('http'),
 
var http = require('http'),
 
url = require('url'),
 
url = require('url'),
path = require('path'),
 
 
mime = require('mime'),
 
mime = require('mime'),
 
path = require('path'),
 
path = require('path'),
Line 113: Line 136:
 
 
 
// File exists and is readable
 
// File exists and is readable
var mimetype = mime.lookup(filename);
+
var mimetype = mime.getType(filename);
 
resp.writeHead(200, {
 
resp.writeHead(200, {
 
"Content-Type": mimetype
 
"Content-Type": mimetype
Line 137: Line 160:
 
=== Port 3456 ===
 
=== Port 3456 ===
  
We are running our server on port 3456 as opposed to the default web server port, which of course is 80.  We therefore need to open our Node.JS application like this:
+
We are running our server on port 3456 as opposed to the default web server port, which of course is 80.  We therefore need to open our Node.js application like this:
  
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/
+
http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension
  
 
However, this won't work on the first time.  ''Why?'' (Try to figure this out on your own before reading the next paragraph.)
 
However, this won't work on the first time.  ''Why?'' (Try to figure this out on your own before reading the next paragraph.)
Line 150: Line 173:
 
# Under "Inbound", create a custom TCP rule for port 3456, and apply your changes.
 
# Under "Inbound", create a custom TCP rule for port 3456, and apply your changes.
  
== Node.JS Security ==
+
== Node.js Security ==
  
 
Just because we're writing in JavaScript doesn't mean that we can forget about the security practices you've been learning in the last three months of CSE 330.  XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.
 
Just because we're writing in JavaScript doesn't mean that we can forget about the security practices you've been learning in the last three months of CSE 330.  XSS is still a problem, as is the use of ''eval'', CSRF attacks, and so on.
  
Here is a good slideshow introducing Node.JS as well as some security vulnerabilities to look out for: http://www.slideshare.net/ASF-WS/asfws-2012-nodejs-security-old-vulnerabilities-in-new-dresses-par-sven-vetsch
+
The two links below have good information about security vulnerabilities specific to Node.js that you should think about when crafting your application.
 +
 
 +
# Book from PragProg ($24, 2015): https://pragprog.com/book/kdnodesec/secure-your-node-js-web-application
 +
# Slideshow from a Switzerland Conference on Application Security (2012): http://www.slideshare.net/ASF-WS/asfws-2012-nodejs-security-old-vulnerabilities-in-new-dresses-par-sven-vetsch
  
== Node.JS Conventions ==
+
== Node.js Conventions ==
  
 
There ought to be a standard way to save metadata about your app, right?  Well, ''npm'' introduces a straightforward way to do this in the form of a ''package.json'' file.
 
There ought to be a standard way to save metadata about your app, right?  Well, ''npm'' introduces a straightforward way to do this in the form of a ''package.json'' file.
Line 165: Line 191:
 
$ npm init
 
$ npm init
 
</source>
 
</source>
 +
 +
One nifty feature of ''package.json'' is the way it automatically handles dependencies.  For example, you can put the following lines in your ''package.json'':
 +
 +
<source lang="javascript">
 +
  "dependencies": {
 +
    "mime": "~2.4.6",
 +
    "socket.io": "~1.0.0"
 +
  },
 +
</source>
 +
 +
Then, back on the command line, when you run
 +
 +
<source lang="bash">
 +
$ npm install
 +
</source>
 +
 +
it automatically installs the dependencies for you!  No more headaches of trying to figure out which modules you need to install; all that information is saved for you in your code base.
 +
 +
'''Tip:''' Once you set up your ''package.json'' file, you can automatically add a dependency to it by using the ''--save'' argument on ''npm install'': <code>npm install --save socket.io</code>
  
 
[[Category:Module 7]]
 
[[Category:Module 7]]

Latest revision as of 23:01, 22 March 2024

AJAX is a useful technology for web applications that need to pull periodic updates from your server. But what do you do when you have thousands of clients all requesting data that changes real-time? A PHP+AJAX approach could choke without a lot of effort on your part. It is situations like these that Node.js comes to the rescue. Node.js is a web framework, made in JavaScript, that's become extremely popular over the last few years.

In general, JavaScript frameworks are very trendy right now; it can be hard to keep up with the new cool thing. In fact, there's a joke website https://dayssincelastjavascriptframework.com/, that "tracks" how long it's been since a new framework has come out.

Non-Blocking I/O

You dabbled in the concept of non-blocking I/O when you implemented AJAX in the previous JavaScript module. Node.js uses this execution model at its core.

The core idea is the use of anonymous functions as callbacks for processing the results of intensive operations. Consider the pseudo-code:

// Blocking I/O Pseudo-Code

var data = database.query("select * from news");
print data[0].title;

The above pseudo-code queries a database, saves the result of the query in a variable named data, and then prints out the first title in data. The problem arises when multiple clients all want to access the server at the same time. In a blocking I/O model, a client needs to wait until all other clients are finished before it can start its process. With intensive operations and thousands of clients, this can easily crash your server!

The solution is to use non-blocking I/O. Consider this revised pseudo-code:

// Non-Blocking I/O Pseudo-Code

database.query("select * from news", function(data){
	print data[0].title;
});

The revised pseudo-code achieves the same end result as the first version. However, it allows other clients to perform tasks while this process is waiting for the database to be queried! The code occurs in two parts: first, we start the query. Second, when the query is complete, our anonymous callback function is executed. In Node.js, callback functions are usually passed one or more parameters.

Better yet, with non-blocking I/O, you have no concurrency issues, because everything runs in just one thread. Nifty!

Installing Node.js

Run the following commands to install Node and NPM:

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
-
$ source ~/.bashrc
-
$ nvm install 16


Your First Node.js Application: Hello World

Let's try running the example application given on the front page of nodejs.org:

const http = require('http');
http.createServer(function (req, res) {
	res.writeHead(200, {
		'Content-Type': 'text/plain'
	});
	res.end('Hello World\n');
}).listen(3456);
console.log('Server running at http://localhost:3456/');

Paste the example into a file named `hello.js`, and then run it by calling `node hello.js`. You should now be able to see your Hello World by visiting your server at port 3456! (Press ^C to stop the application.) Note: If you're running the code from your EC2 instance, you will need to open port 3456.

Let's walk through each line in the above example.

  • var http = require('http'); enables us to use Node's built-in HTTP server. We've been spoiled that languages like PHP automatically load all of the libraries that we need. However, most functionality in Node.js is NOT loaded by default. When you want to use any non-fundamental feature of Node, you need to require it into a variable, as this example does with the HTTP package. You should be familiar with this concept when you needed to use ArrayList and other specialty packages in Java in CSE 132.
  • http.createServer(function (req, res) { ... }).listen(3456); uses the HTTP server API to listen for HTTP connections on port 3456. The anonymous callback function will be called whenever a connection is made.
  • res.writeHead(200, { 'Content-Type': 'text/plain' }); specifies the Content-Type header to be "text/plain".
  • res.end('Hello World\n'); writes "Hello World" to the response.

Note the use of non-blocking I/O, even in this one little example. Whenever a connection is made, the callback function will be called and passed parameters associated with that connection.

A Static File Server Using Node.js

Node.js does not automatically serve up static files in the way that an Apache/PHP does. We need to either use an extension that handles this for us, like Express, or we need to write our own code to make a simple static file server. The latter option is really not all that difficult, and it gives good insight into how a static file server works.

Installing Packages

In order for the example code below to work, you need to install the mime package. To do this, cd to the working directory where you will save your Node.js JavaScript file and run:

$ npm install mime@3.0.0 --save

npm is the package manager for Node.js. Unlike all of the package managers we've used up to this point, npm installs dependencies locally, not globally. This means that you can have two different projects using different versions of the same package. npm will create a node_modules directory in the CWD and will install the packages into it.

Note: When using npm to install local dependencies, do not use sudo. It will screw up your file permissions.

Static Fileserver Code

The following code will make an HTTP server on port 3456 that serves up all static files in <STATIC DIRECTORY NAME> relative to app.js.

Make an attempt to understand this code. It's important that you are comfortable with the non-blocking nature, as well as how we handle file I/O.

// Require the functionality we need to use:
var http = require('http'),
	url = require('url'),
	mime = require('mime'),
	path = require('path'),
	fs = require('fs');

// Make a simple fileserver for all of our static content.
// Everything underneath <STATIC DIRECTORY NAME> will be served.
var app = http.createServer(function(req, resp){
	var filename = path.join(__dirname, "<STATIC DIRECTORY NAME>", url.parse(req.url).pathname);
	(fs.exists || path.exists)(filename, function(exists){
		if (exists) {
			fs.readFile(filename, function(err, data){
				if (err) {
					// File exists but is not readable (permissions issue?)
					resp.writeHead(500, {
						"Content-Type": "text/plain"
					});
					resp.write("Internal server error: could not read file");
					resp.end();
					return;
				}
				
				// File exists and is readable
				var mimetype = mime.getType(filename);
				resp.writeHead(200, {
					"Content-Type": mimetype
				});
				resp.write(data);
				resp.end();
				return;
			});
		}else{
			// File does not exist
			resp.writeHead(404, {
				"Content-Type": "text/plain"
			});
			resp.write("Requested file not found: "+filename);
			resp.end();
			return;
		}
	});
});
app.listen(3456);

Port 3456

We are running our server on port 3456 as opposed to the default web server port, which of course is 80. We therefore need to open our Node.js application like this:

http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com:3456/path.extension

However, this won't work on the first time. Why? (Try to figure this out on your own before reading the next paragraph.)

We need to open port 3456 on our Amazon EC2 instance. Remember when you opened port 80 way back in Module 2? We need to do the same sort of procedure again now.

  1. Log into your EC2 management console
  2. Go to "Security Groups"
  3. Select your security group from Module 2
  4. Under "Inbound", create a custom TCP rule for port 3456, and apply your changes.

Node.js Security

Just because we're writing in JavaScript doesn't mean that we can forget about the security practices you've been learning in the last three months of CSE 330. XSS is still a problem, as is the use of eval, CSRF attacks, and so on.

The two links below have good information about security vulnerabilities specific to Node.js that you should think about when crafting your application.

  1. Book from PragProg ($24, 2015): https://pragprog.com/book/kdnodesec/secure-your-node-js-web-application
  2. Slideshow from a Switzerland Conference on Application Security (2012): http://www.slideshare.net/ASF-WS/asfws-2012-nodejs-security-old-vulnerabilities-in-new-dresses-par-sven-vetsch

Node.js Conventions

There ought to be a standard way to save metadata about your app, right? Well, npm introduces a straightforward way to do this in the form of a package.json file.

To make your npm file with a step-by-step prompt, run in your project root directory:

$ npm init

One nifty feature of package.json is the way it automatically handles dependencies. For example, you can put the following lines in your package.json:

  "dependencies": {
    "mime": "~2.4.6",
    "socket.io": "~1.0.0"
  },

Then, back on the command line, when you run

$ npm install

it automatically installs the dependencies for you! No more headaches of trying to figure out which modules you need to install; all that information is saved for you in your code base.

Tip: Once you set up your package.json file, you can automatically add a dependency to it by using the --save argument on npm install: npm install --save socket.io