-
Notifications
You must be signed in to change notification settings - Fork 753
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an HttpClient interface and NodeHttpClient implementation.
- Loading branch information
1 parent
c85cb03
commit 1efb6fb
Showing
10 changed files
with
819 additions
and
176 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
'use strict'; | ||
|
||
/* eslint-disable class-methods-use-this */ | ||
|
||
/** | ||
* Encapsulates the logic for issuing a request to the Stripe API. This is an | ||
* experimental interface and is not yet stable. | ||
*/ | ||
class HttpClient { | ||
makeRequest( | ||
host, | ||
port, | ||
path, | ||
method, | ||
headers, | ||
requestData, | ||
protocol, | ||
timeout | ||
) { | ||
throw new Error('makeRequest not implemented.'); | ||
} | ||
|
||
/** Helper to make a consistent timeout error across implementations. */ | ||
static makeTimeoutError() { | ||
const timeoutErr = new TypeError(HttpClient.TIMEOUT_ERROR_CODE); | ||
timeoutErr.code = HttpClient.TIMEOUT_ERROR_CODE; | ||
return timeoutErr; | ||
} | ||
} | ||
|
||
HttpClient.TIMEOUT_ERROR_CODE = 'ETIMEDOUT'; | ||
|
||
HttpClient.Response = class { | ||
constructor(statusCode, headers) { | ||
this._statusCode = statusCode; | ||
this._headers = headers; | ||
} | ||
|
||
getStatusCode() { | ||
return this._statusCode; | ||
} | ||
|
||
getHeaders() { | ||
return this._headers; | ||
} | ||
|
||
getRawResponse() { | ||
throw new Error('getRawResponse not implemented.'); | ||
} | ||
|
||
toStream(streamCompleteCallback) { | ||
throw new Error('toStream not implemented.'); | ||
} | ||
|
||
toJSON() { | ||
throw new Error('toJSON not implemented.'); | ||
} | ||
}; | ||
|
||
module.exports = HttpClient; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
'use strict'; | ||
|
||
const http = require('http'); | ||
const https = require('https'); | ||
|
||
const HttpClient = require('./HttpClient'); | ||
|
||
const defaultHttpAgent = new http.Agent({keepAlive: true}); | ||
const defaultHttpsAgent = new https.Agent({keepAlive: true}); | ||
|
||
/** | ||
* HTTP client which uses the Node `http` and `https` packages to issue | ||
* requests.` | ||
*/ | ||
class NodeHttpClient extends HttpClient { | ||
constructor(agent) { | ||
super(); | ||
this._agent = agent; | ||
} | ||
|
||
makeRequest( | ||
host, | ||
port, | ||
path, | ||
method, | ||
headers, | ||
requestData, | ||
protocol, | ||
timeout | ||
) { | ||
const isInsecureConnection = protocol === 'http'; | ||
|
||
let agent = this._agent; | ||
if (!agent) { | ||
agent = isInsecureConnection ? defaultHttpAgent : defaultHttpsAgent; | ||
} | ||
|
||
const requestPromise = new Promise((resolve, reject) => { | ||
const req = (isInsecureConnection ? http : https).request({ | ||
host: host, | ||
port: port, | ||
path, | ||
method, | ||
agent, | ||
headers, | ||
ciphers: 'DEFAULT:!aNULL:!eNULL:!LOW:!EXPORT:!SSLv2:!MD5', | ||
}); | ||
|
||
req.setTimeout(timeout, () => { | ||
req.destroy(HttpClient.makeTimeoutError()); | ||
}); | ||
|
||
req.on('response', (res) => { | ||
resolve(new NodeHttpResponse(res)); | ||
}); | ||
|
||
req.on('error', (error) => { | ||
reject(error); | ||
}); | ||
|
||
req.once('socket', (socket) => { | ||
if (socket.connecting) { | ||
socket.once( | ||
isInsecureConnection ? 'connect' : 'secureConnect', | ||
() => { | ||
// Send payload; we're safe: | ||
req.write(requestData); | ||
req.end(); | ||
} | ||
); | ||
} else { | ||
// we're already connected | ||
req.write(requestData); | ||
req.end(); | ||
} | ||
}); | ||
}); | ||
|
||
return requestPromise; | ||
} | ||
} | ||
|
||
class NodeHttpResponse extends HttpClient.Response { | ||
constructor(res) { | ||
super(res.statusCode, res.headers || {}); | ||
this._res = res; | ||
} | ||
|
||
getRawResponse() { | ||
return this._res; | ||
} | ||
|
||
toStream(streamCompleteCallback) { | ||
this._res.once('end', () => streamCompleteCallback()); | ||
return this._res; | ||
} | ||
|
||
toJSON() { | ||
return new Promise((resolve, reject) => { | ||
let response = ''; | ||
|
||
this._res.setEncoding('utf8'); | ||
this._res.on('data', (chunk) => { | ||
response += chunk; | ||
}); | ||
this._res.once('end', () => { | ||
try { | ||
resolve(JSON.parse(response)); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
NodeHttpClient.Response = NodeHttpResponse; | ||
|
||
module.exports = NodeHttpClient; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.