-
Notifications
You must be signed in to change notification settings - Fork 10.1k
Added a hono version of workers examples #21258
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
Changes from 7 commits
60b8da3
2c01742
3417ab1
22a1519
3583c21
f202f1f
06a26d0
4885dba
5ee2391
6cdc391
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -137,4 +137,72 @@ async def on_fetch(request): | |
| return fetch(urlunparse(url)) | ||
| ``` | ||
|
|
||
| </TabItem> <TabItem label="Hono" icon="seti:typescript"> | ||
|
|
||
| ```ts | ||
| import { Hono } from "hono"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the following is best.
diff --git a/src/content/docs/workers/examples/ab-testing.mdx b/src/content/docs/workers/examples/ab-testing.mdx
index a4c8dfe16..22fedf721 100644
--- a/src/content/docs/workers/examples/ab-testing.mdx
+++ b/src/content/docs/workers/examples/ab-testing.mdx
@@ -148,23 +148,23 @@ const app = new Hono();
const NAME = "myExampleWorkersABTest";
// Middleware to handle A/B testing logic
-app.use("*", async (c) => {
- // Enable Passthrough to allow direct access to control and test routes
- if (c.req.path.startsWith("/control") || c.req.path.startsWith("/test")) {
- return fetch(c.req.raw);
- }
+
+// Enable Passthrough to allow direct access to control and test routes
+app.all("/control/*", (c) => fetch(c.req.raw));
+app.all("/test/*", (c) => fetch(c.req.raw));
+
+app.all("*", async (c) => {
+ const url = new URL(c.req.url);
// Determine which group this requester is in
const abTestCookie = getCookie(c, NAME);
if (abTestCookie === "control") {
// User is in control group
- c.req.path = "/control" + c.req.path;
- return fetch(url);
+ url.pathname = "/control" + c.req.path;
} else if (abTestCookie === "test") {
// User is in test group
- url.pathname = "/test" + url.pathname;
- return fetch(c.req.url);
+ url.pathname = "/test" + c.req.path;
} else {
// If there is no cookie, this is a new client
// Choose a group and set the cookie (50/50 split)
@@ -172,17 +172,10 @@ app.use("*", async (c) => {
// Update URL path based on assigned group
if (group === "control") {
- c.req.path = "/control" + c.req.path;
+ url.pathname = "/control" + c.req.path;
} else {
- c.req.path = "/test" + c.req.path;
+ url.pathname = "/test" + c.req.path;
}
-
- // Fetch from origin with modified path
- const res = await fetch(c.req.url);
-
- // Create a new response to avoid immutability issues
- const newResponse = new Response(res.body, res);
-
// Set cookie to enable persistent A/B sessions
setCookie(c, NAME, group, {
path: "/",
@@ -191,15 +184,11 @@ app.use("*", async (c) => {
// httpOnly: true,
// sameSite: 'strict',
});
+ }
- // Copy the Set-Cookie header to the response
- newResponse.headers.set(
- "Set-Cookie",
- c.res.headers.get("Set-Cookie") || "",
- );
+ const res = await fetch(url);
- return newResponse;
- }
+ return c.body(res.body!, res);
});
export default app; |
||
| import { getCookie, setCookie } from "hono/cookie"; | ||
|
|
||
| const app = new Hono(); | ||
|
|
||
| const NAME = "myExampleWorkersABTest"; | ||
|
|
||
| // Middleware to handle A/B testing logic | ||
| app.use("*", async (c) => { | ||
| // Enable Passthrough to allow direct access to control and test routes | ||
| if (c.req.path.startsWith("/control") || c.req.path.startsWith("/test")) { | ||
| return fetch(c.req.raw); | ||
| } | ||
|
|
||
| // Determine which group this requester is in | ||
| const abTestCookie = getCookie(c, NAME); | ||
|
|
||
| if (abTestCookie === "control") { | ||
| // User is in control group | ||
| c.req.path = "/control" + c.req.path; | ||
| return fetch(url); | ||
| } else if (abTestCookie === "test") { | ||
| // User is in test group | ||
| url.pathname = "/test" + url.pathname; | ||
| return fetch(c.req.url); | ||
| } else { | ||
| // If there is no cookie, this is a new client | ||
| // Choose a group and set the cookie (50/50 split) | ||
| const group = Math.random() < 0.5 ? "test" : "control"; | ||
|
|
||
| // Update URL path based on assigned group | ||
| if (group === "control") { | ||
| c.req.path = "/control" + c.req.path; | ||
| } else { | ||
| c.req.path = "/test" + c.req.path; | ||
| } | ||
|
|
||
| // Fetch from origin with modified path | ||
| const res = await fetch(c.req.url); | ||
|
|
||
| // Create a new response to avoid immutability issues | ||
| const newResponse = new Response(res.body, res); | ||
|
|
||
| // Set cookie to enable persistent A/B sessions | ||
| setCookie(c, NAME, group, { | ||
| path: "/", | ||
| // Add additional cookie options as needed: | ||
| // secure: true, | ||
| // httpOnly: true, | ||
| // sameSite: 'strict', | ||
| }); | ||
|
|
||
| // Copy the Set-Cookie header to the response | ||
| newResponse.headers.set( | ||
| "Set-Cookie", | ||
| c.res.headers.get("Set-Cookie") || "", | ||
| ); | ||
|
|
||
| return newResponse; | ||
| } | ||
| }); | ||
|
|
||
| export default app; | ||
| ``` | ||
|
|
||
| </TabItem> </Tabs> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,6 +56,32 @@ export default { | |
| } satisfies ExportedHandler; | ||
| ``` | ||
|
|
||
| </TabItem> <TabItem label="Hono" icon="seti:typescript"> | ||
|
|
||
| ```ts | ||
| import { Hono } from "hono"; | ||
|
|
||
| type Bindings = {}; | ||
|
||
|
|
||
| const app = new Hono<{ Bindings: Bindings }>(); | ||
|
|
||
| app.get("*", async (c) => { | ||
| // Access the raw request to get the cf object | ||
| const req = c.req.raw; | ||
|
|
||
| // Check if the cf object is available | ||
| const data = | ||
| req.cf !== undefined | ||
| ? req.cf | ||
| : { error: "The `cf` object is not available inside the preview." }; | ||
|
|
||
| // Return the data formatted with 2-space indentation | ||
| return c.json(data); | ||
| }); | ||
|
|
||
| export default app; | ||
| ``` | ||
|
|
||
| </TabItem> <TabItem label="Python" icon="seti:python"> | ||
|
|
||
| ```py | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,34 @@ export default { | |
| } satisfies ExportedHandler; | ||
| ``` | ||
|
|
||
| </TabItem> <TabItem label="Hono" icon="seti:typescript"> | ||
|
|
||
| ```ts | ||
| import { Hono } from 'hono'; | ||
|
|
||
| type Bindings = {}; | ||
|
||
|
|
||
| const app = new Hono<{ Bindings: Bindings }>(); | ||
|
|
||
| app.get('*', async (c) => { | ||
| // someHost is set up to return JSON responses | ||
| const someHost = "https://jsonplaceholder.typicode.com"; | ||
| const url1 = someHost + "/todos/1"; | ||
| const url2 = someHost + "/todos/2"; | ||
|
|
||
| // Fetch both URLs concurrently | ||
| const responses = await Promise.all([fetch(url1), fetch(url2)]); | ||
|
|
||
| // Parse JSON responses concurrently | ||
| const results = await Promise.all(responses.map(r => r.json())); | ||
|
|
||
| // Return aggregated results | ||
| return c.json(results); | ||
| }); | ||
|
|
||
| export default app; | ||
| ``` | ||
|
|
||
| </TabItem> <TabItem label="Python" icon="seti:python"> | ||
|
|
||
| ```py | ||
|
|
||
coffee-mug marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,15 +266,15 @@ use worker::*; | |
|
|
||
| #[event(fetch)] | ||
| async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> { | ||
| let basic_user = "admin"; | ||
| // You will need an admin password. This should be | ||
| // attached to your Worker as an encrypted secret. | ||
| // Refer to https://developers.cloudflare.com/workers/configuration/secrets/ | ||
| let basic_pass = match env.secret("PASSWORD") { | ||
| Ok(s) => s.to_string(), | ||
| Err(_) => "password".to_string(), | ||
| }; | ||
| let url = req.url()?; | ||
| let basic_user = "admin"; | ||
| // You will need an admin password. This should be | ||
| // attached to your Worker as an encrypted secret. | ||
| // Refer to https://developers.cloudflare.com/workers/configuration/secrets/ | ||
| let basic_pass = match env.secret("PASSWORD") { | ||
| Ok(s) => s.to_string(), | ||
| Err(_) => "password".to_string(), | ||
| }; | ||
| let url = req.url()?; | ||
|
|
||
| match url.path() { | ||
| "/" => Response::ok("Anyone can access the homepage."), | ||
|
|
@@ -328,6 +328,52 @@ async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> { | |
| } | ||
| _ => Response::error("Not Found.", 404), | ||
| } | ||
|
|
||
| } | ||
| ``` | ||
| </TabItem> </Tabs> | ||
|
|
||
| ```` | ||
| </TabItem> <TabItem label="Hono" icon="seti:typescript"> | ||
|
|
||
| ```ts | ||
| /** | ||
| * Shows how to restrict access using the HTTP Basic schema with Hono. | ||
| * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication | ||
| * @see https://tools.ietf.org/html/rfc7617 | ||
| */ | ||
|
|
||
| import { Hono } from 'hono'; | ||
|
||
| import { auth } from 'hono/basic-auth'; | ||
coffee-mug marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Define environment interface | ||
| interface Env { | ||
| Bindings: { | ||
| PASSWORD: string; | ||
| }; | ||
| } | ||
|
|
||
| const app = new Hono<Env>(); | ||
|
|
||
| // Public homepage - accessible to everyone | ||
| app.get('/', (c) => { | ||
| return c.text("Anyone can access the homepage."); | ||
| }); | ||
|
|
||
| // Logout route | ||
| app.get('/logout', (c) => { | ||
| return c.text("Logged out.", 401); | ||
| }); | ||
|
|
||
| // Admin route - protected with Basic Auth | ||
| app.get('/admin', async (c, next) => { | ||
| return basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD })(c, next); | ||
| }); | ||
|
|
||
| // Handle 404 for any other routes | ||
| app.notFound((c) => { | ||
| return c.text("Not Found.", 404); | ||
| }); | ||
|
|
||
| export default app; | ||
| ```` | ||
|
|
||
| </TabItem> </Tabs> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is like a nitpick, but you can use
c.html():