要开发 HTTP 服务器程序,从头处理 TCP 连接,解析 HTTP 是不现实的。这些工作实际上已经由 Node.js 自带的 http 模块完成了。应用程序并不直接和 HTTP 协议打交道,而是操作 http 模块提供的 request 和 response 对象。
request & response
request : 封装了 HTTP 请求,我们调用 request 对象的属性和方法就可以拿到所有 HTTP 请求的信息;
response : 封装了 HTTP 响应,我们操作 response 对象的方法,就可以把 HTTP 响应返回给浏览器。
下面是一个简单的服务器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'use strict' ;
var http = require ('http' );
var server = http.createServer(function (request, response ) {
console .log(request.method + ': ' + request.url);
response.writeHead(200 , {'Content-Type' : 'text/html' });
response.end('<h1>Hello world!</h1>' );
});
server.listen(8080 );
console .log('Server is running at http://127.0.0.1:8080/' );
在命令提示符下运行该程序,可以看到以下输出:
1
2
$ node web.js
Server is running at http://127.0.0.1:8080/
文件服务器 让我们继续扩展上面的 Web 程序。我们可以设定一个目录,然后让 Web 程序变成一个文件服务器。要实现这一点,我们只需要解析 request.url 中的路径,然后在本地找到对应的文件,把文件内容发送出去就可以了。
解析 URL 需要用到 Node.js 提供的 url 模块,它使用起来非常简单,通过 parse() 将一个字符串解析为一个 Url 对象:
1
2
3
4
'use strict' ;
var url = require ('url' );
var requestUrl = 'http://user:pass@host.com:8080/path/to/file?query=string#hash' ;
var urlObj = url.parse(requestUrl);
使用 url.parse() 解析获取到的 urlObj 对象包含如下内容:
protocol: ‘http:’,
slashes: true,
auth: ‘user:pass’,
host: ‘host.com:8080’,
port: ‘8080’,
hostname: ‘host.com’,
hash : ‘#hash’,
search : ‘?query=string’,
query : ‘query=string’,
pathname : ‘/path/to/file’,
path: ‘/path/to/file?query=string’,
href: ‘http://user:pass@host.com:8080/path/to/file?query=string#hash ‘
处理本地文件目录需要使用 Node.js 提供的 path 模块,它可以方便地构造目录:
1
2
3
4
5
6
7
'use strict' ;
var path = require ('path' );
var workDir = path.resolve('.' );
var filePath = path.join(workDir, 'pub' , 'index.html' );
使用 path 模块可以正确处理操作系统相关的文件路径。在 Windows 系统下,返回的路径类似于C:\Users\a284628487\static\index.html
,这样,我们就不关心怎么拼接路径了。
最后,我们实现一个文件服务器 file_server.js :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
'use strict' ;
var
fs = require ('fs' ),
url = require ('url' ),
path = require ('path' ),
http = require ('http' );
var root = path.resolve(process.argv[2 ] || '.' );
console .log('Static root dir: ' + root);
var server = http.createServer(function (request, response ) {
var pathname = url.parse(request.url).pathname;
var filepath = path.join(root, pathname);
fs.stat(filepath, function (err, stats ) {
if (!err && stats.isFile()) {
console .log('200 ' + request.url);
response.writeHead(200 );
fs.createReadStream(filepath).pipe(response);
} else {
console .log('404 ' + request.url);
response.writeHead(404 );
response.end('404 Not Found' );
}
});
});
server.listen(8080 );
console .log('Server is running at http://127.0.0.1:8080/' );
注:没有必要手动读取文件内容。由于 response 对象本身是一个 Writable Stream ,直接用 pipe() 方法就实现了自动读取文件内容并输出到 HTTP 响应。
在命令行运行node file_server.js /path/to/dir
,把 /path/to/dir
改成本地的一个有效的目录,然后在浏览器中输入http://localhost:8080/index.html
。
只要当前目录下存在文件 index.html ,服务器就可以把文件内容发送给浏览器。观察控制台输出:
1
2
3
4
200 /index.html
200 /css/uikit.min.css
200 /js/jquery.min.js
200 /fonts/fontawesome-webfont.woff2
第一个请求是浏览器请求 index.html 页面,后续请求是浏览器解析 HTML 后发送的其它资源请求。
GET & POST
由于 GET 请求直接被嵌入在路径中,URL 是完整的请求路径,包括了 ? 后面的部分,因此可以手动解析后面的内容作为 GET 请求的参数。url 模块中的 parse() 函数提供了这个功能。
1
2
3
4
5
6
7
8
var http = require ('http' );
var url = require ('url' );
var util = require ('util' );
http.createServer(function (req, res ) {
res.writeHead(200 , {'Content-Type' : 'text/plain' });
res.end(util.inspect(url.parse(req.url, true )));
}).listen(3000 );
在浏览器中访问http://localhost:3000/user?name=w3c&email=w3c@w3cschool.cc
然后查看返回结果。
POST 请求的内容全部的都在请求体中,http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作。比如上传文件,而很多时候我们可能并不需要理会请求体的内容,恶意的 POST 请求会大大消耗服务器的资源,所有 node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var http = require ('http' );
var querystring = require ('querystring' );
var util = require ('util' );
http.createServer(function (req, res ) {
var post = '' ;
req.on('data' , function (chunk ) {
post += chunk;
});
req.on('end' , function ( ) {
post = querystring.parse(post);
res.end(util.inspect(post));
});
}).listen(3000 );
1
2
3
4
5
6
7
8
9
10
11
12
{ host : '127.0.0.1:8081' ,
connection : 'keep-alive' ,
'cache-control' : 'max-age=0' ,
'upgrade-insecure-requests' : '1' ,
'user-agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' ,
accept : 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' ,
'accept-encoding' : 'gzip, deflate, sdch' ,
'accept-language' : 'zh-CN,zh;q=0.8,en;q=0.6' ,
cookie : '_ga=GA1.1.988884254.1470363885; uid=1991; uname=ccf' ,
'if-none-match' : 'W/"2-156367aed28"' ,
'if-modified-since' : 'Fri, 29 Jul 2016 11:47:21 GMT'
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[ 'Host' ,
'127.0.0.1:8081' ,
'Connection' ,
'keep-alive' ,
'Cache-Control' ,
'max-age=0' ,
'Upgrade-Insecure-Requests' ,
'1' ,
'User-Agent' ,
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36' ,
'Accept' ,
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' ,
'Accept-Encoding' ,
'gzip, deflate, sdch' ,
'Accept-Language' ,
'zh-CN,zh;q=0.8,en;q=0.6' ,
'Cookie' ,
'_ga=GA1.1.988884254.1470363885; uid=1991; uname=ccf' ,
'If-None-Match' ,
'W/"2-156367aed28"' ,
'If-Modified-Since' ,
'Fri, 29 Jul 2016 11:47:21 GMT'
]
cookie 1
2
3
var cookie = req.headers.cookie;
var cookie = req.headers['cookie' ];
response 可以使用 response 对象的 setHeader 和 writeHead 写入响应头信息。
1
2
res.writeHead(200 , {'Content-Type' : 'text/plain' });
res.setHeader('Content-Type' , 'text/plain' );
write 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fs.readFile(pathname.substr(1 ), function (err, data ) {
if (err) {
console .log(err);
response.writeHead(404 , {'Content-Type' : 'text/html' });
} else {
response.writeHead(200 , {'Content-Type' : 'text/html' });
response.write(data.toString());
}
response.end();
});
cookie 1
2
resp.setHeader('Set-Cookie' , "age=26" );
resp.setHeader('Set-Cookie' , ['a=000' , 't=1111' , 'w=2222' ]);
Client Web 应用架构,目前最主流的三个 Web 服务器是Apache
、Nginx
、IIS
。
Client 客户端,一般指浏览器,浏览器可以通过 HTTP 协议向服务器请求数据。
Server 服务端,一般指 Web 服务器,可以接收客户端请求,并向客户端发送响应数据。
Business 业务层, 通过 Web 服务器处理应用程序,如与数据库交互,逻辑运算,调用外部程序等。
Data 数据层,一般由数据库组成。
使用 http 发起请求:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var http = require ('http' );
var options = {
host : 'localhost' ,
port : '8081' ,
path : '/index.htm'
};
var callback = function (response ) {
var body = '' ;
response.on('data' , function (data ) {
body += data;
});
response.on('end' , function ( ) {
console .log(body);
});
}
var req = http.request(options, callback);
req.end();
端口 端口的作用:通过端口来区分出同一电脑内不同应用或者进程,从而实现一条物理网线(通过分组交换技术-比如internet)同时链接多个程序。端口号是一个 16位的uint , 所以其范围为 1 to 65535 (对TCP来说, port 0 被保留,不能被使用. 对于UDP来说, source端的端口号是可选的,为0时表示无端口).app.listen(3000) ,进程就被打标,电脑接收到的3000端口的网络消息就会被发送给我们启动的这个进程;