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

scaffolding for multiple APIs on one server #922

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
7 changes: 4 additions & 3 deletions dropshot/examples/pagination-multiple-sorts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,10 @@ async fn main() -> Result<(), String> {
.map_err(|error| format!("failed to create logger: {}", error))?;
let mut api = ApiDescription::new();
api.register(example_list_projects).unwrap();
let server = HttpServerStarter::new(&config_dropshot, api, ctx, &log)
.map_err(|error| format!("failed to create server: {}", error))?
.start();
let server = HttpServerStarter::new(&config_dropshot, ctx, &log)
.api(api)
.start()
.map_err(|error| format!("failed to create server: {}", error))?;

// Print out some example requests to start with.
print_example_requests(log, &server.local_addr());
Expand Down
44 changes: 42 additions & 2 deletions dropshot/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,15 @@ pub struct HttpRouter<Context: ServerContext> {
#[derive(Debug)]
struct HttpRouterNode<Context: ServerContext> {
/// Handlers, etc. for each of the HTTP methods defined for this node.
methods: BTreeMap<String, ApiEndpoint<Context>>,
pub methods: BTreeMap<String, ApiEndpoint<Context>>,
/// Edges linking to child nodes.
edges: Option<HttpRouterEdges<Context>>,
pub edges: Option<HttpRouterEdges<Context>>,
}

#[derive(Debug)]
enum HttpRouterEdges<Context: ServerContext> {
/// Outgoing edges for literal paths.
// TODO why box here?
Literals(BTreeMap<String, Box<HttpRouterNode<Context>>>),
/// Outgoing edge for variable-named paths.
VariableSingle(String, Box<HttpRouterNode<Context>>),
Expand Down Expand Up @@ -218,6 +219,28 @@ impl<Context: ServerContext> HttpRouterNode<Context> {
pub fn new() -> Self {
HttpRouterNode { methods: BTreeMap::new(), edges: None }
}

// Recursive merge into the target router.
fn merge(self, router: &mut HttpRouter<Context>) {
let Self { methods, edges } = self;

// Insert all endpoints.
methods.into_iter().for_each(|(_, endpoint)| router.insert(endpoint));

// Recur as needed.
match edges {
Some(HttpRouterEdges::Literals(children)) => {
children.into_values().for_each(|child| {
child.merge(router);
})
}
Some(HttpRouterEdges::VariableSingle(_, edge))
| Some(HttpRouterEdges::VariableRest(_, edge)) => {
edge.merge(router)
}
None => (),
}
}
}

impl<Context: ServerContext> HttpRouter<Context> {
Expand Down Expand Up @@ -396,6 +419,10 @@ impl<Context: ServerContext> HttpRouter<Context> {
node.methods.insert(methodname, endpoint);
}

pub fn merge(&mut self, other: HttpRouter<Context>) {
other.root.merge(self)
}

/// Look up the route handler for an HTTP request having method `method` and
/// URI path `path`. A successful lookup produces a `RouterLookupResult`,
/// which includes both the handler that can process this request and a map
Expand Down Expand Up @@ -491,6 +518,19 @@ impl<Context: ServerContext> HttpRouter<Context> {
}
}

impl<Context: ServerContext> Extend<(String, String, ApiEndpoint<Context>)>
for HttpRouter<Context>
{
fn extend<
T: IntoIterator<Item = (String, String, ApiEndpoint<Context>)>,
>(
&mut self,
iter: T,
) {
iter.into_iter().for_each(|(_, _, endpoint)| self.insert(endpoint))
}
}

/// Insert a variable into the set after checking for duplicates.
fn insert_var(
path: &str,
Expand Down
134 changes: 82 additions & 52 deletions dropshot/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,29 +99,56 @@ pub struct ServerConfig {
}

pub struct HttpServerStarter<C: ServerContext> {
app_state: Arc<DropshotState<C>>,
local_addr: SocketAddr,
wrapped: WrappedHttpServerStarter<C>,
handler_waitgroup: WaitGroup,
// app_state: Arc<DropshotState<C>>,
// local_addr: SocketAddr,
// wrapped: WrappedHttpServerStarter<C>,
// handler_waitgroup: WaitGroup,
config: ConfigDropshot,
apis: Vec<ApiDescription<C>>,
private: C,
logger: Logger,
tls: Option<ConfigTls>,
}

impl<C: ServerContext> HttpServerStarter<C> {
pub fn new(
config: &ConfigDropshot,
api: ApiDescription<C>,
private: C,
log: &Logger,
) -> Result<HttpServerStarter<C>, GenericError> {
Self::new_with_tls(config, api, private, log, None)
pub fn new(config: &ConfigDropshot, private: C, log: &Logger) -> Self {
Self::new_with_tls(config, private, log, None)
}

pub fn new_with_tls(
config: &ConfigDropshot,
api: ApiDescription<C>,
private: C,
log: &Logger,
tls: Option<ConfigTls>,
) -> Result<HttpServerStarter<C>, GenericError> {
) -> Self {
Self {
config: config.clone(),
apis: Default::default(),
private,
logger: log.clone(),
tls,
}
}

/// Add an API to the server.
pub fn api(mut self, api: ApiDescription<C>) -> Self {
self.apis.push(api);
self
}

fn pre_start(
self,
) -> Result<
(
Arc<DropshotState<C>>,
SocketAddr,
WrappedHttpServerStarter<C>,
WaitGroup,
),
GenericError,
> {
let Self { config, apis, private, logger, tls } = self;

let server_config = ServerConfig {
// We start aggressively to ensure test coverage.
request_body_max_bytes: config.request_body_max_bytes,
Expand All @@ -130,59 +157,62 @@ impl<C: ServerContext> HttpServerStarter<C> {
default_handler_task_mode: config.default_handler_task_mode,
};

// TODO build up the router
let mut router = HttpRouter::new();
for api in apis {
router.merge(api.into_router())
}

let handler_waitgroup = WaitGroup::new();
let starter = match &tls {
let (wrapped, app_state, local_addr) = match &tls {
Some(tls) => {
let (starter, app_state, local_addr) =
InnerHttpsServerStarter::new(
config,
&config,
server_config,
api,
router,
private,
log,
&logger,
tls,
handler_waitgroup.worker(),
)?;
HttpServerStarter {
(
WrappedHttpServerStarter::Https(starter),
app_state,
local_addr,
wrapped: WrappedHttpServerStarter::Https(starter),
handler_waitgroup,
}
)
}
None => {
let (starter, app_state, local_addr) =
InnerHttpServerStarter::new(
config,
&config,
server_config,
api,
router,
private,
log,
&logger,
handler_waitgroup.worker(),
)?;
HttpServerStarter {
app_state,
local_addr,
wrapped: WrappedHttpServerStarter::Http(starter),
handler_waitgroup,
}
(WrappedHttpServerStarter::Http(starter), app_state, local_addr)
}
};

for (path, method, _) in &starter.app_state.router {
debug!(starter.app_state.log, "registered endpoint";
for (path, method, _) in &app_state.router {
debug!(app_state.log, "registered endpoint";
"method" => &method,
"path" => &path
);
}

Ok(starter)
Ok((app_state, local_addr, wrapped, handler_waitgroup))
}

pub fn start(self) -> HttpServer<C> {
pub fn start(self) -> Result<HttpServer<C>, GenericError> {
let (app_state, local_addr, wrapped, handler_waitgroup) =
self.pre_start()?;

let (tx, rx) = tokio::sync::oneshot::channel::<()>();
let log_close = self.app_state.log.new(o!());
let join_handle = match self.wrapped {
let log_close = app_state.log.new(o!());
let join_handle = match wrapped {
WrappedHttpServerStarter::Http(http) => http.start(rx, log_close),
WrappedHttpServerStarter::Https(https) => {
https.start(rx, log_close)
Expand All @@ -192,9 +222,8 @@ impl<C: ServerContext> HttpServerStarter<C> {
r.map_err(|e| format!("waiting for server: {e}"))?
.map_err(|e| format!("server stopped: {e}"))
});
info!(self.app_state.log, "listening");
info!(app_state.log, "listening");

let handler_waitgroup = self.handler_waitgroup;
let join_handle = async move {
// After the server shuts down, we also want to wait for any
// detached handler futures to complete.
Expand All @@ -207,15 +236,15 @@ impl<C: ServerContext> HttpServerStarter<C> {
let probe_registration = match usdt::register_probes() {
Ok(_) => {
debug!(
self.app_state.log,
app_state.log,
"successfully registered DTrace USDT probes"
);
ProbeRegistration::Succeeded
}
Err(e) => {
let msg = e.to_string();
error!(
self.app_state.log,
app_state.log,
"failed to register DTrace USDT probes: {}", msg
);
ProbeRegistration::Failed(msg)
Expand All @@ -224,19 +253,19 @@ impl<C: ServerContext> HttpServerStarter<C> {
#[cfg(not(feature = "usdt-probes"))]
let probe_registration = {
debug!(
self.app_state.log,
app_state.log,
"DTrace USDT probes compiled out, not registering"
);
ProbeRegistration::Disabled
};

HttpServer {
Ok(HttpServer {
probe_registration,
app_state: self.app_state,
local_addr: self.local_addr,
app_state,
local_addr,
closer: CloseHandle { close_channel: Some(tx) },
join_future: join_handle.boxed().shared(),
}
})
}
}

Expand Down Expand Up @@ -276,7 +305,7 @@ impl<C: ServerContext> InnerHttpServerStarter<C> {
fn new(
config: &ConfigDropshot,
server_config: ServerConfig,
api: ApiDescription<C>,
router: HttpRouter<C>,
private: C,
log: &Logger,
handler_waitgroup_worker: waitgroup::Worker,
Expand All @@ -287,7 +316,7 @@ impl<C: ServerContext> InnerHttpServerStarter<C> {
let app_state = Arc::new(DropshotState {
private,
config: server_config,
router: api.into_router(),
router,
log: log.new(o!("local_addr" => local_addr)),
local_addr,
tls_acceptor: None,
Expand Down Expand Up @@ -561,7 +590,7 @@ impl<C: ServerContext> InnerHttpsServerStarter<C> {
fn new(
config: &ConfigDropshot,
server_config: ServerConfig,
api: ApiDescription<C>,
router: HttpRouter<C>,
private: C,
log: &Logger,
tls: &ConfigTls,
Expand All @@ -588,7 +617,7 @@ impl<C: ServerContext> InnerHttpsServerStarter<C> {
let app_state = Arc::new(DropshotState {
private,
config: server_config,
router: api.into_router(),
router,
log: logger,
local_addr,
tls_acceptor: Some(acceptor),
Expand Down Expand Up @@ -1115,9 +1144,10 @@ mod test {
let log_context = LogContext::new("test server", &config_logging);
let log = &log_context.log;

let server = HttpServerStarter::new(&config_dropshot, api, 0, log)
.unwrap()
.start();
let server = HttpServerStarter::new(&config_dropshot, 0, log)
.api(api)
.start()
.unwrap();

(server, TestConfig { log_context })
}
Expand Down
8 changes: 4 additions & 4 deletions dropshot/src/test_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -491,10 +491,10 @@ impl<Context: ServerContext> TestContext<Context> {
);

// Set up the server itself.
let server =
HttpServerStarter::new(&config_dropshot, api, private, &log)
.unwrap()
.start();
let server = HttpServerStarter::new(&config_dropshot, private, &log)
.api(api)
.start()
.unwrap();

let server_addr = server.local_addr();
let client_log = log.new(o!("http_client" => "dropshot test suite"));
Expand Down
Loading