nu_command/network/http/
get.rs

1use crate::network::http::client::{
2    RequestFlags, RequestMetadata, check_response_redirection, expand_unix_socket_path,
3    http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
4    request_add_custom_headers, request_handle_response, request_set_timeout, send_request_no_body,
5};
6use crate::network::http::client::{add_unix_socket_flag, http_client_pool};
7use nu_engine::command_prelude::*;
8
9use super::client::RedirectMode;
10
11#[derive(Clone)]
12pub struct HttpGet;
13
14impl Command for HttpGet {
15    fn name(&self) -> &str {
16        "http get"
17    }
18
19    fn signature(&self) -> Signature {
20        let sig = Signature::build("http get")
21            .input_output_types(vec![(Type::Nothing, Type::Any)])
22            .allow_variants_without_examples(true)
23            .required(
24                "URL",
25                SyntaxShape::String,
26                "The URL to fetch the contents from.",
27            )
28            .named(
29                "user",
30                SyntaxShape::Any,
31                "the username when authenticating",
32                Some('u'),
33            )
34            .named(
35                "password",
36                SyntaxShape::Any,
37                "the password when authenticating",
38                Some('p'),
39            )
40            .named(
41                "max-time",
42                SyntaxShape::Duration,
43                "max duration before timeout occurs",
44                Some('m'),
45            )
46            .named(
47                "headers",
48                SyntaxShape::Any,
49                "custom headers you want to add ",
50                Some('H'),
51            )
52            .switch(
53                "raw",
54                "fetch contents as text rather than a table",
55                Some('r'),
56            )
57            .switch(
58                "insecure",
59                "allow insecure server connections when using SSL",
60                Some('k'),
61            )
62            .switch(
63                "full",
64                "returns the full response instead of only the body",
65                Some('f'),
66            )
67            .switch(
68                "allow-errors",
69                "do not fail if the server returns an error code",
70                Some('e'),
71            )
72            .switch("pool", "using a global pool as a client", None)
73            .param(
74                Flag::new("redirect-mode")
75                    .short('R')
76                    .arg(SyntaxShape::String)
77                    .desc(
78                        "What to do when encountering redirects. Default: 'follow'. Valid \
79                         options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
80                    )
81                    .completion(Completion::new_list(RedirectMode::MODES)),
82            )
83            .filter()
84            .category(Category::Network);
85
86        add_unix_socket_flag(sig)
87    }
88
89    fn description(&self) -> &str {
90        "Fetch the contents from a URL."
91    }
92
93    fn extra_description(&self) -> &str {
94        "Performs HTTP GET operation."
95    }
96
97    fn search_terms(&self) -> Vec<&str> {
98        vec![
99            "network", "fetch", "pull", "request", "download", "curl", "wget",
100        ]
101    }
102
103    fn run(
104        &self,
105        engine_state: &EngineState,
106        stack: &mut Stack,
107        call: &Call,
108        input: PipelineData,
109    ) -> Result<PipelineData, ShellError> {
110        run_get(engine_state, stack, call, input)
111    }
112
113    fn examples(&self) -> Vec<Example<'_>> {
114        vec![
115            Example {
116                description: "Get content from example.com",
117                example: "http get https://www.example.com",
118                result: None,
119            },
120            Example {
121                description: "Get content from example.com, with username and password",
122                example: "http get --user myuser --password mypass https://www.example.com",
123                result: None,
124            },
125            Example {
126                description: "Get content from example.com, with custom header using a record",
127                example: "http get --headers {my-header-key: my-header-value} https://www.example.com",
128                result: None,
129            },
130            Example {
131                description: "Get content from example.com, with custom headers using a list",
132                example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
133                result: None,
134            },
135            Example {
136                description: "Get the response status code",
137                example: r#"http get https://www.example.com | metadata | get http_response.status"#,
138                result: None,
139            },
140            Example {
141                description: "Check response status while streaming",
142                example: r#"http get --allow-errors https://example.com/file | metadata access {|m| if $m.http_response.status != 200 { error make {msg: "failed"} } else { } } | lines"#,
143                result: None,
144            },
145            Example {
146                description: "Get from Docker daemon via Unix socket",
147                example: "http get --unix-socket /var/run/docker.sock http://localhost/containers/json",
148                result: None,
149            },
150        ]
151    }
152}
153
154struct Arguments {
155    url: Value,
156    headers: Option<Value>,
157    raw: bool,
158    insecure: bool,
159    user: Option<String>,
160    password: Option<String>,
161    timeout: Option<Value>,
162    full: bool,
163    allow_errors: bool,
164    redirect: Option<Spanned<String>>,
165    unix_socket: Option<Spanned<String>>,
166    pool: bool,
167}
168
169pub fn run_get(
170    engine_state: &EngineState,
171    stack: &mut Stack,
172    call: &Call,
173    _input: PipelineData,
174) -> Result<PipelineData, ShellError> {
175    let args = Arguments {
176        url: call.req(engine_state, stack, 0)?,
177        headers: call.get_flag(engine_state, stack, "headers")?,
178        raw: call.has_flag(engine_state, stack, "raw")?,
179        insecure: call.has_flag(engine_state, stack, "insecure")?,
180        user: call.get_flag(engine_state, stack, "user")?,
181        password: call.get_flag(engine_state, stack, "password")?,
182        timeout: call.get_flag(engine_state, stack, "max-time")?,
183        full: call.has_flag(engine_state, stack, "full")?,
184        allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
185        redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
186        unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
187        pool: call.has_flag(engine_state, stack, "pool")?,
188    };
189    helper(engine_state, stack, call, args)
190}
191
192// Helper function that actually goes to retrieve the resource from the url given
193// The Option<String> return a possible file extension which can be used in AutoConvert commands
194fn helper(
195    engine_state: &EngineState,
196    stack: &mut Stack,
197    call: &Call,
198    args: Arguments,
199) -> Result<PipelineData, ShellError> {
200    let span = args.url.span();
201    let Spanned {
202        item: (requested_url, _),
203        span: request_span,
204    } = http_parse_url(call, span, args.url)?;
205    let redirect_mode = http_parse_redirect_mode(args.redirect)?;
206
207    let cwd = engine_state.cwd(None)?;
208    let unix_socket_path = expand_unix_socket_path(args.unix_socket, &cwd);
209
210    let mut request = if args.pool {
211        http_client_pool(engine_state, stack).get(&requested_url)
212    } else {
213        let client = http_client(
214            args.insecure,
215            redirect_mode,
216            unix_socket_path,
217            engine_state,
218            stack,
219        )?;
220        client.get(&requested_url)
221    };
222
223    request = request_set_timeout(args.timeout, request)?;
224    request = request_add_authorization_header(args.user, args.password, request);
225    request = request_add_custom_headers(args.headers, request)?;
226    let (response, request_headers) =
227        send_request_no_body(request, request_span, call.head, engine_state.signals());
228
229    let request_flags = RequestFlags {
230        raw: args.raw,
231        full: args.full,
232        allow_errors: args.allow_errors,
233    };
234
235    let response = response?;
236
237    check_response_redirection(redirect_mode, span, &response)?;
238    request_handle_response(
239        engine_state,
240        stack,
241        RequestMetadata {
242            requested_url: &requested_url,
243            span,
244            headers: request_headers,
245            redirect_mode,
246            flags: request_flags,
247        },
248        response,
249    )
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    #[test]
257    fn test_examples() {
258        use crate::test_examples;
259
260        test_examples(HttpGet {})
261    }
262}