欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Chapter 2. Doing Interesting Things

程序员文章站 2022-04-04 15:55:17
...

 

1. Node is specifically focused on solving the problem of building network applications—that is, applications that do a lot of input/output (I/O).

 

2. The net module contains all the TCP stuff for Node. From that, we can create a TCP server by calling the net.createServer() method. We add an event listener by using the on(“connection”, handler(client) ) method. Whenever the connection event happens, the event listener will call the handler function we gave it. A connection event happens when a new client connects to the server. The connection event passes us a reference to the TCP socket for our new client when it calls our callback function. By calling Socket.write, we can send messages to the newly connected client. Call the Socket.end method to close the connection. We need to call listen() on the server so Node knows which port to listen on. The data event is the event that is called each time client sends some data to the server, you can call Socket.on(“data”, process(data)) to add event handler for it. Node simply stores the data as binary until we ask for it in some other kind of encoding, if you use console.log to print it, the bytes in hex will be printed. We can use the toString() method to translate Buffer data into a regular string. When a socket disconnects, it fires the end event to indicate that it’s about to close. Socket.writable indicates whether the socket is available for writing and Socket.destroy close and destroy the socket object. error event on Socket will be triggered if clients throws any error.

 

3. Example of a chat server:

 

var net = require('net')
var chatServer = net.createServer(),
    clientList = []


chatServer.on('connection', function(client) {
  client.name = client.remoteAddress + ':' + client.remotePort
  client.write('Hi ' + client.name + '!\n');
  console.log(client.name + ' joined')

  clientList.push(client)

  client.on('data', function(data) {
    broadcast(data, client)
  })

  client.on('end', function() {
    console.log(client.name + ' quit')
    clientList.splice(clientList.indexOf(client), 1)
  })
  
  client.on('error', function(e) {
    console.log(e)
  })
})

function broadcast(message, client) {
  var cleanup = []
  for(var i=0;i<clientList.length;i+=1) {
    if(client !== clientList[i]) {

      if(clientList[i].writable) {
        clientList[i].write(client.name + " says " + message)
      } else {
        cleanup.push(clientList[i])
        clientList[i].destroy()
      }

    }
  }  //Remove dead Nodes out of write loop to avoid trashing loop index 
  for(i=0;i<cleanup.length;i+=1) {
    clientList.splice(clientList.indexOf(cleanup[i]), 1)
  }
}

chatServer.listen(9000)

 

 

4. The Express module is a web framework for Node that makes it much easier to create web applications by adding support for common tasks, such as MVC, to the existing http server.

 

5. Instead of including the http module, we include express module. Express is actually getting http behind the scenes. We call createServer() to make a server and call listen() to make it listen to a specific port. Instead of attaching an event listener to the request event, we can call methods matching the HTTP verbs. When we call get(), we can create a callback function that will match GET requests only to a URL that matches the first argument of the call. 

 

6. Express provides a convenience method on the response (http.response) object named send(), and this method issues both the HTTP headers as well as a response.end() call:

 

var app = express.createServer()
app.get('/', function(req, res) {
  res.send('Welcome to Node Twitter')
})
app.listen(8000)
 

 

 

7. In JavaScript everything happens in an event loop. That means new events don’t get called until we’ve finished evaluating the code of the existing loop pass.

 

8. The server.listen() call is actually asynchronous because binding to a TCP port takes time. The addition of event listeners (via server.get() and server.post()) is synchronous.

 

9. A middleware is a small piece of code that sits in between the original request event and the route we defined with server.post(). We use middleware to reuse code for common tasks such as authentication or logging. One middleware included in Express is called bodyParser. This middleware’s job is to stream the POST data from the client and then turn it into a JavaScript object that we can use. We simply include it by specifying it in the arguments we give to the server.post() route. Notice that we call express.bodyParser(). This function call actually returns another function. We use this standard style for middleware to allow you to pass configuration to the middleware if you want to. If we didn’t include the middleware, we would have to manually write code to accept the data event provided by the request object. Only after we had streamed in all the POST data could we call the code in the server.post() route.

 

10. The express.bodyParser adds a property to request called request.body. This property (if it exists) contains an object representing the POST data. The express.bodyParser middleware will work only for POST requests with the content-type HTTP header of application/x-www-form-urlencoded (that’s what a web page form would send.)or application/json. Both of these are easy to parse into key/value pairs as properties of the request.body object.

 

11. If we give response.send() an object ( such as {status:”ok”, message:”done”}), it automatically serializes it as JSON and sends the correct HTTP headers.

 

12. assert is a core module in Node that lets us test return values in various ways. When a value doesn’t match the expected criteria, an exception is thrown.

 

13. The http library doesn’t just contain objects to serve HTTP; it also provides a client. The http.request() factory creates a new http.Request object. The http.request() constructor takes two arguments: the first is the config object, and the second is a callback. The config is a configuration object we pass that has a list of properties including the hostname, the port, URL path, HTTP method, and some HTTP headers. The callback is attached to the response event for the http.Request. It’s similar to an http.Server, except we have only one object in the response

 

14. By using assert.strictEqual, we are checking that data matches the expected data using ===. If it doesn’t, an assert exception is thrown.

 

15. response.setEncoding() allows us to define the encoding of all the received data. By setting this to utf8, we ensure that any data we receive will be treated as the right kind of string. 

 

16. In Express, we can use express.bodyDecoder to catch all the data in a request and stream it. But we don’t have the same luxury in the client, so we’ll do it by hand. We simply attach a function to the data event on response. Whenever data happens, we append it to our data variable. 

 

17. We can listen for the end event of the response and then take further action on all of the data. 

 

18. Calling write() on request lets us send data (if this is a POST request). We call end() to indicate that we are finished sending data with the request object.

 

19. A sample basic API of web app:

 

var express = require('express')
var app = express.createServer()
app.listen(8000)
var tweets = []
app.get('/', function(req, res) {
    res.send('Welcome to Node Twitter')
})
app.post('/send', express.bodyParser(), function(req, res) {
    if (req.body && req.body.tweet) {
        tweets.push(req.body.tweet)
        res.send({status:"ok", message:"Tweet received"})
    } else {
        //no tweet?
        res.send({status:"nok", message:"No tweet received"})
    }
})
app.get('/tweets', function(req,res) {
    res.send(tweets)
})

  and corresponding test codes:

 

var http = require('http'),
assert = require('assert')
var opts = {
    host: 'localhost',
    port: 8000,
    path: '/send',
    method: 'POST',
    headers: {'content-type':'application/x-www-form-urlencoded'}
}
var req = http.request(opts, function(res) {
    res.setEncoding('utf8')
    var data = ""
    res.on('data', function(d) {
        data += d
    })
    res.on('end', function() {
        assert.strictEqual(data, '{"status":"ok","message":"Tweet received"}')
    })
})
req.write('tweet=test')
req.end()

 

 

20. Express supports an MVC (model, view, controller) approach oriented around the routing of requests. The routes act like controllers, providing a way to join the data model with a view.

 

21. Express offers a few different templating languages and is extensible to allow the addition of more.

 

22. EJS simply embeds JavaScript into the templates with a few simple tags to define how the JavaScript is interpreted. The JavaScript to be evaluated is between the <% and %> tags. You can simply list the variable or reference you wish to include in the page following = in the <% tags.

 

23. The layout file in Express defines a skeleton to use for your site. It’s some basic view boilerplate you will use almost everywhere. Partials are mini-templates for code that is expected to repeat again and again with different data. We use the layout template on all the pages on the site (unless we turn it off), we need some way to say where the specific template being rendered goes. Express provides the body variable for this task. This variable will contain the rendered contents of the specific template we load.

 

24. We use response.render() as the call to render a template. The first argument is the name of the specific template we want to render. Whatever is in the specific template will be rendered into the layout template where the body variable was. The second argument we pass to response.render() is a configuration object. The locals property of the configuration object contains the data used to render this template. All of these variables will be available to both the layout template and the page template.

 

25. If partials get passed an array, they will repeat for each item in the array. The variable each partial is using to access the array is the same as the name of the template.

 

26. An example :

EJS layout template:

 

<!DOCTYPE html>
<html lang="en">
    <head>
      <meta charset="utf-8">
      <%- partial('partials/stylesheet', stylesheets) %>
      <title><%= title %></title>
    </head>
    <body>
        <h1><%= header %></h1>
        <%- body %>
    </body>
</html>

 The page(index) template:

 

<form action="/send" method="POST">
  <input type="text" length="140" name="tweet">
  <input type="submit" value="Tweet">
</form>
<%- partial('partials/chirp', tweets) %>

 The chirp partial:

<p><%= chirp %></p>

 The stylesheet partial:

<link rel="stylesheet" type="text/css" href="<%- stylesheet %>">

 Two routes:

app.post('/send', express.bodyParser(), function(req, res) {
  if (req.body && req.body.tweet) {

    tweets.push(req.body.tweet)
    
    if(acceptsHtml(req.headers['accept'])) {
      res.redirect('/', 302)
    } else {
      res.send({status:"ok", message:"Tweet received"})
    }

  } else {
    //no tweet?
    res.send({status:"nok", message:"No tweet received"})
  }
})
function acceptsHtml(header) {
  var accepts = header.split(',')
  for(i=0;i<accepts.length;i+=0) {
    if (accepts[i] === 'text/html') { return true }
  }

  return false
}
app.get('/', function(req, res) {
  var title = 'Chirpie',
      header = 'Welcome to Chirpie'

  res.render('index', {
    locals: {
      'title': title,
      'header': header,
      'tweets': tweets,
      stylesheets: ['/public/style.css']
    }
  })
})