25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

875 lines
25 KiB

  1. // Copyright 2010-2012 Mikeal Rogers
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. var http = require('http')
  15. , https = false
  16. , tls = false
  17. , url = require('url')
  18. , util = require('util')
  19. , stream = require('stream')
  20. , qs = require('querystring')
  21. , mimetypes = require('./mimetypes')
  22. , oauth = require('./oauth')
  23. , uuid = require('./uuid')
  24. , ForeverAgent = require('./forever')
  25. , Cookie = require('./vendor/cookie')
  26. , CookieJar = require('./vendor/cookie/jar')
  27. , cookieJar = new CookieJar
  28. , tunnel = require('./tunnel')
  29. ;
  30. if (process.logging) {
  31. var log = process.logging('request')
  32. }
  33. try {
  34. https = require('https')
  35. } catch (e) {}
  36. try {
  37. tls = require('tls')
  38. } catch (e) {}
  39. function toBase64 (str) {
  40. return (new Buffer(str || "", "ascii")).toString("base64")
  41. }
  42. // Hacky fix for pre-0.4.4 https
  43. if (https && !https.Agent) {
  44. https.Agent = function (options) {
  45. http.Agent.call(this, options)
  46. }
  47. util.inherits(https.Agent, http.Agent)
  48. https.Agent.prototype._getConnection = function(host, port, cb) {
  49. var s = tls.connect(port, host, this.options, function() {
  50. // do other checks here?
  51. if (cb) cb()
  52. })
  53. return s
  54. }
  55. }
  56. function isReadStream (rs) {
  57. if (rs.readable && rs.path && rs.mode) {
  58. return true
  59. }
  60. }
  61. function copy (obj) {
  62. var o = {}
  63. Object.keys(obj).forEach(function (i) {
  64. o[i] = obj[i]
  65. })
  66. return o
  67. }
  68. var isUrl = /^https?:/
  69. var globalPool = {}
  70. function Request (options) {
  71. stream.Stream.call(this)
  72. this.readable = true
  73. this.writable = true
  74. if (typeof options === 'string') {
  75. options = {uri:options}
  76. }
  77. var reserved = Object.keys(Request.prototype)
  78. for (var i in options) {
  79. if (reserved.indexOf(i) === -1) {
  80. this[i] = options[i]
  81. } else {
  82. if (typeof options[i] === 'function') {
  83. delete options[i]
  84. }
  85. }
  86. }
  87. options = copy(options)
  88. this.init(options)
  89. }
  90. util.inherits(Request, stream.Stream)
  91. Request.prototype.init = function (options) {
  92. var self = this
  93. if (!options) options = {}
  94. if (!self.pool) self.pool = globalPool
  95. self.dests = []
  96. self.__isRequestRequest = true
  97. // Protect against double callback
  98. if (!self._callback && self.callback) {
  99. self._callback = self.callback
  100. self.callback = function () {
  101. if (self._callbackCalled) return // Print a warning maybe?
  102. self._callback.apply(self, arguments)
  103. self._callbackCalled = true
  104. }
  105. }
  106. if (self.url) {
  107. // People use this property instead all the time so why not just support it.
  108. self.uri = self.url
  109. delete self.url
  110. }
  111. if (!self.uri) {
  112. throw new Error("options.uri is a required argument")
  113. } else {
  114. if (typeof self.uri == "string") self.uri = url.parse(self.uri)
  115. }
  116. if (self.proxy) {
  117. if (typeof self.proxy == 'string') self.proxy = url.parse(self.proxy)
  118. // do the HTTP CONNECT dance using koichik/node-tunnel
  119. if (http.globalAgent && self.uri.protocol === "https:") {
  120. self.tunnel = true
  121. var tunnelFn = self.proxy.protocol === "http:"
  122. ? tunnel.httpsOverHttp : tunnel.httpsOverHttps
  123. var tunnelOptions = { proxy: { host: self.proxy.hostname
  124. , port: +self.proxy.port }
  125. , ca: this.ca }
  126. self.agent = tunnelFn(tunnelOptions)
  127. self.tunnel = true
  128. }
  129. }
  130. self._redirectsFollowed = self._redirectsFollowed || 0
  131. self.maxRedirects = (self.maxRedirects !== undefined) ? self.maxRedirects : 10
  132. self.followRedirect = (self.followRedirect !== undefined) ? self.followRedirect : true
  133. self.followAllRedirects = (self.followAllRedirects !== undefined) ? self.followAllRedirects : false;
  134. if (self.followRedirect || self.followAllRedirects)
  135. self.redirects = self.redirects || []
  136. self.headers = self.headers ? copy(self.headers) : {}
  137. self.setHost = false
  138. if (!self.headers.host) {
  139. self.headers.host = self.uri.hostname
  140. if (self.uri.port) {
  141. if ( !(self.uri.port === 80 && self.uri.protocol === 'http:') &&
  142. !(self.uri.port === 443 && self.uri.protocol === 'https:') )
  143. self.headers.host += (':'+self.uri.port)
  144. }
  145. self.setHost = true
  146. }
  147. self.jar(options.jar)
  148. if (!self.uri.pathname) {self.uri.pathname = '/'}
  149. if (!self.uri.port) {
  150. if (self.uri.protocol == 'http:') {self.uri.port = 80}
  151. else if (self.uri.protocol == 'https:') {self.uri.port = 443}
  152. }
  153. if (self.proxy && !self.tunnel) {
  154. self.port = self.proxy.port
  155. self.host = self.proxy.hostname
  156. } else {
  157. self.port = self.uri.port
  158. self.host = self.uri.hostname
  159. }
  160. if (self.onResponse === true) {
  161. self.onResponse = self.callback
  162. delete self.callback
  163. }
  164. self.clientErrorHandler = function (error) {
  165. if (self._aborted) return
  166. if (self.setHost) delete self.headers.host
  167. if (self.req._reusedSocket && error.code === 'ECONNRESET'
  168. && self.agent.addRequestNoreuse) {
  169. self.agent = { addRequest: self.agent.addRequestNoreuse.bind(self.agent) }
  170. self.start()
  171. self.req.end()
  172. return
  173. }
  174. if (self.timeout && self.timeoutTimer) {
  175. clearTimeout(self.timeoutTimer);
  176. self.timeoutTimer = null;
  177. }
  178. self.emit('error', error)
  179. }
  180. if (self.onResponse) self.on('error', function (e) {self.onResponse(e)})
  181. if (self.callback) self.on('error', function (e) {self.callback(e)})
  182. if (options.form) {
  183. self.form(options.form)
  184. }
  185. if (options.oauth) {
  186. self.oauth(options.oauth)
  187. }
  188. if (self.uri.auth && !self.headers.authorization) {
  189. self.headers.authorization = "Basic " + toBase64(self.uri.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':'))
  190. }
  191. if (self.proxy && self.proxy.auth && !self.headers['proxy-authorization'] && !self.tunnel) {
  192. self.headers['proxy-authorization'] = "Basic " + toBase64(self.proxy.auth.split(':').map(function(item){ return qs.unescape(item)}).join(':'))
  193. }
  194. if (options.qs) self.qs(options.qs)
  195. if (self.uri.path) {
  196. self.path = self.uri.path
  197. } else {
  198. self.path = self.uri.pathname + (self.uri.search || "")
  199. }
  200. if (self.path.length === 0) self.path = '/'
  201. if (self.proxy && !self.tunnel) self.path = (self.uri.protocol + '//' + self.uri.host + self.path)
  202. if (options.json) {
  203. self.json(options.json)
  204. } else if (options.multipart) {
  205. self.multipart(options.multipart)
  206. }
  207. if (self.body) {
  208. var length = 0
  209. if (!Buffer.isBuffer(self.body)) {
  210. if (Array.isArray(self.body)) {
  211. for (var i = 0; i < self.body.length; i++) {
  212. length += self.body[i].length
  213. }
  214. } else {
  215. self.body = new Buffer(self.body)
  216. length = self.body.length
  217. }
  218. } else {
  219. length = self.body.length
  220. }
  221. if (length) {
  222. self.headers['content-length'] = length
  223. } else {
  224. throw new Error('Argument error, options.body.')
  225. }
  226. }
  227. var protocol = self.proxy && !self.tunnel ? self.proxy.protocol : self.uri.protocol
  228. , defaultModules = {'http:':http, 'https:':https}
  229. , httpModules = self.httpModules || {}
  230. ;
  231. self.httpModule = httpModules[protocol] || defaultModules[protocol]
  232. if (!self.httpModule) throw new Error("Invalid protocol")
  233. if (options.ca) self.ca = options.ca
  234. if (!self.agent) {
  235. if (options.agentOptions) self.agentOptions = options.agentOptions
  236. if (options.agentClass) {
  237. self.agentClass = options.agentClass
  238. } else if (options.forever) {
  239. self.agentClass = protocol === 'http:' ? ForeverAgent : ForeverAgent.SSL
  240. } else {
  241. self.agentClass = self.httpModule.Agent
  242. }
  243. }
  244. if (self.pool === false) {
  245. self.agent = false
  246. } else {
  247. self.agent = self.agent || self.getAgent()
  248. if (self.maxSockets) {
  249. // Don't use our pooling if node has the refactored client
  250. self.agent.maxSockets = self.maxSockets
  251. }
  252. if (self.pool.maxSockets) {
  253. // Don't use our pooling if node has the refactored client
  254. self.agent.maxSockets = self.pool.maxSockets
  255. }
  256. }
  257. self.once('pipe', function (src) {
  258. if (self.ntick) throw new Error("You cannot pipe to this stream after the first nextTick() after creation of the request stream.")
  259. self.src = src
  260. if (isReadStream(src)) {
  261. if (!self.headers['content-type'] && !self.headers['Content-Type'])
  262. self.headers['content-type'] = mimetypes.lookup(src.path.slice(src.path.lastIndexOf('.')+1))
  263. } else {
  264. if (src.headers) {
  265. for (var i in src.headers) {
  266. if (!self.headers[i]) {
  267. self.headers[i] = src.headers[i]
  268. }
  269. }
  270. }
  271. if (src.method && !self.method) {
  272. self.method = src.method
  273. }
  274. }
  275. self.on('pipe', function () {
  276. console.error("You have already piped to this stream. Pipeing twice is likely to break the request.")
  277. })
  278. })
  279. process.nextTick(function () {
  280. if (self._aborted) return
  281. if (self.body) {
  282. if (Array.isArray(self.body)) {
  283. self.body.forEach(function(part) {
  284. self.write(part)
  285. })
  286. } else {
  287. self.write(self.body)
  288. }
  289. self.end()
  290. } else if (self.requestBodyStream) {
  291. console.warn("options.requestBodyStream is deprecated, please pass the request object to stream.pipe.")
  292. self.requestBodyStream.pipe(self)
  293. } else if (!self.src) {
  294. self.headers['content-length'] = 0
  295. self.end()
  296. }
  297. self.ntick = true
  298. })
  299. }
  300. Request.prototype.getAgent = function () {
  301. var Agent = this.agentClass
  302. var options = {}
  303. if (this.agentOptions) {
  304. for (var i in this.agentOptions) {
  305. options[i] = this.agentOptions[i]
  306. }
  307. }
  308. if (this.ca) options.ca = this.ca
  309. var poolKey = ''
  310. // different types of agents are in different pools
  311. if (Agent !== this.httpModule.Agent) {
  312. poolKey += Agent.name
  313. }
  314. if (!this.httpModule.globalAgent) {
  315. // node 0.4.x
  316. options.host = this.host
  317. options.port = this.port
  318. if (poolKey) poolKey += ':'
  319. poolKey += this.host + ':' + this.port
  320. }
  321. if (options.ca) {
  322. if (poolKey) poolKey += ':'
  323. poolKey += options.ca
  324. }
  325. if (!poolKey && Agent === this.httpModule.Agent && this.httpModule.globalAgent) {
  326. // not doing anything special. Use the globalAgent
  327. return this.httpModule.globalAgent
  328. }
  329. // already generated an agent for this setting
  330. if (this.pool[poolKey]) return this.pool[poolKey]
  331. return this.pool[poolKey] = new Agent(options)
  332. }
  333. Request.prototype.start = function () {
  334. var self = this
  335. if (self._aborted) return
  336. self._started = true
  337. self.method = self.method || 'GET'
  338. self.href = self.uri.href
  339. if (log) log('%method %href', self)
  340. self.req = self.httpModule.request(self, function (response) {
  341. if (self._aborted) return
  342. if (self._paused) response.pause()
  343. self.response = response
  344. response.request = self
  345. if (self.httpModule === https &&
  346. self.strictSSL &&
  347. !response.client.authorized) {
  348. var sslErr = response.client.authorizationError
  349. self.emit('error', new Error('SSL Error: '+ sslErr))
  350. return
  351. }
  352. if (self.setHost) delete self.headers.host
  353. if (self.timeout && self.timeoutTimer) {
  354. clearTimeout(self.timeoutTimer);
  355. self.timeoutTimer = null;
  356. }
  357. if (response.headers['set-cookie'] && (!self._disableCookies)) {
  358. response.headers['set-cookie'].forEach(function(cookie) {
  359. if (self._jar) self._jar.add(new Cookie(cookie))
  360. else cookieJar.add(new Cookie(cookie))
  361. })
  362. }
  363. if (response.statusCode >= 300 && response.statusCode < 400 &&
  364. (self.followAllRedirects ||
  365. (self.followRedirect && (self.method !== 'PUT' && self.method !== 'POST' && self.method !== 'DELETE'))) &&
  366. response.headers.location) {
  367. if (self._redirectsFollowed >= self.maxRedirects) {
  368. self.emit('error', new Error("Exceeded maxRedirects. Probably stuck in a redirect loop."))
  369. return
  370. }
  371. self._redirectsFollowed += 1
  372. if (!isUrl.test(response.headers.location)) {
  373. response.headers.location = url.resolve(self.uri.href, response.headers.location)
  374. }
  375. self.uri = response.headers.location
  376. self.redirects.push(
  377. { statusCode : response.statusCode
  378. , redirectUri: response.headers.location
  379. }
  380. )
  381. self.method = 'GET'; // Force all redirects to use GET
  382. delete self.req
  383. delete self.agent
  384. delete self._started
  385. if (self.headers) {
  386. delete self.headers.host
  387. }
  388. if (log) log('Redirect to %uri', self)
  389. self.init()
  390. return // Ignore the rest of the response
  391. } else {
  392. self._redirectsFollowed = self._redirectsFollowed || 0
  393. // Be a good stream and emit end when the response is finished.
  394. // Hack to emit end on close because of a core bug that never fires end
  395. response.on('close', function () {
  396. if (!self._ended) self.response.emit('end')
  397. })
  398. if (self.encoding) {
  399. if (self.dests.length !== 0) {
  400. console.error("Ingoring encoding parameter as this stream is being piped to another stream which makes the encoding option invalid.")
  401. } else {
  402. response.setEncoding(self.encoding)
  403. }
  404. }
  405. self.dests.forEach(function (dest) {
  406. self.pipeDest(dest)
  407. })
  408. response.on("data", function (chunk) {
  409. self._destdata = true
  410. self.emit("data", chunk)
  411. })
  412. response.on("end", function (chunk) {
  413. self._ended = true
  414. self.emit("end", chunk)
  415. })
  416. response.on("close", function () {self.emit("close")})
  417. self.emit('response', response)
  418. if (self.onResponse) {
  419. self.onResponse(null, response)
  420. }
  421. if (self.callback) {
  422. var buffer = []
  423. var bodyLen = 0
  424. self.on("data", function (chunk) {
  425. buffer.push(chunk)
  426. bodyLen += chunk.length
  427. })
  428. self.on("end", function () {
  429. if (self._aborted) return
  430. if (buffer.length && Buffer.isBuffer(buffer[0])) {
  431. var body = new Buffer(bodyLen)
  432. var i = 0
  433. buffer.forEach(function (chunk) {
  434. chunk.copy(body, i, 0, chunk.length)
  435. i += chunk.length
  436. })
  437. if (self.encoding === null) {
  438. response.body = body
  439. } else {
  440. response.body = body.toString()
  441. }
  442. } else if (buffer.length) {
  443. response.body = buffer.join('')
  444. }
  445. if (self._json) {
  446. try {
  447. response.body = JSON.parse(response.body)
  448. } catch (e) {}
  449. }
  450. self.callback(null, response, response.body)
  451. })
  452. }
  453. }
  454. })
  455. if (self.timeout && !self.timeoutTimer) {
  456. self.timeoutTimer = setTimeout(function() {
  457. self.req.abort()
  458. var e = new Error("ETIMEDOUT")
  459. e.code = "ETIMEDOUT"
  460. self.emit("error", e)
  461. }, self.timeout)
  462. // Set additional timeout on socket - in case if remote
  463. // server freeze after sending headers
  464. if (self.req.setTimeout) { // only works on node 0.6+
  465. self.req.setTimeout(self.timeout, function(){
  466. if (self.req) {
  467. self.req.abort()
  468. var e = new Error("ESOCKETTIMEDOUT")
  469. e.code = "ESOCKETTIMEDOUT"
  470. self.emit("error", e)
  471. }
  472. })
  473. }
  474. }
  475. self.req.on('error', self.clientErrorHandler)
  476. self.emit('request', self.req)
  477. }
  478. Request.prototype.abort = function() {
  479. this._aborted = true;
  480. if (this.req) {
  481. this.req.abort()
  482. }
  483. else if (this.response) {
  484. this.response.abort()
  485. }
  486. this.emit("abort")
  487. }
  488. Request.prototype.pipeDest = function (dest) {
  489. var response = this.response
  490. // Called after the response is received
  491. if (dest.headers) {
  492. dest.headers['content-type'] = response.headers['content-type']
  493. if (response.headers['content-length']) {
  494. dest.headers['content-length'] = response.headers['content-length']
  495. }
  496. }
  497. if (dest.setHeader) {
  498. for (var i in response.headers) {
  499. dest.setHeader(i, response.headers[i])
  500. }
  501. dest.statusCode = response.statusCode
  502. }
  503. if (this.pipefilter) this.pipefilter(response, dest)
  504. }
  505. // Composable API
  506. Request.prototype.setHeader = function (name, value, clobber) {
  507. if (clobber === undefined) clobber = true
  508. if (clobber || !this.headers.hasOwnProperty(name)) this.headers[name] = value
  509. else this.headers[name] += ',' + value
  510. return this
  511. }
  512. Request.prototype.setHeaders = function (headers) {
  513. for (i in headers) {this.setHeader(i, headers[i])}
  514. return this
  515. }
  516. Request.prototype.qs = function (q, clobber) {
  517. var uri = {
  518. protocol: this.uri.protocol,
  519. host: this.uri.host,
  520. pathname: this.uri.pathname,
  521. query: clobber ? q : qs.parse(this.uri.query),
  522. hash: this.uri.hash
  523. };
  524. if (!clobber) for (var i in q) uri.query[i] = q[i]
  525. this.uri= url.parse(url.format(uri))
  526. return this
  527. }
  528. Request.prototype.form = function (form) {
  529. this.headers['content-type'] = 'application/x-www-form-urlencoded; charset=utf-8'
  530. this.body = qs.stringify(form).toString('utf8')
  531. return this
  532. }
  533. Request.prototype.multipart = function (multipart) {
  534. var self = this
  535. self.body = []
  536. if (!self.headers['content-type']) {
  537. self.headers['content-type'] = 'multipart/related;boundary="frontier"';
  538. } else {
  539. self.headers['content-type'] = self.headers['content-type'].split(';')[0] + ';boundary="frontier"';
  540. }
  541. if (!multipart.forEach) throw new Error('Argument error, options.multipart.')
  542. multipart.forEach(function (part) {
  543. var body = part.body
  544. if(!body) throw Error('Body attribute missing in multipart.')
  545. delete part.body
  546. var preamble = '--frontier\r\n'
  547. Object.keys(part).forEach(function(key){
  548. preamble += key + ': ' + part[key] + '\r\n'
  549. })
  550. preamble += '\r\n'
  551. self.body.push(new Buffer(preamble))
  552. self.body.push(new Buffer(body))
  553. self.body.push(new Buffer('\r\n'))
  554. })
  555. self.body.push(new Buffer('--frontier--'))
  556. return self
  557. }
  558. Request.prototype.json = function (val) {
  559. this.setHeader('content-type', 'application/json')
  560. this.setHeader('accept', 'application/json')
  561. this._json = true
  562. if (typeof val === 'boolean') {
  563. if (typeof this.body === 'object') this.body = JSON.stringify(this.body)
  564. } else {
  565. this.body = JSON.stringify(val)
  566. }
  567. return this
  568. }
  569. Request.prototype.oauth = function (_oauth) {
  570. var form
  571. if (this.headers['content-type'] &&
  572. this.headers['content-type'].slice(0, 'application/x-www-form-urlencoded'.length) ===
  573. 'application/x-www-form-urlencoded'
  574. ) {
  575. form = qs.parse(this.body)
  576. }
  577. if (this.uri.query) {
  578. form = qs.parse(this.uri.query)
  579. }
  580. if (!form) form = {}
  581. var oa = {}
  582. for (var i in form) oa[i] = form[i]
  583. for (var i in _oauth) oa['oauth_'+i] = _oauth[i]
  584. if (!oa.oauth_version) oa.oauth_version = '1.0'
  585. if (!oa.oauth_timestamp) oa.oauth_timestamp = Math.floor( (new Date()).getTime() / 1000 ).toString()
  586. if (!oa.oauth_nonce) oa.oauth_nonce = uuid().replace(/-/g, '')
  587. oa.oauth_signature_method = 'HMAC-SHA1'
  588. var consumer_secret = oa.oauth_consumer_secret
  589. delete oa.oauth_consumer_secret
  590. var token_secret = oa.oauth_token_secret
  591. delete oa.oauth_token_secret
  592. var baseurl = this.uri.protocol + '//' + this.uri.host + this.uri.pathname
  593. var signature = oauth.hmacsign(this.method, baseurl, oa, consumer_secret, token_secret)
  594. // oa.oauth_signature = signature
  595. for (var i in form) {
  596. if ( i.slice(0, 'oauth_') in _oauth) {
  597. // skip
  598. } else {
  599. delete oa['oauth_'+i]
  600. }
  601. }
  602. this.headers.authorization =
  603. 'OAuth '+Object.keys(oa).sort().map(function (i) {return i+'="'+oauth.rfc3986(oa[i])+'"'}).join(',')
  604. this.headers.authorization += ',oauth_signature="'+oauth.rfc3986(signature)+'"'
  605. return this
  606. }
  607. Request.prototype.jar = function (jar) {
  608. var cookies
  609. if (this._redirectsFollowed === 0) {
  610. this.originalCookieHeader = this.headers.cookie
  611. }
  612. if (jar === false) {
  613. // disable cookies
  614. cookies = false;
  615. this._disableCookies = true;
  616. } else if (jar) {
  617. // fetch cookie from the user defined cookie jar
  618. cookies = jar.get({ url: this.uri.href })
  619. } else {
  620. // fetch cookie from the global cookie jar
  621. cookies = cookieJar.get({ url: this.uri.href })
  622. }
  623. if (cookies && cookies.length) {
  624. var cookieString = cookies.map(function (c) {
  625. return c.name + "=" + c.value
  626. }).join("; ")
  627. if (this.originalCookieHeader) {
  628. // Don't overwrite existing Cookie header
  629. this.headers.cookie = this.originalCookieHeader + '; ' + cookieString
  630. } else {
  631. this.headers.cookie = cookieString
  632. }
  633. }
  634. this._jar = jar
  635. return this
  636. }
  637. // Stream API
  638. Request.prototype.pipe = function (dest, opts) {
  639. if (this.response) {
  640. if (this._destdata) {
  641. throw new Error("You cannot pipe after data has been emitted from the response.")
  642. } else if (this._ended) {
  643. throw new Error("You cannot pipe after the response has been ended.")
  644. } else {
  645. stream.Stream.prototype.pipe.call(this, dest, opts)
  646. this.pipeDest(dest)
  647. return dest
  648. }
  649. } else {
  650. this.dests.push(dest)
  651. stream.Stream.prototype.pipe.call(this, dest, opts)
  652. return dest
  653. }
  654. }
  655. Request.prototype.write = function () {
  656. if (!this._started) this.start()
  657. this.req.write.apply(this.req, arguments)
  658. }
  659. Request.prototype.end = function (chunk) {
  660. if (chunk) this.write(chunk)
  661. if (!this._started) this.start()
  662. this.req.end()
  663. }
  664. Request.prototype.pause = function () {
  665. if (!this.response) this._paused = true
  666. else this.response.pause.apply(this.response, arguments)
  667. }
  668. Request.prototype.resume = function () {
  669. if (!this.response) this._paused = false
  670. else this.response.resume.apply(this.response, arguments)
  671. }
  672. Request.prototype.destroy = function () {
  673. if (!this._ended) this.end()
  674. }
  675. // organize params for post, put, head, del
  676. function initParams(uri, options, callback) {
  677. if ((typeof options === 'function') && !callback) callback = options;
  678. if (typeof options === 'object') {
  679. options.uri = uri;
  680. } else if (typeof uri === 'string') {
  681. options = {uri:uri};
  682. } else {
  683. options = uri;
  684. uri = options.uri;
  685. }
  686. return { uri: uri, options: options, callback: callback };
  687. }
  688. function request (uri, options, callback) {
  689. if ((typeof options === 'function') && !callback) callback = options;
  690. if (typeof options === 'object') {
  691. options.uri = uri;
  692. } else if (typeof uri === 'string') {
  693. options = {uri:uri};
  694. } else {
  695. options = uri;
  696. }
  697. if (callback) options.callback = callback;
  698. var r = new Request(options)
  699. return r
  700. }
  701. module.exports = request
  702. request.defaults = function (options) {
  703. var def = function (method) {
  704. var d = function (uri, opts, callback) {
  705. var params = initParams(uri, opts, callback);
  706. for (var i in options) {
  707. if (params.options[i] === undefined) params.options[i] = options[i]
  708. }
  709. return method(params.uri, params.options, params.callback)
  710. }
  711. return d
  712. }
  713. var de = def(request)
  714. de.get = def(request.get)
  715. de.post = def(request.post)
  716. de.put = def(request.put)
  717. de.head = def(request.head)
  718. de.del = def(request.del)
  719. de.cookie = def(request.cookie)
  720. de.jar = def(request.jar)
  721. return de
  722. }
  723. request.forever = function (agentOptions, optionsArg) {
  724. var options = {}
  725. if (optionsArg) {
  726. for (option in optionsArg) {
  727. options[option] = optionsArg[option]
  728. }
  729. }
  730. if (agentOptions) options.agentOptions = agentOptions
  731. options.forever = true
  732. return request.defaults(options)
  733. }
  734. request.get = request
  735. request.post = function (uri, options, callback) {
  736. var params = initParams(uri, options, callback);
  737. params.options.method = 'POST';
  738. return request(params.uri, params.options, params.callback)
  739. }
  740. request.put = function (uri, options, callback) {
  741. var params = initParams(uri, options, callback);
  742. params.options.method = 'PUT'
  743. return request(params.uri, params.options, params.callback)
  744. }
  745. request.head = function (uri, options, callback) {
  746. var params = initParams(uri, options, callback);
  747. params.options.method = 'HEAD'
  748. if (params.options.body ||
  749. params.options.requestBodyStream ||
  750. (params.options.json && typeof params.options.json !== 'boolean') ||
  751. params.options.multipart) {
  752. throw new Error("HTTP HEAD requests MUST NOT include a request body.")
  753. }
  754. return request(params.uri, params.options, params.callback)
  755. }
  756. request.del = function (uri, options, callback) {
  757. var params = initParams(uri, options, callback);
  758. params.options.method = 'DELETE'
  759. return request(params.uri, params.options, params.callback)
  760. }
  761. request.jar = function () {
  762. return new CookieJar
  763. }
  764. request.cookie = function (str) {
  765. if (str && str.uri) str = str.uri
  766. if (typeof str !== 'string') throw new Error("The cookie function only accepts STRING as param")
  767. return new Cookie(str)
  768. }