爱妃科技nodejs http api | | 爱妃科技
正在加载
请稍等

菜单

红楼飞雪 梦

15526773247

文章

Home nodejs nodejs http api
Home nodejs nodejs http api

nodejs http api

nodejs by

Node.js 的核心功能之一就是作为Web 服务器,这是这个系统的一个重要部分,所以当Ryan Dahl 发起此项目时,他为V8 重写了HTTP 模块,使其能够非阻塞运行。虽然最开始的HTTP 实现已经蜕变了许多,API 和内部实现不断升级,但核心操作还是保持不变的。Node 实现的HTTP 模块是非阻塞的,且速度很快,其中许多代码已经从C 迁移到了JavaScript。HTTP 使用的模式在Node 里很常见。父工厂类提供了很容易创建新服务器的方法。http.createServer() 方法为我们提供了构建新的HTTPServer 类的实例。我们可以为新的类定义Node 接受到HTTP 请求时的操作。HTTP 模块及其他Node 模块还有一些共性的地方,比如Server 类能够触发的事件,还有传给回调函数的数据结构。了解这3 种类型有助于你更好地使用HTTP 模块。

当我们提到父类(pseudoclass)时,指的是Douglas Crockford所著的JavaScript: The Good Parts( O’Reilly)
一书中的定义。从现在起,我们将使用“类”的说法来代替“父类”。

HTTP服务器

HTTP 服务器也许是Node 最常用的使用情景了.在前几篇文章我 们设置了一个HTTP 服务器, 并用它来处理非常简单的请求。其实,HTTP 还有很多的功能。HTTP 模块的服务器部分提供了构建复杂、全面的Web 服务器的原始工具。在本章里,我们会继续探索处理请求及发送响应的机理。即使你使用了更高级的服务器框架(如Express),它们背后采用的许多概念也 都是从这里介绍的内容延伸而来的。正如前面的例子所示,使用HTTP 服务器的第一步就是调用http.createServer()方法来创建一个新的服务器。这会返回一个新的Server 类的实例,里面只包含少数的方法,因为更多的功能是通过使用事件来提供的。http 服务器类有6 个事件和3 个方法。还要注意的是,大部分方法只是用来初始化服务器,而事件则是用来处理它的运行时操作。让我们从创建一个最小的HTTP 服务器代码开始

非常简短的HTTP 服务器

require('http').createServer(function (req, res) {
  res.writeHead(200, {
  });
  res.end('hello world');
}).listen(8125);

这个例子并不是好的代码风格,但是它演示了几个关 键点,稍后我们再改进代码风格。首先,我们用require 包含了http 模块。注意我们使用了链式方法来使用这个模块,而不需要先赋值给一个变量。Node 里的许多东西会返回一个函数2,这样我们就能直接调用这些函数了。包含http 模块后,我们调用createServer。这里并不是必须输入参数,但我们传入了一个函数,并让它绑定在request 事件上。最后,我们让createServer 创建的服务器监听(listen)8125 端口。希望你在真实情景下从不需要写类似的代码,但这里的代码显示了语法的灵活和语言的简洁。让我们把代码写得更清晰些。下面重写的代码变得更好理解和维 护了。

简短但可读性更好的HTTP 服务器

var http = require('http');
var server = http.createServer();
var handleReq = function (req, res) {
  res.writeHead(200, {
  });
  res.end('hello world');
};
server.on('request', handleReq);
server.listen(8125);

这个例子依旧是实现了最小的Web 服务器,但这次,我们开始把东西赋值给命名变量。比起使用链式调用,这让代码变得更容易读一些,而且还可以重用。例如,在一个文件里可能会多次使用 http,这样的情形并不少见。如果你想同时使用HTTP服务器和HTTP 客户端,重用该模块对象就会很有用。即使JavaScript 并不逼你去考虑内存,也不意味着你可以把不需要的对象草率地到处乱扔。然后我们还是用了命名函数来处理request 事件,用来替换之前的匿名回调函数。这与内存使用没什么关系,只是增加了可读性。我们并不是说你不该使用匿名函数,但如果可以把代码放在容易查找的地方, 会更有助于维护它。

因 为我们没有在调用创建http 服务器对象的工厂方法时传入request 事件监听器,所以需要显式地添加该事件监听器,这可以利用EventEmitter 调用on 方法来完成。最后,和之前的例子一样,我们调用listen 方法来监听想要的端口。http 类还提供了其他函数,这个例子只是演示了最重要的几个。http 服务器支持几种事件, 都是和客户端的TCP 或者HTTP 连接相关联的。connection 和close 事件表示了与客户端的TCP 连接的建立与关闭。要注意,如果客户端使用的是HTTP 1.1 协议,这是支持keepalive 的。这意味着这些TCP 连接可能会跨越多个HTTP 请求。request、checkContinue、upgrade 和clientError 事件是关联在HTTP 请求上的。我们已经使用过request 事件,表示的是新的HTTP 请求。checkContinue 事件是一个特殊事件,当客户端以数据流的方式将数据发送给服务器时,你可以对HTTP 请求进行更直接的控制。客户端发送数据给服务器时,需要检查当前状态下能否继续,这就会触发此事件。 如果这个事件绑定了事件处理器,则request 请求就不会被触发。upgrade 事件在一个客户端请求协议升级时会触发。除非绑定了此事件的事件处理器,否则http 服务器将拒绝HTTP 升级请求。最后,clientError 事件会把客户端发送的error 事件传递出来。HTTP 服务器可以抛出若干事件,最常见的是request,但你也会在TCP 连接的整个生命周期中获得其他事件。当为请求创建一个新的TCP 流时,就会触发connection 事件。这个事件会把TCP流作为参数传给该请求。该数据流也可以在request 使用的时候,通过request.connection 变量获得。但每个流只会触发connection 事件一次,所以可能会出现从一个客户端来的多个请求只对应一次connection 事件的情况。

HTTP客户端

如果你 想向远程服务器发起HTTP 连接,Node 也是很好的选择。Node 在许多情景下都很适合使用,如使用Web service,连接到文档数据库,或是抓取网页。你可以使用同样的http 模块来发起HTTP 请求,但应该使用http.ClientRequest 类。该类有两个工厂方法:一个通用的方法和一个便捷的方法。让我们看看的通用方法的例子。

创建HTTP 请求

var http = require('http');
var opts = {
  host: 'www.google.com'
  port: 80,
  path: '/',
  method: 'GET'
};
var req = http.request(opts, function (res) {
  console.log(res);
  res.on('data', function (data) {
    console.log(data);
  });
});
req.end();

你首先要查看的是配置(options)对象,它定义了请求的许多功能。我们必须提供 host 名字(虽然IP 地址也是可以的)、端口(port) 和路径(path)。方法(method)是可选项,如果没有指定, 默认会设置为GET。在本质上,这个例子指定了往http://www.google.com/ 的80 端口发起HTTP GET 请求。接下来,我们要用配置(options)对象来创建一个http.ClientRequest 实例,就是调用http.request() 这个工厂方法,并传入options 对象和回调函数(可选)。传入的回调函数会监听response 事件,并在接收到response 事件时,处理request 的数据。在之前的例子里,我们只是简单地把response 对象打印到终端上。但要注意一点,HTTP 请求的正文内容实际上是通过response 对象的数据流
获 得的。而且你可以订阅response 对象的data 事件,以便于数据可用时就能处理最后需要注意的一点是,需要结束(end())该请求。因为这是一个GET 请求,所以我们并不会往服务器发送任何数据。但对于其他的HTTP 方法,比如PUT 或POST,你可能需要发送数据。request 会等待end() 方法调用后,才初始化HTTP请求,因为在那之前,它不确定我们是否还会发送数据。

1. 提交HTTP GET 请求

GET 是很常见的HTTP 使用方式,因此提供了一个专门的工厂方法来更方便地使用它,

简单的HTTP GET 请求

var http = require('http');
var opts = {
  host: 'www.google.com'
  port: 80,
  path: '/',
};
var req = http.get(opts, function (res) {
  console.log(res);
  res.on('data', function (data) {
    console.log(data);
  });
});

这个例子的http.get() 和之前的例子做了一样的事情,但更加明确。我们把method属性从配置对象中去掉了,还把request.end() 也移除了,因为这些都已经隐含说明了。如果运行了这两个例子,你得到的结果将是Buffer 对象的裸数据。Buffer 是Node 特殊定义的类,用来支持任意二进制数据的存储。虽然你也可以直接使用这些内容,但通常要指定编码方式,如UTF-8(一种Unicode 字符的编码格式),这可以通过response.setEncoding() 方法来指定

对比裸Buffer 输出与指定编码格式的输出

> var http = require('http');
> var req = http.get({host:'www.google.com', port:80, path:'/'},
function(res) {
... console.log(res);
... res.on('data', function(c) { console.log(c); });
... });
> <Buffer 3c 21 64 6f 63 74 79 70
...
65 2e 73 74>
<Buffer 61 72 74 54 69
...
69 70 74 3e>
>
> var req = http.get({host:'www.google.com', port:80, path:'/'},
function(res) {
... res.setEncoding('utf8');
... res.on('data', function(c) { console.log(c); });
... });
> <!doctype html><html><head><meta http-equiv="content-type
...
load.t.prt=(f=(new Date).getTime());
})();
</script>
>
在第一个例子中, 我们没有调用ClientResponse.setEncoding(),而且得到的是Buffer 中的块数据。虽然输出是简略的打印内容,但也能看出并非只有一个Buffer,而是分开了几个Buffer 才把数据返回完整。在第二个例子中,因为我们设置了res.setEncoding('utf8'),数据以UTF-8 的格式返回了。 从服务器返 回的数据还是一样,分成了几块,但这次发给程序的是以正确的编码显示的字符串,而不再是Buffer 的裸数据。虽然打印的内容可能表现得还不够明显,但每个原始Buffer 对应打印出来的都是一个字符串。

 2. 发送HTTP POST 和PUT 数据

不是所有的HTTP 请求都是用GET 方法的,你还需要调用POST、PUT 和其他HTTP方法,它们会改变对方的数据。这和发送GET 请求的功能一样,只不过你还需要往上发送一些数据

往上传服务写入数据

var options = {
  host: 'www.example.com',
  port: 80,
  path: '/submit',
  method: 'POST'
};
var req = http.request(options, function (res) {
  res.setEncoding('utf8');
  res.on('data', function (chunk) {
    console.log('BODY: ' + chunk);
  });
});
req.write('my data');
req.write('more of my data');
req.end();

增加了http.ClientRequest.write() 方法。可以用这个方法发送上行数据流。之前解释过, 它要求你显式地调用http.
ClientRequest.end() 方法来表示数据发送完毕。每当调用ClientRequest.write() 时,数据会马上上传(不会被缓存),但服务器在ClientRequest.end()调用之前是不会响应你的数据请求的。你可以把一个流 (Stream)的data 事件和ClientRequest.write() 绑定在一起,这样就能把数据以流的形式发送给服务器了。比如当需要把硬盘上的一个文件通过HTTP 发送给远程服务器时,这会是个好主意。

3. ClientResponse 对象

ClientResponse 对象保存了关于请求的许多信息,而且都很直观。它的一些显著的属性也很有用,包statusCode(包含了HTTP 状态)和header 属性(响应头对象)。ClientResponse 上还挂了多个数据流和属性,你也许会直接使用它。

URL

URL 模块提供了解析和处理URL 字符串的便利工具,当你需要和URL 打交道时会非常有用。该模块提供了3 个方法:parse、format 和resolve,在Node 命令行里演示如何使用parse。

用URL 模块解析URL

> var URL = require('url');
> var myUrl = "http://www.nodejs.org/some/url/?with=query&param=that&are=
awesome#alsoahash";
> myUrl
'http://www.nodejs.org/some/url/?with=query&param=that&are=awesome
#alsoahash'
> parsedUrl = URL.parse(myUrl);
{ href: 'http://www.nodejs.org/some/url/?with=query&param=that&are=
awesome#alsoahash'
, protocol: 'http:'
, slashes: true
, host: 'www.nodejs.org'
, hostname: 'www.nodejs.org'
, hash: '#alsoahash'
, search: '?with=query&param=that&are=awesome'
, query: 'with=query&param=that&are=awesome'
, pathname: '/some/url/'
}
> parsedUrl = URL.parse(myUrl, true);
{ href: 'http://www.nodejs.org/some/url/?with=query&param=that&are=
awesome#alsoahash'
, protocol: 'http:'
, slashes: true
, host: 'www.nodejs.org'
, hostname: 'www.nodejs.org'
, hash: '#alsoahash'
, search: '?with=query&param=that&are=awesome'
, query:
{ with: 'query'
, param: 'that'
, are: 'awesome'
}
, pathname: '/some/url/'
}
>

第一件事情当然是要先包含URL 模块。注意所有的模块名称都是小写的。我们创建的url 字符串包含了需要被解析的所有部分。解析真的很简单:只要对该字符串调用URL 模块的parse 方法。它返回的数据结构代表了解析出来的URL 的各个部分,它产生的组成部分如下:

• href
• protocol
• host
• auth
• hostname
• port
• pathname
• search
• query
• hash

href 是原始输入用来解析的完整URL。protocol 是用在URL 里的协议( 如http://、https://、ftp:// 等)。host 是URL 里完整的hostname。这可以是本地服务器的hostname,比如打印机服务器,也可以是如www.google.com 一样完整的域名。它还可能包含了端口(如8080),或用户名和密码(如un:pw@ftpserver.com)。hostname 的不同部分会进一步细分到:auth(包含用户证书)、port( 单纯是端口)、hostname( 包含URL 的主机名)。重点是,hostname 依然是完整的主机名,包含了顶级域名(如.com 和.net 等)和特定的服务器。如果URL http://sport.yahoo.com/nhl,hostname 不会单独给你顶级域名(yahoo.com)或只给你主机(sport),而是会给你完整的主机名(sport.yahoo.com)。URL 模块并没有能力把hostname 细分成单独的部分,如域名或顶级域名。
URL 的下一组成员是关于host 部分后面的所有东西。pathname 是跟在host 之后的整个文件路径,例如http://sports.yahoo.com/nhl 的pathname 就是/nhl。下一个是search 部分,保存了URL 中HTTP GET 的参数。比如URL 是http://mydomain.com/?foo=bar&baz=qux,search 部分对应的是?foo=bar&baz=qux。注意它包含了?。query 参数和search 部分类似,它包含二者中的一项,具体要看parse 被调用的方法。parse 可以有两个参数:url 字符串,及一个可选的布尔值,用来确定queryString是否该用querystring 模块来解析(下一小节再详细介绍)。如果第二个参数是false,query 将包含一个与search 类似的字符串,但去掉了开头的?。如果你没有传入第二个参数,那么默认为false。URL 的最后一个部分是片段部分( 称为hash)。这是URL 中在# 之后的部分。通常, 这是用来指向HTML 页面内的命名锚记(anchor)。比如http://abook.com/#chapter2 可能是指向包含整书内容的网页的第2 章。在这个例子中,hash 部分就包含了#chapter2。同样,该字符串包含了“#”。有些网站,如http://tiwtter.com,使用更复杂的片段来做AJAX 应用,但基本原则是一样的。所以假如用户mentions 的Twitter 账号URL 是http://twitter.com/#!/mentions,那么它的pathname是/,但hash 是#!/mentions。

querystring

querystring 模块是用来处理query 字符串的简单辅助模块。上一小节已经讨论过,query 字符串是在URL 尾部编码过的参数。但是如果只是把它当做JavaScript字符串来使用时,处理这些参数未免很烦琐。querystring 模块提供了从query字符串中轻松提取对象的方法。它的主要功能有parse 和decode,还包括一些内部辅助函数,如escape、unescape、unescapeBuffer、encode 和stringify。如果你有一个query 字符串,你可以使用parse 来把它变成一个对象

在Node 终端里使用querystring 模块解析查询字符串

> var qs = require('querystring');
> qs.parse('a=1&b=2&c=d');
{ a: '1', b: '2', c: 'd' }
>

例子中,该类的parse 方法把query 字符串转换成为一个对象,其中属性是对应query 字符串中的关键字和变量值。你还需要注意几件事情。第一,数字是返回成字符串的,并非数字类型。JavaScript 是弱类型语言,用一个数值运算就能够轻松把一个字符串强制转换成数字。但是需要时刻考虑那些无法强制转换的情况。其次要注意的是,你传入的query 字符串不能包含URL 中标记的?。一个典型的URL 例子是http://www. bobsdiscount.com/?item=304&location=san+francisco。query 字符串以? 开始,表示文件路径已经结束。但如果你把? 也包含在传进去解析的字符串中,第一个关键字就以? 开头了,你肯定不想得到这样的结果。这个库在许多使用情景下都非常有用,因为除了URL 外,很多地方会使用到query字符串。当你从一个HTTP POST 发送的内容是x-formencoded 格式的时候,它也
是 以query 字符串的形式呈现的。所有的浏览器厂商都为这一做法制定了标准。默认情况下,HTML 里的form 都会用这个方式发送数据到服务器上去。querystring 模块也被URL 模块用作辅助模块。特别是在解析URL 的时候,你可以指定URL 模块把query 字符串转换成对象返回给你,而不是给你一个简单的字符串。这在上一小节已经详细介绍过了,但parse 方法是使用了querystring 模块来解析的。querystring 另外一个重要的部分是encode。该函数把输入的keyvalue格式的对象转换成query 字符串的格式。如果你需要使用HTTP 请求(特别是POST 数据),这会非常方便。你可以在操作时使用JavaScript 对象,然后在需要进行数据传输时再轻松地把它编码成需要的格式。所有的JavaScript 对象都可以使用,但最好是使用的对象只包含需要的数据,因为encode 方法会把对象所有的属性都添加进来。但是,如果属性的值不是string、Boolean 或number 中的一种,它就不能被序列化,返回的内容中关键字(key)对应的值会是空的。

把对象编码成查询字符串

> var myObj = {'a':1, 'b':5, 'c':'cats', 'func': function(){console.
log('dogs')}}
> qs.encode(myObj);
'a=1&b=5&c=cats&func='
>

 

24 2015-06

 

我要 分享

 

 

本文 作者

 

相关 文章