Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Thank you to Upstash for reaching out to sponsor this project!
<li>Price scales to zero with per request pricing</li>
<li>Built-in REST API designed for serverless and edge functions</li>
</ul>

[Start for free in 30 seconds!](https://upstash.com/?utm_source=serverless-http)
</td>
</tr>
Expand Down Expand Up @@ -53,6 +53,18 @@ Thank you to Upstash for reaching out to sponsor this project!
* [Genezio](https://genezio.com/deploy-nodejs-express-on-genezio-serverless/)
* Azure (Experimental, untested, probably outdated)

### Proxy trust
Serverless-http may use `x-forwarded-for` headers to provide accurate client IP addresses when your application is behind proxies or load balancers.

Proxy trust can be enabled using the `proxyTrust (Function|Array|String)` option, see [proxy-addr](https://www.npmjs.com/package/proxy-addr) for details:

```javascript
const handler = serverless(app, {
proxyTrust: () => true // Enable full proxy trust
proxyTrust: ['loopback', '192.168.0.0/16'] // Enable only specific address
});
```

## Deploy a Hello Word on Genezio
:rocket: You can deploy your own hello world example using the Express framework to Genezio with one click:

Expand Down
1 change: 1 addition & 0 deletions lib/provider/aws/create-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ module.exports = (event, context, options) => {
body,
remoteAddress,
url,
proxyTrust: options.proxyTrust,
});

req.requestContext = event.requestContext;
Expand Down
12 changes: 10 additions & 2 deletions lib/provider/azure/create-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,17 @@ function requestBody(request) {
throw new Error(`Unexpected request.body type: ${typeof request.rawBody}`);
}

module.exports = (request) => {
function requestRemoteAddress(event) {
return event.requestContext.identity.sourceIp;
}


module.exports = (request, options) => {
const method = request.method;
const query = request.query;
const headers = requestHeaders(request);
const body = requestBody(request);
const remoteAddress = requestRemoteAddress(request);

const req = new Request({
method,
Expand All @@ -38,7 +44,9 @@ module.exports = (request) => {
url: url.format({
pathname: request.url,
query
})
}),
remoteAddress,
proxyTrust: options.proxyTrust,
});
req.requestContext = request.requestContext;
return req;
Expand Down
10 changes: 8 additions & 2 deletions lib/request.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict';

const http = require('http');
const proxyAddr = require('proxy-addr');
const { PassThrough } = require('stream');

module.exports = class ServerlessRequest extends http.IncomingMessage {
constructor({ method, url, headers, body, remoteAddress }) {
constructor({ method, url, headers, body, remoteAddress, proxyTrust }) {
// Create a real readable socket for IncomingMessage instead of a stub.
const socket = new PassThrough();
socket.encrypted = true;
Expand All @@ -17,8 +18,13 @@ module.exports = class ServerlessRequest extends http.IncomingMessage {
headers['content-length'] = Buffer.byteLength(body);
}

Object.defineProperty(this, 'ip', {
get: () => {
return proxyAddr(this, proxyTrust || proxyAddr.compile([]));
}
});

Object.assign(this, {
ip: remoteAddress,
complete: true,
httpVersion: '1.1',
httpVersionMajor: '1',
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"pino": "^8.15.0",
"pino-http": "^8.5.0",
"polka": "^0.5.2",
"proxy-addr": "^2.0.7",
"reflect-metadata": "^0.1.13",
"restana": "^4.0.7",
"sails": "^1.2.3",
Expand Down
3 changes: 2 additions & 1 deletion serverless-http.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ declare namespace ServerlessHttp {
request?: Object | Function,
response?: Object | Function,
binary?: boolean | Function | string | string[],
basePath?: string
basePath?: string,
proxyTrust?: Function | Array | String
}
/**
* AWS Lambda APIGatewayProxyHandler-like handler.
Expand Down
73 changes: 73 additions & 0 deletions test/express.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,79 @@ describe('express', () => {
});
});

it('ip should come from identity source ip for aws', () => {
app.use(morgan('short'));
app.use((req, res) => {
res.status(200).send(req.ip);
});

return request(app, {
httpMethod: 'GET',
requestContext: {
identity: {
sourceIp: '127.0.0.1'
}
}
})
.then(response => {
expect(response.statusCode).to.equal(200);
expect(response.body).to.equal('127.0.0.1');
});
});

it('ip should come from x-forwarded-for header if present', () => {
app.use(morgan('short'));
app.use((req, res) => {
res.status(200).send(req.ip);
});

return request(app, {
httpMethod: 'GET',
headers: {
'x-forwarded-for': '1.3.3.7'
},
requestContext: {
identity: {
sourceIp: '127.0.0.1'
}
}
},
{
proxyTrust: () => true
})
.then(response => {
expect(response.statusCode).to.equal(200);
expect(response.body).to.equal('1.3.3.7');
});
});


it('ip should come from x-forwarded-for header if present 2', () => {
app.use(morgan('short'));
app.use((req, res) => {
res.status(200).send(req.ip);
});

return request(app, {
httpMethod: 'GET',
headers: {
'x-forwarded-for': '192.0.0.1, 1.3.3.7'
},
requestContext: {
identity: {
sourceIp: '127.0.0.1'
}
}
},
{
proxyTrust: () => true
})
.then(response => {
expect(response.statusCode).to.equal(200);
expect(response.body).to.equal('192.0.0.1');
});
});

it('destroy weird', () => {
app.use((req, res) => {
// this was causing a .destroy is not a function error
Expand Down