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

Checkup #736

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Disabled 2025-01-23
# pkg is long since out of date (now with a moderate CVE), and binary builds of Sandbox have not been a priority

name: Binary build

# Push tests pushes
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [ 16.x, 18.x, 20.x ]
node-version: [ 18.x, 20.x, 22.x ]
os: [ windows-latest, ubuntu-latest, macOS-latest ]

# Go
Expand Down
12 changes: 12 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

---

## [7.0.0] 2025-01-25

### Changed

- Upgrade to `router` v2; lots of related refactoring
- Upgrade other deps to resolve CVE warnings
- Breaking change: removed support for Node.js 16.x (now EOL, and no longer available to created in AWS Lambda)
- Added Node.js 22.x to CI
- Disabled binary builds: `pkg` is long since out of date (now with a moderate CVE), and binary builds of Sandbox have not been a priority

---

## [6.0.5] 2024-04-29

### Changed
Expand Down
39 changes: 19 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,45 +31,44 @@
"dependencies": {
"@architect/asap": "~7.0.10",
"@architect/create": "~5.0.3",
"@architect/hydrate": "~4.0.6",
"@architect/inventory": "~4.0.5",
"@architect/hydrate": "~4.0.8",
"@architect/inventory": "~4.0.6",
"@architect/utils": "~4.0.6",
"@aws-lite/client": "^0.21.1",
"@aws-lite/dynamodb": "^0.3.4",
"@aws-lite/client": "^0.22.4",
"@aws-lite/dynamodb": "^0.3.9",
"@begin/hashid": "~1.0.0",
"chalk": "4.1.2",
"chokidar": "~3.6.0",
"chokidar": "~4.0.3",
"depstatus": "~1.1.1",
"dynalite": "~3.2.2",
"finalhandler": "~1.2.0",
"finalhandler": "~1.3.1",
"glob": "~10.3.12",
"http-proxy": "~1.18.1",
"lambda-runtimes": "~2.0.2",
"lambda-runtimes": "~2.0.5",
"minimist": "~1.2.8",
"router": "~1.3.8",
"router": "~2.0.0",
"run-parallel": "~1.2.0",
"run-series": "~1.1.9",
"send": "~0.18.0",
"send": "~1.1.0",
"server-destroy": "~1.0.1",
"tmp": "~0.2.3",
"tree-kill": "~1.2.2",
"update-notifier-cjs": "~5.1.6",
"ws": "~8.17.0"
"update-notifier-cjs": "~5.1.7",
"ws": "~8.18.0"
},
"devDependencies": {
"@architect/eslint-config": "~3.0.0",
"@architect/functions": "~8.1.1",
"@architect/functions": "~8.1.7",
"@architect/req-res-fixtures": "git+https://github.com/architect/req-res-fixtures.git",
"@aws-lite/apigatewaymanagementapi": "~0.0.8",
"@aws-lite/ssm": "~0.2.3",
"@aws-lite/apigatewaymanagementapi": "~0.0.10",
"@aws-lite/ssm": "~0.2.5",
"cross-env": "~7.0.3",
"eslint": "~9.1.1",
"fs-extra": "~11.2.0",
"nyc": "~15.1.0",
"pkg": "~5.8.1",
"eslint": "~9.19.0",
"fs-extra": "~11.3.0",
"nyc": "~17.1.0",
"proxyquire": "~2.1.3",
"tap-arc": "~1.2.2",
"tape": "~5.7.5",
"tap-arc": "~1.3.2",
"tape": "~5.9.0",
"tiny-json-http": "~7.5.1"
}
}
15 changes: 11 additions & 4 deletions src/http/invoke-http/http/_req-fmt.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let headerFormatter = require('./_req-header-fmt')
* - Mocks request object shape from API Gateway <> Lambda proxy integration
* - Unlike the REST request formatter, we build this out as we go (instead of mostly in one big lump) because params are conditionally omitted
*/
module.exports = function requestFormatter ({ method, path, req }) {
module.exports = function requestFormatter ({ method, path, req, catchallTrailingSlash }) {
let { body, params, resource, url } = req
let { pathname, query } = URL.parse(url)

Expand Down Expand Up @@ -78,13 +78,20 @@ module.exports = function requestFormatter ({ method, path, req }) {

// Path parameters
if (Object.keys(params).length) {
// Try to work around router's '0' param jic someone actually used that
if (hasCatchall) {
request.pathParameters = { ...params, proxy: params['0'] }
delete request.pathParameters['0']
// Router 2 no longer supports bare wildcards (`/*`), wildcards must now be "named", so they're now found at `/*_arc_catchall` (which returns an `_arc_catchall` property)
let proxy = params['_arc_catchall'].join('/')
request.pathParameters = { ...params, proxy }
delete request.pathParameters['_arc_catchall']
}
else request.pathParameters = params
}
// Router 2 also no longer catches trailing slashes at the trailing wildcard level, so we have to work around that as well
if (catchallTrailingSlash) {
request.pathParameters = request.pathParameters
? { ...request.pathParameters, proxy: '' }
: { proxy: '' }
}

// Body
if (typeof body === 'string') {
Expand Down
4 changes: 2 additions & 2 deletions src/http/invoke-http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ let _livereload = require('./utils/livereload')
* Formats and validates HTTP request and response event objects
*/
module.exports = function invokeHTTP (params) {
let { apiType, inventory, lambda, ports } = params
let { apiType, inventory, lambda, ports, catchallTrailingSlash } = params
let { method, path } = lambda

method = method.toUpperCase()
Expand All @@ -30,7 +30,7 @@ module.exports = function invokeHTTP (params) {
request = requestFormatterRest({ method, path, req }, httpApiV1)
}
else {
request = requestFormatterHttp({ method, path, req })
request = requestFormatterHttp({ method, path, req, catchallTrailingSlash })
}

// Run the Lambda sig locally
Expand Down
9 changes: 6 additions & 3 deletions src/http/invoke-http/rest/_req-fmt.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,15 @@ module.exports = function requestFormatter ({ method, path, req }, httpApi) {

// Path parameters
if (httpApi && params && Object.keys(params).length) {
// Try to work around router's '0' param jic someone actually used that
if (hasCatchall) {
request.pathParameters = { ...params, proxy: params['0'] }
delete request.pathParameters['0']
// Router 2 no longer supports bare wildcards (`/*`), wildcards must now be "named", so they're now found at `/*_arc_catchall` (which returns an `_arc_catchall` property)
let proxy = params['_arc_catchall'].join('/')
request.pathParameters = { ...params, proxy }
delete request.pathParameters['_arc_catchall']
}
else request.pathParameters = params
// Router 2 also no longer catches trailing slashes at the trailing wildcard level, so we have to work around that as well
// However, REST APIs have not been supported in Arc for years now, so I am not doing the work to figure out if this behavior needs to be replicated
}

return request
Expand Down
22 changes: 21 additions & 1 deletion src/http/register-http/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ module.exports = function reg (app, params) {
if (lambda.arcStaticAssetProxy) return
let { method, path } = lambda

// Router 2 introduced some important changes to wildcard behavior
// Taking the following example Arc route: `get /foo/*`:
// - Previously, trailing wildcards would return a param named `0` with the remaining path as a string, e.g. `{ '0': 'bar' }`
// - Now, wildcards must be named (and have thusly been internally renamed to `/*_arc_catchall`), and return a named property as an array, e.g. : `/foo/*_arc_catchall` becomes `{ '_arc_catchall': [ 'bar' ] }`
// - Previously, trailing slashes would match the wildcard level, and return the normal `0` property as an empty string, e.g. `get /foo/` returns `{ '0': '' }`
// - Now, trailing slashes no longer match at the wildcard level (via changes to path-to-regexp), and thus must have their own special aliased path (see: `catchallTrailingSlash`)
let hasCatchall = path.endsWith('/*')
if (hasCatchall) {
path = path += '_arc_catchall'
}

// Methods not implemented by Arc for legacy REST APIs
if (apiType === 'rest') {
let httpOnly = [ 'any', 'head', 'options' ]
let hasCatchall = path.includes('*')
if (!httpOnly.includes(method) && !hasCatchall) {
let exec = invoker({ lambda, ...params })
app[method](path, exec)
Expand All @@ -22,12 +32,22 @@ module.exports = function reg (app, params) {
let exec = invoker({ lambda, ...params })
if (method !== 'any') {
app[method](path, exec)
if (path.endsWith('/*_arc_catchall')) {
let trailingSlashCatchall = path.replace('*_arc_catchall', '')
let exec = invoker({ lambda, ...params, catchallTrailingSlash: true })
app[method](trailingSlashCatchall, exec)
}
}
// In the case of `any`, register all methods
else {
let methods = [ 'get', 'post', 'put', 'patch', 'head', 'delete', 'options' ]
for (let method of methods) {
app[method](path, exec)
if (path.endsWith('/*_arc_catchall')) {
let trailingSlashCatchall = path.replace('*_arc_catchall', '')
let exec = invoker({ lambda, ...params, catchallTrailingSlash: true })
app[method](trailingSlashCatchall, exec)
}
}
}
}
Expand Down
44 changes: 34 additions & 10 deletions test/integration/http/http-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,30 @@ function runTests (runType, t) {
})
})

t.test(`${mode} get /nodejs22.x`, t => {
t.plan(15)
let rawPath = '/nodejs22.x'
tiny.get({
url: url + rawPath,
}, function _got (err, result) {
if (err) t.end(err)
else {
checkResult(t, result.body, {
message: 'Hello from get /nodejs22.x (running nodejs22.x)',
routeKey: 'GET /nodejs22.x',
rawPath,
pathParameters: undefined,
cookies: undefined,
queryStringParameters: undefined,
rawQueryString: '',
headers: '🤷🏽‍♀️',
isBase64Encoded: false,
body: undefined,
})
}
})
})

t.test(`${mode} get /nodejs20.x`, t => {
t.plan(15)
let rawPath = '/nodejs20.x'
Expand Down Expand Up @@ -238,18 +262,18 @@ function runTests (runType, t) {
})
})

t.test(`${mode} get /python3.11`, t => {
t.test(`${mode} get /python3.13`, t => {
t.plan(16)
let rawPath = '/python3.11'
let rawPath = '/python3.13'
tiny.get({
url: url + rawPath,
}, function _got (err, result) {
if (isWindowsPythonStalling(err, t)) return
else if (err) t.end(err)
else {
checkResult(t, result.body, {
message: 'Hello from get /python3.11 (running python3.11)',
routeKey: 'GET /python3.11',
message: 'Hello from get /python3.13 (running python3.13)',
routeKey: 'GET /python3.13',
rawPath,
pathParameters: undefined,
cookies: undefined,
Expand All @@ -260,7 +284,7 @@ function runTests (runType, t) {
body: undefined,
context: {
aws_request_id: true, // Just check for presence
function_name: 'sandbox-get-python3_11',
function_name: 'sandbox-get-python3_13',
function_version: '$LATEST',
invoked_function_arn: 'sandbox',
memory_limit_in_mb: 1152,
Expand All @@ -270,18 +294,18 @@ function runTests (runType, t) {
})
})

t.test(`${mode} get /python3.8`, t => {
t.test(`${mode} get /python3.9`, t => {
t.plan(16)
let rawPath = '/python3.8'
let rawPath = '/python3.9'
tiny.get({
url: url + rawPath,
}, function _got (err, result) {
if (isWindowsPythonStalling(err, t)) return
else if (err) t.end(err)
else {
checkResult(t, result.body, {
message: 'Hello from get /python3.8 (running python3.8)',
routeKey: 'GET /python3.8',
message: 'Hello from get /python3.9 (running python3.9)',
routeKey: 'GET /python3.9',
rawPath,
pathParameters: undefined,
cookies: undefined,
Expand All @@ -292,7 +316,7 @@ function runTests (runType, t) {
body: undefined,
context: {
aws_request_id: true, // Just check for presence
function_name: 'sandbox-get-python3_8',
function_name: 'sandbox-get-python3_9',
function_version: '$LATEST',
invoked_function_arn: 'sandbox',
memory_limit_in_mb: 1152,
Expand Down
Loading
Loading