2016年1月29日 星期五

【Node.js】LEVEL 7 - Persisting Data

7.2 Simple Redis Commands

Let's start practicing using the redis key-value store from our node application.

Task 1:
Require the redis module and assign it to a variable called redis.

Task 2:
Create a redis client and assign it to a variable called client.

Task 3:
On the client, set the name property to your name.

```js var redis = require('redis'); var client = redis.createClient(); client.set('name', 'Answer'); ```

7.3 Get A Key

We have already stored a value in the question key. Use the redis client to issue a get command to redis to retrieve and then log the value.

Task 1:
Use the redis client to issue a get command using the 'question' key to retrieve a value. Remember, the get function takes a callback which expects two arguments, error and data.

Task 2:
Log the value retrieved with console.log.

```js var redis = require('redis'); var client = redis.createClient(); client.get('question', function(err, data){ console.log(data); }); ```

7.4 Working With Lists 1

As we saw in the video, redis can do more than just simple key-value pairs. We are going to be using redis' LISTS later to add persistence to our live-moderation app, so let's practice using them now.

Task 1:
Using the redis client's lpush command, insert question1 into the questions list. Then, console.log the result you receive. Remember, the lpush function takes a callback as its last argument, which expects an error and value to be passed as arguments.

Task 2:
Using the redis client's lpush command, insert question2 into the questions list. Then console.log the result you receive.

```js var redis = require('redis'); var client = redis.createClient(); var question1 = "Where is the dog?"; var question2 = "Where is the cat?"; client.lpush('questions', question1, function(err, data){ console.log(data); }); client.lpush('questions', question2, function(err, data){ console.log(data); }); ```

7.5 Working With Lists 2

Now that we have seeded the questions list, use the lrange() command to return all of the items and log them.

Task 1:
Use the lrange() command to return all of the items from the questions key.

Task 2:
Now that we have called lrange(), use console.log to log the result from redis.

```js var redis = require('redis'); var client = redis.createClient(); client.lrange('questions', 0, -1, function(err, data){ console.log(data); }); ```

7.6 Persisting Questions

Let's go back to our live-moderation app and add some persistence, first to the questions people ask.

Task:
Use the lpush command to add new questions to the list named questions. Do this inside the listener for the 'question' event.

```js var express = require('express'); var app = express(); var server = require('http').createServer(app); var socket = require('socket.io'); var io = socket.listen(server); var redis = require('redis'); var redisClient = redis.createClient(); io.sockets.on('connection', function(client) { client.on('answer', function(question, answer) { client.broadcast.emit('answer', question, answer); }); client.on('question', function(question) { if(!client.question_asked) { client.question_asked = true; client.broadcast.emit('question', question); redisClient.lpush('questions', question); } }); }); ```

7.7 Emitting Stored Questions

Now that we have questions stored in redis, let's emit them whenever a new client connects to the server through socket.io.

Task 1:
Use the lrange command to retrieve a list of questions that represent the questions list within redis.

Task 2:
Inside of the lrange callback, use a forEach loop to iterate through the questions and emit() each question to the client. Remember, don't use broadcast.emit because we only want to send the questions to the client that is connecting to the server.

```js var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io').listen(server); var redis = require('redis'); var redisClient = redis.createClient(); io.sockets.on('connection', function(client) { redisClient.lrange('questions', 0, -1, function(err, questions){ questions.forEach(function(question){ client.emit("question", question); }); }); client.on('answer', function(question, answer) { client.broadcast.emit('answer', question, answer); }); client.on('question', function(question) { if(!client.question_asked) { client.question_asked = true; client.broadcast.emit('question', question); redisClient.lpush("questions", question); } }); }); ```

7.8 Limiting Questions Stored

Great work! One last thing though, since every time a new question comes in we store it in the questions list, we might run into a problem where there are just too many questions stored in that list.

Task 1:
Add a callback to lpush that will be used to limit the size of the list down to a max of 20.

Task 2:
Use the ltrim command to limit the size of the list stored within redis to a maximum size of 20.

```js var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io').listen(server); var redis = require('redis'); var redisClient = redis.createClient(); io.sockets.on('connection', function(client) { redisClient.lrange("questions", 0, -1, function(err, questions) { questions.forEach(function(question) { client.emit("question", question); }); }); client.on('answer', function(question, answer) { client.broadcast.emit('answer', question, answer); }); client.on('question', function(question) { if(!client.question_asked) { client.question_asked = true; client.broadcast.emit('question', question); redisClient.lpush("questions", question, function(){ redisClient.ltrim('questions', 0, 19); }); } }); }); ```

2016年1月28日 星期四

【Node.js】LEVEL 6 - Socket.io

6.2 Setting Up socket.io Server-Side

So far we've created an Express server. Now we want to start building a real-time Q&A moderation service and we've decided to use socket.io.

Task 1:
Using the http module, create an new http server and pass the express app as the listener for that new server.

Task 2:
Using the socket.io module, listen for requests on the http server. Store the return object of this operation in a variable called io.

Task 3:
Use the object stored in io to listen for client 'connection' events. Remember, the callback function takes one argument, which is the client object that has connected.

Task 4:
When a new client connects, log a message using console.log().

Task 5:
Finally, we want to tell our http server to listen to requests on port 8080.

```js var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); io.on('connection', function(client){ console.log(client + "has connected."); }); server.listen(8080); ```

6.3 Client socket.io Setup

In our html file, load the socket.io.js script and connect to the socket.io server.

Task 1:
Load the socket.io.js script. The socket.io.js path you should use is '/socket.io/socket.io.js'. Express knows to serve the socket.io client js for this path.

Task 2:
Using the global io object that's now available for us, connect to the socket.io server at http://localhost:8080.

```js ```

6.4 Listening For Questions

In our client below, listen for 'question' events from the server and call the insertQuestion function whenever the event fires.

Task 1:
First, listen for 'question' events from the server.

Task 2:
Now, have the event callback function call the insertQuestion function. The insertQuestion function is already created for you, and it's placed in its own file. It expects exactly one argument - the question.

```js ```

6.5 Broadcasting Questions

When a question is submitted to our server, we want to broadcast it out to all the connected clients so they can have a chance to answer it.

Task 1:
In the server, listen for 'question' events from clients.

Task 2:
Now, emit the 'question' event on all the other clients connected, passing them the question data.

```js var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); io.on('connection', function(client) { console.log("Client connected..."); client.on('question', function(question){ client.broadcast.emit('question', question); }); }); server.listen(8080); ```

6.6 Saving Client Data

In our real-time Q&A app, we want to allow each client only one question at a time, but how do we enforce this rule? We can use socket.io's ability to save data on the client, so whenever a question is asked, we first want to check the question_asked value on the client.

Task 1:
First, when a client emits a 'question' event, we want to set the value of question_asked to true.

Task 2:
Second, when a client emits a 'question' event, we want to broadcast that question to the other clients.

Task 3:
Finally, when a client emits a 'question' event, check to make sure question_asked is not already set to true. We only want to allow one question per user, so make sure that we only set the value of question_asked and broadcast the question to other clients when the value of question_asked is not already true.

```js var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); io.on('connection', function(client) { console.log("Client connected..."); client.on('question', function(question) { if(!client.question_asked){ client.question_asked = true; client.broadcast.emit('question', question); } }); }); server.listen(8080); ```

6.7 Answering Questions

Clients can also answer each other's questions, so let's build that feature by first listening for the 'answer' event on the client, which will send us both the question and answer, which we want to broadcast out to the rest of the connected clients.

Task 1:
With the client, listen for the 'answer' event from clients.

Task 2:
Now, emit the 'answer' event on all the other clients connected, passing them the question data.

```js var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); io.sockets.on('connection', function(client) { console.log("Client connected..."); client.on('answer', function(question, answer){ client.broadcast.emit('answer', question, answer); }); client.on('question', function(question) { if(!client.question_asked) { client.question_asked = true; client.broadcast.emit('question', question); } }); }); server.listen(8080); ```

6.8 Answering Question Client

Now on the client, listen for the 'answer' event and then broadcast both the question and the answer to the connected clients.

Task 1:
Listen for the 'answer' event off of the server.

Task 2:
Call the answerQuestion function, passing in both the question and the answer that was broadcast from the server.

```js ```

【Node.js】LEVEL 5 - Express

5.2 Express Routes

Let's create an express route that accepts GET requests on '/tweets' and responds by sending back a static HTML file.

Task 1:
Create a GET route for '/tweets' and give it the proper callback. The callback function should accept two arguments: the request and the response.

Task 2:
Send back the file tweets.html, which lives under the project's root path. Remember to use __dirname to locate tweets.html.

Task 3:
Finally, have the express app listen on port 8080.

```js var express = require('express'); var app = express(); app.get('/tweets', function(request, response){ response.sendFile( __dirname + '/tweets.html'); }).listen(8080); ```

5.3 Route Params

Let's create a route that accepts dynamic arguments in the URL path and responds with the quote from the proper author.

Task 1:
Start by creating a GET route for '/quotes' that takes a name parameter as part of the URL path.

Task 2:
Now, use the name parameter from the URL to retrieve a quote from the quotes object and write it out to the response. Note: No piping here, just write the quote string to the response like you did in previous levels (and then close the response).

```js var express = require('express'); var app = express(); var quotes = { 'einstein': 'Life is like riding a bicycle. To keep your balance you must keep moving', 'berners-lee': 'The Web does not just connect machines, it connects people', 'crockford': 'The good thing about reinventing the wheel is that you can get a round one', 'hofstadter': 'Which statement seems more true: (1) I have a brain. (2) I am a brain.' }; app.get('/quotes/:name', function(req, response){ response.end(quotes[req.params.name]); }); app.listen(8080); ```

5.4 Rendering

Instead of just writing out the quote to the response, let's try using an EJS template to render the response.

Task 1:
First, render the quote.ejs template to the response.

Task 2:
Next, make the name and the quote data available to the template.

Task 3:
Inside quote.ejs, add the code needed to render the data you passed to the template.

app.js ```js var express = require('express'); var app = express(); var quotes = { 'einstein': 'Life is like riding a bicycle. To keep your balance you must keep moving', 'berners-lee': 'The Web does not just connect machines, it connects people', 'crockford': 'The good thing about reinventing the wheel is that you can get a round one', 'hofstadter': 'Which statement seems more true: (1) I have a brain. (2) I am a brain.' }; app.get('/quotes/:name', function(req, res) { var quote = quotes[req.params.name]; res.render('quote.ejs', { name: req.params.name, quote: quote }); }); app.listen(8080); var express = require('express'); ``` views/quote.ejs ```js Quote by <%= name %> <blockquote> <%= quote %> </blockquote> ```

5.5 URL Building

Let's create a page which calls the Twitter search API and displays the last few results for Code School. The first step is to construct the proper URL, which is all you need to do in this challenge.

Complete the URL options which will be sent into the the url module's format method. The URL you'll want to construct is the following: http://search.twitter.com/search.json?q=codeschool

Task 1:
Add the protocol attribute to options.

Task 2:
Add the host attribute to options.

Task 3:
Add the pathname attribute to options.

Task 4:
Add an attribute which takes an object of query parameters, in this case we only need q to search Twitter.

```js var url = require('url'); options = { // add URL options here protocol: 'http:', host : 'search.twitter.com', pathname : '/search.json', query: {q:'codeschool'} }; var searchURL = url.format(options); console.log(searchURL); ```

5.6 Doing the Request

Next, we'll use the request module to make a simple web request and log the response to the console. You can use this example in the README.

Task 1:
To start, require the request module and assign the return function to a variable.

Task 2:
Next, issue a request to searchURL. Remember, the callback function for the request function takes three arguments: error, response and body.

Task 3:
Finally, log the response body to the console using console.log(). ```js var url = require('url'); var options = { protocol: "http:", host: "search.twitter.com", pathname: '/search.json', query: { q: "codeschool"} }; var searchURL = url.format(options); var request = require('request'); request(searchURL, function(error, response , body){ console.log(body); }); ```

5.7 Express Server

Now, let's create an Express server which queries out for the search term and then returns the JSON.

Task 1:
Require the express module.

Task 2:
Create the Express server and name it app.

Task 3:
Create a route for GET requests to / (root path). Remember, the callback function takes two arguments: a request req and a response res.

Task 4:
In our new route, issue a request to searchURL and pipe the results into the response.

Task 5:
Finally, tell app to listen on port 8080.

```js var url = require('url'); var request = require('request'); var express = require('express'); var options = { protocol: "http:", host: "search.twitter.com", pathname: '/search.json', query: { q: "codeschool" } }; var searchURL = url.format(options); var app = express(); app.get('/', function(req, res){ request(searchURL).pipe(res); }).listen(8080); ```

【Node.js】LEVEL 4 - Modules

4.2 Missing Exports

Notice the two different files: high_five.js on the left side and app.js on the right. The code as it's written will not work, high_five.js isn't exporting anything.

Task:
Add the proper exports line to have a successful high five!

high_five.js ```js var highfive = function() { console.log("smack!!"); }; module.exports = highfive; ```

app.js ```js var highfive = require('./high_five.js'); highfive(); ```

4.3 Export A Function

Notice the app.js file with the myRequest function below. Let's refactor myRequest out to its own my_request.js module.

Task:
Move the myRequest function and the http require into my_request.js

my_request.js ```js var http = require('http'); var myRequest = function(message) { var request = http.request('http://codeschool.com', function(response) { response.pipe(process.stdout, { end: false }); }); request.write(message); request.end(); }; module.exports = myRequest; ```

app.js ```js var myRequest = require('./my_request'); myRequest('Hello, this is dog.'); ```

4.4 Exporting An Object

The app.js code on the right side does not work. Once again we forgot to export our functions.

Task 1:
In the logger.js file, export the info function so we can use it in app.js by assigning it to the exports object.

Task 2:
In the logger.js file, export the warn function so we can use it in app.js by assigning it to the exports object.

Task 3:
In the logger.js file, export the error function so we can use it in app.js by assigning it to the exports object.

```js exports.info = function(message) { console.log("Info: " + message); }; exports.warn = function(message) { console.log("Warning: " + message); }; exports.error = function(message) { console.log("Error: " + message); }; ```

4.5 Installing Local Modules

Practice using npm by installing the npm module underscore using the npm install command.

``` npm install underscore ```

4.6 Installing Global Modules

Now install the coffee-script module, but install it globally so you can use the coffee executable that comes with coffee-script.

``` npm install coffee-script -g ```

4.7 Dependency

Add two dependencies to our package.json file, connect and underscore. We'll want to use version 2.1.1 of connect and version 1.3.3 of underscore.

Task 1:
Add the connect dependency to package.json

Task 2:
Add the underscore dependency to package.json

``` { "name": "My Awesome Node App", "version": "1", "dependencies": { "connect": "2.1.1", "underscore": "1.3.3" } } ```

4.8 Semantic Versioning

We want to make sure we are always up-to-date with the most recent patch-level changes to our dependencies when we run npm install.

Task:
Update the connect version on package.json to fetch the latest patch-level changes. All we have to do is add one character to the beginning of the version number.

``` { "name": "My Awesome Node App", "version": "1", "dependencies": { "connect": "~2.1.1", "underscore": "~1.3.3" } } ```

【Node.js】LEVEL 3 - Streams

3.2 File Read Stream

Lets use the fs module to read a file and log its contents to the console.

Task 1:
Use the fs module to create a Readable stream for fruits.txt. Store the new stream in a variable called file.

Task 2:
Next, listen to the readable event on the newly created stream and give it a callback.

Task 3:
Inside the callback, read the data chunks from the stream and print them to the console using console.log() - you might want to use a while loop to do this. Don't forget to call toString() on the data before printing it.

```js var fs = require('fs'); var file = fs.createReadStream('fruits.txt'); file.on('readable', function(){ while(null !== (chunk = file.read())){ console.log(chunk.toString()); } }); ```

3.3 File Piping

Instead of manually listening for the 'readable' event on the Readable stream, let's use pipe to read from the stream and write directly to process.stdout.

Task 1:
Start by removing the code for the readable handler.

Task 2:
Call file.pipe(), passing it the stream to write to.

```js var fs = require('fs'); var file = fs.createReadStream('fruits.txt'); file.pipe(process.stdout); ```

3.4 Fixing Pipe

The following code will throw an error because pipe automatically closed our writable stream.

Task 1:
You'll need to consult the pipe documentation to figure out the option which keeps the Write stream open and dispatches the end event.

Task 2:
Move the logic for handling the request from the http.createServer() callback to your new 'request' event listener. Remember to remove the http.createServer() callback once the code has been moved.

```js var fs = require('fs'); var file = fs.createReadStream('origin.txt'); var destFile = fs.createWriteStream('destination.txt'); file.pipe(destFile, { end: false }); file.on('end', function(){ destFile.end('Finished!'); }); ```

3.5 Download Server

Let's create an HTTP server that will serve index.html.

Task:
Use pipe() to send index.html to the response.

```js var fs = require('fs'); var http = require('http'); http.createServer(function(request, response) { response.writeHead(200, {'Content-Type': 'text/html'}); var file = fs.createReadStream('index.html'); file.pipe(response); }).listen(8080); ```

【Node.js】LEVEL 2 - Events

2.2 Chat Emitter

We're going to create a custom chat EventEmitter.

Task 1:
Create a new EventEmitter object and assign it to a variable called 'chat'.

Task 2:
Next, let's listen for the 'message' event on our new chat object. Remember to add a callback that accepts the message parameter.

Task 3:
Log the message to the console using console.log().

```js var events = require('events'); var EventEmitter = events.EventEmitter; var logger = new EventEmitter(); logger.on('message', function(message){ console.log("ERR: " + message); }); ```

2.3 Emitting Events

Read the existing code below and modify it to emit events.

Task 1:
On the chat object, emit the 'join' event and pass in a custom message as a string.

Task 2:
Now emit the 'message' event on the chat object. Just like before, remember to pass in a custom message as a string.

```js var events = require('events'); var EventEmitter = events.EventEmitter; var chat = new EventEmitter(); var users = [], chatlog = []; chat.on('message', function(message) { chatlog.push(message); }); chat.on('join', function(nickname) { users.push(nickname); }); chat.emit('join', "Hello"); chat.emit('message', "Message:"); ```

2.4 Request Event

Just like you saw in the video, refactor the HTTP server code to explicitly bind a callback to the 'request' event using the on function.

Task 1:
Add an event listener on the server variable that listens to the request event. The event listener should take a callback function with two arguments, request and response.

Task 2:
Move the logic for handling the request from the http.createServer() callback to your new 'request' event listener. Remember to remove the http.createServer() callback once the code has been moved.

```js var http = require('http'); var server = http.createServer(); server.on('request', function(request, response){ response.writeHead(200); response.write("Hello, this is dog"); response.end(); }); server.listen(8080); ```

2.5 Listening Twice

Who said you can only listen for an event once?

Task 1:
Add a second 'request' handler to the HTTP server.

Task 2:
From inside of the new handler, log the message "New request coming in..." using console.log().

```js var http = require('http'); var server = http.createServer(); server.on('request', function(request, response) { response.writeHead(200); response.write("Hello, this is dog"); response.end(); }); server.on('request', function(request, response){ console.log("New request coming in..."); }); server.listen(8080); ```

2.6 Listening for Close

Like our parents always used to say, listening is more important than talking! Modify the server so that we know when it's closed down.

Task 1:
Listen for the 'close' event on the server. The event listener should take a callback function that accepts no arguments.

Task 2:
Inside the 'close' callback, log the message "Closing down the server...".

```js var http = require('http'); var server = http.createServer(); server.on('request', function(request, response) { response.writeHead(200); response.write("Hello, this is dog"); response.end(); }); server.on('request', function(request, response) { console.log("New request coming in..."); }); server.on('close', function(){ console.log("Closing down the server..."); }); server.listen(8080); ```

【Node.js】LEVEL 1 - Intro to Node.js

1.2 Hello You

Let's start with a simple Hello server. Follow the tasks below to create a simple Node server that outputs a greeting.

Task 1:
First, tell the response which status it should have (a successful status is200).

Task 2:
Next, write a message to the response body in the form of "Hello, this is <your name here>".

Task 3:
To finish it up, tell the response to end so the client on the other side knows it has received all the data.

```js var http = require('http'); http.createServer(function(request, response) { response.writeHead(200); response.write("Hello, this is Cain."); response.end(); }).listen(8080); ```

1.3 Convert Blocking

Not everyone knows why it's important to write non-blocking programs in Node.js. One of these unfortunate souls has written some code to read a file off the file-system using the blocking function readFileSync. Convert the code to be non-blocking using the readFile function instead.

Task 1:
Start by changing the call from readFileSync() to readFile().

Task 2:
Next, add a callback method to the readFile() call. This method should accept error and contents parameters.

Task 3:
To finish it up, remove the contents var declaration, and move the call to console.log() inside your callback.

``` var fs = require('fs'); fs.readFile('index.html', function(err, contents){ console.log(contents); }); ```

1.4 Running Your Code

While you could go to the website and easily install node, we happen to have a console below where you can practice running node applications.

Go ahead and run that file we just created to read a file off the filesystem with node file_read.js ``` $ node file_read.js ```

1.5 Read File in Server

Now that you know how to create an HTTP server and how to read a file off the filesystem in a non-blocking way, let's try to combine the two.

Instead of just writing a string to the HTTP response, write the contents of index.html to the response instead.

Task 1:
After response.writeHead(200), add a call to fs.readFile() that reads index.html asynchronously. Remember to pass a callback function, that accepts an error parameter, and a contents parameter.

Task 2:
Now that you have the file contents, write it to the response.

Task 3:
To finish up, end the response after the file contents have been written.

```js var http = require('http'); var fs = require('fs'); http.createServer(function(request, response) { response.writeHead(200); fs.readFile('index.html', function(err, contents){ response.write(contents); response.end(); }); }).listen(8080); ```

1.6 Issuing a Request

Let's see our new server in action. We've already run node app.js, so in the terminal below use curl to issue a request to http://localhost:8080 and we'll see our server respond with the contents of index.html.

``` $ curl http://localhost:8080 ```

1.7 Writing Response Headers

Up until now all we've been sending into the response.writeHead() function is the status code. However, it can take additional parameters.

Task:
Consult the node documentation, and add a 'Content-Type' of 'text/html' to the response.

```js var http = require('http'); var fs = require('fs'); http.createServer(function(request, response) { response.writeHead(200, { 'Content-Type': 'text/html' }); fs.readFile('index.html', function(err, contents) { response.write(contents); response.end(); }); }).listen(8080); ```

1.8 Response End

Our original Hello server can be shortened since the response.end() function optionally takes data as a parameter. Remove the response.write line altogether, and send the hello string as a parameter on the response.end function. This will send the data, and once finished add the end to the response.

Task:
Instead of passing the content to response.write(), pass it to response.end().

```js var http = require('http'); http.createServer(function(request, response) { response.writeHead(200); response.end("Hello, this is dog"); }).listen(8080); ```