a (hacky, wip) multi-tenant oidc-terminating reverse proxy, written in anger on top of pingora
at wip/primary 118 lines 3.4 kB view raw
1//! http utilities 2 3use http::StatusCode; 4use pingora::prelude::*; 5 6/// closure that returns the given status with no cause 7/// 8/// use with [`Option::ok_or_else`] 9pub fn status_error( 10 why: &'static str, 11 src: ErrorSource, 12 status: StatusCode, 13) -> impl FnOnce() -> Box<Error> { 14 move || { 15 Error::create( 16 ErrorType::HTTPStatus(status.into()), 17 src, 18 Some(why.into()), 19 None, 20 ) 21 } 22} 23/// closure that returns `500 Internal Server Error`, marked as caused by the error returned to 24/// the given closure 25/// 26/// use with [`Result::map_err`] 27pub fn internal_error_from<E>(why: &'static str) -> impl FnOnce(E) -> Box<Error> 28where 29 E: Into<Box<dyn ErrorTrait + Send + Sync>>, 30{ 31 move |cause| { 32 Error::create( 33 ErrorType::HTTPStatus(StatusCode::INTERNAL_SERVER_ERROR.into()), 34 ErrorSource::Internal, 35 Some(why.into()), 36 Some(cause.into()), 37 ) 38 } 39} 40 41/// closure that returns `500 Internal Server Error` with no cause 42/// 43/// use with [`Option::ok_or_else`] 44pub fn internal_error(why: &'static str) -> impl FnOnce() -> Box<Error> { 45 move || { 46 Error::create( 47 ErrorType::HTTPStatus(StatusCode::INTERNAL_SERVER_ERROR.into()), 48 ErrorSource::Internal, 49 Some(why.into()), 50 None, 51 ) 52 } 53} 54/// closure that returns the given status, marked as caused by the error returned to the given closure 55/// 56/// use with [`Result::map_err`] 57pub fn status_error_from<E>( 58 why: &'static str, 59 src: ErrorSource, 60 status: http::StatusCode, 61) -> impl FnOnce(E) -> Box<Error> 62where 63 E: Into<Box<dyn ErrorTrait + Send + Sync>>, 64{ 65 move |cause| { 66 Error::create( 67 ErrorType::HTTPStatus(status.into()), 68 src, 69 Some(why.into()), 70 Some(cause.into()), 71 ) 72 } 73} 74 75/// redirect to the given location 76/// 77/// the given callback can be used to inject additional headers, like `set-cookie` 78pub async fn redirect_response( 79 session: &mut Session, 80 to: &str, 81 bld_resp_header: impl FnOnce(&mut ResponseHeader, &Session) -> Result<()>, 82) -> Result<()> { 83 session.set_keepalive(None); 84 session 85 .write_response_header( 86 Box::new({ 87 // per <rfc:draft-ietf-oauth-v2-1#1.6>, any redirect is fine save 307, but HTTP 302 seems to 88 // be their example. 89 let mut resp = ResponseHeader::build(StatusCode::FOUND, Some(0))?; 90 resp.insert_header(http::header::LOCATION, to)?; 91 bld_resp_header(&mut resp, session)?; 92 resp 93 }), 94 true, 95 ) 96 .await?; 97 session.finish_body().await?; 98 Ok(()) 99} 100 101/// fetch the cookies for the current request 102pub fn cookie_jar(req: &'_ RequestHeader) -> Result<Option<cookie_rs::CookieJar<'_>>> { 103 use cookie_rs::CookieJar; 104 105 let Some(raw) = req.headers.get(http::header::COOKIE) else { 106 return Ok(None); 107 }; 108 Ok(Some( 109 raw.to_str() 110 .map_err(Box::<dyn ErrorTrait + Send + Sync>::from) 111 .and_then(|c| Ok(CookieJar::parse(c)?)) 112 .map_err(status_error_from( 113 "bad cookie header", 114 ErrorSource::Downstream, 115 StatusCode::BAD_REQUEST, 116 ))?, 117 )) 118}