Skip to content

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

Merged
merged 10 commits into from
Apr 15, 2025
43 changes: 43 additions & 0 deletions src/content/docs/workers/examples/103-early-hints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,47 @@ def on_fetch(request):
return Response(HTML, headers=headers)
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">

```ts
import { Hono } from "hono";

const app = new Hono();

const CSS = "body { color: red; }";
const HTML = `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Early Hints test</title>
<link rel="stylesheet" href="/test.css">
</head>
<body>
<h1>Early Hints test page</h1>
</body>
</html>
`;

// Serve CSS file
app.get("/test.css", (c) => {
return c.body(CSS, {
headers: {
"content-type": "text/css",
},
});
});

// Serve HTML with early hints
app.get("*", (c) => {
return c.html(HTML, {
headers: {
link: "</test.css>; rel=preload; as=style",
},
});
});

export default app;
```

</TabItem> </Tabs>
53 changes: 53 additions & 0 deletions src/content/docs/workers/examples/ab-testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,57 @@ async def on_fetch(request):
return fetch(urlunparse(url))
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">

```ts
import { Hono } from "hono";
Copy link
Member

Choose a reason for hiding this comment

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

I think the following is best.

  • Use app.all() instead of c.req.path.startsWith(). It's Hono-way.
  • Fixed the logics.
  • Return the response with c.body() to use context values.
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";

// 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));

// Middleware to handle A/B testing logic
app.use("*", 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
url.pathname = "/control" + c.req.path;
} else if (abTestCookie === "test") {
// User is in test group
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)
const group = Math.random() < 0.5 ? "test" : "control";

// Update URL path based on assigned group
if (group === "control") {
url.pathname = "/control" + c.req.path;
} else {
url.pathname = "/test" + c.req.path;
}

// Set cookie to enable persistent A/B sessions
setCookie(c, NAME, group, {
path: "/",
});
}

const res = await fetch(url);

return c.body(res.body, res);
});

export default app;
```

</TabItem> </Tabs>
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,30 @@ export default {
} satisfies ExportedHandler;
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">

```ts
import { Hono } from "hono";

const app = new Hono();

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
Expand Down
26 changes: 26 additions & 0 deletions src/content/docs/workers/examples/aggregate-requests.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,32 @@ export default {
} satisfies ExportedHandler;
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">

```ts
import { Hono } from "hono";

const app = new Hono();

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
Expand Down
41 changes: 41 additions & 0 deletions src/content/docs/workers/examples/alter-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,47 @@ async def on_fetch(request):
return Response(response.body, headers=new_headers)
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">

```ts
import { Hono } from 'hono';

const app = new Hono();

app.use('*', async (c, next) => {
// Process the request with the next middleware/handler
await next();

// After the response is generated, we can modify its headers

// Add a custom header with a value
c.res.headers.append(
"x-workers-hello",
"Hello from Cloudflare Workers with Hono"
);

// Delete headers
c.res.headers.delete("x-header-to-delete");
c.res.headers.delete("x-header2-to-delete");

// Adjust the value for an existing header
c.res.headers.set("x-header-to-change", "NewValue");
});

app.get('*', async (c) => {
// Fetch content from example.com
const response = await fetch("https://example.com");

// Return the response body with original headers
// (our middleware will modify the headers before sending)
return new Response(response.body, {
headers: response.headers
});
});

export default app;
```

</TabItem> </Tabs>

You can also use the [`custom-headers-example` template](https://github.com/kristianfreeman/custom-headers-example) to deploy this code to your custom domain.
35 changes: 35 additions & 0 deletions src/content/docs/workers/examples/auth-with-headers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,39 @@ async def on_fetch(request):
return Response("Sorry, you have supplied an invalid key.", status=403)
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">

```ts
import { Hono } from 'hono';

const app = new Hono();

// Add authentication middleware
app.use('*', async (c, next) => {
/**
* Define authentication constants
*/
const PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK";
const PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey";

// Get the pre-shared key from the request header
const psk = c.req.header(PRESHARED_AUTH_HEADER_KEY);

if (psk === PRESHARED_AUTH_HEADER_VALUE) {
// Correct preshared header key supplied. Continue to the next handler.
await next();
} else {
// Incorrect key supplied. Reject the request.
return c.text("Sorry, you have supplied an invalid key.", 403);
}
});

// Handle all authenticated requests by passing through to origin
app.all('*', async (c) => {
return fetch(c.req.raw);
});

export default app;
```

</TabItem> </Tabs>
72 changes: 61 additions & 11 deletions src/content/docs/workers/examples/basic-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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."),
Expand Down Expand Up @@ -328,6 +328,56 @@ 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 { basicAuth } from "hono/basic-auth";

// Define environment interface
interface Env {
Bindings: {
USERNAME: string;
PASSWORD: string;
};
}

const app = new Hono<Env>();

// Public homepage - accessible to everyone
app.get("/", (c) => {
return c.text("Anyone can access the homepage.");
});

// Admin route - protected with Basic Auth
app.get(
"/admin",
async (c, next) => {
const auth = basicAuth({
username: c.env.USERNAME,
password: c.env.PASSWORD
})

return await auth(c, next);
},
(c) => {
return c.text("🎉 You have private access!", 200, {
"Cache-Control": "no-store",
});
}
);

export default app;
````

</TabItem> </Tabs>
36 changes: 36 additions & 0 deletions src/content/docs/workers/examples/block-on-tls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,42 @@ export default {
} satisfies ExportedHandler;
```

</TabItem> <TabItem label="Hono" icon="seti:typescript">

```ts
import { Hono } from "hono";

const app = new Hono();

// Middleware to check TLS version
app.use("*", async (c, next) => {
// Access the raw request to get the cf object with TLS info
const request = c.req.raw;
const tlsVersion = request.cf?.tlsVersion;

// Allow only TLS versions 1.2 and 1.3
if (tlsVersion !== "TLSv1.2" && tlsVersion !== "TLSv1.3") {
return c.text("Please use TLS version 1.2 or higher.", 403);
}

await next();

});

app.onError((err, c) => {
console.error(
"request.cf does not exist in the previewer, only in production",
);
return c.text(`Error in workers script: ${err.message}`, 500);
});

app.get("/", async (c) => {
return c.text(`TLS Version: ${c.req.raw.cf.tlsVersion}`);
});

export default app;
```

</TabItem> <TabItem label="Python" icon="seti:python">

```py
Expand Down
Loading