oxidecomputer / dropshot

expose REST APIs from a Rust program
Apache License 2.0
839 stars 74 forks source link

Allow usage as building block in larger application #62

Open mraerino opened 3 years ago

mraerino commented 3 years ago

A lot of the building blocks that make up HttpServer are hidden inside the crate. It's not really possible to customize the specifics of the HTTP server by just using dropshot as a building block of a Hyper server.

I'm looking for one of these:

This way people could compose their own HTTP server and the surface area of what dropshot needs to care about is reduced.

Might fix #10

davepacheco commented 3 years ago

This is interesting. Our goals with Dropshot include providing common functionality for the server itself, including basic DoS protection, limits, throttling, etc. That's not to say the server couldn't be separated from...the rest of it. But I'm not sure what "the rest of it" actually is. Right now, there's not a ton, but there's some support for logging, request ids, and pagination extractors, the last of which uses server-wide configuration. The intent is to support a lot more: TLS, the limits and throttling I mentioned, configuration, request tracing, and potentially HTTP features like keepalive. Do you only want to reuse the sugar that lets you turn a bunch of Rust functions into an HttpRouter, or would you want more of this stuff as well, factored somehow to be separate from the TCP server?

mraerino commented 3 years ago

i'd rather use the building blocks that libraries like tower provide: https://docs.rs/tower/0.3.1/tower/ if you are able to lock into the hyper ecosystem this can be very powerful, since that ecosystem is pretty rich and so easy to extend and build upon.

you'll also at some point will need to allow people to mount custom middlewares for custom auth integration and it might be useful to allow usage of the tower::Service or tower::Layer trait for that usecase

mraerino commented 3 years ago

one more point: as a company we'll likely establish common patterns for doing request ids and similar things across services, so it's nice to be able to share them as an independent crate using the tower primitives. not all of our services will use dropshot, so being able to use dropshot as only the API routing layer can be really powerful

mraerino commented 3 years ago

some service might also need to mix APIs made via dropshot with other ways of using HTTP, e.g. implementing a gRPC or graphql api along a REST api

dbanty commented 3 years ago

I don't know if this is relevant to this issue or if I should create a new one, but I've been searching for good ways to implement an automatically documented OpenAPI service in a serverless context (AWS Lambda).

DropShot seems like it may eventually be a good candidate, but separating the endpoint registration and routing from the HttpServer would be a must to make this work as a serverless function (without weird rerouting back to localhost stuff).

davepacheco commented 3 years ago

It's possible that there could be a common component that we could split out, but I'm still not sure what that looks like. As you pointed out, maybe something close to the ApiDescription would work. I don't think impl Service on ApiDescription really addresses it -- how would we cons up the RequestContext argument for the handler functions? What about arguments of type Query and other extractors that are HTTP-specific?

I'm also not sure we want to do this yet. Right now I view these interfaces as somewhat tightly coupled as we flesh out our own needs.

These use cases do seem reasonable and I appreciate why you want to do something like Tower instead. That's just not what we're shooting for with Dropshot. (At least not now -- maybe that will change later.)

mraerino commented 3 years ago

I don't think impl Service on ApiDescription really addresses it -- how would we cons up the RequestContext argument for the handler functions? What about arguments of type Query and other extractors that are HTTP-specific?

Clarifying this: Yes, Service<R> is a very generic interface on purpose, but i'm asking for an implementation of Service<http::Request<B>> where B: Body in this case. the request & response types from the http crate are widely used and since you seem to be using hyper, you most likely are using those types already and implementing that trait in some way already.

i'm really wondering what the difficulties around exposing this area of abstraction is. you can still have an opinionated webserver impl in the crate, but it seems like the main concern of this project is to provide a good rest api interface and i don't see how this would not work well if you offered people to just use the HTTP service without being locked into the webserver / tcp connection implementation.

davepacheco commented 3 years ago

i'm really wondering what the difficulties around exposing this area of abstraction is.

See the question in my last comment:

how would we cons up the RequestContext argument for the handler functions?

To be more concrete: I assume that the impl Service<...> for ApiDescription that you want would look at the HTTP request, use the usual Dropshot routing to find the appropriate handler function, and invoke that handler function. Today, those handler functions consume an argument of type RequestContext. Today, that object contains a reference to the private DropshotState, which includes a user-provided object, global server configuration, a logger, etc. For the impl you're asking for, I don't understand where any of that would come from. Those are not in the ApiDescription -- and by design. The ApiDescription is an abstract description of the API, not an execution context with runtime configuration, logging, state, etc. There has to be more plumbing there that we haven't talked about.

I could imagine taking those pieces and wrapping them into something like an ApiServerContext. So you'd create an ApiServerContext the same way you create an HttpServer today. Then HttpServer would be an even thinner wrapper that joins an ApiServerContext with a hyper Server (instead of today, where it essentially joins an ApiDescription with a hyper Server). There's still more details: the configuration object has to be split out into separate pieces for the server vs. the ApiServerContext.