1use crate::network::http::client::{
2 RequestFlags, RequestMetadata, check_response_redirection, http_client,
3 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 nu_engine::command_prelude::*;
7
8use super::client::RedirectMode;
9
10#[derive(Clone)]
11pub struct HttpGet;
12
13impl Command for HttpGet {
14 fn name(&self) -> &str {
15 "http get"
16 }
17
18 fn signature(&self) -> Signature {
19 Signature::build("http get")
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 "raw",
53 "fetch contents as text rather than a table",
54 Some('r'),
55 )
56 .switch(
57 "insecure",
58 "allow insecure server connections when using SSL",
59 Some('k'),
60 )
61 .switch(
62 "full",
63 "returns the full response instead of only the body",
64 Some('f'),
65 )
66 .switch(
67 "allow-errors",
68 "do not fail if the server returns an error code",
69 Some('e'),
70 )
71 .param(
72 Flag::new("redirect-mode")
73 .short('R')
74 .arg(SyntaxShape::String)
75 .desc(
76 "What to do when encountering redirects. Default: 'follow'. Valid \
77 options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
78 )
79 .completion(Completion::new_list(RedirectMode::MODES)),
80 )
81 .filter()
82 .category(Category::Network)
83 }
84
85 fn description(&self) -> &str {
86 "Fetch the contents from a URL."
87 }
88
89 fn extra_description(&self) -> &str {
90 "Performs HTTP GET operation."
91 }
92
93 fn search_terms(&self) -> Vec<&str> {
94 vec![
95 "network", "fetch", "pull", "request", "download", "curl", "wget",
96 ]
97 }
98
99 fn run(
100 &self,
101 engine_state: &EngineState,
102 stack: &mut Stack,
103 call: &Call,
104 input: PipelineData,
105 ) -> Result<PipelineData, ShellError> {
106 run_get(engine_state, stack, call, input)
107 }
108
109 fn examples(&self) -> Vec<Example<'_>> {
110 vec![
111 Example {
112 description: "Get content from example.com",
113 example: "http get https://www.example.com",
114 result: None,
115 },
116 Example {
117 description: "Get content from example.com, with username and password",
118 example: "http get --user myuser --password mypass https://www.example.com",
119 result: None,
120 },
121 Example {
122 description: "Get content from example.com, with custom header using a record",
123 example: "http get --headers {my-header-key: my-header-value} https://www.example.com",
124 result: None,
125 },
126 Example {
127 description: "Get content from example.com, with custom headers using a list",
128 example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
129 result: None,
130 },
131 Example {
132 description: "Get the response status code",
133 example: r#"http get https://www.example.com | metadata | get http_response.status"#,
134 result: None,
135 },
136 Example {
137 description: "Check response status while streaming",
138 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"#,
139 result: None,
140 },
141 ]
142 }
143}
144
145struct Arguments {
146 url: Value,
147 headers: Option<Value>,
148 raw: bool,
149 insecure: bool,
150 user: Option<String>,
151 password: Option<String>,
152 timeout: Option<Value>,
153 full: bool,
154 allow_errors: bool,
155 redirect: Option<Spanned<String>>,
156}
157
158pub fn run_get(
159 engine_state: &EngineState,
160 stack: &mut Stack,
161 call: &Call,
162 _input: PipelineData,
163) -> Result<PipelineData, ShellError> {
164 let args = Arguments {
165 url: call.req(engine_state, stack, 0)?,
166 headers: call.get_flag(engine_state, stack, "headers")?,
167 raw: call.has_flag(engine_state, stack, "raw")?,
168 insecure: call.has_flag(engine_state, stack, "insecure")?,
169 user: call.get_flag(engine_state, stack, "user")?,
170 password: call.get_flag(engine_state, stack, "password")?,
171 timeout: call.get_flag(engine_state, stack, "max-time")?,
172 full: call.has_flag(engine_state, stack, "full")?,
173 allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
174 redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
175 };
176 helper(engine_state, stack, call, args)
177}
178
179fn helper(
182 engine_state: &EngineState,
183 stack: &mut Stack,
184 call: &Call,
185 args: Arguments,
186) -> Result<PipelineData, ShellError> {
187 let span = args.url.span();
188 let (requested_url, _) = http_parse_url(call, span, args.url)?;
189 let redirect_mode = http_parse_redirect_mode(args.redirect)?;
190
191 let client = http_client(args.insecure, redirect_mode, engine_state, stack)?;
192 let mut request = client.get(&requested_url);
193
194 request = request_set_timeout(args.timeout, request)?;
195 request = request_add_authorization_header(args.user, args.password, request);
196 request = request_add_custom_headers(args.headers, request)?;
197 let (response, request_headers) =
198 send_request_no_body(request, call.head, engine_state.signals());
199
200 let request_flags = RequestFlags {
201 raw: args.raw,
202 full: args.full,
203 allow_errors: args.allow_errors,
204 };
205
206 let response = response?;
207
208 check_response_redirection(redirect_mode, span, &response)?;
209 request_handle_response(
210 engine_state,
211 stack,
212 RequestMetadata {
213 requested_url: &requested_url,
214 span,
215 headers: request_headers,
216 redirect_mode,
217 flags: request_flags,
218 },
219 response,
220 )
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_examples() {
229 use crate::test_examples;
230
231 test_examples(HttpGet {})
232 }
233}