-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.rs
More file actions
121 lines (102 loc) · 3.98 KB
/
Copy pathlib.rs
File metadata and controls
121 lines (102 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use worker::*;
// Export the Durable Object from server_do
pub use server_do::MatchDO;
#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
let router = Router::new();
router
.get_async("/", handle_index)
.get_async("/create", handle_create)
.get_async("/join/:code", handle_join)
.get_async("/ws/:code", handle_websocket)
.run(req, env)
.await
}
async fn handle_index(_req: Request, _ctx: RouteContext<()>) -> Result<Response> {
let html = include_str!("../index.html");
Response::from_html(html)
}
async fn handle_create(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
let code = generate_match_code();
// The DO is created lazily on first stub use; resolving the stub here verifies
// the MATCH binding before handing the code to the client. Use id_from_name +
// get_stub (the long-standing API), not get_by_name, whose getByName runtime
// call postdates this worker's compatibility_date.
let match_do = ctx.env.durable_object("MATCH")?;
let _stub = match_do.id_from_name(&code)?.get_stub()?;
Response::from_json(&serde_json::json!({
"code": code
}))
}
async fn handle_join(_req: Request, ctx: RouteContext<()>) -> Result<Response> {
let code = ctx.param("code").map_or("", |v| v);
if code.len() != 5 {
return Response::error("Invalid match code", 400);
}
// Get the MATCH Durable Object namespace
let match_do = ctx.env.durable_object("MATCH")?;
// Get DO stub by name. id_from_name + get_stub, not get_by_name/getByName —
// see handle_create for why getByName is unavailable in production.
let _stub = match_do.id_from_name(code)?.get_stub()?;
// Return response with WebSocket URL
Response::ok(format!(
"Match {code} found. Connect via WebSocket at /ws/{code}"
))
}
async fn handle_websocket(req: Request, ctx: RouteContext<()>) -> Result<Response> {
let code = ctx.param("code").map_or("", |v| v);
if code.len() != 5 {
return Response::error("Invalid match code", 400);
}
// id_from_name + get_stub, not get_by_name/getByName — see handle_create.
let stub = ctx
.env
.durable_object("MATCH")?
.id_from_name(code)?
.get_stub()?;
// Ensure request method is GET (required for WebSocket upgrade)
if req.method() != Method::Get {
console_error!(
"Worker: WebSocket upgrade requires GET method, got: {:?}",
req.method()
);
return Response::error("WebSocket upgrade requires GET method", 405);
}
// Forward the original Request so the Upgrade/Connection headers reach the DO.
match stub.fetch_with_request(req).await {
Ok(resp) => Ok(resp),
Err(err) => {
let err_str = format!("{err:?}");
console_error!(
"Worker: Error forwarding WebSocket upgrade for code {}: {}",
code,
err_str
);
// Best-effort detection of the Cloudflare free-tier cap by matching the
// error's Debug string; the wording is not a stable contract.
if err_str.contains("Exceeded allowed volume") || err_str.contains("free tier") {
Response::error(
"Service temporarily unavailable due to rate limits. Please try again later.",
503,
)
} else {
Response::error(
format!("Worker failed to forward WebSocket request: {err_str}"),
500,
)
}
}
}
}
/// Generate a random 5-character match code (A-Z, 0-9)
fn generate_match_code() -> String {
use rand::Rng;
let mut rng = rand::thread_rng();
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
(0..5)
.map(|_| {
let idx = rng.gen_range(0..CHARS.len());
CHARS[idx] as char
})
.collect()
}