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