Skip to content

Commit 503c9d0

Browse files
feat: add support for azure function v4 event source (#529)
* feat: add support for azure function v4 event source * docs: add azure http function v4 support to README.md * fix: npm package out of sync issue Co-authored-by: brett-vendia <[email protected]>
1 parent f7f3f6c commit 503c9d0

File tree

21 files changed

+1787
-16
lines changed

21 files changed

+1787
-16
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ exports.handler = handler
7474

7575
## Azure
7676

77-
### Async Azure Function (v3) handler wrapper
77+
### Async Azure Function v3/v4 handler wrapper
7878

7979
The only Azure Function specific code you need to write is a simple `index.js` and a `function.json` like below.
8080

__tests__/integration.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo
240240
delete response.multiValueHeaders.etag
241241
delete response.multiValueHeaders['last-modified']
242242
break
243+
case 'azureHttpFunctionV4':
243244
case 'azureHttpFunctionV3':
244245
expectedResponse.body = Buffer.from(samLogoBase64, 'base64')
245246
expectedResponse.isBase64Encoded = false
@@ -424,6 +425,7 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo
424425
})
425426

426427
switch (eventSourceName) {
428+
case 'azureHttpFunctionV4':
427429
case 'azureHttpFunctionV3':
428430
expectedResponse.cookies = [
429431
{

examples/azure-http-function-v3/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ These prerequisites are required to run and debug your functions locally.
88

99
- Visual Studio Code
1010
- Visual Studio Code - Azure Functions extension
11-
- Azure Functions Core Tools
11+
- Azure Functions Core Tools (`npm install -g azure-functions-core-tools@3 --unsafe-perm true`)
1212
- Node.js
1313

1414
_For further information on how to run an Azure Function locally please check [Develop Azure Functions by using Visual Studio Code](https://docs.microsoft.com/en-us/azure/azure-functions/functions-develop-vs-code?tabs=nodejs)._

examples/azure-http-function-v3/package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/azure-http-function-v3/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"start": "func start"
77
},
88
"dependencies": {
9-
"@vendia/serverless-express": "4.5.2",
9+
"@vendia/serverless-express": "4.8.2",
1010
"body-parser": "1.19.1",
1111
"compression": "^1.7.4",
1212
"cors": "2.8.5",
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const path = require('path')
2+
const express = require('express')
3+
const bodyParser = require('body-parser')
4+
const cors = require('cors')
5+
const compression = require('compression')
6+
const { getCurrentInvoke } = require('../../../src/index') // require('@vendia/serverless-express')
7+
const ejs = require('ejs').__express
8+
const app = express()
9+
const router = express.Router()
10+
11+
app.set('view engine', 'ejs')
12+
app.engine('.ejs', ejs)
13+
14+
router.use(compression())
15+
router.use(cors())
16+
router.use(bodyParser.json())
17+
router.use(bodyParser.urlencoded({ extended: true }))
18+
19+
// NOTE: tests can't find the views directory without this
20+
app.set('views', path.join(__dirname, 'views'))
21+
22+
router.get('/api', (req, res) => {
23+
const currentInvoke = getCurrentInvoke()
24+
const { event = {} } = currentInvoke
25+
const { requestContext = {} } = event
26+
const { domainName = 'localhost:7071' } = requestContext
27+
const apiUrl = `https://${domainName}`
28+
res.render('index', { apiUrl })
29+
})
30+
31+
router.get('/api/vendia', (req, res) => {
32+
res.sendFile(path.join(__dirname, 'vendia-logo.png'))
33+
})
34+
35+
router.get('/api/users', (req, res) => {
36+
res.json(users)
37+
})
38+
39+
router.get('/api/users/:userId', (req, res) => {
40+
const user = getUser(req.params.userId)
41+
42+
if (!user) return res.status(404).json({})
43+
44+
return res.json(user)
45+
})
46+
47+
router.post('/api/users', (req, res) => {
48+
const user = {
49+
id: ++userIdCounter,
50+
name: req.body.name
51+
}
52+
users.push(user)
53+
res.status(201).json(user)
54+
})
55+
56+
router.put('/api/users/:userId', (req, res) => {
57+
const user = getUser(req.params.userId)
58+
59+
if (!user) return res.status(404).json({})
60+
61+
user.name = req.body.name
62+
res.json(user)
63+
})
64+
65+
router.delete('/api/users/:userId', (req, res) => {
66+
const userIndex = getUserIndex(req.params.userId)
67+
68+
if (userIndex === -1) return res.status(404).json({})
69+
70+
users.splice(userIndex, 1)
71+
res.json(users)
72+
})
73+
74+
router.get('/api/cookie', (req, res) => {
75+
res.cookie('Foo', 'bar')
76+
res.cookie('Fizz', 'buzz')
77+
res.json({})
78+
})
79+
80+
const getUser = (userId) => users.find(u => u.id === parseInt(userId))
81+
const getUserIndex = (userId) => users.findIndex(u => u.id === parseInt(userId))
82+
83+
// Ephemeral in-memory data store
84+
const users = [{
85+
id: 1,
86+
name: 'Joe'
87+
}, {
88+
id: 2,
89+
name: 'Jane'
90+
}]
91+
let userIdCounter = users.length
92+
93+
// The serverless-express library creates a server and listens on a Unix
94+
// Domain Socket for you, so you can remove the usual call to app.listen.
95+
// app.listen(3000)
96+
app.use('/', router)
97+
98+
// Export your express server so you can import it in the lambda function.
99+
module.exports = app
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"bindings": [
3+
{
4+
"authLevel": "anonymous",
5+
"type": "httpTrigger",
6+
"direction": "in",
7+
"name": "req",
8+
"route": "{*segments}"
9+
},
10+
{
11+
"type": "http",
12+
"direction": "out",
13+
"name": "$return"
14+
}
15+
]
16+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const serverlessExpress = require('../../../src/index') // require('@vendia/serverless-express')
2+
const app = require('./app')
3+
const cachedServerlessExpress = serverlessExpress({ app })
4+
5+
module.exports = async function (context, req) {
6+
return cachedServerlessExpress(context, req)
7+
}
8.17 KB
Loading
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<title>My Serverless Application</title>
6+
<style>
7+
body {
8+
width: 650px;
9+
margin: auto;
10+
}
11+
12+
h1 {
13+
text-align: center;
14+
}
15+
16+
.resources>h2 {
17+
margin-bottom: 0;
18+
}
19+
20+
.resource>h3,
21+
.resource>p {
22+
display: inline-block;
23+
margin-bottom: 0.5rem;
24+
}
25+
26+
.resource>code {
27+
display: block;
28+
background-color: #eff0f1;
29+
color: #393318;
30+
padding: 5px;
31+
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
32+
white-space: nowrap;
33+
overflow-x: auto;
34+
}
35+
36+
.vendia-logo {
37+
display: block;
38+
width: 10rem;
39+
margin: auto;
40+
}
41+
42+
form {
43+
margin-bottom: 1rem;
44+
}
45+
46+
.form-group {
47+
padding-bottom: 1rem;
48+
}
49+
50+
label {
51+
display: block;
52+
}
53+
</style>
54+
</head>
55+
56+
<body>
57+
<h1>My Serverless Application</h1><img class="vendia-logo" src="/api/vendia">
58+
<p>Welcome to your Azure Function serverless application. This example application has several resources configured for you to explore. State is stored in memory in a given container, and is therefore ephemeral.</p>
59+
<section class="resources">
60+
<h2>Resources</h2>
61+
<section class="resource">
62+
<h3><a class="resource-example-link" href="/api/users">GET /users</a></h3>
63+
<p> &nbsp;Returns a list of all users.</p><code>curl <span><%= apiUrl %></span>/users -H 'accept: application/json'</code>
64+
</section>
65+
<section class="resource">
66+
<h3>POST /users</h3>
67+
<p> &nbsp;Creates a new user.</p><code>curl <span><%= apiUrl %></span>/api/users -X POST -d '{"name":"Sam"}' -H 'accept: application/json'</code>
68+
</section>
69+
<section class="resource">
70+
<h3><a class="resource-example-link" href="/api/users/1">GET /users/:userId</a></h3>
71+
<p> &nbsp;Returns a single user.</p><code>curl <span><%= apiUrl %></span>/api/users/1 -H 'accept: application/json'</code>
72+
</section>
73+
<section class="resource">
74+
<h3>PUT /users/:userId</h3>
75+
<p> &nbsp;Updates an existing user.</p><code>curl <span><%= apiUrl %></span>/api/users/1 -X PUT -d '{"name":"Samantha"}' -H 'accept: application/json'</code>
76+
</section>
77+
<section class="resource">
78+
<h3>DELETE /users/:userId</h3>
79+
<p> &nbsp;Deletes an existing user.</p><code>curl <span><%= apiUrl %></span>/api/users/1 -X DELETE -H 'accept: application/json'</code>
80+
</section>
81+
</section>
82+
<section class="form">
83+
<h2>Form</h2>
84+
<p>Experiment with POST and PUT via the form below. Leave the id empty to create a new user (POST) or enter an id to update a user's name (PUT).</p>
85+
<form>
86+
<div class="form-group"><label for="idField">user id</label><input type="text" name="id" id="idField" placeholder="optional"></div>
87+
<div class="form-group"><label for="nameField">name</label><input type="text" name="name" id="nameField"></div><input type="submit">
88+
</form>
89+
</section>
90+
<script>
91+
var form = document.querySelector('form')
92+
form.addEventListener('submit', function(event) {
93+
event.preventDefault()
94+
var id = document.getElementById('idField').value
95+
var name = document.getElementById('nameField').value
96+
var endpoint = id ? 'users/' + id : 'users'
97+
98+
if (!window.fetch) {
99+
alert('Your browser does not have fetch support, which this demo uses. Try again in a modern browser (https://caniuse.com/fetch) or modify the example to use an alternate approach (e.g. XMLHttpRequest or your library of choice.)')
100+
}
101+
102+
return fetch(endpoint, {
103+
method: id ? 'PUT' : 'POST',
104+
headers: {
105+
'content-type': 'application/json'
106+
},
107+
body: JSON.stringify({
108+
name: name
109+
})
110+
})
111+
})
112+
</script>
113+
</body>
114+
115+
</html>

0 commit comments

Comments
 (0)