forked from honojs/hono
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(adapter): introduce AWS Lambda Adapter (honojs#987)
* feat: introduce AWS Lambda adapter * denoify ignore * export settings
- Loading branch information
Showing
6 changed files
with
214 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export default { | ||
testMatch: ['**/test_lambda/**/*.+(ts|tsx|js)'], | ||
transform: { | ||
'^.+\\.(ts|tsx)$': 'ts-jest', | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// @denoify-ignore | ||
import crypto from 'crypto' | ||
import type { Hono } from '../../hono' | ||
|
||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
globalThis.crypto = crypto | ||
|
||
interface APIGatewayEvent { | ||
httpMethod: string | ||
headers: Record<string, string | undefined> | ||
path: string | ||
body: string | null | ||
isBase64Encoded: boolean | ||
requestContext: { | ||
domainName: string | ||
} | ||
} | ||
|
||
interface APIGatewayProxyResult { | ||
statusCode: number | ||
body: string | ||
headers: Record<string, string> | ||
isBase64Encoded: boolean | ||
} | ||
|
||
export const handle = (app: Hono) => { | ||
return async (event: APIGatewayEvent): Promise<APIGatewayProxyResult> => { | ||
const req = createRequest(event) | ||
const res = await app.fetch(req) | ||
const arrayBuffer = await res.arrayBuffer() | ||
const result: APIGatewayProxyResult = { | ||
statusCode: res.status, | ||
body: String.fromCharCode(...new Uint8Array(arrayBuffer)), | ||
headers: {}, | ||
isBase64Encoded: false, | ||
} | ||
|
||
res.headers.forEach((value, key) => { | ||
result.headers[key] = value | ||
}) | ||
|
||
return result | ||
} | ||
} | ||
|
||
const createRequest = (event: APIGatewayEvent) => { | ||
const url = `https://${event.requestContext.domainName}${event.path}` | ||
const headers = new Headers() | ||
for (const [k, v] of Object.entries(event.headers)) { | ||
if (v) headers.set(k, v) | ||
} | ||
const method = event.httpMethod | ||
|
||
const requestInit: RequestInit = { | ||
headers: headers, | ||
method: method, | ||
} | ||
|
||
if (event.body) { | ||
requestInit.body = event.isBase64Encoded ? atob(event.body) : event.body | ||
} | ||
return new Request(url, requestInit) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// @denoify-ignore | ||
export { handle } from './handler' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { handle } from '../src/adapter/aws-lambda/handler' | ||
import { Hono } from '../src/hono' | ||
import { basicAuth } from '../src/middleware/basic-auth' | ||
|
||
describe('AWS Lambda Adapter for Hono', () => { | ||
const app = new Hono() | ||
|
||
app.get('/', (c) => { | ||
return c.text('Hello Lambda!') | ||
}) | ||
|
||
app.post('/post', async (c) => { | ||
const body = (await c.req.parseBody()) as { message: string } | ||
return c.text(body.message) | ||
}) | ||
|
||
const username = 'hono-user-a' | ||
const password = 'hono-password-a' | ||
app.use('/auth/*', basicAuth({ username, password })) | ||
app.get('/auth/abc', (c) => c.text('Good Night Lambda!')) | ||
|
||
const handler = handle(app) | ||
|
||
it('Should handle a GET request and return a 200 response', async () => { | ||
const event = { | ||
httpMethod: 'GET', | ||
headers: { 'content-type': 'text/plain' }, | ||
path: '/', | ||
body: null, | ||
isBase64Encoded: false, | ||
requestContext: { | ||
domainName: 'example.com', | ||
}, | ||
} | ||
|
||
const response = await handler(event) | ||
expect(response.statusCode).toBe(200) | ||
expect(response.body).toBe('Hello Lambda!') | ||
expect(response.headers['content-type']).toMatch(/^text\/plain/) | ||
expect(response.isBase64Encoded).toBe(false) | ||
}) | ||
|
||
it('Should handle a GET request and return a 404 response', async () => { | ||
const event = { | ||
httpMethod: 'GET', | ||
headers: { 'content-type': 'text/plain' }, | ||
path: '/nothing', | ||
body: null, | ||
isBase64Encoded: false, | ||
requestContext: { | ||
domainName: 'example.com', | ||
}, | ||
} | ||
|
||
const response = await handler(event) | ||
expect(response.statusCode).toBe(404) | ||
}) | ||
|
||
it('Should handle a POST request and return a 200 response', async () => { | ||
const searchParam = new URLSearchParams() | ||
searchParam.append('message', 'Good Morning Lambda!') | ||
const event = { | ||
httpMethod: 'POST', | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
path: '/post', | ||
body: btoa(searchParam.toString()), | ||
isBase64Encoded: true, | ||
requestContext: { | ||
domainName: 'example.com', | ||
}, | ||
} | ||
|
||
const response = await handler(event) | ||
expect(response.statusCode).toBe(200) | ||
expect(response.body).toBe('Good Morning Lambda!') | ||
}) | ||
|
||
it('Should handle a request and return a 401 response with Basic auth', async () => { | ||
const event = { | ||
httpMethod: 'GET', | ||
headers: { | ||
'Content-Type': 'plain/text', | ||
}, | ||
path: '/auth/abc', | ||
body: null, | ||
isBase64Encoded: true, | ||
requestContext: { | ||
domainName: 'example.com', | ||
}, | ||
} | ||
|
||
const response = await handler(event) | ||
expect(response.statusCode).toBe(401) | ||
}) | ||
|
||
it('Should handle a request and return a 200 response with Basic auth', async () => { | ||
const credential = 'aG9uby11c2VyLWE6aG9uby1wYXNzd29yZC1h' | ||
const event = { | ||
httpMethod: 'GET', | ||
headers: { | ||
'Content-Type': 'plain/text', | ||
Authorization: `Basic ${credential}`, | ||
}, | ||
path: '/auth/abc', | ||
body: null, | ||
isBase64Encoded: true, | ||
requestContext: { | ||
domainName: 'example.com', | ||
}, | ||
} | ||
|
||
const response = await handler(event) | ||
expect(response.statusCode).toBe(200) | ||
expect(response.body).toBe('Good Night Lambda!') | ||
}) | ||
}) |