Skip to content

Commit 22a1519

Browse files
committed
Added a hono version of workers examples - fixes
1 parent 3417ab1 commit 22a1519

12 files changed

+671
-896
lines changed

src/content/docs/workers/examples/ab-testing.mdx

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -140,65 +140,66 @@ async def on_fetch(request):
140140
</TabItem> <TabItem label="Hono" icon="seti:typescript">
141141

142142
```ts
143-
import { Hono } from 'hono';
144-
import { getCookie, setCookie } from 'hono/cookie';
143+
import { Hono } from "hono";
144+
import { getCookie, setCookie } from "hono/cookie";
145145

146146
const app = new Hono();
147147

148148
const NAME = "myExampleWorkersABTest";
149149

150150
// Middleware to handle A/B testing logic
151-
app.use('*', async (c) => {
152-
const url = new URL(c.req.url);
153-
154-
// Enable Passthrough to allow direct access to control and test routes
155-
if (url.pathname.startsWith("/control") || url.pathname.startsWith("/test")) {
156-
return fetch(c.req.raw);
157-
}
158-
159-
// Determine which group this requester is in
160-
const abTestCookie = getCookie(c, NAME);
161-
162-
if (abTestCookie === 'control') {
163-
// User is in control group
164-
url.pathname = "/control" + url.pathname;
165-
return fetch(url);
166-
} else if (abTestCookie === 'test') {
167-
// User is in test group
168-
url.pathname = "/test" + url.pathname;
169-
return fetch(url);
170-
} else {
171-
// If there is no cookie, this is a new client
172-
// Choose a group and set the cookie (50/50 split)
173-
const group = Math.random() < 0.5 ? "test" : "control";
174-
175-
// Update URL path based on assigned group
176-
if (group === "control") {
177-
url.pathname = "/control" + url.pathname;
178-
} else {
179-
url.pathname = "/test" + url.pathname;
180-
}
181-
182-
// Fetch from origin with modified path
183-
const res = await fetch(url);
184-
185-
// Create a new response to avoid immutability issues
186-
const newResponse = new Response(res.body, res);
187-
188-
// Set cookie to enable persistent A/B sessions
189-
setCookie(c, NAME, group, {
190-
path: '/',
191-
// Add additional cookie options as needed:
192-
// secure: true,
193-
// httpOnly: true,
194-
// sameSite: 'strict',
195-
});
196-
197-
// Copy the Set-Cookie header to the response
198-
newResponse.headers.set('Set-Cookie', c.res.headers.get('Set-Cookie') || '');
199-
200-
return newResponse;
201-
}
151+
app.use("*", async (c) => {
152+
// Enable Passthrough to allow direct access to control and test routes
153+
if (c.req.path.startsWith("/control") || c.req.path.startsWith("/test")) {
154+
return fetch(c.req.raw);
155+
}
156+
157+
// Determine which group this requester is in
158+
const abTestCookie = getCookie(c, NAME);
159+
160+
if (abTestCookie === "control") {
161+
// User is in control group
162+
c.req.path = "/control" + c.req.path;
163+
return fetch(url);
164+
} else if (abTestCookie === "test") {
165+
// User is in test group
166+
url.pathname = "/test" + url.pathname;
167+
return fetch(c.req.url);
168+
} else {
169+
// If there is no cookie, this is a new client
170+
// Choose a group and set the cookie (50/50 split)
171+
const group = Math.random() < 0.5 ? "test" : "control";
172+
173+
// Update URL path based on assigned group
174+
if (group === "control") {
175+
c.req.path = "/control" + c.req.path;
176+
} else {
177+
c.req.path = "/test" + c.req.path;
178+
}
179+
180+
// Fetch from origin with modified path
181+
const res = await fetch(c.req.url);
182+
183+
// Create a new response to avoid immutability issues
184+
const newResponse = new Response(res.body, res);
185+
186+
// Set cookie to enable persistent A/B sessions
187+
setCookie(c, NAME, group, {
188+
path: "/",
189+
// Add additional cookie options as needed:
190+
// secure: true,
191+
// httpOnly: true,
192+
// sameSite: 'strict',
193+
});
194+
195+
// Copy the Set-Cookie header to the response
196+
newResponse.headers.set(
197+
"Set-Cookie",
198+
c.res.headers.get("Set-Cookie") || "",
199+
);
200+
201+
return newResponse;
202+
}
202203
});
203204

204205
export default app;

src/content/docs/workers/examples/basic-auth.mdx

Lines changed: 18 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,15 @@ use worker::*;
266266

267267
#[event(fetch)]
268268
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
269-
let basic_user = "admin";
270-
// You will need an admin password. This should be
271-
// attached to your Worker as an encrypted secret.
272-
// Refer to https://developers.cloudflare.com/workers/configuration/secrets/
273-
let basic_pass = match env.secret("PASSWORD") {
274-
Ok(s) => s.to_string(),
275-
Err(_) => "password".to_string(),
276-
};
277-
let url = req.url()?;
269+
let basic_user = "admin";
270+
// You will need an admin password. This should be
271+
// attached to your Worker as an encrypted secret.
272+
// Refer to https://developers.cloudflare.com/workers/configuration/secrets/
273+
let basic_pass = match env.secret("PASSWORD") {
274+
Ok(s) => s.to_string(),
275+
Err(_) => "password".to_string(),
276+
};
277+
let url = req.url()?;
278278

279279
match url.path() {
280280
"/" => Response::ok("Anyone can access the homepage."),
@@ -328,8 +328,10 @@ async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
328328
}
329329
_ => Response::error("Not Found.", 404),
330330
}
331+
331332
}
332-
```
333+
334+
````
333335
</TabItem> <TabItem label="Hono" icon="seti:typescript">
334336

335337
```ts
@@ -341,7 +343,6 @@ async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
341343
342344
import { Hono } from 'hono';
343345
import { auth } from 'hono/basic-auth';
344-
import { Buffer } from "node:buffer";
345346
346347
// Define environment interface
347348
interface Env {
@@ -352,74 +353,21 @@ interface Env {
352353
353354
const app = new Hono<Env>();
354355
355-
// Helper function for comparing strings safely
356-
const encoder = new TextEncoder();
357-
function timingSafeEqual(a: string, b: string) {
358-
const aBytes = encoder.encode(a);
359-
const bBytes = encoder.encode(b);
360-
361-
if (aBytes.byteLength !== bBytes.byteLength) {
362-
// Strings must be the same length in order to compare
363-
// with crypto.subtle.timingSafeEqual
364-
return false;
365-
}
366-
367-
return crypto.subtle.timingSafeEqual(aBytes, bBytes);
368-
}
369-
370356
// Public homepage - accessible to everyone
371357
app.get('/', (c) => {
372358
return c.text("Anyone can access the homepage.");
373359
});
374360
375361
// Logout route
376362
app.get('/logout', (c) => {
377-
// Invalidate the "Authorization" header by returning a HTTP 401.
378-
// We do not send a "WWW-Authenticate" header, as this would trigger
379-
// a popup in the browser, immediately asking for credentials again.
380363
return c.text("Logged out.", 401);
381364
});
382365
383366
// Admin route - protected with Basic Auth
384-
app.get('/admin', async (c) => {
385-
const BASIC_USER = "admin";
386-
387-
// You will need an admin password. This should be
388-
// attached to your Worker as an encrypted secret.
389-
// Refer to https://developers.cloudflare.com/workers/configuration/secrets/
390-
const BASIC_PASS = c.env.PASSWORD ?? "password";
391-
392-
// The "Authorization" header is sent when authenticated
393-
const authorization = c.req.header('Authorization');
394-
if (!authorization) {
395-
// Prompts the user for credentials
396-
return c.text("You need to login.", 401, {
397-
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
398-
});
399-
}
400-
401-
const [scheme, encoded] = authorization.split(" ");
402-
403-
// The Authorization header must start with Basic, followed by a space
404-
if (!encoded || scheme !== "Basic") {
405-
return c.text("Malformed authorization header.", 400);
406-
}
407-
408-
const credentials = Buffer.from(encoded, "base64").toString();
409-
410-
// The username & password are split by the first colon
411-
//=> example: "username:password"
412-
const index = credentials.indexOf(":");
413-
const user = credentials.substring(0, index);
414-
const pass = credentials.substring(index + 1);
415-
416-
if (!timingSafeEqual(BASIC_USER, user) || !timingSafeEqual(BASIC_PASS, pass)) {
417-
// Prompts the user for credentials again
418-
return c.text("You need to login.", 401, {
419-
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
420-
});
421-
}
422-
367+
app.get('/admin',
368+
basicAuth({ username: BASIC_USER, password: c.env.PASSWORD }),
369+
async (c) => {
370+
423371
// Success! User is authenticated
424372
return c.text("🎉 You have private access!", 200, {
425373
'Cache-Control': 'no-store',
@@ -432,6 +380,6 @@ app.notFound((c) => {
432380
});
433381
434382
export default app;
435-
```
383+
````
436384
437-
</TabItem> </Tabs>
385+
</TabItem> </Tabs>

src/content/docs/workers/examples/bulk-origin-proxy.mdx

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,32 +77,31 @@ export default {
7777
</TabItem> <TabItem label="Hono" icon="seti:typescript">
7878

7979
```ts
80-
import { Hono } from 'hono';
81-
82-
type Bindings = {};
80+
import { Hono } from "hono";
81+
import { proxy } from "hono/proxy";
8382

8483
// An object with different URLs to fetch
8584
const ORIGINS: Record<string, string> = {
86-
"starwarsapi.yourdomain.com": "swapi.dev",
87-
"google.yourdomain.com": "www.google.com",
85+
"starwarsapi.yourdomain.com": "swapi.dev",
86+
"google.yourdomain.com": "www.google.com",
8887
};
8988

90-
const app = new Hono<{ Bindings: Bindings }>();
91-
92-
app.all('*', async (c) => {
93-
const url = new URL(c.req.url);
94-
95-
// Check if incoming hostname is a key in the ORIGINS object
96-
if (url.hostname in ORIGINS) {
97-
const target = ORIGINS[url.hostname];
98-
url.hostname = target;
99-
100-
// If it is, proxy request to that third party origin
101-
return fetch(url.toString(), c.req.raw);
102-
}
103-
104-
// Otherwise, process request as normal
105-
return fetch(c.req.raw);
89+
const app = new Hono();
90+
91+
app.all("*", async (c) => {
92+
const url = new URL(c.req.url);
93+
94+
// Check if incoming hostname is a key in the ORIGINS object
95+
if (url.hostname in ORIGINS) {
96+
const target = ORIGINS[url.hostname];
97+
url.hostname = target;
98+
99+
// If it is, proxy request to that third party origin
100+
return proxy(url, c.req.raw);
101+
}
102+
103+
// Otherwise, process request as normal
104+
return fetch(c.req.raw);
106105
});
107106

108107
export default app;

src/content/docs/workers/examples/cache-api.mdx

Lines changed: 16 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -135,58 +135,28 @@ async def on_fetch(request, _env, ctx):
135135
</TabItem> <TabItem label="Hono" icon="seti:typescript">
136136

137137
```ts
138-
import { Hono } from 'hono';
139-
import type { Context } from 'hono';
138+
import { Hono } from "hono";
139+
import type { Context } from "hono";
140+
import { cache } from "hono/cache";
140141

141142
type Env = {
142-
// Cloudflare Worker environment
143+
// Cloudflare Worker environment
143144
};
144145

145-
const app = new Hono<{ Bindings: Env }>();
146-
147-
app.use('*', async (c, next) => {
148-
const request = c.req.raw;
149-
const cacheUrl = new URL(request.url);
150-
151-
// Construct the cache key from the cache URL
152-
const cacheKey = new Request(cacheUrl.toString(), request);
153-
const cache = caches.default;
154-
155-
// Check whether the value is already available in the cache
156-
// if not, you will need to fetch it from origin, and store it in the cache
157-
let response = await cache.match(cacheKey);
158-
159-
if (!response) {
160-
console.log(
161-
`Response for request url: ${request.url} not present in cache. Fetching and caching request.`
162-
);
163-
164-
// Continue to the next middleware to handle the request
165-
await next();
166-
167-
// Get the response that was generated
168-
response = new Response(c.res.body, {
169-
status: c.res.status,
170-
headers: c.res.headers,
171-
});
172-
173-
// Cache API respects Cache-Control headers. Setting s-max-age to 10
174-
// will limit the response to be in cache for 10 seconds max
175-
response.headers.append("Cache-Control", "s-maxage=10");
176-
177-
// Use waitUntil to capture the cache.put promise
178-
c.executionCtx.waitUntil(cache.put(cacheKey, response.clone()));
179-
180-
return response;
181-
} else {
182-
console.log(`Cache hit for: ${request.url}.`);
183-
return response;
184-
}
185-
});
146+
const app = new Hono();
147+
148+
// We leverage hono built-in cache helper here
149+
app.get(
150+
"*",
151+
cache({
152+
cacheName: "my-cache",
153+
cacheControl: "max-age=3600", // 1 hour
154+
}),
155+
);
186156

187157
// Add a route to handle the request if it's not in cache
188-
app.get('*', (c) => {
189-
return c.text('Hello from Hono!');
158+
app.get("*", (c) => {
159+
return c.text("Hello from Hono!");
190160
});
191161

192162
export default app;

0 commit comments

Comments
 (0)