我們先大致看看NodeJS提供了哪些和網(wǎng)絡(luò)操作有關(guān)的API。這里并不逐一介紹每個(gè)API的使用方法,官方文檔已經(jīng)做得很好了。
'http'模塊提供兩種使用方式:
作為服務(wù)端使用時(shí),創(chuàng)建一個(gè)HTTP服務(wù)器,監(jiān)聽HTTP客戶端請(qǐng)求并返回響應(yīng)。
首先我們來看看服務(wù)端模式下如何工作。如開門紅中的例子所示,首先需要使用.createServer
方法創(chuàng)建一個(gè)服務(wù)器,然后調(diào)用.listen
方法監(jiān)聽端口。之后,每當(dāng)來了一個(gè)客戶端請(qǐng)求,創(chuàng)建服務(wù)器時(shí)傳入的回調(diào)函數(shù)就被調(diào)用一次??梢钥闯?,這是一種事件機(jī)制。
HTTP請(qǐng)求本質(zhì)上是一個(gè)數(shù)據(jù)流,由請(qǐng)求頭(headers)和請(qǐng)求體(body)組成。例如以下是一個(gè)完整的HTTP請(qǐng)求數(shù)據(jù)內(nèi)容。
POST / HTTP/1.1
User-Agent: curl/7.26.0
Host: localhost
Accept: */*
Content-Length: 11
Content-Type: application/x-www-form-urlencoded
Hello World
可以看到,空行之上是請(qǐng)求頭,之下是請(qǐng)求體。HTTP請(qǐng)求在發(fā)送給服務(wù)器時(shí),可以認(rèn)為是按照從頭到尾的順序一個(gè)字節(jié)一個(gè)字節(jié)地以數(shù)據(jù)流方式發(fā)送的。而http
模塊創(chuàng)建的HTTP服務(wù)器在接收到完整的請(qǐng)求頭后,就會(huì)調(diào)用回調(diào)函數(shù)。在回調(diào)函數(shù)中,除了可以使用request
對(duì)象訪問請(qǐng)求頭數(shù)據(jù)外,還能把request
對(duì)象當(dāng)作一個(gè)只讀數(shù)據(jù)流來訪問請(qǐng)求體數(shù)據(jù)。以下是一個(gè)例子。
http.createServer(function (request, response) {
var body = [];
console.log(request.method);
console.log(request.headers);
request.on('data', function (chunk) {
body.push(chunk);
});
request.on('end', function () {
body = Buffer.concat(body);
console.log(body.toString());
});
}).listen(80);
------------------------------------
POST
{ 'user-agent': 'curl/7.26.0',
host: 'localhost',
accept: '*/*',
'content-length': '11',
'content-type': 'application/x-www-form-urlencoded' }
Hello World
HTTP響應(yīng)本質(zhì)上也是一個(gè)數(shù)據(jù)流,同樣由響應(yīng)頭(headers)和響應(yīng)體(body)組成。例如以下是一個(gè)完整的HTTP請(qǐng)求數(shù)據(jù)內(nèi)容。
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 11
Date: Tue, 05 Nov 2013 05:31:38 GMT
Connection: keep-alive
Hello World
在回調(diào)函數(shù)中,除了可以使用response
對(duì)象來寫入響應(yīng)頭數(shù)據(jù)外,還能把response
對(duì)象當(dāng)作一個(gè)只寫數(shù)據(jù)流來寫入響應(yīng)體數(shù)據(jù)。例如在以下例子中,服務(wù)端原樣將客戶端請(qǐng)求的請(qǐng)求體數(shù)據(jù)返回給客戶端。
http.createServer(function (request, response) {
response.writeHead(200, { 'Content-Type': 'text/plain' });
request.on('data', function (chunk) {
response.write(chunk);
});
request.on('end', function () {
response.end();
});
}).listen(80);
接下來我們看看客戶端模式下如何工作。為了發(fā)起一個(gè)客戶端HTTP請(qǐng)求,我們需要指定目標(biāo)服務(wù)器的位置并發(fā)送請(qǐng)求頭和請(qǐng)求體,以下示例演示了具體做法。
var options = {
hostname: 'www.example.com',
port: 80,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
};
var request = http.request(options, function (response) {});
request.write('Hello World');
request.end();
可以看到,.request
方法創(chuàng)建了一個(gè)客戶端,并指定請(qǐng)求目標(biāo)和請(qǐng)求頭數(shù)據(jù)。之后,就可以把request
對(duì)象當(dāng)作一個(gè)只寫數(shù)據(jù)流來寫入請(qǐng)求體數(shù)據(jù)和結(jié)束請(qǐng)求。另外,由于HTTP請(qǐng)求中GET
請(qǐng)求是最常見的一種,并且不需要請(qǐng)求體,因此http
模塊也提供了以下便捷API。
http.get('http://www.example.com/', function (response) {});
當(dāng)客戶端發(fā)送請(qǐng)求并接收到完整的服務(wù)端響應(yīng)頭時(shí),就會(huì)調(diào)用回調(diào)函數(shù)。在回調(diào)函數(shù)中,除了可以使用response
對(duì)象訪問響應(yīng)頭數(shù)據(jù)外,還能把response
對(duì)象當(dāng)作一個(gè)只讀數(shù)據(jù)流來訪問響應(yīng)體數(shù)據(jù)。以下是一個(gè)例子。
http.get('http://www.example.com/', function (response) {
var body = [];
console.log(response.statusCode);
console.log(response.headers);
response.on('data', function (chunk) {
body.push(chunk);
});
response.on('end', function () {
body = Buffer.concat(body);
console.log(body.toString());
});
});
------------------------------------
200
{ 'content-type': 'text/html',
server: 'Apache',
'content-length': '801',
date: 'Tue, 05 Nov 2013 06:08:41 GMT',
connection: 'keep-alive' }
<!DOCTYPE html>
...
https
模塊與http
模塊極為類似,區(qū)別在于https
模塊需要額外處理SSL證書。
在服務(wù)端模式下,創(chuàng)建一個(gè)HTTPS服務(wù)器的示例如下。
var options = {
key: fs.readFileSync('./ssl/default.key'),
cert: fs.readFileSync('./ssl/default.cer')
};
var server = https.createServer(options, function (request, response) {
// ...
});
可以看到,與創(chuàng)建HTTP服務(wù)器相比,多了一個(gè)options
對(duì)象,通過key
和cert
字段指定了HTTPS服務(wù)器使用的私鑰和公鑰。
另外,NodeJS支持SNI技術(shù),可以根據(jù)HTTPS客戶端請(qǐng)求使用的域名動(dòng)態(tài)使用不同的證書,因此同一個(gè)HTTPS服務(wù)器可以使用多個(gè)域名提供服務(wù)。接著上例,可以使用以下方法為HTTPS服務(wù)器添加多組證書。
server.addContext('foo.com', {
key: fs.readFileSync('./ssl/foo.com.key'),
cert: fs.readFileSync('./ssl/foo.com.cer')
});
server.addContext('bar.com', {
key: fs.readFileSync('./ssl/bar.com.key'),
cert: fs.readFileSync('./ssl/bar.com.cer')
});
在客戶端模式下,發(fā)起一個(gè)HTTPS客戶端請(qǐng)求與http
模塊幾乎相同,示例如下。
var options = {
hostname: 'www.example.com',
port: 443,
path: '/',
method: 'GET'
};
var request = https.request(options, function (response) {});
request.end();
但如果目標(biāo)服務(wù)器使用的SSL證書是自制的,不是從頒發(fā)機(jī)構(gòu)購買的,默認(rèn)情況下https
模塊會(huì)拒絕連接,提示說有證書安全問題。在options
里加入rejectUnauthorized: false
字段可以禁用對(duì)證書有效性的檢查,從而允許https
模塊請(qǐng)求開發(fā)環(huán)境下使用自制證書的HTTPS服務(wù)器。
處理HTTP請(qǐng)求時(shí)url
模塊使用率超高,因?yàn)樵撃K允許解析URL、生成URL,以及拼接URL。首先我們來看看一個(gè)完整的URL的各組成部分。
href
-----------------------------------------------------------------
host path
--------------- ----------------------------
http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
----- --------- -------- ---- -------- ------------- -----
protocol auth hostname port pathname search hash
------------
query
我們可以使用.parse
方法來將一個(gè)URL字符串轉(zhuǎn)換為URL對(duì)象,示例如下。
url.parse('http://user:pass@host.com:8080/p/a/t/h?query=string#hash');
/* =>
{ protocol: 'http:',
auth: 'user:pass',
host: 'host.com:8080',
port: '8080',
hostname: 'host.com',
hash: '#hash',
search: '?query=string',
query: 'query=string',
pathname: '/p/a/t/h',
path: '/p/a/t/h?query=string',
href: 'http://user:pass@host.com:8080/p/a/t/h?query=string#hash' }
*/
傳給.parse
方法的不一定要是一個(gè)完整的URL,例如在HTTP服務(wù)器回調(diào)函數(shù)中,request.url
不包含協(xié)議頭和域名,但同樣可以用.parse
方法解析。
http.createServer(function (request, response) {
var tmp = request.url; // => "/foo/bar?a=b"
url.parse(tmp);
/* =>
{ protocol: null,
slashes: null,
auth: null,
host: null,
port: null,
hostname: null,
hash: null,
search: '?a=b',
query: 'a=b',
pathname: '/foo/bar',
path: '/foo/bar?a=b',
href: '/foo/bar?a=b' }
*/
}).listen(80);
.parse
方法還支持第二個(gè)和第三個(gè)布爾類型可選參數(shù)。第二個(gè)參數(shù)等于true
時(shí),該方法返回的URL對(duì)象中,query
字段不再是一個(gè)字符串,而是一個(gè)經(jīng)過querystring
模塊轉(zhuǎn)換后的參數(shù)對(duì)象。第三個(gè)參數(shù)等于true
時(shí),該方法可以正確解析不帶協(xié)議頭的URL,例如//www.example.com/foo/bar
。
反過來,format
方法允許將一個(gè)URL對(duì)象轉(zhuǎn)換為URL字符串,示例如下。
url.format({
protocol: 'http:',
host: 'www.example.com',
pathname: '/p/a/t/h',
search: 'query=string'
});
/* =>
'http://www.example.com/p/a/t/h?query=string'
*/
另外,.resolve
方法可以用于拼接URL,示例如下。
url.resolve('http://www.example.com/foo/bar', '../baz');
/* =>
http://www.example.com/baz
*/
querystring
模塊用于實(shí)現(xiàn)URL參數(shù)字符串與參數(shù)對(duì)象的互相轉(zhuǎn)換,示例如下。
querystring.parse('foo=bar&baz=qux&baz=quux&corge');
/* =>
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
*/
querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
/* =>
'foo=bar&baz=qux&baz=quux&corge='
*/
zlib
模塊提供了數(shù)據(jù)壓縮和解壓的功能。當(dāng)我們處理HTTP請(qǐng)求和響應(yīng)時(shí),可能需要用到這個(gè)模塊。
首先我們看一個(gè)使用zlib
模塊壓縮HTTP響應(yīng)體數(shù)據(jù)的例子。這個(gè)例子中,判斷了客戶端是否支持gzip,并在支持的情況下使用zlib
模塊返回gzip之后的響應(yīng)體數(shù)據(jù)。
http.createServer(function (request, response) {
var i = 1024,
data = '';
while (i--) {
data += '.';
}
if ((request.headers['accept-encoding'] || '').indexOf('gzip') !== -1) {
zlib.gzip(data, function (err, data) {
response.writeHead(200, {
'Content-Type': 'text/plain',
'Content-Encoding': 'gzip'
});
response.end(data);
});
} else {
response.writeHead(200, {
'Content-Type': 'text/plain'
});
response.end(data);
}
}).listen(80);
接著我們看一個(gè)使用zlib
模塊解壓HTTP響應(yīng)體數(shù)據(jù)的例子。這個(gè)例子中,判斷了服務(wù)端響應(yīng)是否使用gzip壓縮,并在壓縮的情況下使用zlib
模塊解壓響應(yīng)體數(shù)據(jù)。
var options = {
hostname: 'www.example.com',
port: 80,
path: '/',
method: 'GET',
headers: {
'Accept-Encoding': 'gzip, deflate'
}
};
http.request(options, function (response) {
var body = [];
response.on('data', function (chunk) {
body.push(chunk);
});
response.on('end', function () {
body = Buffer.concat(body);
if (response.headers['content-encoding'] === 'gzip') {
zlib.gunzip(body, function (err, data) {
console.log(data.toString());
});
} else {
console.log(data.toString());
}
});
}).end();
net
模塊可用于創(chuàng)建Socket服務(wù)器或Socket客戶端。由于Socket在前端領(lǐng)域的使用范圍還不是很廣,這里先不涉及到WebSocket的介紹,僅僅簡(jiǎn)單演示一下如何從Socket層面來實(shí)現(xiàn)HTTP請(qǐng)求和響應(yīng)。
首先我們來看一個(gè)使用Socket搭建一個(gè)很不嚴(yán)謹(jǐn)?shù)腍TTP服務(wù)器的例子。這個(gè)HTTP服務(wù)器不管收到啥請(qǐng)求,都固定返回相同的響應(yīng)。
net.createServer(function (conn) {
conn.on('data', function (data) {
conn.write([
'HTTP/1.1 200 OK',
'Content-Type: text/plain',
'Content-Length: 11',
'',
'Hello World'
].join('\n'));
});
}).listen(80);
接著我們來看一個(gè)使用Socket發(fā)起HTTP客戶端請(qǐng)求的例子。這個(gè)例子中,Socket客戶端在建立連接后發(fā)送了一個(gè)HTTP GET請(qǐng)求,并通過data
事件監(jiān)聽函數(shù)來獲取服務(wù)器響應(yīng)。
var options = {
port: 80,
host: 'www.example.com'
};
var client = net.connect(options, function () {
client.write([
'GET / HTTP/1.1',
'User-Agent: curl/7.26.0',
'Host: www.baidu.com',
'Accept: */*',
'',
''
].join('\n'));
});
client.on('data', function (data) {
console.log(data.toString());
client.end();
});
更多建議: