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

Support outputting the metadata of a response #240

Merged
merged 17 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
14 changes: 9 additions & 5 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use reqwest::header::{HeaderValue, AUTHORIZATION, WWW_AUTHENTICATE};
use reqwest::StatusCode;

use crate::cli::AuthType;
use crate::middleware::{Context, Middleware};
use crate::middleware::{Context, Middleware, ResponseMeta};
use crate::netrc;
use crate::regex;
use crate::utils::clone_request;
Expand Down Expand Up @@ -82,8 +82,12 @@ impl<'a> DigestAuthMiddleware<'a> {
}

impl<'a> Middleware for DigestAuthMiddleware<'a> {
fn handle(&mut self, mut ctx: Context, mut request: Request) -> Result<Response> {
let response = self.next(&mut ctx, clone_request(&mut request)?)?;
fn handle(
&mut self,
mut ctx: Context,
mut request: Request,
) -> Result<(Response, ResponseMeta)> {
let (response, response_meta) = self.next(&mut ctx, clone_request(&mut request)?)?;
match response.headers().get(WWW_AUTHENTICATE) {
Some(wwwauth) if response.status() == StatusCode::UNAUTHORIZED => {
let mut context = digest_auth::AuthContext::new(
Expand All @@ -99,10 +103,10 @@ impl<'a> Middleware for DigestAuthMiddleware<'a> {
request
.headers_mut()
.insert(AUTHORIZATION, HeaderValue::from_str(&answer)?);
self.print(&mut ctx, response, &mut request)?;
self.print(&mut ctx, response, response_meta, &mut request)?;
Ok(self.next(&mut ctx, request)?)
}
_ => Ok(response),
_ => Ok((response, response_meta)),
}
}
}
Expand Down
65 changes: 46 additions & 19 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub struct Cli {
pub form: bool,

/// Like --form, but force a multipart/form-data request even without files.
#[clap(short = 'm', long, overrides_with_all = &["json", "form"])]
#[clap(long, overrides_with_all = &["json", "form"])]
ducaale marked this conversation as resolved.
Show resolved Hide resolved
pub multipart: bool,

/// Pass raw request data without extra processing.
Expand All @@ -80,13 +80,16 @@ pub struct Cli {
#[clap(long, value_name = "MIME_TYPE")]
pub response_mime: Option<String>,

/// String specifying what the output should contain.
/// String specifying what the output should contain
///
/// Use `H` and `B` for request header and body respectively,
/// and `h` and `b` for response hader and body.
/// 'H' request headers
/// 'B' request body
/// 'h' response headers
/// 'b' response body
/// 'm' response metadata
ducaale marked this conversation as resolved.
Show resolved Hide resolved
///
/// Example: `--print=Hb`
#[clap(short = 'p', long, value_name = "FORMAT")]
#[clap(short = 'p', long, value_name = "FORMAT", verbatim_doc_comment)]
pub print: Option<Print>,

/// Print only the response headers. Shortcut for --print=h.
Expand All @@ -97,9 +100,13 @@ pub struct Cli {
#[clap(short = 'b', long)]
pub body: bool,

/// Print only the response metadata. Shortcut for --print=m.
#[clap(short = 'm', long)]
pub meta: bool,

/// Print the whole request as well as the response.
#[clap(short = 'v', long)]
pub verbose: bool,
#[clap(short = 'v', long, parse(from_occurrences))]
pub verbose: usize,

/// Show any intermediary requests/responses while following redirects with --follow.
#[clap(long)]
Expand Down Expand Up @@ -293,14 +300,14 @@ pub struct Cli {

/// Optional key-value pairs to be included in the request
///
/// - key==value to add a parameter to the URL
/// - key=value to add a JSON field (--json) or form field (--form)
/// - key:=value to add a complex JSON value (e.g. `numbers:=[1,2,3]`)
/// - key@filename to upload a file from filename (with --form)
/// - @filename to use a file as the request body
/// - header:value to add a header
/// - header: to unset a header
/// - header; to add a header with an empty value
/// key==value add a parameter to the URL
/// key=value add a JSON field (--json) or form field (--form)
/// key:=value add a complex JSON value (e.g. `numbers:=[1,2,3]`)
/// key@filename upload a file from filename (with --form)
/// @filename use a file as the request body
/// header:value add a header
/// header: unset a header
/// header; add a header with an empty value
///
/// A backslash can be used to escape special characters (e.g. weird\:key=value).
#[clap(value_name = "REQUEST_ITEM", verbatim_doc_comment)]
Expand Down Expand Up @@ -370,6 +377,7 @@ impl Cli {
{after-help}\
",
)
.max_term_width(100)
.print_long_help()
.unwrap();
} else {
Expand All @@ -396,7 +404,7 @@ impl Cli {

match cli.raw_method_or_url.as_str() {
"help" => {
app.print_long_help().unwrap();
app.max_term_width(100).print_long_help().unwrap();
println!();
safe_exit();
}
Expand Down Expand Up @@ -459,7 +467,7 @@ impl Cli {

/// Set flags that are implied by other flags and report conflicting flags.
fn process_relations(&mut self, matches: &clap::ArgMatches) -> clap::Result<()> {
if self.verbose {
if self.verbose > 0 {
self.all = true;
}
if self.curl_long {
Expand Down Expand Up @@ -739,58 +747,74 @@ pub struct Print {
pub request_body: bool,
pub response_headers: bool,
pub response_body: bool,
pub response_meta: bool,
}

impl Print {
pub fn new(
verbose: bool,
verbose: usize,
headers: bool,
body: bool,
meta: bool,
quiet: bool,
offline: bool,
buffer: &Buffer,
) -> Self {
if verbose {
if verbose > 0 {
Print {
request_headers: true,
request_body: true,
response_headers: true,
response_body: true,
response_meta: verbose > 1,
}
} else if quiet {
Print {
request_headers: false,
request_body: false,
response_headers: false,
response_body: false,
response_meta: false,
}
} else if offline {
Print {
request_headers: true,
request_body: true,
response_headers: false,
response_body: false,
response_meta: false,
}
} else if headers {
Print {
request_headers: false,
request_body: false,
response_headers: true,
response_body: false,
response_meta: false,
}
} else if body || !buffer.is_terminal() {
Print {
request_headers: false,
request_body: false,
response_headers: false,
response_body: true,
response_meta: false,
}
} else if meta {
Print {
request_headers: false,
request_body: false,
response_headers: false,
response_body: false,
response_meta: true,
}
} else {
Print {
request_headers: false,
request_body: false,
response_headers: true,
response_body: true,
response_meta: false,
}
}
}
Expand All @@ -803,13 +827,15 @@ impl FromStr for Print {
let mut request_body = false;
let mut response_headers = false;
let mut response_body = false;
let mut response_meta = false;

for char in s.chars() {
match char {
'H' => request_headers = true,
'B' => request_body = true,
'h' => response_headers = true,
'b' => response_body = true,
'm' => response_meta = true,
char => return Err(anyhow!("{:?} is not a valid value", char)),
}
}
Expand All @@ -819,6 +845,7 @@ impl FromStr for Print {
request_body,
response_headers,
response_body,
response_meta,
};
Ok(p)
}
Expand Down
20 changes: 16 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ fn run(args: Cli) -> Result<i32> {
args.verbose,
args.headers,
args.body,
args.meta,
args.quiet,
args.offline,
&buffer,
Expand All @@ -466,11 +467,11 @@ fn run(args: Cli) -> Result<i32> {
}

if !args.offline {
let response = {
let (response, response_meta) = {
let history_print = args.history_print.unwrap_or(print);
let mut client = ClientWithMiddleware::new(&client);
if args.all {
client = client.with_printer(|prev_response, next_request| {
client = client.with_printer(|prev_response, prev_response_meta, next_request| {
if history_print.response_headers {
printer.print_response_headers(&prev_response)?;
}
Expand All @@ -482,6 +483,9 @@ fn run(args: Cli) -> Result<i32> {
)?;
printer.print_separator()?;
}
if history_print.response_meta {
printer.print_response_meta(prev_response_meta)?;
}
if history_print.request_headers {
printer.print_request_headers(next_request, &*cookie_jar)?;
}
Expand Down Expand Up @@ -527,8 +531,16 @@ fn run(args: Cli) -> Result<i32> {
args.quiet,
)?;
}
} else if print.response_body {
printer.print_response_body(response, response_charset, response_mime)?;
} else {
if print.response_body {
printer.print_response_body(response, response_charset, response_mime)?;
if print.response_meta {
printer.print_separator()?;
}
}
if print.response_meta {
printer.print_response_meta(response_meta)?;
}
}
}

Expand Down
46 changes: 35 additions & 11 deletions src/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
use std::time::Instant;

use anyhow::Result;
use reqwest::blocking::{Client, Request, Response};

pub struct ResponseMeta {
pub starting_time: Instant,
}

impl ResponseMeta {
fn new(starting_time: Instant) -> Self {
ResponseMeta { starting_time }
}
}

pub struct Context<'a, 'b> {
client: &'a Client,
printer: Option<&'a mut (dyn FnMut(Response, &mut Request) -> Result<()> + 'b)>,
printer: Option<&'a mut (dyn FnMut(Response, ResponseMeta, &mut Request) -> Result<()> + 'b)>,
middlewares: &'a mut [Box<dyn Middleware + 'b>],
}

impl<'a, 'b> Context<'a, 'b> {
fn new(
client: &'a Client,
printer: Option<&'a mut (dyn FnMut(Response, &mut Request) -> Result<()> + 'b)>,
printer: Option<
&'a mut (dyn FnMut(Response, ResponseMeta, &mut Request) -> Result<()> + 'b),
>,
middlewares: &'a mut [Box<dyn Middleware + 'b>],
) -> Self {
Context {
Expand All @@ -20,9 +34,13 @@ impl<'a, 'b> Context<'a, 'b> {
}
}

fn execute(&mut self, request: Request) -> Result<Response> {
fn execute(&mut self, request: Request) -> Result<(Response, ResponseMeta)> {
match self.middlewares {
[] => Ok(self.client.execute(request)?),
[] => {
let starting_time = Instant::now();
let response = self.client.execute(request)?;
Ok((response, ResponseMeta::new(starting_time)))
}
[ref mut head, tail @ ..] => head.handle(
#[allow(clippy::needless_option_as_deref)]
Context::new(self.client, self.printer.as_deref_mut(), tail),
Expand All @@ -33,15 +51,21 @@ impl<'a, 'b> Context<'a, 'b> {
}

pub trait Middleware {
fn handle(&mut self, ctx: Context, request: Request) -> Result<Response>;
fn handle(&mut self, ctx: Context, request: Request) -> Result<(Response, ResponseMeta)>;

fn next(&self, ctx: &mut Context, request: Request) -> Result<Response> {
fn next(&self, ctx: &mut Context, request: Request) -> Result<(Response, ResponseMeta)> {
ctx.execute(request)
}

fn print(&self, ctx: &mut Context, response: Response, request: &mut Request) -> Result<()> {
fn print(
&self,
ctx: &mut Context,
response: Response,
response_meta: ResponseMeta,
request: &mut Request,
) -> Result<()> {
if let Some(ref mut printer) = ctx.printer {
printer(response, request)?;
printer(response, response_meta, request)?;
}

Ok(())
Expand All @@ -50,7 +74,7 @@ pub trait Middleware {

pub struct ClientWithMiddleware<'a, T>
where
T: FnMut(Response, &mut Request) -> Result<()>,
T: FnMut(Response, ResponseMeta, &mut Request) -> Result<()>,
{
client: &'a Client,
printer: Option<T>,
Expand All @@ -59,7 +83,7 @@ where

impl<'a, T> ClientWithMiddleware<'a, T>
where
T: FnMut(Response, &mut Request) -> Result<()> + 'a,
T: FnMut(Response, ResponseMeta, &mut Request) -> Result<()> + 'a,
{
pub fn new(client: &'a Client) -> Self {
ClientWithMiddleware {
Expand All @@ -79,7 +103,7 @@ where
self
}

pub fn execute(&mut self, request: Request) -> Result<Response> {
pub fn execute(&mut self, request: Request) -> Result<(Response, ResponseMeta)> {
let mut ctx = Context::new(
self.client,
self.printer.as_mut().map(|p| p as _),
Expand Down
Loading