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(adapter): Added aws-handler support for APIGatewayProxyEventV2 #1009

Merged
merged 9 commits into from
May 3, 2023

Conversation

schonert
Copy link
Contributor

Super exicted about the slim adapter! Got the chance to start using it yesterday and ran into some missing mappings when calling lambda directly through the function URL.

  • Renamed APIGatewayEvent to APIGatewayProxyEvent to follow the convention from @types/aws-lambda
  • Added APIGatewayEventV2. The type used by function urls
  • Added support for sending files through streams. Ran into issues while using with serveStatic. Files will be converted to base64.
  • Broke the handler into smaller functions. It became quite bloated when accounting for base64 and event mapping

Submitted directly through github.com - will catch up on any red flags 👍

@schonert
Copy link
Contributor Author

All tests are green 👍

src/adapter/aws-lambda/handler.ts Outdated Show resolved Hide resolved
src/adapter/aws-lambda/handler.ts Outdated Show resolved Hide resolved
src/adapter/aws-lambda/handler.ts Outdated Show resolved Hide resolved
src/adapter/aws-lambda/handler.ts Outdated Show resolved Hide resolved
@yusukebe
Copy link
Member

Hi @schonert !

Thank you for creating PR! I am glad you wish Lambda adapter for Hono.

I commented on several points of concern. And I also get the following error in the test. How about making queryStringParameters optional and checking it in the adapter?

SS

@yusukebe
Copy link
Member

@schonert

Sorry! The package.json was wrong, and I've fixed it. So, please merge main into your branch.

@schonert
Copy link
Contributor Author

schonert commented Apr 6, 2023

@yusukebe

Sorry about the hold-up. Merged main into PR branch 👍

@schonert
Copy link
Contributor Author

schonert commented Apr 6, 2023

I see the tests did not pass - strange since I recall running before submitting. I'll take a look

Copy link
Member

@yusukebe yusukebe left a comment

Choose a reason for hiding this comment

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

@schonert

Do you run yarn test:lambda?

And I've leave some comments.


// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: asking for asyncIterator
for await (const chunk of stream) {
Copy link
Member

Choose a reason for hiding this comment

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

We can use TextDecoder():

  const decoder = new TextDecoder()
  let string = ''

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore: asking for asyncIterator
  for await (const chunk of stream) {
    string += decoder.decode(chunk)
  }

}

const createResult = async (res: Response): Promise<APIGatewayProxyResult> => {
const isBase64Encoded = res.body instanceof ReadableStream
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure about this, and should look into it, but res.body will always be a ReadableStream? What do you think?

@schonert
Copy link
Contributor Author

@yusukebe did you get the chance to check out my comments?

@yusukebe
Copy link
Member

Hi @schonert !

Which are your comments? I've left the comments on your code, so please check it again!

@schonert
Copy link
Contributor Author

@yusukebe i simply replied to your comments

#1009 (comment)
#1009 (comment)

@yusukebe
Copy link
Member

@schonert

Did you leave comments? I can't see any of your comments.

@schonert
Copy link
Contributor Author

Hmm strange. I've tagged you and linked? Attached a screenshot below 👇
image

@yusukebe
Copy link
Member

Hi @schonert !

Yeah, it's strange, but now I understand your comments well, with the help of your screenshots. Thanks a lot!

According to the Web API specs, the Response body is always a ReadableStream, even if it's plain text. So I think we don't have to check if it's a ReadableStream or not. How about writing it like this?:

const body = await fromReadableToString(res) // always use `fromReadableToString`

const result: APIGatewayProxyResult = {
  //...
  isBase64Encoded: true, // it must be base64
}

We'll have to rewrite the tests, but I think this approach might be the best.

@schonert
Copy link
Contributor Author

So we just simply always base64? Would be a simple and straightforward solution. Not aware of any downsides 👍🏻

@yusukebe
Copy link
Member

@schonert

So we just simply always base64?

Absolutely! So, could you please rewrite the code and tests? We can run the CI again once you've completed the changes.

@schonert
Copy link
Contributor Author

schonert commented May 3, 2023

@yusukebe finally got around to implementing the changes above.
Been using Github codespace - which was an awful slow experience. Should have just cloned the project down from day one 😅

@yusukebe
Copy link
Member

yusukebe commented May 3, 2023

Hi @schonert !

Great! It's time to merge! Thanks!

@yusukebe yusukebe merged commit a2e6167 into honojs:main May 3, 2023
8 checks passed
mastondzn pushed a commit to mastondzn/synopsisbot that referenced this pull request May 22, 2023
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [hono](https://hono.dev/) ([source](https://togithub.com/honojs/hono))
| [`3.1.8` ->
`3.2.1`](https://renovatebot.com/diffs/npm/hono/3.1.8/3.2.1) |
[![age](https://badges.renovateapi.com/packages/npm/hono/3.2.1/age-slim)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://badges.renovateapi.com/packages/npm/hono/3.2.1/adoption-slim)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://badges.renovateapi.com/packages/npm/hono/3.2.1/compatibility-slim/3.1.8)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://badges.renovateapi.com/packages/npm/hono/3.2.1/confidence-slim/3.1.8)](https://docs.renovatebot.com/merge-confidence/)
|

---

### Release Notes

<details>
<summary>honojs/hono</summary>

### [`v3.2.1`](https://togithub.com/honojs/hono/releases/tag/v3.2.1)

[Compare
Source](https://togithub.com/honojs/hono/compare/v3.2.0...v3.2.1)

This is a patch release.

#### What's Changed

- fix(app): `app.mount()` supports `/` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1119

**Full Changelog**:
honojs/hono@v3.2.0...v3.2.1

### [`v3.2.0`](https://togithub.com/honojs/hono/releases/tag/v3.2.0)

[Compare
Source](https://togithub.com/honojs/hono/compare/v3.1.8...v3.2.0)

> New routers, presets, `app.mount()`, Node server v1. Let's go!

Hono *v3.2* is now available! It introduces many features while
maintaining simplicity.

-   Two new routers: LinearRouter and PatternRouter.
-   Presets: `hono/tiny`, `hono/quick`.
-   `app.mount()`
-   Release of Node adapter server v1.0.0!
-   Support for AWS Lambda function URLs.
-   Cookie Middleware.
-   Renaming `hono/nextjs` to `hono/vercel`.

Let's take a look!

#### New Routers

We introduce two new routers: LinearRouter and PatternRouter.

##### LinearRouter

**LinearRouter** is "*quick*". While RegExpRouter is one of the fastest
routers in the JavaScript world, it's a bit slow when registering
routing paths.

```ts
app.get('/', handler) // <=== Registering routing paths - a little slow

//...

app.fetch(request)  // <=== Handle request - ultra-fast
```

So, in environments that are initialized with every request, such as
Fastly Compute@Edge, RegExpRouter may not be the best choice.

**LinearRouter** registers routes very quickly, even compared to other
fast JavaScript routers. The following is one of the benchmark results,
which includes the route registration phase.

    • GET /user/lookup/username/hey
-----------------------------------------------------
-----------------------------
LinearRouter 1.82 µs/iter (1.7 µs … 2.04 µs) 1.84 µs 2.04 µs 2.04 µs
MedleyRouter 4.44 µs/iter (4.34 µs … 4.54 µs) 4.48 µs 4.54 µs 4.54 µs
FindMyWay 60.36 µs/iter (45.5 µs … 1.9 ms) 59.88 µs 78.13 µs 82.92 µs
KoaTreeRouter 3.81 µs/iter (3.73 µs … 3.87 µs) 3.84 µs 3.87 µs 3.87 µs
TrekRouter 5.84 µs/iter (5.75 µs … 6.04 µs) 5.86 µs 6.04 µs 6.04 µs

    summary for GET /user/lookup/username/hey
      LinearRouter
       2.1x faster than KoaTreeRouter
       2.45x faster than MedleyRouter
       3.21x faster than TrekRouter
       33.24x faster than FindMyWay

And according to [this
comment](https://togithub.com/honojs/hono/issues/1109#issuecomment-1551115448),
it will be 40% faster on Fastly Compute@Edge.

#### PatternRouter

**PatternRouter** is "*tiny*". By default, Hono uses SmartRouter with
RegExpRouter and TrieRouter. Although not the fastest, we made it even
smaller.

If you need to reduce size for resource-limited environments, you can
use PatternRouter.

An application using only PatternRouter is about 12KB in size.

    yusuke $ esbuild --outdir=dist --bundle --minify ./src/index.ts

      dist/index.js  11.9kb

    ⚡ Done in 9ms

#### Presets: `hono/tiny`, `hono/quick`

Hono has several routers, each designed for a specific purpose. You can
specify the router you want to use in the Hono constructor.

**Presets** are provided for common use cases, so you don't have to
specify the router each time. The Hono class imported from all presets
is the same, the only difference being the router. Therefore, you can
use them interchangeably.

We introduce `hono/tiny` and `hono/quick` today.

##### `hono/tiny`

Preset `hono/tiny` means using only PatternRouter.

```ts
this.router = new PatternRouter()
```

To use `hono/tiny`, you only have to import `hono/tiny` and use `Hono`
as usual.

```ts
import { Hono } from 'hono/tiny'

const app = new Hono()

//...
```

##### `hono/quick`

Preset `hono/quick` means using only LinearRouter.

```ts
this.router = new LinearRouter()
```

You can also use `hono/quick` like other presets.

```ts
import { Hono } from 'hono/quick'
```

##### Which preset should I use?

We now offer three presets: `hono`, `hono/tiny`, and `hono/quick`. You
might be wondering, "Which preset should I use?" Please refer to the
followings.

| Preset | Suitable platforms |
| ------------ |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| `hono` | This is highly recommended for most use cases. Although the
registration phase may be slower than `hono/quick`, it exhibits high
performance once booted. It's ideal for long-life servers built with
**Deno**, **Bun**, or **Node.js**. For environments such as **Cloudflare
Workers**, **Deno Deploy**, **Lagon**, where v8 isolates are utilized,
this preset is suitable too. Because the isolates persist for a certain
amount of time after booting. |
| `hono/quick` | This preset is designed for environments where the
application is initialized for every request. **Fastly Compute@Edge**
operates in this manner, thus this preset is recommended for use it. |
| `hono/tiny` | This is the smallest router package and is suitable for
environments where resources are limited.

#### `app.mount()`

By using new feature `app.mount()`, you can integrate applications using
other frameworks, such as *itty-router*, with Hono.

```ts
// Create itty-router application
const ittyRouter = IttyRouter()

// Handle `GET /itty-router/hello`
ittyRouter.get('/hello', () => new Response('Hello from itty-router!'))

// Hono application
const app = new Hono()

// Hono application
app.mount('/itty-router', ittyRouter.handle)
```

Also *Remix*:

```ts
import { Hono } from 'hono'
import { env } from 'hono/adapter'
import { serveStatic } from 'hono/cloudflare-workers'

import { createRequestHandler } from '@&#8203;remix-run/cloudflare'
import * as build from './build'

// Remix application
// @&#8203;ts-ignore
const handleRemixRequest = createRequestHandler(build, process.env.NODE_ENV)

// Hono application
const app = new Hono()

// Static files for Remix
app.get(
  '/remix/build/*',
  serveStatic({
    root: './',
  })
)

// Mount Remix app
app.mount('/remix', handleRemixRequest, (c) => {
  return { env: env(c) }
})
```

This implies that we can mount applications built with any framework,
such as *itty-router*, *Remix*, *Qwik*, or *SolidJS*, into our Hono
application.

##### Ecosystem

With this implementation, we introduce two concepts: **adapt** and
**mount**. **Adapt** refers to Hono's ability to adapt to any runtime,
while **mount** implies that Hono can integrate with any framework.
Along with middleware, these capabilities allow us to create a
comprehensive ecosystem, as depicted below:


![SS](https://togithub.com/honojs/hono/assets/10682/74247b3e-0e36-4bc2-9ccd-e98f99b49466)

With these special features, Hono will not just be a web framework, it
will be like a "**Glue**".

One of the greatest aspects of this concept is that the framework does
not have to create individual adapters for various platforms such as
Cloudflare Worker, Cloudflare Pages, Vercel, Deno, and Bun.


![SS](https://togithub.com/honojs/hono/assets/10682/d077dffb-da79-442f-803e-4c155f637b93)

If your framework is based on the Web Standard API, there is no
additional work required. Hono can mount it and enable your framework to
run on any runtime.


![SS](https://togithub.com/honojs/hono/assets/10682/38ed968f-69a4-4b41-baaa-5dac5cd69679)

Furthermore, we can use Hono's middleware for other frameworks. For
example, to add Basic authentication to an application built with
*ittry-router*, there's no need to implement it from scratch. Just add
Hono's middleware.

```ts
app.use('/another-app/admin/*', basicAuth({ username, password }))
```

This is the ecosystem we wanted creating.

#### Node.js adapter server v1.0.0 has been released

The first major release of the **Node.js adapter server**, "*v1.0.0*",
is now available! This version uses only the **native** Web Standard
APIs available in Node.js v18 or higher. The size has been significantly
reduced and this means we are really following to Web Standard APIs.


![SS](https://user-images.githubusercontent.com/10682/235049566-126708ab-89f3-417b-b0f0-38121578054b.png)

You can start using it right away by installing it from npm.

    npm install @&#8203;hono/node-server

Then, simply import the `serve` function and adapt it to your Hono
application.

```ts
import { serve } from '@&#8203;hono/node-server'
import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hono meets Node.js'))

serve(app, (info) => {
  console.log(`Listening on http://localhost:${info.port}`)
})
```

#### Support for routing includes a hostname

The new `getPath()` function now supports **routing that includes a
hostname**.

```ts
const app = new Hono({
  getPath: (req) => req.url.replace(/^https?:\/\//, ''),
})

app.get('www1.example.com/hello', (c) => c.text('hello www1'))
app.get('www2.example.com/hello', (c) => c.text('hello www2'))
```

With `getPath()`, you can also handle the host header value for routing.

```ts
const app = new Hono({
  getPath: (req) => req.headers.get('host') + req.url.replace(/^https?:\/\/[^\/]+/, ''),
})

app.get('www1.example.com/hello', (c) => c.text('hello www1'))

// The following request will match the route:
// new Request('http://www1.example.com/hello', {
//  headers: { host: 'www1.example.com' },
// })
```

#### Support for AWS Lambda functions URLs

The AWS Lambda adapter now supports **[Lambda functions
URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html)**.

#### Cookie Middleware

We're introducing a new **Cookie Middleware**.

```ts
import { getCookie, setCookie } from 'hono/cookie'

// ...

app.get('/cookie', (c) => {
  const yummyCookie = getCookie(c, 'yummy_cookie')
  // ...
  setCookie(c, 'delicious_cookie', 'macha')
  //
}
```

And `c.req.cookie()` and `c.cookie()` are deprecated and will be removed
in the next major version, *v4*.

#### `hono/nextjs` to `hono/vercel`

We've created `hono/vercel` and deprecated `hono/nextjs`. `hono/nextjs`
will be removed in *v4*.

#### Other updates

-   `rewriteRequestPath` option for the serve-static
-   `app.routerName`
-   Support for clearing response headers
-   Remove HTTP status messages from `http-status.ts`
-   Performance improvements
-   Bug fixes

#### Thanks

Thank you to all our contributors!

#### All changes

- perf: shorten code by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1051
- perf: use `===` instead of `startsWith` and `endsWith` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1053
- fix(deno): export `HTTPException` from `mod.ts` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1058
- chore: update eslint by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1060
- chore: move runtime tests into `runtime_tests` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1062
- feat(adapter): Added aws-handler support for APIGatewayProxyEventV2 by
[@&#8203;schonert](https://togithub.com/schonert) in
[honojs/hono#1009
- feat: Allow getPath to be replaced. by
[@&#8203;usualoma](https://togithub.com/usualoma) in
[honojs/hono#1064
- feat(serve-static): `rewriteRequestPath` option for Workers/Deno/Bun
by [@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1065
- fix(jsx): fix a `jsx-runtime` bug by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1070
- feat(context): clear the header with `c.header(key, undefined)` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1071
- feat: Cookie Middleware and deprecate `c.req.cookie()` / `c.cookie()`
by [@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1066
- feat(adapter): add `hono/vercel` / deprecate `hono/nextjs` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1073
- fix: enable specifying router from args by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1079
- fix(context): enable extending `ContextVarableMap` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1080
- feat(preset): add `hono/quick` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1074
- fix: remove `type.ts` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1082
- fix(getQueryParam): Fixed a bug in getQueryParam when a partial
matching key was found. by
[@&#8203;usualoma](https://togithub.com/usualoma) in
[honojs/hono#1085
- fix(context): Fix typo in charset, again. by
[@&#8203;usualoma](https://togithub.com/usualoma) in
[honojs/hono#1086
- feat(adapter): AWS Lambda Adapter supports requests via Lambda
Function URLs. by [@&#8203;watany-dev](https://togithub.com/watany-dev)
in
[honojs/hono#1076
- chore:denoify by [@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1095
- fix(lambda): don't encode text by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1091
- fix(cloudflare-workers): serveStatic `rewriteRequestPath` option by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1098
- feat(app): `onError()` supports async by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1090
- fix(types): set `/` for generics `basePath` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1083
- feat(deno): export cookie middleware by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1103
- fix(app): export `fire()` correctly by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1106
- ci: use `setup-bun` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1108
- feat(app): `app.routerName()` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1105
- feat: `app.mount()` by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1104
- fix(preset): fix constructor args by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1113
- fix(router): `PatternRouter` supports a hostname, added tests by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1114
- docs(readme): update by
[@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1115
- v3.2 by [@&#8203;yusukebe](https://togithub.com/yusukebe) in
[honojs/hono#1117

#### New Contributors

- [@&#8203;schonert](https://togithub.com/schonert) made their first
contribution in
[honojs/hono#1009
- [@&#8203;watany-dev](https://togithub.com/watany-dev) made their first
contribution in
[honojs/hono#1076

**Full Changelog**:
honojs/hono@v3.1.8...v3.2.0

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://app.renovatebot.com/dashboard#github/synopsisgg/bot).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS45OC4wIiwidXBkYXRlZEluVmVyIjoiMzUuOTguMCIsInRhcmdldEJyYW5jaCI6Im1haW4ifQ==-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants