• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict'
2
3/**
4 * request.js
5 *
6 * Request class contains server only options
7 */
8
9const url = require('url')
10const Headers = require('./headers.js')
11const Body = require('./body.js')
12const clone = Body.clone
13const extractContentType = Body.extractContentType
14const getTotalBytes = Body.getTotalBytes
15
16const PARSED_URL = Symbol('url')
17
18/**
19 * Request class
20 *
21 * @param   Mixed   input  Url or Request instance
22 * @param   Object  init   Custom options
23 * @return  Void
24 */
25class Request {
26  constructor (input, init) {
27    if (!init) init = {}
28    let parsedURL
29
30    // normalize input
31    if (!(input instanceof Request)) {
32      if (input && input.href) {
33        // in order to support Node.js' Url objects; though WHATWG's URL objects
34        // will fall into this branch also (since their `toString()` will return
35        // `href` property anyway)
36        parsedURL = url.parse(input.href)
37      } else {
38        // coerce input to a string before attempting to parse
39        parsedURL = url.parse(`${input}`)
40      }
41      input = {}
42    } else {
43      parsedURL = url.parse(input.url)
44    }
45
46    let method = init.method || input.method || 'GET'
47
48    if ((init.body != null || (input instanceof Request && input.body !== null)) &&
49      (method === 'GET' || method === 'HEAD')) {
50      throw new TypeError('Request with GET/HEAD method cannot have body')
51    }
52
53    let inputBody = init.body != null
54      ? init.body
55      : input instanceof Request && input.body !== null
56        ? clone(input)
57        : null
58
59    Body.call(this, inputBody, {
60      timeout: init.timeout || input.timeout || 0,
61      size: init.size || input.size || 0
62    })
63
64    // fetch spec options
65    this.method = method.toUpperCase()
66    this.redirect = init.redirect || input.redirect || 'follow'
67    this.headers = new Headers(init.headers || input.headers || {})
68
69    if (init.body != null) {
70      const contentType = extractContentType(this)
71      if (contentType !== null && !this.headers.has('Content-Type')) {
72        this.headers.append('Content-Type', contentType)
73      }
74    }
75
76    // server only options
77    this.follow = init.follow !== undefined
78      ? init.follow : input.follow !== undefined
79      ? input.follow : 20
80    this.compress = init.compress !== undefined
81      ? init.compress : input.compress !== undefined
82      ? input.compress : true
83    this.counter = init.counter || input.counter || 0
84    this.agent = init.agent || input.agent
85
86    this[PARSED_URL] = parsedURL
87    Object.defineProperty(this, Symbol.toStringTag, {
88      value: 'Request',
89      writable: false,
90      enumerable: false,
91      configurable: true
92    })
93  }
94
95  get url () {
96    return url.format(this[PARSED_URL])
97  }
98
99  /**
100   * Clone this request
101   *
102   * @return  Request
103   */
104  clone () {
105    return new Request(this)
106  }
107}
108
109Body.mixIn(Request.prototype)
110
111Object.defineProperty(Request.prototype, Symbol.toStringTag, {
112  value: 'RequestPrototype',
113  writable: false,
114  enumerable: false,
115  configurable: true
116})
117
118exports = module.exports = Request
119
120exports.getNodeRequestOptions = function getNodeRequestOptions (request) {
121  const parsedURL = request[PARSED_URL]
122  const headers = new Headers(request.headers)
123
124  // fetch step 3
125  if (!headers.has('Accept')) {
126    headers.set('Accept', '*/*')
127  }
128
129  // Basic fetch
130  if (!parsedURL.protocol || !parsedURL.hostname) {
131    throw new TypeError('Only absolute URLs are supported')
132  }
133
134  if (!/^https?:$/.test(parsedURL.protocol)) {
135    throw new TypeError('Only HTTP(S) protocols are supported')
136  }
137
138  // HTTP-network-or-cache fetch steps 5-9
139  let contentLengthValue = null
140  if (request.body == null && /^(POST|PUT)$/i.test(request.method)) {
141    contentLengthValue = '0'
142  }
143  if (request.body != null) {
144    const totalBytes = getTotalBytes(request)
145    if (typeof totalBytes === 'number') {
146      contentLengthValue = String(totalBytes)
147    }
148  }
149  if (contentLengthValue) {
150    headers.set('Content-Length', contentLengthValue)
151  }
152
153  // HTTP-network-or-cache fetch step 12
154  if (!headers.has('User-Agent')) {
155    headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)')
156  }
157
158  // HTTP-network-or-cache fetch step 16
159  if (request.compress) {
160    headers.set('Accept-Encoding', 'gzip,deflate')
161  }
162  if (!headers.has('Connection') && !request.agent) {
163    headers.set('Connection', 'close')
164  }
165
166  // HTTP-network fetch step 4
167  // chunked encoding is handled by Node.js
168
169  return Object.assign({}, parsedURL, {
170    method: request.method,
171    headers: headers.raw(),
172    agent: request.agent
173  })
174}
175