-
Notifications
You must be signed in to change notification settings - Fork 753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an HttpClient interface and NodeHttpClient implementation. #1218
Changes from all commits
3f6a815
90293d3
c23b6e6
ca74ca9
f68ec30
066004b
861da4f
f83a043
5ca4726
e076999
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
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'; | ||
|
||
class HttpClientResponse { | ||
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, HttpClientResponse}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
'use strict'; | ||
|
||
const http = require('http'); | ||
const https = require('https'); | ||
|
||
const {HttpClient, HttpClientResponse} = require('./HttpClient'); | ||
|
||
const defaultHttpAgent = new http.Agent({keepAlive: true}); | ||
const defaultHttpsAgent = new https.Agent({keepAlive: true}); | ||
Comment on lines
+8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason not to move this into the constructor? It'd be nice to not have side effects on import in the case that this is being imported in a non-node environment. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do need a singleton -- if the user instantiates multiple stripe clients (without overriding the agent) then they should all share the same connection pool. We could lazily instantiate it when the constructor is called, though. Although, maybe we should instead ensure that this file is not required in non-node environments? It is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is resolved by @dcr-stripe 's latest changes |
||
|
||
/** | ||
* 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 NodeHttpClientResponse(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 NodeHttpClientResponse extends HttpClientResponse { | ||
constructor(res) { | ||
super(res.statusCode, res.headers || {}); | ||
this._res = res; | ||
} | ||
|
||
getRawResponse() { | ||
return this._res; | ||
} | ||
|
||
toStream(streamCompleteCallback) { | ||
// The raw response is itself the stream, so we just return that. To be | ||
// backwards comaptible, we should invoke the streamCompleteCallback only | ||
// once the stream has been fully consumed. | ||
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); | ||
} | ||
}); | ||
}); | ||
} | ||
} | ||
|
||
module.exports = {NodeHttpClient, NodeHttpClientResponse}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great