6LABQWDWWQUEDTXZBKETGLG65FZ66FJLLY5IW3IFCMYJUO65Q4BAC use zhur_common::*;use serde::{Deserialize, Serialize};pub mod err;pub use err::InvocationError;pub mod http;pub use http::*;/// Struct representing a Zhur app invocation.#[derive(Deserialize, Serialize, Debug)]pub struct Invocation {/// The name of the user/org to whom the app belongs.pub owner: String,/// The name of the app itself.pub app_name: String,/// The input for the app.pub payload: Vec<u8>}
use serde::{Deserialize, Serialize};use std::collections::BTreeMap;#[derive(Debug, Deserialize, Serialize)]/// A Serde-friendly representation of a HTTP request.pub struct HttpReq {/// The method of the request.pub method: String,/// The path of the request.pub path: String,/// Headers represented as pairs of `String`s.pub headers: BTreeMap<String, String>,/// Query strings represented as pairs of `String`s.pub query_params: BTreeMap<String, String>,/// Cookies represented as pairs of `String`s.pub cookies: BTreeMap<String, String>,/// The IP address of the requester.pub ip_addr: String,/// The body of the request, as bytes.pub body: Vec<u8>}#[derive(Debug, Deserialize, Serialize)]/// A Serde-friendly representation of an HTTP response.pub struct HttpRes {/// Headers represented as pairs of `String`s.pub headers: BTreeMap<String, String>,/// An optional `Set-Cookie` header. Setting multiple cookies is not supported and additional attributes aren't supported yet either.pub set_cookie: Option<(String, String)>,/// The body of the response, as bytes.pub body: Vec<u8>,/// The status code.pub status: u16,}impl Default for HttpRes {fn default() -> Self {Self {headers: BTreeMap::new(),set_cookie: None,body: vec![],status: 200}}}
use std::fmt::Display;use super::*;/// Everything that can go wrong with an invocation. Can be generated by the gateway or the core.#[derive(Deserialize, Serialize, Debug)]pub enum InvocationError {// The variants below are gateway-side:/// The HTTP request from Hyper could not be converted into a Serde-friendly form.MalformedRequest,/// No app was specified.NoId,/// No app could be identified with the information given.MalformedId(String),/// The gateway can't reach the core.NoCore,/// The core did not reply correctly.MalformedReply,/// The core did not respond within the timeout period.TimedOut,// The variants below are core-side:/// No app identified by the two ID substrings exists, or it is disabled/hidden.NoSuchApp(String, String),/// An internal problem occurred within the core.OtherInternal}impl Display for InvocationError {fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {let text = match self {Self::MalformedRequest => "The HTTP request from Hyper could not be converted into a Serde-friendly form.".to_owned(),Self::NoId => "No app was specified at all.".to_owned(),Self::MalformedId(id) => format!("The ID \"{}\" is not a valid Zhur app ID.", id),Self::NoCore => "The Zhur gateway could not connect to the Zhur core.".to_owned(),Self::MalformedReply => "The reply from the Zhur core server could not be properly deserialized as an app's output.".to_owned(),Self::TimedOut => "The Zhur core could be reached, but timed out before responding.".to_owned(),Self::NoSuchApp(owner, app_name) => format!("The Zhur core could not find an app named {}:{}. It may have been disabled.", owner, app_name),Self::OtherInternal => "The core encountered an internal error that prevented it from returning a proper reply.".to_owned()};f.write_str(&text)}}
# zhur_invkTypes common to `zhur_core` and `zhur_gate`.
[package]name = "zhur_invk"version = "0.1.0"authors = ["oreganoli <3611916+oreganoli@users.noreply.github.com>"]edition = "2018"# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html[dependencies]zhur_common = { path = "../zhur_common" }serde = { version = "1.0.118", features = ["derive"] }
let text = format!("Hi there, {}!", req.ip);Ok(Response::new(text.into()))
let invocation = req.into_invoc().await;// TODO: actually send the invocation to the core and try to get something back.// Also TODO: extract all this stuff into its own function so we can ? it.match &invocation {Ok(i) => {let text = format!("Got an OK invocation for {}:{} of length {}", &i.owner, &i.app_name, &i.payload.len());info!("{}", &text);Ok(Response::new(text.into()))},Err(e) => {let text = format!("Got an invocation error: {}", e);warn!("{}", &text);Ok(Response::new(text.into()))}}
use std::collections::BTreeMap;use super::{FullRequest, HttpReq, InvocationError};use zhur_common::log::*;use zhur_invk::Invocation;/// Simplifies a `FullRequest` into an `HttpReq` of ours.pub async fn simplify_req(req: FullRequest) -> Result<HttpReq, InvocationError> {use http::header::COOKIE;let method = req.req.method().to_string();let uri = req.req.uri();let path = uri.path().to_owned();let query_params = match uri.query() {Some(s) => parse_query_string(s),None => BTreeMap::new()};// dbg!(&query_params);let cookie_str = match req.req.headers().get(COOKIE) {Some(hv) => match hv.to_str() {Ok(s) => Some(s),Err(_) => {warn!("Got a Cookie string that is not a valid ASCII string. Discarding.");None}},None => None};let cookies = match cookie_str {Some(s) => cookie_map(s),None => BTreeMap::new()};let mut headers = BTreeMap::new();for (name, val) in req.req.headers().iter() {if name == "Cookie" {continue;}match val.to_str() {Ok(s) => {headers.insert(name.to_string(), s.to_owned());},Err(_) => {warn!("Found a header that could not be parsed as a string. Discarding.");continue;}}}let body = match hyper::body::to_bytes(req.req.into_body()).await {Ok(b) => b.to_vec(),Err(_) => return Err(InvocationError::MalformedRequest)};Ok(HttpReq {body,path,method,cookies,headers,query_params,ip_addr: req.ip.clone()})}impl FullRequest {/// Turns a `FullRequest` into an `Invocation`.pub async fn into_invoc(self) -> Result<Invocation, InvocationError> {use zhur_common::bincode::serialize;use http::header::HOST;let host = match self.req.headers().get(HOST) {Some(s) => match s.to_str() {Ok(s) => s,Err(_) => {warn!("Received an HTTP request with a non-UTF-text Host header, returning malformed ID error.");return Err(InvocationError::MalformedId("(not valid UTF-8 text)".into()))}},None => {warn!("Received an HTTP request with no Host header, returning a no ID error.");return Err(InvocationError::NoId)}}.to_owned();let segments = host.split('.').collect::<Vec<_>>();if segments.is_empty() {warn!("Received an HTTP request with an empty Host header, returning a no ID error.");return Err(InvocationError::NoId);} else if segments.len() < 2 {warn!("Received an HTTP request with a Host header that could not be transformed into an app ID: \"{}\"", host);return Err(InvocationError::MalformedId(host.into()));}let req_simple = simplify_req(self).await?;let req_bytes = match serialize(&req_simple) {Ok(b) => b,Err(_) => return Err(InvocationError::MalformedRequest)};let result = Invocation {owner: segments[0].to_owned(),app_name: segments[1].to_owned(),payload: req_bytes};Ok(result)}}/// Parses a query string of the form "a=b&c=d" into params. Note: Hyper takes care of extracting said string out of a URI already, so no need to worry about ?.fn parse_query_string(s: &str) -> BTreeMap<String, String> {let mut output = BTreeMap::new();let pairs = s.split("&");for pair in pairs {let mut param_val = pair.split("=");let param = match param_val.next() {Some(p) => p,None => continue};let val = match param_val.next() {Some(v) => v,None => continue};output.insert(param.to_owned(), val.to_owned());}output}/// Produces a map of cookies to their values given a cookie string.fn cookie_map(cookie_str: &str) -> BTreeMap<String, String> {let mut output = BTreeMap::new();for cookie in cookie_str.split("; ") {let mut name_val = cookie.split("=");let name = match name_val.next() {Some(n) => n,None => continue};let val = match name_val.next() {Some(v) => v,None => continue};output.insert(name.to_owned(), val.to_owned());}output}