Skip to content
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
3 changes: 1 addition & 2 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@

https://github.com/oxidecomputer/dropshot/compare/v0.8.0\...HEAD[Full list of commits]

=== Other notable changes

* https://github.com/oxidecomputer/dropshot/pull/452[#452] Dropshot no longer enables the `slog` cargo features `max_level_trace` and `release_max_level_debug`. Previously, clients were unable to set a release log level of `trace`; now they can. However, clients that did not select their own max log levels will see behavior change from the levels Dropshot was choosing to the default levels of `slog` itself (`debug` for debug builds and `info` for release builds).
* https://github.com/oxidecomputer/dropshot/pull/451[#451] There are now response types to support 302 ("Found"), 303 ("See Other"), and 307 ("Temporary Redirect") HTTP response codes. See `HttpResponseFound`, `HttpResponseSeeOther`, and `HttpResponseTemporaryRedirect`.

== 0.8.0 (released 2022-09-09)

Expand Down
168 changes: 167 additions & 1 deletion dropshot/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Interface for implementing HTTP endpoint handler functions.
*
* For information about supported endpoint function signatures, argument types,
* extractors, and return types, see the top-level documentation dependencies: () for this crate.
* extractors, and return types, see the top-level documentation for this crate.
* As documented there, we support several different sets of function arguments
* and return types.
*
Expand Down Expand Up @@ -1436,6 +1436,172 @@ impl From<HttpResponseUpdatedNoContent> for HttpHandlerResult {
}
}

/** Describes headers associated with a 300-level response. */
#[derive(JsonSchema, Serialize)]
#[doc(hidden)]
pub struct RedirectHeaders {
/** HTTP "Location" header */
/*
* What type should we use to represent header values?
*
* It's tempting to use `http::HeaderValue` here. But in HTTP, header
* values can contain bytes that aren't valid Rust strings. See
* `http::header::HeaderValue`. We could propagate this nonsense all the
* way to the OpenAPI spec, encoding the Location header as, say,
* base64-encoded bytes. This sounds really annoying to consumers. It's
* also a fair bit more work to implement. We'd need to create a separate
* type for this field so that we can impl `Serialize` and `JsonSchema` on
* it, and we'd need to also impl serialization of byte sequences in
* `MapSerializer`. Ugh.
*
* We just use `String`. This might contain values that aren't valid in
* HTTP response headers. But we can at least validate that at runtime, and
* it sure is easier to implement!
*/
location: String,
}

/** See `http_response_found()` */
pub type HttpResponseFound =
HttpResponseHeaders<HttpResponseFoundStatus, RedirectHeaders>;

/**
* `http_response_found` returns an HTTP 302 "Found" response with no response
* body.
*
* The sole argument will become the value of the `Location` header. This is
* where you want to redirect the client to.
*
* Per MDN and RFC 9110 S15.4.3, you might want to use 307 ("Temporary
* Redirect") or 303 ("See Other") instead.
*/
pub fn http_response_found(
location: String,
) -> Result<HttpResponseFound, HttpError> {
let _ = http::HeaderValue::from_str(&location)
.map_err(|e| http_redirect_error(e, &location))?;
Ok(HttpResponseHeaders::new(
HttpResponseFoundStatus,
RedirectHeaders { location },
))
}

fn http_redirect_error(
error: http::header::InvalidHeaderValue,
location: &str,
) -> HttpError {
HttpError::for_internal_error(format!(
"error encoding redirect URL {:?}: {:#}",
location, error
))
}

/**
* This internal type impls HttpCodedResponse. Consumers should use
* `HttpResponseFound` instead, which includes metadata about the `Location`
* header.
*/
#[doc(hidden)]
pub struct HttpResponseFoundStatus;
impl HttpCodedResponse for HttpResponseFoundStatus {
type Body = Empty;
const STATUS_CODE: StatusCode = StatusCode::FOUND;
const DESCRIPTION: &'static str = "redirect (found)";
}
impl From<HttpResponseFoundStatus> for HttpHandlerResult {
fn from(_: HttpResponseFoundStatus) -> HttpHandlerResult {
HttpResponseFoundStatus::for_object(Empty)
}
}

/** See `http_response_see_other()` */
pub type HttpResponseSeeOther =
HttpResponseHeaders<HttpResponseSeeOtherStatus, RedirectHeaders>;

/**
* `http_response_see_other` returns an HTTP 303 "See Other" response with no
* response body.
*
* The sole argument will become the value of the `Location` header. This is
* where you want to redirect the client to.
*
* Use this (as opposed to 307 "Temporary Redirect") when you want the client to
* follow up with a GET, rather than whatever method they used to make the
* current request. This is intended to be used after a PUT or POST to show a
* confirmation page or the like.
*/
pub fn http_response_see_other(
location: String,
) -> Result<HttpResponseSeeOther, HttpError> {
let _ = http::HeaderValue::from_str(&location)
.map_err(|e| http_redirect_error(e, &location))?;
Ok(HttpResponseHeaders::new(
HttpResponseSeeOtherStatus,
RedirectHeaders { location },
))
}

/**
* This internal type impls HttpCodedResponse. Consumers should use
* `HttpResponseSeeOther` instead, which includes metadata about the `Location`
* header.
*/
#[doc(hidden)]
pub struct HttpResponseSeeOtherStatus;
impl HttpCodedResponse for HttpResponseSeeOtherStatus {
type Body = Empty;
const STATUS_CODE: StatusCode = StatusCode::SEE_OTHER;
const DESCRIPTION: &'static str = "redirect (see other)";
}
impl From<HttpResponseSeeOtherStatus> for HttpHandlerResult {
fn from(_: HttpResponseSeeOtherStatus) -> HttpHandlerResult {
HttpResponseSeeOtherStatus::for_object(Empty)
}
}

/** See `http_response_temporary_redirect()` */
pub type HttpResponseTemporaryRedirect =
HttpResponseHeaders<HttpResponseTemporaryRedirectStatus, RedirectHeaders>;

/**
* `http_response_temporary_redirect` represents an HTTP 307 "Temporary
* Redirect" response with no response body.
*
* The sole argument will become the value of the `Location` header. This is
* where you want to redirect the client to.
*
* Use this (as opposed to 303 "See Other") when you want the client to use the
* same request method and body when it makes the follow-up request.
*/
pub fn http_response_temporary_redirect(
location: String,
) -> Result<HttpResponseTemporaryRedirect, HttpError> {
let _ = http::HeaderValue::from_str(&location)
.map_err(|e| http_redirect_error(e, &location))?;
Ok(HttpResponseHeaders::new(
HttpResponseTemporaryRedirectStatus,
RedirectHeaders { location },
))
}

/**
* This internal type impls HttpCodedResponse. Consumers should use
* `HttpResponseTemporaryRedirect` instead, which includes metadata about the
* `Location` header.
*/
#[doc(hidden)]
pub struct HttpResponseTemporaryRedirectStatus;
impl HttpCodedResponse for HttpResponseTemporaryRedirectStatus {
type Body = Empty;
const STATUS_CODE: StatusCode = StatusCode::TEMPORARY_REDIRECT;
const DESCRIPTION: &'static str = "redirect (temporary redirect)";
}
impl From<HttpResponseTemporaryRedirectStatus> for HttpHandlerResult {
fn from(_: HttpResponseTemporaryRedirectStatus) -> HttpHandlerResult {
HttpResponseTemporaryRedirectStatus::for_object(Empty)
}
}

#[derive(Serialize, JsonSchema)]
pub struct NoHeaders {}

Expand Down
6 changes: 6 additions & 0 deletions dropshot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,9 @@ pub use config::ConfigDropshot;
pub use config::ConfigTls;
pub use error::HttpError;
pub use error::HttpErrorResponseBody;
pub use handler::http_response_found;
pub use handler::http_response_see_other;
pub use handler::http_response_temporary_redirect;
pub use handler::Extractor;
pub use handler::ExtractorMetadata;
pub use handler::FreeformBody;
Expand All @@ -642,8 +645,11 @@ pub use handler::HttpResponse;
pub use handler::HttpResponseAccepted;
pub use handler::HttpResponseCreated;
pub use handler::HttpResponseDeleted;
pub use handler::HttpResponseFound;
pub use handler::HttpResponseHeaders;
pub use handler::HttpResponseOk;
pub use handler::HttpResponseSeeOther;
pub use handler::HttpResponseTemporaryRedirect;
pub use handler::HttpResponseUpdatedNoContent;
pub use handler::NoHeaders;
pub use handler::Path;
Expand Down
3 changes: 2 additions & 1 deletion dropshot/src/test_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ pub const TEST_HEADER_2: &str = "x-dropshot-test-header-2";

// List of allowed HTTP headers in responses.
// Used to make sure we don't leak headers unexpectedly.
const ALLOWED_HEADERS: [AllowedHeader<'static>; 7] = [
const ALLOWED_HEADERS: [AllowedHeader<'static>; 8] = [
AllowedHeader::new("content-length"),
AllowedHeader::new("content-type"),
AllowedHeader::new("date"),
AllowedHeader::new("location"),
AllowedHeader::new("x-request-id"),
AllowedHeader {
name: "transfer-encoding",
Expand Down
3 changes: 3 additions & 0 deletions dropshot/tests/fail/bad_endpoint12.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ error[E0277]: the trait bound `String: HttpCodedResponse` is not satisfied
HttpResponseDeleted
HttpResponseOk<T>
HttpResponseUpdatedNoContent
dropshot::handler::HttpResponseFoundStatus
dropshot::handler::HttpResponseSeeOtherStatus
dropshot::handler::HttpResponseTemporaryRedirectStatus
= note: required because of the requirements on the impl of `HttpResponse` for `String`
note: required because of the requirements on the impl of `ResultTrait` for `Result<String, HttpError>`
--> tests/fail/bad_endpoint12.rs:16:6
Expand Down
2 changes: 1 addition & 1 deletion dropshot/tests/fail/bad_endpoint4.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ error[E0277]: the trait bound `QueryParams: schemars::JsonSchema` is not satisfi
(T0, T1, T2, T3)
(T0, T1, T2, T3, T4)
(T0, T1, T2, T3, T4, T5)
and 144 others
and 145 others
note: required by a bound in `dropshot::Query`
--> src/handler.rs
|
Expand Down
2 changes: 1 addition & 1 deletion dropshot/tests/fail/bad_endpoint7.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ error[E0277]: the trait bound `Ret: serde::ser::Serialize` is not satisfied
(T0, T1, T2, T3)
(T0, T1, T2, T3, T4)
(T0, T1, T2, T3, T4, T5)
and 219 others
and 220 others
= note: required because of the requirements on the impl of `dropshot::handler::HttpResponseContent` for `Ret`
note: required by a bound in `HttpResponseOk`
--> src/handler.rs
Expand Down
Loading