Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

state: move instantiation into State::from_request #526

Merged
merged 1 commit into from
Mar 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,7 @@ members = [
# finalizer
"examples/finalizers/",

# custom_service
"examples/custom_service/",

]
14 changes: 14 additions & 0 deletions examples/custom_service/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "gotham_examples_custom_service"
description = "Using Gotham in a pre-existing Hyper service"
version = "0.0.0"
publish = false
edition = "2018"

[dependencies]
anyhow = "1.0"
futures = "0.3"
gotham = { path = "../../gotham" }
http = "0.2"
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1.0", features = ["full"] }
19 changes: 19 additions & 0 deletions examples/custom_service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Custom Service

An example of using Gotham within a pre-existing Hyper service. This is useful
for advanced use cases where you might want to wrap Gotham in order logic, or
pre-populate your state handing it off to Gotham.

## License

Licensed under your option of:

* [MIT License](../../LICENSE-MIT)
* [Apache License, Version 2.0](../../LICENSE-APACHE)

## Community

The following policies guide participation in our project and our community:

* [Code of conduct](../../CODE_OF_CONDUCT.md)
* [Contributing](../../CONTRIBUTING.md)
78 changes: 78 additions & 0 deletions examples/custom_service/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! An example usage of Gotham from another service.

use anyhow::{Context as _, Error};
use futures::future::{BoxFuture, FutureExt};
use gotham::{
router::{builder::*, Router},
service::call_handler,
state::State,
};
use http::{Request, Response};
use hyper::{server::conn::Http, service::Service, Body};
use std::net::SocketAddr;
use std::panic::AssertUnwindSafe;
use std::task;
use tokio::net::TcpListener;

#[derive(Clone)]
struct MyService {
router: Router,
addr: SocketAddr,
}

impl Service<Request<Body>> for MyService {
type Response = Response<Body>;
type Error = Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;

fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> task::Poll<Result<(), Self::Error>> {
task::Poll::Ready(Ok(()))
}

fn call(&mut self, req: Request<Body>) -> Self::Future {
// NOTE: You don't *have* to use call_handler for this (you could use `router.handle`), but
// call_handler will catch panics and return en error response.
let state = State::from_request(req, self.addr);
call_handler(self.router.clone(), AssertUnwindSafe(state)).boxed()
}
}

pub fn say_hello(state: State) -> (State, &'static str) {
(state, "hello world")
}

#[tokio::main]
pub async fn main() -> Result<(), Error> {
let router = build_simple_router(|route| {
// For the path "/" invoke the handler "say_hello"
route.get("/").to(say_hello);
});

let addr = "127.0.0.1:7878";
let listener = TcpListener::bind(&addr).await?;

println!("Listening for requests at http://{}", addr);

loop {
let (socket, addr) = listener
.accept()
.await
.context("Error accepting connection")?;

let service = MyService {
router: router.clone(),
addr,
};

let task = async move {
Http::new()
.serve_connection(socket, service)
.await
.context("Error serving connection")?;

Result::<_, Error>::Ok(())
};

tokio::spawn(task);
}
}
2 changes: 1 addition & 1 deletion gotham/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub mod helpers;
pub mod middleware;
pub mod pipeline;
pub mod router;
mod service;
pub mod service;
pub mod state;

/// Test utilities for Gotham and Gotham consumer apps.
Expand Down
50 changes: 5 additions & 45 deletions gotham/src/service/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,19 @@ use std::net::SocketAddr;
use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::sync::Arc;
use std::thread;

use futures::prelude::*;
use futures::task::{self, Poll};
use http::request;
use hyper::service::Service;
use hyper::upgrade::OnUpgrade;
use hyper::{Body, Request, Response};
use log::debug;

use crate::handler::NewHandler;

use crate::helpers::http::request::path::RequestPathSegments;
use crate::state::client_addr::put_client_addr;
use crate::state::{set_request_id, State};
use crate::state::State;

mod trap;

pub use trap::call_handler;

/// Wraps a `NewHandler` which will be used to serve requests. Used in `gotham::os::*` to bind
/// incoming connections to `ConnectedGothamService` values.
pub(crate) struct GothamService<T>
Expand Down Expand Up @@ -76,43 +71,8 @@ where
}

fn call<'a>(&'a mut self, req: Request<Body>) -> Self::Future {
let mut state = State::new();

put_client_addr(&mut state, self.client_addr);

let (
request::Parts {
method,
uri,
version,
headers,
mut extensions,
..
},
body,
) = req.into_parts();

state.put(RequestPathSegments::new(uri.path()));
state.put(method);
state.put(uri);
state.put(version);
state.put(headers);
state.put(body);

if let Some(on_upgrade) = extensions.remove::<OnUpgrade>() {
state.put(on_upgrade);
}

{
let request_id = set_request_id(&mut state);
debug!(
"[DEBUG][{}][Thread][{:?}]",
request_id,
thread::current().id(),
);
};

trap::call_handler(self.handler.clone(), AssertUnwindSafe(state)).boxed()
let state = State::from_request(req, self.client_addr);
call_handler(self.handler.clone(), AssertUnwindSafe(state)).boxed()
}
}

Expand Down
5 changes: 1 addition & 4 deletions gotham/src/service/trap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ where
///
/// Timing information is recorded and logged, except in the case of a panic where the timer is
/// moved and cannot be recovered.
pub(super) async fn call_handler<T>(
t: T,
state: AssertUnwindSafe<State>,
) -> anyhow::Result<Response<Body>>
pub async fn call_handler<T>(t: T, state: AssertUnwindSafe<State>) -> anyhow::Result<Response<Body>>
where
T: NewHandler + Send + UnwindSafe,
{
Expand Down
50 changes: 49 additions & 1 deletion gotham/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,22 @@ mod data;
mod from_state;
pub mod request_id;

use log::trace;
use log::{debug, trace};

use http::request;
use hyper::upgrade::OnUpgrade;
use hyper::{Body, Request};
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::net::SocketAddr;

pub use crate::state::client_addr::client_addr;
pub use crate::state::data::StateData;
pub use crate::state::from_state::FromState;
pub use crate::state::request_id::request_id;

use crate::helpers::http::request::path::RequestPathSegments;
use crate::state::client_addr::put_client_addr;
pub(crate) use crate::state::request_id::set_request_id;

/// Provides storage for request state, and stores one item of each type. The types used for
Expand Down Expand Up @@ -69,6 +75,48 @@ impl State {
f(&mut State::new())
}

/// Instantiate a new `State` for a given `Request`. This is primarily useful if you're calling
/// Gotham from your own Hyper service.
pub fn from_request(req: Request<Body>, client_addr: SocketAddr) -> Self {
let mut state = Self::new();

put_client_addr(&mut state, client_addr);

let (
request::Parts {
method,
uri,
version,
headers,
mut extensions,
..
},
body,
) = req.into_parts();

state.put(RequestPathSegments::new(uri.path()));
state.put(method);
state.put(uri);
state.put(version);
state.put(headers);
state.put(body);

if let Some(on_upgrade) = extensions.remove::<OnUpgrade>() {
state.put(on_upgrade);
}

{
let request_id = set_request_id(&mut state);
debug!(
"[DEBUG][{}][Thread][{:?}]",
request_id,
std::thread::current().id(),
);
};

state
}

/// Puts a value into the `State` storage. One value of each type is retained. Successive calls
/// to `put` will overwrite the existing value of the same type.
///
Expand Down