核心模块由一些精简高效的库组成,提供了基本的API
Node.js出现的目的就是编写高性能Web服务器,因此http模块提供了一个高效的HTTP服务器和一个简易的HTTP客户端
http.Server 是一个基于事件的HTTP 服务器,它的核心由Node.js 下层C++ 部分实现,而接口由JavaScript 封装,兼顾了高性能与简易性
http.request 则是一个HTTP 客户端,用于向HTTP 服务器发起请求
http.Server 是 http 模块中的 HTTP 服务器对象,用 Node.js 做的所有基于 HTTP 协议的系统都是基于 http.Server 实现的。它提供了一套封装级别很低的 API,仅仅是流控制和简单的消息解析,所有的高层功能都要通过它的接口来实现
官方示例抢先看
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337);
console.log('Server running at http://127.0.0.1:1337/');
http.createServer()创建了一个 http.Server 的实例对象,将一个函数作为 HTTP 请求处理函数。这个函数接受两个参数,分别是请求对象( req )和响应对象( res )。在函数体内, res 显式地写回了响应代码 200 (表示请求成功),指定响应头为'Content-Type': 'text/html' ,然后写入响应体 '<h1>Node.js</h1>' ,通过 res.end结束并发送。最后该实例还调用了 listen 函数,启动服务器并监听 3000 端口
注:在终端中运行这个脚本时,并不会结束后立即退出,而是一直等待,直到按下Ctrl + C 才会结束。这是因为listen 函数中创建了事件监听器,使得Node.js进程不会退出事件循环
注:在Linux系统下,监听1024以下端口需要root权限,有两种方式可以解决
一种方式是使用sudo命令运行Node.js(一般推荐这种方式,这样可以保证仅为有需要的JS脚本提供root权限)
sudo node server.js
另一种方式是使用chmod +s 命令让Node.js总是以root权限运行(因为这种方式让任何JS脚本都有了root权限,不太安全)
sudo chown root /usr/local/bin/node
sudo chmod +s /usr/local/bin/node
在JavaScript中,一个函数可以作为另一个函数的参数传递。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。
Node.js中函数的使用与Javascript类似
function say(word) {
console.log(word);
}
function execute(someFunction, value) {
someFunction(value);
}
execute(say, "Hello");
我们把 say 函数作为execute函数的第一个参数进行了传递。这里传递的不是 say 的返回值,而是 say 本身!
这样一来, say 就变成了execute 中的本地变量 someFunction ,execute可以通过调用 someFunction() (带括号的形式)来使用 say 函数。
当然,因为 say 有一个变量, execute 在调用 someFunction 时可以传递这样一个变量。
我们可以把一个函数作为变量传递。但是我们不一定要绕这个"先定义,再传递"的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:
function execute(someFunction, value) {
someFunction(value);
}
execute(function(word){ console.log(word) }, "Hello");
我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数 。
带着这些知识,我们再来看看我们简约而不简单的HTTP服务器:
var http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
现在它看上去应该清晰了很多:我们向 createServer 函数传递了一个匿名函数。
用这样的代码也可以达到同样的目的:
var http = require("http");
function onRequest(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
http.Server 是一个基于事件的HTTP 服务器,所有的请求都被封装为独立的事件,只需要对它的事件编写响应函数即可实现HTTP 服务器的所有功能
包括以下几个事件
当客户端请求到来时,该事件被触发,回调函数中提供两个参数 req 和res,分别是http.ServerRequest 对象和 http.ServerResponse 的实例,表示请求和响应信息
当TCP 连接建立时,该事件被触发,提供一个参数 socket,为net.Socket 的实例。connection 事件的粒度要大于 request,因为客户端在Keep-Alive 模式下可能会在同一个连接内发送多次请求
当服务器关闭时,该事件被触发。注意不是在用户连接断开时
除此之外还有 checkContinue、upgrade、clientError 事件
在这些事件中,最常用的就是 request了,因此http 提供了一个简式写法
http.createServer([requestListener])
功能是创建一个HTTP 服务器并将requestListener 作为 request 事件的监听函数,这也是官方示例中使用的方法
事实上它显式的实现方法是
var http = require('http');
var server = new http.Server();
server.on('request', function(req, res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Node.js</h1>');
res.end('<p>Hello World</p>');
});
server.listen(3000);
console.log("HTTP server is listening at port 3000.");
下面着重讲解一下,request事件发生时,回调函数的两个参数req和res,分别对应 http.ServerRequest对象和http.ServerResponse对象
http.ServerRequest 是HTTP 请求的信息,一般由http.Server 的 request 事件发送,作为回调函数的第一个参数传递,通常简称 request 或 req
HTTP 请求一般可以分为两部分:请求头(Request Header)和请求体(Requset Body)
以上内容由于长度较短都可以在请求头解析完成后立即读取。而请求体可能相对较长,需要一定的时间传输,因此 http.ServerRequest 提供了以下3个事件用于控制请求体传输
data事件:当请求体数据到来时,该事件被触发。该事件提供一个参数 chunk,表示接收到的数据。如果该事件没有被监听,那么请求体将会被抛弃。该事件可能会被调用多次。
end事件 :当请求体数据传输完成时,该事件被触发,此后将不会再有数据到来。
close事件: 用户当前请求结束(或中断)时,该事件被触发。不同于 end,如果用户强制终止了传输,也会调用close
ServerRequest 提供一些属性
complete 客户端请求是否已经发送完成(true、false)
httpVersion HTTP 协议版本,通常是1.0 或1.1
method HTTP 请求方法,如 GET、POST、PUT、DELETE 等
url 原始的请求路径,例如/static/image/x.jpg 或/user?name=byvoid
headers HTTP 请求头
trailers HTTP 请求尾(不常见)
connection 当前HTTP 连接套接字,为net.Socket 的实例
socket connection属性的别名
client client属性的别名
可以看到http.ServerRequest的属性中并没有类似于PHP中的$GR或者$POST,那么如何接收客户端的表单请求呢?
由于 GET 请求直接被嵌入在路径中, URL是完整的请求路径,包括了 ? 后面的部分,因此你可以手动解析后面的内容作为 GET请求的参数
Node.js 的 url 模块中的 parse 函数提供了这个功能
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://127.0.0.1:3000/user?name=byvoid&[email protected],我们可以看到浏览器返回的结果
{
search: '?name=byvoid&[email protected]',
query: { name: 'byvoid', email: '[email protected]' },
pathname: '/user',
path: '/user?name=byvoid&[email protected]',
href: '/user?name=byvoid&[email protected]'
}
通过 url.parse,原始的 path 被解析为一个对象,其中 query 就是我们所谓的 GET
请求的内容,而路径则是 pathname
GET 请求把所有的内容编码到访问路径中, POST 请求的内容全部都在请求体中。http.ServerRequest 并没有一个属性内容为请求体,原因是等待请求体传输可能是一件耗时的工作,所以 Node.js 默认是不会解析请求体的,当你需要的时候,需要手动来做
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);
上面代码并没有在请求响应函数中向客户端返回信息,而是定义了一个 post 变量,用
于在闭包中暂存请求体的信息。通过 req 的 data 事件监听函数,每当接受到请求体的数据,就累加到 post 变量中。在 end 事件触发后,通过 querystring.parse 将 post 解析为真正的 POST 请求格式,然后向客户端返回。
注:不要在真正的生产应用中使用上面这种简单的方法来获取 POST 请求,因为它有严重的效率问题和安全问题,这只是示例
http.ServerResponse 是返回给客户端的信息,决定了用户最终能看到的结果。它也是由 http.Server 的 request 事件的回调函数中发送,作为第二个参数传递,一般简称为
response 或 res
response.writeHead(statusCode, [headers])
向请求的客户端发送响应头。statusCode 是HTTP 状态码,如200 (请求成功)、404 (未找到)等。headers 是一个类似关联数组的对象,表示响应头的每个属性。该函数在一个请求内最多只能调用一次,如果不调用,则会自动生成一个响应头
response.write(data, [encoding])
向请求的客户端发送响应内容。data 是一个 Buffer 或字符串,表示要发送的内容。如果 data 是字符串,那么需要指定encoding 来说明它的编码方式,默认是utf-8。在response.end 调用之前,response.write 可以被多次调用。
response.end([data], [encoding])
结束响应,告知客户端所有发送已经完成。当所有要返回的内容发送完毕的时候,该函数 必须 被调用一次。它接受两个可选参数,意义和response.write 相同。如果不调用该函数,客户端将永远处于等待状态
Node.js除了可以作为服务器之外,还可以作为客户端,去向服务器发出HTTP请求
..........
在开发Node.js 实现的HTTP 应用时会发现,无论你修改了代码的哪一部份,都必须终止Node.js 再重新运行才会奏效。这是因为Node.js 只有在第一次引用到某部份时才会去解析脚本文件,以后都会直接访问内存,避免重复载入
Node.js的这种设计虽然有利于提高性能,却不利于开发调试,因为我们在开发过程中总是希望修改后立即看到效果,而不是每次都要终止进程并重启。supervisor 模块可以帮助你实现这个功能,它会监视你对代码的改动,并自动重启Node.js
使用方法很简单,首先使用npm 全局安装supervisor
npm install -g supervisor
如果你使用的是Linux 或Mac,直接键入上面的命令很可能会有权限错误。原因是npm 需要把supervisor 安装到系统目录,需要管理员授权,可以使用 sudo npm install -g supervisor 命令来安装。
接下来,使用supervisor 命令启动app.js
supervisor app.js
当代码被改动时,运行的脚本会被终止,然后重新启动,解决开发中的调试问题
url模块包含了 解析URL的实用函数
作为核心模块,引入很简单
var url = require('url');
不同的URL包含了不同的字段,看一个例子
http://user:[email protected]:8080/p/a/t/h?query=string#hash
href:所解析的完整的URL,协议名和主机名都转换为小写
http://user:[email protected]:8080/p/a/t/h?query=string#hash
protocol:请求的协议,小写
http:
auth:URL中身份验证信息部分
user:pass
host: URL主机名,小写, 包括端口信息
host.com:8080
hostname:主机的主机名部分, 小写
host.com
port::主机的端口号部分
8080
pathname:URL的路径部分,位于主机名(或者端口)之后,查询字符串之前(包括最初的斜线)
/p/a/t/h
search:URL 的“查询字符串”部分,包括开头的问号
?query=string
path:pathname 和 search 连在一起
/p/a/t/h?query=string
query:查询字符串中的参数部分(问号后面部分字符串),或者使用 querystring.parse() 解析后返回的对象
query=string
or
{'query':'string'}
hash::URL 的 “#” 后面部分(包括 # 符号)
返回#hash
该方法用于将URL字符串解析为一个对象,接收三个参数
第一个参数,字符串,必须,表示要解析的URL
第二个参数,布尔值,可选,表示是否使用 querystring 模块来解析 URL 中的查询字符串部分,默认为 false,为true时查询字符串将以对象形式表示
第三个参数,布尔值,可选,表示是否把诸如 //foo/bar 这样的URL解析为 { host: 'foo', pathname: '/bar' } 而不是 { pathname: '//foo/bar' },默认为 false
var http = require('http');
var url = require('url');
http.createServer(function(req,res){
var urlObj = url.parse(req.url);
//可以根据需要取得url的任意部分
console.log(urlObj.host);
console.log(urlObj.protocol);
.........
}).listen(3000);
该方法接收一个被解析的URL 对象,返回格式化后的 URL 字符串(url.parse()方法的反操作)
给定一个基础URL路径,和一个href URL路径,拼接它们
url.resolve('/one/two/three', 'four') '/one/two/four'
url.resolve('http://ex.com/', '/one') 'http://ex.com/one'
url.resolve('http://ex.com/one', '/two') 'http://ex.com/two'
querystring模块包含了处理查询字符串的实用函数,用于实现URL参数字符串与参数对象的互相转换
作为核心模块,引入很简单
var querystring = require('querystring');
该方法用于将参数对象序列化为一个查询字符串,接收四个参数
第一个参数,对象,必须,表示要序列化的参数对象
第二个参数,字符串,可选,表示参数字符串的分隔符,替代"&"符
第三个参数,字符串,可选,表示参数字符串的分配符,替代"="符
第四个参数,对象,可选,表示字符串的编码方式
var querystring = require('querystring');
querystring.stringify({ a: '1', b: ['2', '3'], c: '' })
返回'a=1&b=2&b=3&c='
querystring.stringify({ a: '1', b: '2'},"-","+")
返回'a+1-b+2
querystring.stringify({ a: '中文', b: 'car' }, null, null,{ encodeURIComponent: gbkEncodeURIComponent })
返回'a=%D6%D0%CE%C4&b=car'
该方法用于将一个查询字符串解析为对象
第一个参数,字符串,必须,表示要解析的查询字符串
第二个参数,字符串,可选,表示参数字符串的分隔符,替代"&"符
第三个参数,字符串,可选,表示参数字符串的分配符,替代"="符
第四个参数,对象,可选,表示字符串的编码方式
querystring.parse('foo=bar&baz=qux&baz=quux&corge')
返回{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
用于参数编码
用于参数解码
net模块提供了异步网络封装,包含了一些创建TCP服务器和客户端的接口,使用net模块能很快的开发出基于TCP的服务端和客户端
作为核心模块,引入很简单
var net = require('net');
创建一个新的TCP服务,使用createServer()方法,该方法接收两个参数,返回一个服务器实例对象
var tcpServer = net.createServer([options],[connectionListener])
第一个参数,可选,表示配置项,默认 { allowHalfOpen: false }
第二个参数,可选,表示回调函数(”TCP连接事件“发生时的监听器)
var net = require('net');
var server = net.createServer(function(c){
console.log('server connected');
c.on('end', function() {
console.log('server disconnected');
});
c.write('hello\r\n');
c.pipe(c);
});
server.listen(8124, function() { //'listening' listener
console.log('server bound');
});
除了上面的方法,还可以使用
net.connect(options, [connectionListener])
net.createConnection(options, [connectionListener])
创建一个新的TCP服务器,并且返回创建的socket对象(而不是作为回调函数的参数传入了)
第一个参数,必须,表示配置项,通常包含port、host、localAddress属性
第二个参数,可选,表示回调函数(”TCP连接事件“发生时的监听器)
注:Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求
var net = require('net');
var client = net.connect({port: 8124},function(){
console.log('client connected');
client.write('world!\r\n');
});
client.on('data', function(data) {
console.log(data.toString());
client.end();
});
client.on('end', function() {
console.log('client disconnected');
});
上面的方法,还可以这样使用(参数形式变化)
net.connect(port, [host], [connectListener])
net.createConnection(port, [host], [connectListener])
第一个参数,必须,表示监听的端口号
第二个参数,可选,表示坚挺的主机名(默认为localhost)
第三个参数,可选,表示回调函数(”TCP连接事件“发生时的监听器)
返回远程连接的IP地址
返回远程连接的端口号
返回本地的IP地址
返回本地的端口号
返回所接收的字节数
返回所发送的字节数
......
.......