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
192fn 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}