Skip to content
44 changes: 44 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,48 @@ 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.body(HTML, {
Copy link
Member

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():

return c.html(HTML, {
  headers: {
    link: '</test.css>; rel=preload; as=style',
  },
})

headers: {
"content-type": "text/html",
"link": "</test.css>; rel=preload; as=style",
},
});
});

export default app;
```

</TabItem> </Tabs>
67 changes: 67 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,71 @@ async def on_fetch(request):
return fetch(urlunparse(url))
```

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

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

const app = new Hono();

const NAME = "myExampleWorkersABTest";

// Middleware to handle A/B testing logic
app.use('*', async (c) => {
const url = new URL(c.req.url);

// Enable Passthrough to allow direct access to control and test routes
if (url.pathname.startsWith("/control") || url.pathname.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
url.pathname = "/control" + url.pathname;
return fetch(url);
} else if (abTestCookie === 'test') {
// User is in test group
url.pathname = "/test" + url.pathname;
return fetch(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") {
url.pathname = "/control" + url.pathname;
} else {
url.pathname = "/test" + url.pathname;
}

// Fetch from origin with modified path
const res = await fetch(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
Expand Up @@ -56,6 +56,32 @@ export default {
} satisfies ExportedHandler;
```

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

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

type Bindings = {};
Copy link
Member

Choose a reason for hiding this comment

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

This Bindings is unnecessary.

diff --git a/src/content/docs/workers/examples/accessing-the-cloudflare-object.mdx b/src/content/docs/workers/examples/accessing-the-cloudflare-object.mdx
index 98c703c4c..2c3d21d87 100644
--- a/src/content/docs/workers/examples/accessing-the-cloudflare-object.mdx
+++ b/src/content/docs/workers/examples/accessing-the-cloudflare-object.mdx
@@ -61,9 +61,7 @@ export default {
 ```ts
 import { Hono } from "hono";

-type Bindings = {};
-
-const app = new Hono<{ Bindings: Bindings }>();
+const app = new Hono();

 app.get("*", async (c) => {
        // Access the raw request to get the cf object


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
Expand Down
28 changes: 28 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,34 @@ export default {
} satisfies ExportedHandler;
```

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

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

type Bindings = {};
Copy link
Member

Choose a reason for hiding this comment

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

This is unnecessary.


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
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>
104 changes: 104 additions & 0 deletions src/content/docs/workers/examples/basic-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -330,4 +330,108 @@ async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
}
}
```
</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';
Copy link
Member

Choose a reason for hiding this comment

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

The logic is wrong. Please rewrite the code to this:

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) => {
		// Success! User is authenticated
		return c.text("🎉 You have private access!", 200, {
			"Cache-Control": "no-store",
		});
	},
);

export default app;

Copy link
Member

Choose a reason for hiding this comment

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

@coffee-mug

Can you see this comment and fix it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Woaw, i am really sorry for that, should be fixed and conform to your example now.

import { auth } from 'hono/basic-auth';
import { Buffer } from "node:buffer";

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

const app = new Hono<Env>();

// Helper function for comparing strings safely
const encoder = new TextEncoder();
function timingSafeEqual(a: string, b: string) {
const aBytes = encoder.encode(a);
const bBytes = encoder.encode(b);

if (aBytes.byteLength !== bBytes.byteLength) {
// Strings must be the same length in order to compare
// with crypto.subtle.timingSafeEqual
return false;
}

return crypto.subtle.timingSafeEqual(aBytes, bBytes);
}

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

// Logout route
app.get('/logout', (c) => {
// Invalidate the "Authorization" header by returning a HTTP 401.
// We do not send a "WWW-Authenticate" header, as this would trigger
// a popup in the browser, immediately asking for credentials again.
return c.text("Logged out.", 401);
});

// Admin route - protected with Basic Auth
app.get('/admin', async (c) => {
const 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/
const BASIC_PASS = c.env.PASSWORD ?? "password";

// The "Authorization" header is sent when authenticated
const authorization = c.req.header('Authorization');
if (!authorization) {
// Prompts the user for credentials
return c.text("You need to login.", 401, {
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
});
}

const [scheme, encoded] = authorization.split(" ");

// The Authorization header must start with Basic, followed by a space
if (!encoded || scheme !== "Basic") {
return c.text("Malformed authorization header.", 400);
}

const credentials = Buffer.from(encoded, "base64").toString();

// The username & password are split by the first colon
//=> example: "username:password"
const index = credentials.indexOf(":");
const user = credentials.substring(0, index);
const pass = credentials.substring(index + 1);

if (!timingSafeEqual(BASIC_USER, user) || !timingSafeEqual(BASIC_PASS, pass)) {
// Prompts the user for credentials again
return c.text("You need to login.", 401, {
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"',
});
}

// Success! User is authenticated
return c.text("🎉 You have private access!", 200, {
'Cache-Control': 'no-store',
});
});

// Handle 404 for any other routes
app.notFound((c) => {
return c.text("Not Found.", 404);
});

export default app;
```

</TabItem> </Tabs>
Loading