HTTP
是Node
的一个核心模块,同时也是一些很底层的API
。
Node
有为数不多的内置模块,HTTP
是其中之一,fs
也是。这些内置模块你通常可以在Node
的官方网站的DOCS
获取并了解他们的使用方法和API
。HTTP
的相关API
放在了这里:https://nodejs.org/dist/latest-v6.x/docs/api/http.html
这是
v6.11.0
的版本,就是写博客的最近版本。可以在官网上获取到最新版本。其中标记为Stability:2 - Stable
的表示为可放心食用的API
,标记为Stability:1 - Experimental
的表示为试验性质的API
,标记为Stability:0 - Deprecated
的表示为被弃用的API
。
在文档中很简要地介绍了一句:
To use the HTTP server and client one must require('http').
基础
- 引入
http
模块。
var http = require('http');
- 创建
HTTP
服务器
创建HTTP
服务器要调用http.createServer()
函数。它只有一个回调函数,服务器每次收到HTTP
请求后都会调用这个回调函数。
这个请求回调会收到两个参数,请求对象 – req
,响应对象 – res
。
var http = require('http');
var server = http.createServer (function (req,res){
// 处理请求
});
服务器每收到一条HTTP
请求,都会用新的req
和res
对象触发回调函数。注意,在触发回调函数之前,Node
会解析请求的HTTP
头,并将他们作为req
对象的一部分提供给回调函数。但Node
不会在回调函数被触发之前对请求体的解析,HTTP
服务器解析完HTTP
头,就将控制权转交给回调函数。
- 处理请求
你可以按照自己的需求处理请求,我们这里首先调用res.write()
方法,将会响应一个数据块,然后用res.end()
方法来结束这个响应。
var http = require('http');
var server = http.createServer (function (req,res){
res.write ('Hello World');
res.end ();
});
res.write ()
和res.end ()
也能简写成res.end('Hello World')
。
在res.write ()
和res.end ()
之前,还可以对HTTP
的响应头部字段进行修改。方法有res.setHeader(field,value)
、res.getHeader(field)
、res.removeHeader(field)
。上面的例子中,我们使用了默认的状态码200
和默认的响应头。
var body = 'Hello World';
res.setHeader ('Content-Length', body.length);
res.setHeader ('Content-Type', 'text/plain');
res.statusCode = 200;
res.end(body);
- 绑定一个端口
var http = require('http');
var server = http.createServer (function (req,res){
res.write ('Hello World');
res.end ();
});
server.listen(3000);
实例
RESTful Web服务
REST
(Representational State Transfer)是一种使用HTTP
谓词提供精简的API
服务。按照惯例,HTTP
谓词GET
、POST
、PUT
、DELETE
,分别根由URL
指定的资源的获取、创建、更新和移除相对应。
具体的
RESTful
服务,我建议参考StackOverflow关于这个的问答: https://stackoverflow.com/questions/671118/what-exactly-is-restful-programming
其次,我们在这里使用到了cURL
这个工具,代替Web浏览器来于Web服务器交互。
cURL
的官网在这里: https://curl.haxx.se/。github
的地址: https://github.com/curl/curl。官网有关于cURL
的全部教程。
cRUL
是个很棒的工具,自己也只是了解一点点。比如我们输入: curl -I http://xiaokedada.com
, 则我们得到:
HTTP/1.1 200 OK
Date: Thu, 08 Jun 2017 02:08:09 GMT
Server: Apache/2.4.23
Last-Modified: Wed, 07 Jun 2017 14:01:00 GMT
ETag: "13c00c3-8e4d-5515f2a869975"
Accept-Ranges: bytes
Content-Length: 36429
Vary: Accept-Encoding,User-Agent
Content-Type: text/html
更多有关cURL
工具待后续了解,那个时候,也会另开一篇文章吧。还要介绍的一点就是有关cURL
的安装。我们在下载页根据系统找到对应的版本,比如我下载的就是Win64 x86_64 CAB
版本,然后在解压到本地文件夹,在1386
文件夹中找到CURL.exe
,将它拷贝到Windows/System32
文件中。
现在,我们RESTful
的例子如下:
var http = require('http'),
url = require('url'),
items = []
var server = http.createServer(function (req, res) {
switch (req.method) {
case 'POST': //请求资源
var item = ''
req.setEncoding('utf8') //对于文本格式的待办事项而言,我们不需要二进制,将它设定为utf8
req.on('data', function (chunk) { //只要读入新的utf8字符串,就能触发data事件
item += chunk
})
req.on('end', function () {
items.push(item)
res.end('OK\n')
})
break
case 'GET': //获取资源
items.forEach(function (item, i) {
res.write(i + ')' + item + '\n')
})
res.end()
break
case 'DELETE':
var path = url.parse(req.url).pathname
var i = parseInt(path.slice(1), 10)
if (isNaN(i)) {
res.statusCode = 404
res.end('Invalid item id')
} else if (!items[i]) {
res.end('Item not found')
} else {
items.splice(i, 1)
res.end('OK\n')
}
break
}
});
server.listen(3000);
特别注意的是,url
模块可以的parse()
可以解析出pathname
。让我们在Node
的REPL
( Read–Eval–Print Loop)环境,也就是我们的命令行环境验证以下,还是以本博客首页为例。
> require('url').parse('http://xiaokdedada.com');
Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'xiaokdedada.com',
port: null,
hostname: 'xiaokdedada.com',
hash: null,
search: null,
query: null,
pathname: '/',
path: '/',
href: 'http://xiaokdedada.com/' }
为了简单,我们的RESTful
省略了PUT
谓词。我们来验证我们的RESTful
服务器。首先,我们运行我们的.js
文件,开启3000
端口。然后,再开一个命令行:
$ curl -d "Hello World" http://localhost:3000
OK
$ curl -d "I am timi" http://localhost:3000
OK
$ curl http://localhost:3000
0) Hello World
1) I am timi
很棒是不是?
提供文件服务
文件服务通常涉及到的是文件的读写,在程序中,我们通常用的是管道(pipe
)。
var stream = fs.createReadStream(path);
stream.pipe(res); //res是响应,res.end()会在stream.pipe()内部调用
在
Node
中,所有继承了EventEmitter
的类都有可能有error
事件。对于fs.ReadStream
这样的专用EventEmitter
,有预先定义的data
和end
事件。
从表单中接受用户输入
我们看这个例子:
var http = require('http'),
qs = require('querystring'),
items = [];
var server = http.createServer (function(req,res){
if ('/' == req.url){ //所有不是"/"的URL都会得到一个404 Not Found
switch (req.method){
case 'GET':
show(res);
break;
case 'POST':
add(req,res);
break;
default:
badRequest(res);
}
} else {
notFound(res);
}
});
server.listen(3000);
function show (res){
var html = '<html><head><title>Todo List</title></head><body>' + '<h1>Todo List</h1>' + '<ul>' + items.map (function(item){
return '<li>' + item + '</li>'
}).join('') + '</ul>' + '<form method="post" action="/">' + '<p><input type="text" name="item" /></p>' + '<p><input type="submit" value="Add Item" /></p>' + '</form></body></html>';
res.setHeader('Content-Type','text/html');
res.setHeader('Content-Length', Buffer.byteLength(html));
res.end (html);
}
function notFound (res){
res.statusCode = 404;
res.setHeader ('Content-Type', 'text/plain');
res.end ('Not Found');
}
function badRequest (res){
res.statusCode = 400;
res.setHeader ('Content-Type', 'text/plain');
res.end ('Bad Request');
}
function add (req, res){
var body = '';
req.setEncoding ('utf8');
req.on ('data', function(chunk){body += chunk});
req.on ('end', function(){
var obj = qs.parse(body);
items.push (obj.item);
show (res);
});
}
在add()
函数解析请求实体时,我们用到了Node
的querystring
模块。官方文档中是这样介绍的:
The querystring module provides utilities for parsing and formatting URL query strings.
看一下我们在REPL
上的解析。
> var qs = require('querystring');var body = 'item=timi+I+love+you';qs.parse(body);
{ item: 'timi I love you' }