来自对《了不起的Node.js》一书的学习

HTTP协议

  1. 超文本传输协议,又称为HTTP,是一种Web协议。
  2. HTTP协议构建在请求和响应的概念上,对应在Node.js中就是由http.ClientRequest和http.ServerResponse这个两个构造器构造出来的对象(Node.js v6.2.0)。
  3. HTTP协议在请求和响应消息前使用头信息(header)来描述不同的消息内容。
  4. 发送内容的类型(type)由头信息中的Content-Type头信息来标注,Web页面会分发许多不同类型的内容:文本(text)、HTML、XML、JSON、PNG以及JPEG图片,等等。
  5. 在Node.js中,Node会默认在响应消息的头信息中加入Transfer-Encoding和Connection。
  6. Transfer-Encoding头信息的默认值为chunked,主要的原因是Node天生的异步机制,这样响应就可以逐步产生。
      一般情况下,HTTP的Header包含Content-length域来指明报文体的长度。有时服务生成HTTP回应是无法确定消息大小的,比如大文件的下载,或者后台需要复杂的逻辑才能全部处理页面的请求,这时就需要实时生成消息长度,服务一般使用chunked编码。
  7. 在Node.js中TCP服务器和HTTP服务器的实现,都调用了createServer方法,并且当客户端连入时都会执行一个回调函数。不同的是,回调函数中的对象的类型。在net服务器中(TCP服务器),是个连接(connection)对象,而在HTTP服务器中,则是请求和响应对象。
      之所以会这样,原因有两个。
      1)HTTP服务器是更高层的API,提供了控制和HTTP协议相关的一些功能。实际上Node.js为我们封装了请求消息和响应消息对象。
      2)同时也是一个更为重要的原因是,浏览器在访问站点时不会就只用一个连接。
      默认情况下,Node会告诉浏览器始终保持连接,通过它发送更多的请求。这是通过此前我们看到的Connection头信息中的keep-alive值来通知浏览器的。为了提高性能(因为浏览器不想浪费时间去重新建立和关闭TCP连接),这样做通常是对的。不过,我们也可以调用writeHead方法,传递一个不同的值,如Close,来将其重写掉。

一个简单的Web服务器

  1. 创建模块:npm init。
{
"name": "http-form",
"version": "0.0.1",
"description": "An HTTP server that process forms",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}

初始化项目。
2. 创建server.js,向客户端输出一个表单:

var http = require('http');

http.createServer(function(req, res) {
// console.log(req.url);

res.writeHead(200, {'Content-Type': 'text/html'});
res.end([
'<form method="POST" action="/url">',
'<h1>My form</h1>',
'<fieldset>',
'<label>Personal information</label>',
'<p>What is your name?</p>',
'<input type="text" name="name">',
'<p><button>Submit</button></p>',
'</form>'
].join(''));
}).listen(3000);

其中,表单提交的地址为/url,同时采用POST方法。

3. 使用 node server.js 来运行服务器。

4. 在浏览器中访问:127.0.0.1:3000,我们会在浏览器中看到输出来的表单。

Node中的HTTP_http协议


页面显示的结果。

5. 为了实现接收表单提交的数据,我们来将上述server.js代码中的console.log这一行代码注释掉,在重新运行服务器,看看在命令行中会出现什么。

Node中的HTTP_http协议_02


  我们在浏览器中修改url地址呢?在127.0.0.1:300后面加上 /url ,我们想访问这样的地址,会出现什么样的结果呢?

Node中的HTTP_html_03


  看,我们请求的 /url 地址被输出显示出来了(有点莫名其妙,我们先不管——居然这样访问,不会404报错,而是被服务器正确接收,在server.js的回调方法中打印出用户访问的相对地址),同时在浏览器中显示的结果还是同刚才访问的页面一样。

  那么我们就可以在server.js中的回调方法中判断当前请求的地址,来对应是否在form表单请求的地址,如果是,则对该请求进行相应的处理。

6. 在server.js中需要对请求对象上的url属性进行检测。server.js的代码如下所示:

var http = require('http');

http.createServer(function(req, res) {
// console.log(req.url);
if('/' == req.url) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end([
'<form method="POST" action="/url">',
'<h1>My form</h1>',
'<fieldset>',
'<label>Personal information</label>',
'<p>What is your name?</p>',
'<input type="text" name="name">',
'<p><button>Submit</button></p>',
'</form>'
].join(''));
} else if('/url' == req.url) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('You sent a <em>' + req.method + '</em> request.');
}

}).listen(3000);

  再次运行node server.js,刷新页面看看,这次提交表单会有什么变化吧。

7. 这会能猜到结果了吧,当我们直接提交表单后会跳转 /url 链接,此时服务器接收到发现请求是 /url 地址,就进行了相应的处理,返回相应的内容被浏览器接收,最后给浏览器显示出来。

8. 那么我们现在在浏览器访问其他地址呢,会发现浏览器会请求很久很久,最后提示我们:

Node中的HTTP_http协议_04


  这是因为我们在服务器中没有对其他地址进行响应的操作。

  注:Node.js会将主机名后所有的内容都放在(req.)url属性中。

9. 接下来我们需要接收form表单提交过来的数据。需要注意的是:请求消息也可以包含Content-Type头信息。就像响应消息有Content-Type头信息来告诉浏览器它返回了什么数据一样,浏览器发送请求是也可以告诉服务器我发送了什么类型数据给你(服务器)了。server.js代码如下:

var http = require('http');

http.createServer(function(req, res) {
// console.log(req.url);
if('/' == req.url) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.end([
'<form method="POST" action="/url">',
'<h1>My form</h1>',
'<fieldset>',
'<label>Personal information</label>',
'<p>What is your name?</p>',
'<input type="text" name="name">',
'<p><button>Submit</button></p>',
'</form>'
].join(''));
} else if('/url' == req.url && 'POST' == req.method) {
var body = '';
req.on('data', function(chunk) {
body += chunk;
});
req.on('end', function() {
res.writeHead(200, {'Content-Type': 'text/html'});
// res.end('You sent a <em>' + req.method + '</em> request.');
res.end('<p>Content-Type: ' + req.headers['content-type'] + '</p>'
+ '<p>Data:</p><pre>' + body + '</pre>');
});
}

}).listen(3000);

  运行结果如下:

Node中的HTTP_服务器_05


  结果显示:提交的数据Content-Type,即数据格式为键值对字符串的形式,或者说是一个查询字符串(key1=value1&key2=value2&…)。我们需要对该字符串进行解析,生成我们想要的对象,便于操作。好在Node.js为我们提供了一个称为querystring的模块,方便地对这类字符串进行解析。

  querystring模块将一个字符串解析成一个对象。这个解析处理方法和Node解析headers消息的方式类似,Node将HTTP请求数据中的headers信息从字符串解析成一个方便处理的headers对象。

10. 使用querystring的parse方法对请求内容进行解析,然后从解析生成的对象中获取name值,并将其展示给用户。server.js部分代码修改如下:

  引入querystring模块:

var qs = require('querystring');

  修改req.end中的回调:

req.on('end', function() {
res.writeHead(200, {'Content-Type': 'text/html'});
// res.end('You sent a <em>' + req.method + '</em> request.');
/*res.end('<p>Content-Type: ' + req.headers['content-type'] + '</p>'
+ '<p>Data:</p><pre>' + body + '</pre>');*/
res.end('<p>Your name is <b>' + qs.parse(body).name + '</b></p>');
});

  重新运行后正确显示结果。
11. 如果URL没有匹配到任何判断条件,那么服务器将会一直没有响应,浏览器一直都处在挂起的状态。要解决这样的问题,我们可以在服务器不知道如何处理请求时,发送404(Not Found)状态码给客户端。server.js部分代码修改如下:

} else {
res.writeHead(404);
res.end('Not Found');
}

  这个时候,我们才真正完成了第一个HTTP Web服务器!!!