Skip to content
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

feat: add Blobs context to edge functions #6109

Merged
merged 4 commits into from
Oct 31, 2023
Merged
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
1 change: 1 addition & 0 deletions src/commands/dev/dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ const dev = async (options, command) => {

await startProxyServer({
addonsUrls,
blobsContext,
config,
configPath: configPathOverride,
debug: options.debug,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/blobs/blobs.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { getPathInProject } from '../settings.mjs'
* @param {boolean} options.debug
* @param {string} options.projectRoot
* @param {string} options.siteID
* @returns {BlobsContext}
* @returns {Promise<BlobsContext>}
*/
export const getBlobsContext = async ({ debug, projectRoot, siteID }) => {
const token = uuidv4()
Expand Down
2 changes: 1 addition & 1 deletion src/lib/edge-functions/bootstrap.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { env } from 'process'

const latestBootstrapURL = 'https://650bfd807b21ed000893e25c--edge.netlify.com/bootstrap/index-combined.ts'
const latestBootstrapURL = 'https://6539213a19a93a000876a033--edge.netlify.com/bootstrap/index-combined.ts'

export const getBootstrapURL = () => env.NETLIFY_EDGE_BOOTSTRAP || latestBootstrapURL
1 change: 1 addition & 0 deletions src/lib/edge-functions/headers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Buffer } from 'buffer'

export const headers = {
BlobsInfo: 'x-nf-blobs-info',
DeployID: 'x-nf-deploy-id',
FeatureFlags: 'x-nf-feature-flags',
ForwardedHost: 'x-forwarded-host',
Expand Down
8 changes: 8 additions & 0 deletions src/lib/edge-functions/proxy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
*
* @param {object} config
* @param {*} config.accountId
* @param {import("../blobs/blobs.mjs").BlobsContext} config.blobsContext
* @param {*} config.config
* @param {*} config.configPath
* @param {*} config.debug
Expand All @@ -85,6 +86,7 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
*/
export const initializeProxy = async ({
accountId,
blobsContext,
config,
configPath,
debug,
Expand Down Expand Up @@ -151,6 +153,12 @@ export const initializeProxy = async ({
req.headers[headers.Site] = createSiteInfoHeader(siteInfo)
req.headers[headers.Account] = createAccountInfoHeader({ id: accountId })

if (blobsContext?.edgeURL && blobsContext?.token) {
req.headers[headers.BlobsInfo] = Buffer.from(
JSON.stringify({ url: blobsContext.edgeURL, token: blobsContext.token }),
).toString('base64')
}

await registry.initialize()

const url = new URL(req.url, `http://${LOCAL_HOST}:${mainPort}`)
Expand Down
24 changes: 8 additions & 16 deletions src/lib/functions/netlify-function.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -195,22 +195,14 @@ export default class NetlifyFunction {
const environment = {}

if (this.blobsContext) {
if (this.runtimeAPIVersion === 2) {
// For functions using the v2 API, we inject the context object into an
// environment variable.
environment.NETLIFY_BLOBS_CONTEXT = Buffer.from(JSON.stringify(this.blobsContext)).toString('base64')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realised we don't need this. The bootstrap layer can take the data from the client context and set the environment variable, like it's doing in production.

} else {
const payload = JSON.stringify({
url: this.blobsContext.edgeURL,
token: this.blobsContext.token,
})

// For functions using the Lambda compatibility mode, we pass the
// context as part of the `clientContext` property.
context.custom = {
...context?.custom,
blobs: Buffer.from(payload).toString('base64'),
}
const payload = JSON.stringify({
url: this.blobsContext.edgeURL,
token: this.blobsContext.token,
})

context.custom = {
...context?.custom,
blobs: Buffer.from(payload).toString('base64'),
}
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/functions/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const createHandler = function (options) {
'client-ip': [remoteAddress],
'x-nf-client-connection-ip': [remoteAddress],
'x-nf-account-id': [options.accountId],
'x-nf-site-id': [options?.siteInfo?.id],
[efHeaders.Geo]: Buffer.from(JSON.stringify(geoLocation)).toString('base64'),
}).reduce((prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }), {})
const rawQuery = new URLSearchParams(requestQuery).toString()
Expand Down
3 changes: 3 additions & 0 deletions src/utils/proxy-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
* @param {object} params
* @param {string=} params.accountId
* @param {*} params.addonsUrls
* @param {import("../lib/blobs/blobs.mjs").BlobsContext} blobsContext
* @param {import('../commands/types.js').NetlifyOptions["config"]} params.config
* @param {string} [params.configPath] An override for the Netlify config path
* @param {boolean} params.debug
Expand All @@ -58,6 +59,7 @@ export const generateInspectSettings = (edgeInspect, edgeInspectBrk) => {
export const startProxyServer = async ({
accountId,
addonsUrls,
blobsContext,
config,
configPath,
debug,
Expand All @@ -76,6 +78,7 @@ export const startProxyServer = async ({
}) => {
const url = await startProxy({
addonsUrls,
blobsContext,
config,
configPath: configPath || site.configPath,
debug,
Expand Down
2 changes: 2 additions & 0 deletions src/utils/proxy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ export const getProxyUrl = function (settings) {
export const startProxy = async function ({
accountId,
addonsUrls,
blobsContext,
config,
configPath,
debug,
Expand All @@ -693,6 +694,7 @@ export const startProxy = async function ({
const secondaryServerPort = settings.https ? await getAvailablePort() : null
const functionsServer = settings.functionsPort ? `http://127.0.0.1:${settings.functionsPort}` : null
const edgeFunctionsProxy = await initializeEdgeFunctionsProxy({
blobsContext,
config,
configPath,
debug,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
publish = "public"
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getStore } from '@netlify/blobs'

export default async () => {
const store = getStore('my-store')
const metadata = {
name: 'Netlify',
features: {
blobs: true,
functions: true,
},
}

await store.set('my-key', 'hello world', { metadata })

const entry = await store.getWithMetadata('my-key')

return Response.json(entry)
}

export const config = {
path: '/blobs',
}

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"@netlify/blobs": "^4.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
origin

This file was deleted.

This file was deleted.

This file was deleted.

37 changes: 26 additions & 11 deletions tests/integration/commands/dev/edge-functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import process from 'process'

import execa from 'execa'
import { describe, expect, expectTypeOf, test } from 'vitest'

import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js'
Expand Down Expand Up @@ -30,6 +31,10 @@ const routes = [
},
]

const setup = async ({ fixture }) => {
await execa('npm', ['install'], { cwd: fixture.directory })
}

describe.skipIf(isWindows)('edge functions', () => {
setupFixtureTests('dev-server-with-edge-functions', { devServer: true, mockApi: { routes } }, () => {
test<FixtureTestContext>('should run edge functions in correct order', async ({ devServer }) => {
Expand Down Expand Up @@ -115,17 +120,6 @@ describe.skipIf(isWindows)('edge functions', () => {

expect(res2.body).toContain('<p>An unhandled error in the function code triggered the following message:</p>')
})

test<FixtureTestContext>('should run an edge function that imports an npm module', async ({ devServer }) => {
const res = await got(`http://localhost:${devServer.port}/with-npm-module`, {
method: 'GET',
throwHttpErrors: false,
retry: { limit: 0 },
})

expect(res.statusCode).toBe(200)
expect(res.body).toBe('Hello from an npm module!')
})
})

setupFixtureTests('dev-server-with-edge-functions', { devServer: true, mockApi: { routes } }, () => {
Expand All @@ -146,4 +140,25 @@ describe.skipIf(isWindows)('edge functions', () => {
expect(devServer.output).not.toContain('Removed edge function')
})
})

setupFixtureTests(
'dev-server-with-edge-functions-and-npm-modules',
{ devServer: true, mockApi: { routes }, setup },
() => {
test<FixtureTestContext>('should run an edge function that uses the Blobs npm module', async ({ devServer }) => {
const res = await got(`http://localhost:${devServer.port}/blobs`, {
method: 'GET',
throwHttpErrors: false,
retry: { limit: 0 },
})

expect(res.statusCode).toBe(200)
expect(JSON.parse(res.body)).toEqual({
data: 'hello world',
fresh: false,
metadata: { name: 'Netlify', features: { blobs: true, functions: true } },
})
})
},
)
})
4 changes: 2 additions & 2 deletions tests/integration/utils/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ export async function setupFixtureTests(
if (options.mockApi) mockApi = await startMockApi(options.mockApi)
fixture = await Fixture.create(fixturePath, { apiUrl: mockApi?.apiUrl })

await options.setup?.({ fixture, mockApi })

if (options.devServer) {
devServer = await startDevServer({
cwd: fixture.directory,
Expand All @@ -156,8 +158,6 @@ export async function setupFixtureTests(
},
})
}

await options.setup?.({ devServer, fixture, mockApi })
}, HOOK_TIMEOUT)

beforeEach<FixtureTestContext>((context) => {
Expand Down