-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
handles HEAD
requests automatically when there exists a GET
route that would otherwise match. 405 Method Not Allowed
returns for now.
#84
Comments
I've looked into this and I don't think a clean solution is possible given the current design of the router. When you compose two routes with One possible solution is to make Not sure if entirely removing explicit matching on I checked with warp (the framework axum is most alike from a design perspective) and it handles Honestly I would be fine with leaving things as is. User's who want to support |
REST services used from browsers commonly need use axum::{
extract::{Query, UrlParams},
response,
};
#[get_head]
async fn list_foos(
UrlParams(user_id): UrlParams<Uuid>,
params: Query<PaginationParams>,
db: extension<sqlx::PgPool>,
) -> crate::Result<response::Json<Vec<Foo>>> {
Ok(Foo::list_by_user(user_id, &pagination, db.deref()).await?)
}
// generates
async fn list_foos(
UrlParams(user_id): UrlParams<Uuid>,
params: Query<PaginationParams>,
db: Extension<sqlx::PgPool>,
) -> crate::Result<response::Json<Vec<Foo>>> {
Ok(Foo::list_by_user(user_id, &pagination, db.deref()).await?)
}
async fn head_list_foos(
_: UrlParams<Uuid>,
_: Query<PaginationParams>,
_: Extension<sqlx::PgPool>,
) -> StatusCode {
StatusCode::OK
} Two notes:
|
That's a fair assumption to make I think but I see your point. Mdn mentions that headers should be identical across GET and HEAD so the actual handler (and possibly database) would probably have to be called in case you're setting Kinda feels like something that is hard for a framework to get right, in the general case 🤔 Is requiring users to explicitly handle HEAD in the way that makes sense for their app perhaps the best approach or is there a "goo enough" middle ground? |
Scrap everything I wrote. It doesn't belong on this issue; I mixed up HEAD and OPTIONS 😅 I have some additional thoughts for supporting HEAD and OPTIONS easier for some response types, but it's super late here, will write that up tomorrow. |
We usually use If a handler is returning static files by |
It can be done by making a use axum::{prelude::*, service};
use bytes::Bytes;
use http::Response;
use http_body::Empty;
use std::{
net::SocketAddr,
task::{Context, Poll},
};
use tower::Service;
use tower_http::services::ServeFile;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let serve_file_svc = ServeFile::new("Cargo.toml");
let app = route(
"/Cargo.toml",
service::get(serve_file_svc.clone()).head(DropResponseBody(serve_file_svc)),
);
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
hyper::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
#[derive(Clone)]
struct DropResponseBody<S>(S);
impl<R, S, ResBody> Service<R> for DropResponseBody<S>
where
S: Service<R, Response = Response<ResBody>> + Clone,
S::Future: Send + 'static,
{
type Response = Response<Empty<Bytes>>;
type Error = S::Error;
type Future = futures::future::BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx)
}
fn call(&mut self, req: R) -> Self::Future {
let future = self.0.call(req);
Box::pin(async move {
let response = future.await?;
// discard the response body
let response = response.map(|_body| Empty::new());
Ok(response)
})
}
} |
It would be better to be able to automatically discard the response body on HEAD handler. |
Does any other frameworks do this? Doesn't seem like Rocket does
Mdn makes it pretty clear that dropping response bodies on HEAD automatically is the way to go though. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD Edit: Also see https://httpwg.org/specs/rfc7231.html#HEAD: "The HEAD method is identical to GET except that the server MUST NOT send a message body in the response" |
I thing especially for files the right solution would be to provide some way to support both methods at once, but with a response-type specific implementation. For example a plain file serving route would ideally use sendfile internally for GET and stat for HEAD. Do the |
Are those linux syscalls or something? I'm not familiar with them. |
They're C functions probably translating directly to syscalls, yes.
I've opened a separate issue for that: tower-rs/tower-http#113 |
In Python land, Werkzeug (and thus Flask which is built on it) straight up refuse routing HEAD, because of its official semantics it's considered a "special" method, and a HEAD request is always routed to the GET handler, then the body is dropped during response. It's still possible to code against it by checking the request method inside the handler, but not at the routing layer. See this comment by the maintainer on a recent issue. In the same ecosystem, |
Being able to add routes for HEAD requests still feels like a good idea to me. Then you can avoid generating the body since it'll be stripped anyway. |
I'm not sure it's wise to disallow separate HEAD handlers, but I do think that using an |
Adding an example for it sounds good! |
As tokio-rs#84 add get-head request, adding an example.
As tokio-rs#84 add get-head request, adding an example.
As tokio-rs#84 add get-head request, adding an example.
As tokio-rs#84 add get-head request, adding an example.
As tokio-rs#84 add get-head request, adding an example.
As tokio-rs#84 add get-head request, adding an example.
As tokio-rs#84 add get-head request, adding an example.
As #84 add get-head request, adding an example.
Feature Request
Motivation
Proposal
handles HEAD requests automatically when there exists a GET route that would otherwise match. It does this by stripping the body from the response, if there is one. You can also specialize the handling of a HEAD request by declaring a route for it; won't interfere with HEAD requests your application explicitly handles.
Alternatives
Just like rocket did.
head-requests
The text was updated successfully, but these errors were encountered: