an http based roguelike rpg about jelqing
Blame: src/http/run.rs
782069860236 | atagen | 2026-10-10 12:44 UTC | 1 | //! Plumbing shared by every resource under `/runs/{id}`. |
782069860236 | atagen | 2026-10-10 12:44 UTC | 2 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 3 | use axum::{ |
782069860236 | atagen | 2026-10-10 12:44 UTC | 4 | http::{HeaderName, HeaderValue, StatusCode, header}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 5 | response::{IntoResponse, Response}, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 6 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 7 | use hyper_util::rt::TokioIo; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 8 | use tokio::io::AsyncWriteExt; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 9 | |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 10 | use super::{ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 11 | AppState, ctx::Ctx, links, middleware::Compress, registry::Lock, resource::Resource, shame, |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 12 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 13 | use crate::{ |
0fdc47364c8c | atagen | 2026-10-11 04:45 UTC | 14 | game::{ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 15 | Cause, Gauges, Run, Shown, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 16 | balance::{FRICTION_MAX, SUSPICION_LIMIT}, |
0fdc47364c8c | atagen | 2026-10-11 04:45 UTC | 17 | }, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 18 | headers::{ |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 19 | X_CASH, X_EQ, X_FLOOR, X_FORESKIN, X_FRICTION, X_GAINS_ARRIVED, X_GIRTH, X_LENGTH, |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 20 | X_OBSERVED_BY, X_SELF_REPORTED_LENGTH, X_SHAME, X_STAMINA, X_SUSPICION, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 21 | }, |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 22 | locale::Locale, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 23 | problem::{PROBLEM_BASE, Problem}, |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 24 | records::Record, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 25 | secret::Secret, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 26 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 27 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 28 | /// Which household hazards stand between a request and the run. |
782069860236 | atagen | 2026-10-10 12:44 UTC | 29 | #[derive(Clone, Copy, PartialEq, Eq)] |
782069860236 | atagen | 2026-10-10 12:44 UTC | 30 | pub enum Scope { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 31 | Home, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 32 | Pockets, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 33 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 34 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 35 | pub struct RunResource { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 36 | pub scope: Scope, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 37 | pub resource: Resource<Run>, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 38 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 39 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 40 | /// What the server writes over the connection after a `101`. |
782069860236 | atagen | 2026-10-10 12:44 UTC | 41 | #[derive(Clone)] |
782069860236 | atagen | 2026-10-10 12:44 UTC | 42 | pub struct Greeting(pub String); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 43 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 44 | pub fn records_unavailable(error: &rusqlite::Error) -> Response { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 45 | Problem::new( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 46 | StatusCode::SERVICE_UNAVAILABLE, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 47 | "records-unavailable", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 48 | "The records are unavailable. Nothing has been forgotten, it is only out of reach.", |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 49 | ) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 50 | .with("error", error.to_string()) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 51 | .into_response() |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 52 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 53 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 54 | pub fn serve(state: &AppState, mut ctx: Ctx, target: &RunResource) -> Response { |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 55 | let id = ctx.param("id").to_owned(); |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 56 | let Some(slot) = state.runs.get(&id) else { |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 57 | return match state.records.find(&id) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 58 | Ok(Some(Record { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 59 | cause: Some(cause), |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 60 | turn, |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 61 | .. |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 62 | })) => gone(cause.slug(), turn), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 63 | Ok(_) => Problem::new( |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 64 | StatusCode::NOT_FOUND, |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 65 | "no-such-run", |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 66 | "There is no such run. There may never have been.", |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 67 | ) |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 68 | .into_response(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 69 | Err(error) => records_unavailable(&error), |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 70 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 71 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 72 | let (response, record) = match Lock::take(&slot) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 73 | Lock::Poisoned(mut run) => { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 74 | run.lose(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 75 | let response = gone(Cause::Lost.slug(), run.turn); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 76 | (response, Some(Record::of(&run))) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 77 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 78 | Lock::Held(mut run) => { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 79 | if let Some(ending) = &run.ended { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 80 | return gone(ending.cause.slug(), ending.turn); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 81 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 82 | run.seen(ctx.now()); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 83 | let response = gate(&mut run, &ctx, target.scope) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 84 | .unwrap_or_else(|| target.resource.dispatch(&mut run, &ctx)); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 85 | let response = finish(&mut run, &ctx, &state.config.secret, response); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 86 | let record = run.status().finished().then(|| Record::of(&run)); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 87 | (response, record) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 88 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 89 | }; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 90 | if let Some(record) = record { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 91 | if let Err(error) = state.records.save(&record) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 92 | return records_unavailable(&error); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 93 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 94 | if record.cause.is_some() { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 95 | state.runs.remove(&id); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 96 | } |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 97 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 98 | greet(response, &mut ctx) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 99 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 100 | |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 101 | fn gone(cause: &str, turn: u32) -> Response { |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 102 | Problem::new(StatusCode::GONE, "run-ended", "This run has ended.") |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 103 | .with("cause", format!("{PROBLEM_BASE}{cause}")) |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 104 | .with("turn", turn) |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 105 | .with("graveyard", "/graveyard") |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 106 | .into_response() |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 107 | } |
159f0998263b | atagen | 2026-10-12 11:01 UTC | 108 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 109 | fn gate(run: &mut Run, ctx: &Ctx, scope: Scope) -> Option<Response> { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 110 | if scope == Scope::Pockets { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 111 | return None; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 112 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 113 | if run.needs_portal() { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 114 | let portal = run.path("/portal"); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 115 | return Some( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 116 | Problem::new( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 117 | StatusCode::NETWORK_AUTHENTICATION_REQUIRED, |
782069860236 | atagen | 2026-10-10 12:44 UTC | 118 | "captive-portal", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 119 | "The network requires authentication. The hotel would like you to agree to some terms.", |
782069860236 | atagen | 2026-10-10 12:44 UTC | 120 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 121 | .with("portal", portal.as_str()) |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 122 | .header( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 123 | header::LINK, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 124 | format!("<{portal}>; rel=\"{}portal\"", links::REL_BASE), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 125 | ) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 126 | .into_response(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 127 | ); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 128 | } |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 129 | let authorization = ctx |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 130 | .header(header::AUTHORIZATION) |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 131 | .map(|text| Locale::negotiate(ctx.header(header::ACCEPT_LANGUAGE)).canonicalise(text)); |
ef7d6dee5976 | atagen | 2026-10-10 13:23 UTC | 132 | run.answer_door(authorization.as_deref()) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 133 | .err() |
782069860236 | atagen | 2026-10-10 12:44 UTC | 134 | .map(IntoResponse::into_response) |
782069860236 | atagen | 2026-10-10 12:44 UTC | 135 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 136 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 137 | fn stats(gauges: &Gauges, shame: u32) -> Vec<(HeaderName, String)> { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 138 | let mut stats = vec![ |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 139 | match gauges.length { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 140 | Shown::Measured(length) => (X_LENGTH, format!("{length:.2}")), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 141 | Shown::SelfReported(length) => (X_SELF_REPORTED_LENGTH, format!("{length:.2}")), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 142 | }, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 143 | (X_GIRTH, format!("{:.2}", gauges.girth)), |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 144 | ( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 145 | X_FORESKIN, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 146 | gauges.foreskin.map_or("gone".into(), |f| format!("{f:.2}")), |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 147 | ), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 148 | (X_FRICTION, format!("{}/{FRICTION_MAX}", gauges.friction)), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 149 | (X_EQ, gauges.eq.to_string()), |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 150 | ( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 151 | X_STAMINA, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 152 | format!("{}/{}", gauges.stamina, gauges.max_stamina), |
d604371299fb | atagen | 2026-10-10 13:54 UTC | 153 | ), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 154 | ( |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 155 | X_SUSPICION, |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 156 | format!("{}/{SUSPICION_LIMIT}", gauges.suspicion), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 157 | ), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 158 | (X_SHAME, shame.to_string()), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 159 | (X_CASH, gauges.cash.to_string()), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 160 | (X_FLOOR, gauges.floor.to_string()), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 161 | ]; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 162 | if let Some(observer) = gauges.observed { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 163 | stats.push((X_OBSERVED_BY, observer.name().into())); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 164 | } |
886159fd0fe7 | atagen | 2026-10-13 12:48 UTC | 165 | if let Some(challenge) = &gauges.mum_challenge { |
9683736a2c5e | atagen | 2026-10-10 13:15 UTC | 166 | stats.push((header::VIA, "1.1 mum".into())); |
9683736a2c5e | atagen | 2026-10-10 13:15 UTC | 167 | stats.push((header::VARY, "Mum".into())); |
886159fd0fe7 | atagen | 2026-10-13 12:48 UTC | 168 | stats.push((header::WWW_AUTHENTICATE, challenge.clone())); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 169 | } |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 170 | if let Some(arrived) = &gauges.arrived { |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 171 | let mut gains = format!("length={:.3}, girth={:.3}", arrived.length, arrived.girth); |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 172 | if let Some(foreskin) = arrived.foreskin { |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 173 | gains.push_str(&format!(", foreskin={foreskin:.3}")); |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 174 | } |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 175 | stats.push((X_GAINS_ARRIVED, gains)); |
fcc6cf6d5edc | atagen | 2026-10-13 11:31 UTC | 176 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 177 | // RFC 9111 obsoleted Warning, which is why nobody heeds it. |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 178 | for warning in &gauges.warnings { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 179 | stats.push((header::WARNING, format!("199 - \"{warning}\""))); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 180 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 181 | stats |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 182 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 183 | |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 184 | pub fn finish(run: &mut Run, ctx: &Ctx, secret: &Secret, mut response: Response) -> Response { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 185 | let status = response.status(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 186 | if status == StatusCode::NOT_MODIFIED { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 187 | return response; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 188 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 189 | let (shame, shame_cookie) = shame::settle(run, ctx, secret); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 190 | let gauges = run.gauges(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 191 | let link = links::header(run); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 192 | let headers = response.headers_mut(); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 193 | for (name, value) in stats(&gauges, shame) { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 194 | if let Ok(value) = HeaderValue::from_str(&value) { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 195 | headers.append(name, value); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 196 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 197 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 198 | if let Ok(link) = HeaderValue::from_str(&link) { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 199 | headers.append(header::LINK, link); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 200 | } |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 201 | headers.append(header::SET_COOKIE, shame_cookie); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 202 | if gauges.ruler { |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 203 | headers.append(header::SET_COOKIE, shame::ruler_cookie(secret)); |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 204 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 205 | let bodiless = status.is_informational() || status == StatusCode::NO_CONTENT; |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 206 | if gauges.ring && !bodiless { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 207 | response.extensions_mut().insert(Compress); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 208 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 209 | response |
782069860236 | atagen | 2026-10-10 12:44 UTC | 210 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 211 | |
782069860236 | atagen | 2026-10-10 12:44 UTC | 212 | fn greet(response: Response, ctx: &mut Ctx) -> Response { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 213 | if response.status() != StatusCode::SWITCHING_PROTOCOLS { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 214 | return response; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 215 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 216 | let (Some(Greeting(text)), Some(upgrade)) = ( |
782069860236 | atagen | 2026-10-10 12:44 UTC | 217 | response.extensions().get::<Greeting>().cloned(), |
8b8e83329a1d | atagen | 2026-10-12 12:01 UTC | 218 | ctx.take_upgrade(), |
782069860236 | atagen | 2026-10-10 12:44 UTC | 219 | ) else { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 220 | return response; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 221 | }; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 222 | tokio::spawn(async move { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 223 | if let Ok(upgraded) = upgrade.await { |
782069860236 | atagen | 2026-10-10 12:44 UTC | 224 | let mut io = TokioIo::new(upgraded); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 225 | let _ = io.write_all(text.as_bytes()).await; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 226 | let _ = io.shutdown().await; |
782069860236 | atagen | 2026-10-10 12:44 UTC | 227 | } |
782069860236 | atagen | 2026-10-10 12:44 UTC | 228 | }); |
782069860236 | atagen | 2026-10-10 12:44 UTC | 229 | response |
782069860236 | atagen | 2026-10-10 12:44 UTC | 230 | } |