1use crate::network::http::client::add_unix_socket_flag;
2use crate::network::http::client::{
3 RequestFlags, RequestMetadata, check_response_redirection, expand_unix_socket_path,
4 http_client, http_parse_redirect_mode, http_parse_url, request_add_authorization_header,
5 request_add_custom_headers, request_handle_response, request_set_timeout, send_request_no_body,
6};
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 .param(
73 Flag::new("redirect-mode")
74 .short('R')
75 .arg(SyntaxShape::String)
76 .desc(
77 "What to do when encountering redirects. Default: 'follow'. Valid \
78 options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').",
79 )
80 .completion(Completion::new_list(RedirectMode::MODES)),
81 )
82 .filter()
83 .category(Category::Network);
84
85 add_unix_socket_flag(sig)
86 }
87
88 fn description(&self) -> &str {
89 "Fetch the contents from a URL."
90 }
91
92 fn extra_description(&self) -> &str {
93 "Performs HTTP GET operation."
94 }
95
96 fn search_terms(&self) -> Vec<&str> {
97 vec![
98 "network", "fetch", "pull", "request", "download", "curl", "wget",
99 ]
100 }
101
102 fn run(
103 &self,
104 engine_state: &EngineState,
105 stack: &mut Stack,
106 call: &Call,
107 input: PipelineData,
108 ) -> Result<PipelineData, ShellError> {
109 run_get(engine_state, stack, call, input)
110 }
111
112 fn examples(&self) -> Vec<Example<'_>> {
113 vec![
114 Example {
115 description: "Get content from example.com",
116 example: "http get https://www.example.com",
117 result: None,
118 },
119 Example {
120 description: "Get content from example.com, with username and password",
121 example: "http get --user myuser --password mypass https://www.example.com",
122 result: None,
123 },
124 Example {
125 description: "Get content from example.com, with custom header using a record",
126 example: "http get --headers {my-header-key: my-header-value} https://www.example.com",
127 result: None,
128 },
129 Example {
130 description: "Get content from example.com, with custom headers using a list",
131 example: "http get --headers [my-header-key-A my-header-value-A my-header-key-B my-header-value-B] https://www.example.com",
132 result: None,
133 },
134 Example {
135 description: "Get the response status code",
136 example: r#"http get https://www.example.com | metadata | get http_response.status"#,
137 result: None,
138 },
139 Example {
140 description: "Check response status while streaming",
141 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"#,
142 result: None,
143 },
144 Example {
145 description: "Get from Docker daemon via Unix socket",
146 example: "http get --unix-socket /var/run/docker.sock http://localhost/containers/json",
147 result: None,
148 },
149 ]
150 }
151}
152
153struct Arguments {
154 url: Value,
155 headers: Option<Value>,
156 raw: bool,
157 insecure: bool,
158 user: Option<String>,
159 password: Option<String>,
160 timeout: Option<Value>,
161 full: bool,
162 allow_errors: bool,
163 redirect: Option<Spanned<String>>,
164 unix_socket: Option<Spanned<String>>,
165}
166
167pub fn run_get(
168 engine_state: &EngineState,
169 stack: &mut Stack,
170 call: &Call,
171 _input: PipelineData,
172) -> Result<PipelineData, ShellError> {
173 let args = Arguments {
174 url: call.req(engine_state, stack, 0)?,
175 headers: call.get_flag(engine_state, stack, "headers")?,
176 raw: call.has_flag(engine_state, stack, "raw")?,
177 insecure: call.has_flag(engine_state, stack, "insecure")?,
178 user: call.get_flag(engine_state, stack, "user")?,
179 password: call.get_flag(engine_state, stack, "password")?,
180 timeout: call.get_flag(engine_state, stack, "max-time")?,
181 full: call.has_flag(engine_state, stack, "full")?,
182 allow_errors: call.has_flag(engine_state, stack, "allow-errors")?,
183 redirect: call.get_flag(engine_state, stack, "redirect-mode")?,
184 unix_socket: call.get_flag(engine_state, stack, "unix-socket")?,
185 };
186 helper(engine_state, stack, call, args)
187}
188
189fn helper(
192 engine_state: &EngineState,
193 stack: &mut Stack,
194 call: &Call,
195 args: Arguments,
196) -> Result<PipelineData, ShellError> {
197 let span = args.url.span();
198 let Spanned {
199 item: (requested_url, _),
200 span: request_span,
201 } = http_parse_url(call, span, args.url)?;
202 let redirect_mode = http_parse_redirect_mode(args.redirect)?;
203
204 let cwd = engine_state.cwd(None)?;
205 let unix_socket_path = expand_unix_socket_path(args.unix_socket, &cwd);
206
207 let client = http_client(
208 args.insecure,
209 redirect_mode,
210 unix_socket_path,
211 engine_state,
212 stack,
213 )?;
214 let mut request = client.get(&requested_url);
215
216 request = request_set_timeout(args.timeout, request)?;
217 request = request_add_authorization_header(args.user, args.password, request);
218 request = request_add_custom_headers(args.headers, request)?;
219 let (response, request_headers) =
220 send_request_no_body(request, request_span, call.head, engine_state.signals());
221
222 let request_flags = RequestFlags {
223 raw: args.raw,
224 full: args.full,
225 allow_errors: args.allow_errors,
226 };
227
228 let response = response?;
229
230 check_response_redirection(redirect_mode, span, &response)?;
231 request_handle_response(
232 engine_state,
233 stack,
234 RequestMetadata {
235 requested_url: &requested_url,
236 span,
237 headers: request_headers,
238 redirect_mode,
239 flags: request_flags,
240 },
241 response,
242 )
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_examples() {
251 use crate::test_examples;
252
253 test_examples(HttpGet {})
254 }
255}