Skip to content
Open
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,22 @@ ES|Spain|webservices.amazon.es
UK|United Kingdom|webservices.amazon.co.uk
US|United States|webservices.amazon.com

## Proxy

To allow node-apac through proxy, set the proxy configuration

```javascript
var opHelper = new OperationHelper({
awsId: '[YOUR AWS ID HERE]',
awsSecret: '[YOUR AWS SECRET HERE]',
assocId: '[YOUR ASSOCIATE TAG HERE]',
proxyHostname: '[YOUR PROXY HOSTNAME HERE]',
proxyPort: '[YOUR PROXY PORT HERE]', # default 8080
proxyUsername: '[YOUR PROXY USERNAME HERE]'
proxyPassword: '[YOUR PROXY PASSWORD HERE]'
});
```

## Contributing

Feel free to submit a pull request. If you'd like, you may discuss the change with me first by submitting an issue.
Expand Down
24 changes: 22 additions & 2 deletions lib/operation-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Throttler = require('./throttler')
const locale = require('./locale')

const https = require('https')
const http = require('http')
const xml2js = require('xml2js')

const defaultXml2JsOptions = {
Expand Down Expand Up @@ -34,6 +35,10 @@ class OperationHelper {
this.baseUri = params.baseUri || OperationHelper.defaultBaseUri
this.xml2jsOptions = Object.assign({}, defaultXml2JsOptions, params.xml2jsOptions)
this.throttler = new Throttler(params.maxRequestsPerSecond)
if (typeof(params.proxyHostname) === 'string') this.proxyHostname = params.proxyHostname
this.proxyPort = params.proxyPort || 8080
if (typeof(params.proxyUsername) === 'string') this.proxyUsername = params.proxyUsername
if (typeof(params.proxyPassword) === 'string') this.proxyPassword = params.proxyPassword

// set version
if (typeof(params.version) === 'string') OperationHelper.version = params.version
Expand Down Expand Up @@ -91,13 +96,28 @@ class OperationHelper {
var options = {
hostname: host,
path: uri,
method: 'GET'
method: 'GET',
}
if (this.proxyHostname) {
options = {
hostname: this.proxyHostname,
port: this.proxyPort,
path: 'https://' + host + uri,
method: 'GET',
headers: {
Host: host
}
}
if (this.proxyUsername) {
var auth = 'Basic ' + new Buffer(this.proxyUsername + ':' + this.proxyPassword).toString('base64')
options.headers["Proxy-Authorization"] = auth
}
}

var responseBody = ''

const promise = new Promise((resolve, reject) => {
var request = https.request(options, function (response) {
var request = (this.proxyHostname? http: https).request(options, function (response) {
response.setEncoding('utf8')

response.on('data', function (chunk) {
Expand Down
118 changes: 118 additions & 0 deletions lib/operation-helper.specs.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict"

const https = require('https')
const http = require('http')
const EventEmitter = require('events')
const xml2js = require('xml2js')
const proxyquire = require('proxyquire')
Expand Down Expand Up @@ -243,6 +244,123 @@ describe('OperationHelper', function () {
})
})

describe('#execute with proxy', () => {
let requestMock, responseMock, result, outputResponseBody
const responseBody = '<it><is><some>xml</some></is></it>'
const xml2jsOptions = {foo: 'bar'}
const expectedXml2jsOptions = Object.assign({explicitArray: false}, xml2jsOptions)
const proxyHostname ='localhost'
const proxyPort = 8080
const proxyUsername = 'username'
const proxyPassword = 'password'
const testURI = '/onca/xml';

context('happy path proxy', () => {
let opHelper

beforeEach(() => {
opHelper = new OperationHelper({
awsId: 'testAwsId',
awsSecret: 'testAwsSecret',
assocId: 'testAssocId',
proxyHostname: proxyHostname,
proxyPort: proxyPort,
proxyUsername: proxyUsername,
proxyPassword: proxyPassword,
xml2jsOptions
})

responseMock = new EventEmitter()
responseMock.setEncoding = sinon.spy()

requestMock = new EventEmitter()
requestMock.end = () => {
responseMock.emit('data', responseBody.substr(0, 5))
responseMock.emit('data', responseBody.substr(5))
responseMock.emit('end')
}

sinon.stub(http, 'request').returns(requestMock).callsArgWith(1, responseMock)
sinon.stub(opHelper, 'generateUri').returns(testURI)
sinon.spy(xml2js, 'parseString')
})

afterEach(() => {
http.request.restore()
xml2js.parseString.restore()
})

const doAssertions = () => {
it('should create an http request to proxy with the correct options', () => {
expect(http.request.callCount).to.equal(1)
expect(http.request.firstCall.args[0]).to.eql({
hostname: proxyHostname,
port: proxyPort,
method: 'GET',
headers: {
Host: locale.DEFAULT_ENDPOINT,
"Proxy-Authorization": "Basic " + new Buffer(proxyUsername + ':' + proxyPassword).toString('base64')
},
path: 'https://' + locale.DEFAULT_ENDPOINT + testURI,
})
})

it('should set the response encoding to utf8 (via proxy)', () => {
expect(responseMock.setEncoding.calledWith('utf8'))
})

it('should provide the raw response body', () => {
expect(outputResponseBody).to.equal(responseBody)
})

it('should pass the xml2jsOptions to xml2js', () => {
expect(xml2js.parseString.firstCall.args[1]).to.eql(expectedXml2jsOptions)
})

it('should parse XML and return result as object', () => {
expect(result).to.eql({
it: {
is: {
some: 'xml'
}
}
})
})
}

context('(traditional callback)', () => {
beforeEach((done) => {
opHelper.execute('ItemSearch', {
'SearchIndex': 'Books',
'Keywords': 'harry potter',
'ResponseGroup': 'ItemAttributes,Offers'
}, function (err, _results, _rawResponseBody) {
result = _results
outputResponseBody = _rawResponseBody
done()
})
})

doAssertions()
})

context('(promise)', () => {
beforeEach(() => {
return opHelper.execute('ItemSearch', {
'SearchIndex': 'Books',
'Keywords': 'harry potter',
'ResponseGroup': 'ItemAttributes,Offers'
}).then((response) => {
result = response.result
outputResponseBody = response.responseBody
})
})

doAssertions()
})
})
})

context('when the request has an error', () => {
const error = new Error('testErrorMessage')
let thrownError, opHelper
Expand Down