-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Node.js Getting Started Lab -+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ As a lab, you can help each other. But each has to have these working on their own laptop. ================================================================================ Task 1 - REPL ================================================================================ Run node within a terminal window. This will take you into a Read-Eval-Print-Loop (REPL) interface in which you can enter JavaScript commands. Run a few test lines: $ node > let x = "Hello world"; > console.log(x); > for (let i=0; i<10; i++) { console.log(x);} When in REPL, you can type control-d (End of file) to quit node. REPL is always available and useful if you want to try out something in JavaScript or Node. ================================================================================ Task 2 - Hello World ================================================================================ It is a traditional milestone with any new language to create a "Hello World" program. Node is just JavaScript. So to have it print "Hello World", create a simple program (name it helloworld.js) with one line: console.log("Hello World"); You run Node programs via the "node" command node helloworld.js (or simply: node helloworld) The program should execute and print "Hello World" to your terminal window. ***Show a TA that you have completed this task. (All these tasks can be shown at once when completed.) ================================================================================ Task 3 - Sum Calculator ================================================================================ Create a "sum" node program that will take a variable number of parameters and print their sum to the console. To find out how command line arguments are handled in Node, check out the documentation: http://nodejs.org/api/process.html#process_process_argv It is a good idea to bookmark the API documentation. The main page is: http://nodejs.org/api/ Run with tests such as: node sum.js 3 45 52 22 78 ***Show a TA that you have completed this task ================================================================================ Task 4 - List files in a directory - i.e. ls (aka dir) command ================================================================================ Find the file system module documentation at http://nodejs.org/api/fs.html You will see there is a large list of available methods, both synchronous (blocking) and asynchronous (non-blocking). Focus on only using the asynchronous ones. A simple program to list the files in the current directory would be: let fs = require("fs"); // include the file system module fs.readdir(".", // read the current directory "." function(err,files) { // a callback for when the directory read is complete if (err) { // asynch method errors are returned as an argument console.error(err); // print the error to stderr (on the console) process.exit(1); // exit the program with an error code } files.forEach(function(file) { // for each element in the files array console.log(file); // print it to the console }) ; } ); Part of why Node is very interesting is the performance it gains from being event driven. Node does not use threads, it is non-blocking and uses events extensively. So operations that might take some time (e.g. reading a file from the filesystem, reading a TCP stream, writing to a database) are done asynchronously. That is, the operations are initiated, and callbacks are used when the operation is complete. The above simple program demonstrates this pattern. The readdir method initiates the file system directory read operation, and when it is complete, the anonymous function is called. This fuction has two arguments, an error (in case there was one), and the returned data. In this case the data is the list of file names. Exceptions don't make sense with this form of asynchronous functions. Once the operation has been initiated, then the original code completes execution and leaves any try/catch block. There is no code to catch the exception. Node uses the convention with many asynchronous methods of having the first argument to the callback function be the error if there was one. If error == false, there was no error. Therefore callback functions should first check that there was no error before doing normal processing. TASK: First get the above program working. Then extend it by having it take an optional command line argument of a directory path (not a file path) and list the files at that path. E.g. node ls.js /Users If no path is given, then "." should be assumed. ***Show a TA that you have completed this task ================================================================================ Task 5 - Expand the cat command ================================================================================ The Unix "cat" command is intended to be able to "concatenate" multiple files. Enhance the cat command shown in class and given below to handle multiple files. //-----seed code for cat ------------------------------------------------------- // include the filesystem module let fs = require('fs'); function catFile(filename) { // If a filename parameter is not provided, give an error and exit if (!filename) { console.error("Usage: node cat "); process.exit(1); } // A ReadStream will emit events when data is available, when done, or error let rs = fs.createReadStream(filename); rs.setEncoding('utf8'); // set how the printable characters are represented /* * Define an anonymous callback to handle errors on the file-reading stream * * Typically in synchronous I/O, an error would result in an exception * being thrown. With asynchronous I/O (used here), the program does not * wait so the error might not happen for a while. Therefore an error is * either returned as an argument to a callback function, or in this case of * using a "streaming" style, an 'error' event is emitted. */ rs.on('error', function(e) { switch(e.code) { case "EISDIR": console.error("Error: "+filename+" is a directory"); break; case "ENOENT": console.error("Error: "+filename+" not found"); break; } process.exit(1); // Exit the program with an error code }); // Define a callback for when a chunk of data is available from the file rs.on('data', function(data) { console.log(data); }); // Define a callback for when the end of file has been reached. rs.on('end', function() { process.exit(0); // Exit the node.js program normally (i.e. no errors) }); } /* * At this point the function catFile has been defined, but not run. Therefore * get the 3rd argument which should be a file path, and use it as the argument * to catFile(). */ let filename = process.argv[2]; catFile(filename); //-----end cat------------------------------------------------------------------ The code above will work for one argument. Modify it to handle any number of arguments. For example, you should be able to execute it as: > node cat.js file1 file2 file3 If you have an error in, say, file 2 being a directory, or a file not found, your solution should still cat file 1 and file3 Again, using asynchronous IO makes this very interesting, for you do not have the flow of control going through the program line by line. In other words, the above program is not executed line-by-line from the beginning. Rather, a set of callback functions have been defined for the conditions of 'error', 'data', and 'end'. The error handler might be or might not be executed, data is handled multiple times depending on the size of the file, and end is handled once per file. You can't cat the set of files in a loop for then reads of all 3 will be started simultaneously. This cat'ing of files will happen in parallel, and could interleave the files into a mess. So how do you begin cat'ing the 2nd file only once the first has been written to the console.log()? (Don't use Promises nor async/await for this task (if you know what those are). We will be covering them shortly.) ***Show a TA that you have completed this task ================================== Task 6 - hello world server ================================== For this task, get the following simple hello world server running. It will respond "Hello World" to any request. Then have it return a simple bit of html. //-----hello world server------------------------------------------------------- // Include the http module let http = require('http'); /* * Create an http server, and supply a handler (aka callback) for when requests * arrive. The handler will be called with each HTTP request, and will be given * a request and response object. */ http.createServer( (request, response) => { // Give the OK response status, and set the content type response.writeHead(200, {'Content-Type': 'text/plain'}); // response.write writes back to the client. response.end is the final write response.end('Hello World\n'); }).listen(50000) // Use a dynamic port to have this process listen to console.log('Server running on localhost:50000'); //-----end hello world server--------------------------------------------------- How does it work? In Node, you add additional modules to your program by using "require()". You can require a file name (e.g. require("my/utilities.js") or require built-in modules that have been pre-compiled into Node (e.g. require("http"). The built-in modules include the many API methods included in the Node documentation. In the hello world server, the "http" module is being included. The "require" returns an object of type http, which can then be used to invoke the http methods that implement the HTTP protocol. Find and review the http.createServer API in the node documentation. The variable need not be named http. For example, I could have written: let foo = require('http'); foo.createServer... But by common practice, the variable referring to the object returned by the module is given the same name as the module itself. For example: let fs = require("fs"); let url = require("url"); As its name suggests, http.createServer() creates an http server, and returns a new web server object. This object will handle http requests, and make http responses. http.createServer() takes one argument, a request handler. The request handler that is passed to http.createServer() is a function with two arguments, the first (request) is an http.IncomingMessage and the second (response) is http.ServerResponse. Here are their two APIs. https://nodejs.org/api/http.html#http_http_incomingmessage https://nodejs.org/api/http.html#http_class_http_serverresponse In hello world server, we are only making use of the http.ServerResponse, which is a writable stream to which we can write our response. The response will include a header that returns 200 OK, and a string. See the API for more details on response.writeHead() and response.end(). As mentioned earlier, the http.createServer() returns an http server object. The only thing we need to do with that object is call its listen() method, so we don't bother saving it to a variable. server.listen() has one required argument which is the port to listen to. Task: Modify the program to return some simple html instead of plain text. ***Show a TA that you have completed this task ================================================================================ In summary, when you have completed all the tasks, show a TA each of the following during office hours before Wednesday. (All 3 TAs have office hours today and Tuesday. Find out when via Piazza -> Resources -> Staff) - helloworld.js - sum.js - ls.js - cat.js - helloworldserver.js ================================================================================