Skip to main content

nu_command/network/http/
head.rs

1use crate::network::http::client::{
2    RedirectMode, add_unix_socket_flag, check_response_redirection, expand_unix_socket_path,
3    extract_response_headers, handle_response_status, headers_to_nu, http_client, http_client_pool,
4    http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
5    request_add_custom_headers, request_set_timeout, send_request_no_body,
6};
7use nu_engine::command_prelude::*;
8use nu_protocol::Signals;
9
10#[derive(Clone)]
11pub struct HttpHead;
12
13impl Command for HttpHead {
14    fn name(&self) -> &str {
15        "http head"
16    }
17
18    fn signature(&self) -> Signature {
19        let sig = Signature::build("http head")
20            .input_output_types(vec![(Type::Nothing, Type::Any)])
21            .allow_variants_without_examples(true)
22            .required(
23                "URL",
24                SyntaxShape::String,
25                "The URL to fetch the contents from.",
26            )
27            .named(
28                "user",
29                SyntaxShape::Any,
30                "The username when authenticating.",
31                Some('u'),
32            )
33            .named(
34                "password",
35                SyntaxShape::Any,
36                "The password when authenticating.",
37                Some('p'),
38            )
39            .named(
40                "max-time",
41                SyntaxShape::Duration,
42                "Max duration before timeout occurs.",
43                Some('m'),
44            )
45            .named(
46                "headers",
47                SyntaxShape::Any,
48                "Custom headers you want to add.",
49                Some('H'),
50            )
51            .switch(
52                "insecure",
53                "Allow insecure server connections when using SSL.",
54                Some('k'),
55            )
56            .switch("pool", "Using a global pool as a client.", None)
57            .param(
58                Flag::new("redirect-mode")
59                    .short('R')
60                    .arg(SyntaxShape::String)
61                    .desc(
62                        "What to do when encountering redirects. Default: 'follow'. Valid \
63                         options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
64                    )
65                    .completion(Completion::new_list(RedirectMode::MODES)),
66            )
67            .filter()
68            .category(Category::Network);
69
70        add_unix_socket_flag(sig)
71    }
72
73    fn description(&self) -> &str {
74        "Get the headers from a URL."
75    }
76
77    fn extra_description(&self) -> &str {
78        "Performs HTTP HEAD operation."
79    }
80
81    fn search_terms(&self) -> Vec<&str> {
82        vec!["network", "request", "curl", "wget", "headers", "header"]
83    }
84
85    fn run(
86        &self,
87        engine_state: &EngineState,
88        stack: &mut Stack,
89        call: &Call,
90        input: PipelineData,
91    ) -> Result<PipelineData, ShellError> {
92        run_head(engine_state, stack, call, input)
93    }
94
95    fn examples(&self) -> Vec<Example<'_>> {
96        vec![
97            Example {
98                description: "Get headers from example.com.",
99                example: "http head https://www.example.com",
100                result: None,
101            },
102            Example {
103                description: "Get headers from example.com, with username and password.",
104                example: "http head --user myuser --password mypass https://www.example.com",
105                result: None,
106            },
107            Example {
108                description: "Get headers from example.com, with custom header using a record.",
109                example: "http head --headers {my-header-key: my-header-value} https://www.example.com",
110                result: None,
111            },
112            Example {
113                description: "Get headers from example.com, with custom header using a list.",
114                example: "http head --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
115                result: None,
116            },
117        ]
118    }
119}
120
121struct Arguments {
122    url: Value,
123    headers: Option<Value>,
124    insecure: bool,
125    user: Option<String>,
126    password: Option<String>,
127    timeout: Option<Value>,
128    redirect: Option<Spanned<String>>,
129    unix_socket: Option<Spanned<String>>,
130    pool: bool,
131}
132
133fn run_head(
134    engine_state: &EngineState,
135    stack: &mut Stack,
136    call: &Call,
137    _input: PipelineData,
138) -> Result<PipelineData, ShellError> {
139    let args = Arguments {
140        url: call.req(engine_state, stack, 0)?,
141        headers: call.get_flag(engine_state, stack, "headers")?,
142        insecure: call.has_flag(engine_state, stack, "insecure")?,
143        user: call.get_flag(engine_state, stack, "user")?,
144        password: call.get_flag(engine_state, stack, "password")?,
145        timeout: call.get_flag(engine_state, stack, "max-time")?,
146        redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
147        unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
148        pool: call.has_flag(engine_state, stack, "pool")?,
149    };
150
151    helper(engine_state, stack, call, args, engine_state.signals())
152}
153
154// Helper function that actually goes to retrieve the resource from the url given
155fn helper(
156    engine_state: &EngineState,
157    stack: &mut Stack,
158    call: &Call,
159    args: Arguments,
160    signals: &Signals,
161) -> Result<PipelineData, ShellError> {
162    let span = args.url.span();
163    let Spanned {
164        item: (requested_url, _),
165        span: request_span,
166    } = http_parse_url(call, span, args.url)?;
167    let redirect_mode = http_parse_redirect_mode(args.redirect)?;
168
169    let cwd = engine_state.cwd(None)?;
170    let unix_socket_path = expand_unix_socket_path(args.unix_socket, &cwd);
171
172    let mut request = if args.pool {
173        http_client_pool(engine_state, stack)?.head(&requested_url)
174    } else {
175        let client = http_client(
176            args.insecure,
177            redirect_mode,
178            unix_socket_path,
179            engine_state,
180            stack,
181        )?;
182        client.head(&requested_url)
183    };
184
185    request = request_set_timeout(args.timeout, request)?;
186    request = request_add_authorization_header(args.user, args.password, request);
187    request = request_add_custom_headers(args.headers, request)?;
188
189    let (response, _request_headers) =
190        send_request_no_body(request, request_span, call.head, signals);
191    let response = response?;
192    check_response_redirection(redirect_mode, span, &response)?;
193    handle_response_status(&response, redirect_mode, &requested_url, span, false)?;
194    headers_to_nu(&extract_response_headers(&response), span)
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    #[test]
202    fn test_examples() {
203        use crate::test_examples;
204
205        test_examples(HttpHead {})
206    }
207}