wget (http_client) command #20

This commit is contained in:
sagie gur ari 2020-01-18 16:41:03 +00:00
parent 9a6e94ce1f
commit 80d09ca94c
13 changed files with 415 additions and 25 deletions

View file

@ -2,6 +2,7 @@
### v0.1.8
* wget (http_client) command #20
* Reduce binary executable size.
* Fix CLI help documentation.

View file

@ -47,6 +47,7 @@
* [std::fs::Write (writefile)](#std__fs__Write)
* [std::math::Calc (calc)](#std__math__Calc)
* [std::net::Hostname (hostname)](#std__net__Hostname)
* [std::net::HttpClient (http_client, wget)](#std__net__HttpClient)
* [std::process::Execute (exec)](#std__process__Execute)
* [std::process::Exit (exit, quit, q)](#std__process__Exit)
* [std::string::Contains (contains)](#std__string__Contains)
@ -1681,6 +1682,70 @@ name = hostname
#### Aliases:
hostname
<a name="std__net__HttpClient"></a>
## std::net::HttpClient
```sh
var = http_client [--method=HTTP-method] [--post-data=payload] [-O file] URL
```
Invokes a HTTP request.<br>
The request method by default is GET but can be modified by the ```--method``` parameter.<br>
The ```-O``` parameter will redirect a valid response output to the provided file, otherwise all response text will be set to the
output variable.<br>
When redirecting to file, the output would be the response size.<br>
The ```--post-data``` parameter enables to pass a payload to POST http requests.<br>
In case of errors or error HTTP response codes, false will be returned.
#### Parameters
* Optional HTTP Method, for example --method=HTTP-GET or --method=HTTP-POST (currently only GET and POST are supported).
* Optional post payload via ```--post-data``` parameter.
* Optional redirection of output to file via ```-O``` parameter.
* The target URL
#### Return Value
The response text or in case of output redirection to file, the response size.<br>
In case of errors, it will return false.
#### Examples
```sh
function test_get
response = http_client https://www.rust-lang.org/
found = contains ${response} Rust
assert ${found}
end
function test_get_to_file
file = set ./target/_duckscript_test/http_client/page.html
rm ${file}
response_size = http_client -O ${file} https://www.rust-lang.org/
response = readfile ${file}
found = contains ${response} Rust
assert ${found}
assert ${response_size}
end
function test_post
payload = set {\"login\":\"login\",\"password\":\"password\"}
response = http_client --method=HTTP-POST --post-data=${payload} https://reqbin.com/echo/post/json
found = contains ${response} success
assert ${found}
end
```
#### Aliases:
http_client, wget
<a name="std__process__Execute"></a>
## std::process::Execute
```sh

View file

@ -2,11 +2,12 @@
absolute_paths_not_starting_with_crate,
ambiguous_associated_items,
anonymous_parameters,
array_into_iter,
bindings_with_variant_name,
const_err,
dead_code,
deprecated,
deprecated_in_future,
duplicate_macro_exports,
ellipsis_inclusive_range_patterns,
exceeding_bitshifts,
explicit_outlives_requirements,
@ -14,20 +15,20 @@
ill_formed_attribute_input,
illegal_floating_point_literal_pattern,
improper_ctypes,
incomplete_features,
indirect_structural_match,
invalid_type_param_default,
invalid_value,
irrefutable_let_patterns,
keyword_idents,
late_bound_lifetime_arguments,
legacy_constructor_visibility,
legacy_directory_ownership,
macro_expanded_macro_exports_accessed_by_absolute_paths,
meta_variable_misuse,
missing_copy_implementations,
missing_docs,
missing_fragment_specifier,
mutable_borrow_reservation_conflict,
mutable_transmutes,
nested_impl_trait,
no_mangle_const_items,
no_mangle_generic_items,
non_ascii_idents,
@ -37,24 +38,24 @@
non_upper_case_globals,
order_dependent_trait_objects,
overflowing_literals,
parenthesized_params_in_types_and_modules,
overlapping_patterns,
path_statements,
patterns_in_fns_without_body,
plugin_as_library,
private_doc_tests,
private_in_public,
proc_macro_derive_resolution_fallback,
pub_use_of_private_extern_crate,
safe_extern_statics,
redundant_semicolon,
safe_packed_borrows,
soft_unstable,
stable_features,
trivial_bounds,
trivial_casts,
trivial_numeric_casts,
type_alias_bounds,
tyvar_behind_raw_pointer,
uncommon_codepoints,
unconditional_recursion,
unions_with_drop_fields,
unknown_crate_types,
unnameable_test_items,
unreachable_code,

View file

@ -2,11 +2,12 @@
absolute_paths_not_starting_with_crate,
ambiguous_associated_items,
anonymous_parameters,
array_into_iter,
bindings_with_variant_name,
const_err,
dead_code,
deprecated,
deprecated_in_future,
duplicate_macro_exports,
ellipsis_inclusive_range_patterns,
exceeding_bitshifts,
explicit_outlives_requirements,
@ -14,20 +15,20 @@
ill_formed_attribute_input,
illegal_floating_point_literal_pattern,
improper_ctypes,
incomplete_features,
indirect_structural_match,
invalid_type_param_default,
invalid_value,
irrefutable_let_patterns,
keyword_idents,
late_bound_lifetime_arguments,
legacy_constructor_visibility,
legacy_directory_ownership,
macro_expanded_macro_exports_accessed_by_absolute_paths,
meta_variable_misuse,
missing_copy_implementations,
missing_docs,
missing_fragment_specifier,
mutable_borrow_reservation_conflict,
mutable_transmutes,
nested_impl_trait,
no_mangle_const_items,
no_mangle_generic_items,
non_ascii_idents,
@ -37,24 +38,24 @@
non_upper_case_globals,
order_dependent_trait_objects,
overflowing_literals,
parenthesized_params_in_types_and_modules,
overlapping_patterns,
path_statements,
patterns_in_fns_without_body,
plugin_as_library,
private_doc_tests,
private_in_public,
proc_macro_derive_resolution_fallback,
pub_use_of_private_extern_crate,
safe_extern_statics,
redundant_semicolon,
safe_packed_borrows,
soft_unstable,
stable_features,
trivial_bounds,
trivial_casts,
trivial_numeric_casts,
type_alias_bounds,
tyvar_behind_raw_pointer,
uncommon_codepoints,
unconditional_recursion,
unions_with_drop_fields,
unknown_crate_types,
unnameable_test_items,
unreachable_code,

View file

@ -23,6 +23,7 @@ include = [
]
[dependencies]
attohttpc = "^0.10"
duckscript = { version = "^0.1.6", path = "../duckscript" }
fs_extra = "^1"
home = "^0.5"

View file

@ -2,11 +2,12 @@
absolute_paths_not_starting_with_crate,
ambiguous_associated_items,
anonymous_parameters,
array_into_iter,
bindings_with_variant_name,
const_err,
dead_code,
deprecated,
deprecated_in_future,
duplicate_macro_exports,
ellipsis_inclusive_range_patterns,
exceeding_bitshifts,
explicit_outlives_requirements,
@ -14,20 +15,20 @@
ill_formed_attribute_input,
illegal_floating_point_literal_pattern,
improper_ctypes,
incomplete_features,
indirect_structural_match,
invalid_type_param_default,
invalid_value,
irrefutable_let_patterns,
keyword_idents,
late_bound_lifetime_arguments,
legacy_constructor_visibility,
legacy_directory_ownership,
macro_expanded_macro_exports_accessed_by_absolute_paths,
meta_variable_misuse,
missing_copy_implementations,
missing_docs,
missing_fragment_specifier,
mutable_borrow_reservation_conflict,
mutable_transmutes,
nested_impl_trait,
no_mangle_const_items,
no_mangle_generic_items,
non_ascii_idents,
@ -37,24 +38,24 @@
non_upper_case_globals,
order_dependent_trait_objects,
overflowing_literals,
parenthesized_params_in_types_and_modules,
overlapping_patterns,
path_statements,
patterns_in_fns_without_body,
plugin_as_library,
private_doc_tests,
private_in_public,
proc_macro_derive_resolution_fallback,
pub_use_of_private_extern_crate,
safe_extern_statics,
redundant_semicolon,
safe_packed_borrows,
soft_unstable,
stable_features,
trivial_bounds,
trivial_casts,
trivial_numeric_casts,
type_alias_bounds,
tyvar_behind_raw_pointer,
uncommon_codepoints,
unconditional_recursion,
unions_with_drop_fields,
unknown_crate_types,
unnameable_test_items,
unreachable_code,

View file

@ -0,0 +1,57 @@
```sh
var = http_client [--method=HTTP-method] [--post-data=payload] [-O file] URL
```
Invokes a HTTP request.<br>
The request method by default is GET but can be modified by the ```--method``` parameter.<br>
The ```-O``` parameter will redirect a valid response output to the provided file, otherwise all response text will be set to the
output variable.<br>
When redirecting to file, the output would be the response size.<br>
The ```--post-data``` parameter enables to pass a payload to POST http requests.<br>
In case of errors or error HTTP response codes, false will be returned.
#### Parameters
* Optional HTTP Method, for example --method=HTTP-GET or --method=HTTP-POST (currently only GET and POST are supported).
* Optional post payload via ```--post-data``` parameter.
* Optional redirection of output to file via ```-O``` parameter.
* The target URL
#### Return Value
The response text or in case of output redirection to file, the response size.<br>
In case of errors, it will return false.
#### Examples
```sh
function test_get
response = http_client https://www.rust-lang.org/
found = contains ${response} Rust
assert ${found}
end
function test_get_to_file
file = set ./target/_duckscript_test/http_client/page.html
rm ${file}
response_size = http_client -O ${file} https://www.rust-lang.org/
response = readfile ${file}
found = contains ${response} Rust
assert ${found}
assert ${response_size}
end
function test_post
payload = set {\"login\":\"login\",\"password\":\"password\"}
response = http_client --method=HTTP-POST --post-data=${payload} https://reqbin.com/echo/post/json
found = contains ${response} success
assert ${found}
end
```

View file

@ -0,0 +1,158 @@
use crate::utils::{io, pckg};
use attohttpc;
use duckscript::types::command::{Command, CommandResult};
use std::fs::File;
#[cfg(test)]
#[path = "./mod_test.rs"]
mod mod_test;
static HTTP_METHOD_PREFIX: &str = "--method=HTTP-";
static POST_DATA_PREFIX: &str = "--post-data=";
enum Method {
Get,
Post,
}
struct Options {
method: Method,
payload: Option<String>,
output_file: Option<String>,
}
impl Options {
fn new() -> Options {
Options {
method: Method::Get,
payload: None,
output_file: None,
}
}
}
enum LookingFor {
Flag,
OutputFile,
}
fn parse_options(arguments: &Vec<String>) -> Result<Options, String> {
let mut options = Options::new();
let mut looking_for = LookingFor::Flag;
for argument in arguments {
match looking_for {
LookingFor::Flag => match argument.as_str() {
"-O" => looking_for = LookingFor::OutputFile,
_ => {
if argument.starts_with(HTTP_METHOD_PREFIX) {
let method = argument[HTTP_METHOD_PREFIX.len()..].to_lowercase();
match method.as_str() {
"get" => options.method = Method::Get,
"post" => options.method = Method::Post,
_ => {
return Err(
format!("Unsupported HTTP method: {}", method).to_string()
);
}
}
} else if argument.starts_with(POST_DATA_PREFIX) {
let payload = argument[POST_DATA_PREFIX.len()..].to_string();
options.payload = Some(payload);
}
looking_for = LookingFor::Flag
}
},
LookingFor::OutputFile => options.output_file = Some(argument.to_string()),
}
}
Ok(options)
}
fn do_request(url: String, options: Options) -> CommandResult {
let request = match options.method {
Method::Get => attohttpc::get(url),
Method::Post => attohttpc::post(url),
};
let response_result = match options.method {
Method::Get => request.send(),
Method::Post => match options.payload {
Some(ref payload) => request.text(payload).send(),
None => request.send(),
},
};
match response_result {
Ok(response) => {
if response.is_success() {
match options.output_file {
Some(file) => match io::create_parent_directory(&file) {
Ok(_) => match io::delete_file(&file) {
Ok(_) => match File::create(file) {
Ok(file_struct) => match response.write_to(file_struct) {
Ok(size) => CommandResult::Continue(Some(size.to_string())),
Err(error) => CommandResult::Error(error.to_string()),
},
Err(error) => CommandResult::Error(error.to_string()),
},
Err(error) => CommandResult::Error(error),
},
Err(error) => CommandResult::Error(error.to_string()),
},
None => match response.text() {
Ok(text) => CommandResult::Continue(Some(text.to_string())),
Err(error) => CommandResult::Error(error.to_string()),
},
}
} else {
CommandResult::Error(
format!("Error response, status code: {:?}", response.status()).to_string(),
)
}
}
Err(error) => CommandResult::Error(error.to_string()),
}
}
struct CommandImpl {
package: String,
}
impl Command for CommandImpl {
fn name(&self) -> String {
pckg::concat(&self.package, "HttpClient")
}
fn aliases(&self) -> Vec<String> {
vec!["http_client".to_string(), "wget".to_string()]
}
fn help(&self) -> String {
include_str!("help.md").to_string()
}
fn run(&self, arguments: Vec<String>) -> CommandResult {
if arguments.is_empty() {
CommandResult::Error("URL not provided.".to_string())
} else {
let len = arguments.len() - 1;
let url = arguments[len].to_string();
match parse_options(&arguments[0..len].to_vec()) {
Ok(options) => do_request(url, options),
Err(error) => CommandResult::Error(error),
}
}
}
}
pub(crate) fn create(package: &str) -> Box<dyn Command> {
Box::new(CommandImpl {
package: package.to_string(),
})
}

View file

@ -0,0 +1,53 @@
use super::*;
use crate::test;
use crate::test::{CommandValidation, SetCommand};
#[test]
fn common_functions() {
test::test_common_command_functions(create(""));
}
#[test]
fn run_no_args() {
test::run_script_and_error(vec![create("")], "out = http_client", "out");
}
#[test]
fn run_get() {
test::run_script_and_validate(
vec![create("")],
"out = http_client https://www.rust-lang.org/",
CommandValidation::Contains("out".to_string(), "Rust".to_string()),
);
}
#[test]
fn run_get_to_file() {
let file = "./target/_duckscript/http_client/page.html";
let result = io::delete_file(file);
assert!(result.is_ok());
test::run_script_and_validate(
vec![create("")],
"out = http_client -O ./target/_duckscript/http_client/page.html https://www.rust-lang.org/",
CommandValidation::Ignore
);
let read_result = io::read_text_file(file);
assert!(read_result.is_ok());
let text = read_result.unwrap();
assert!(text.contains("Rust"));
}
#[test]
fn run_post() {
test::run_script_and_validate(
vec![create(""), Box::new(SetCommand {})],
r#"
payload = test_set {\"login\":\"login\",\"password\":\"password\"}
out = http_client --method=HTTP-POST --post-data=${payload} https://reqbin.com/echo/post/json
"#,
CommandValidation::Contains("out".to_string(), "success".to_string()),
);
}

View file

@ -1,4 +1,5 @@
mod hostname;
mod http_client;
use crate::utils::pckg;
use duckscript::types::command::Commands;
@ -10,6 +11,7 @@ pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptEr
let package = pckg::concat(parent, PACKAGE);
commands.set(hostname::create(&package))?;
commands.set(http_client::create(&package))?;
Ok(())
}

View file

@ -139,6 +139,7 @@ impl Command for OnErrorCommand {
pub(crate) enum CommandValidation {
None,
Ignore,
Match(String, String),
Contains(String, String),
Any(String, Vec<String>),
@ -236,6 +237,7 @@ pub(crate) fn run_script_and_validate(
&values
)
}
CommandValidation::Ignore => (),
};
context

View file

@ -1,5 +1,5 @@
use duckscript::types::error::{ErrorInfo, ScriptError};
use std::fs::{create_dir_all, File};
use std::fs::{create_dir_all, remove_file, File};
use std::io::{Read, Write};
use std::path::Path;
@ -150,3 +150,21 @@ pub(crate) fn create_empty_file(file: &str) -> Result<(), String> {
}
}
}
pub(crate) fn delete_file(file: &str) -> Result<(), String> {
let file_path = Path::new(file);
if file_path.exists() {
match remove_file(file) {
Ok(_) => Ok(()),
Err(error) => Err(format!(
"Unable to delete file: {} error: {}",
file,
error.to_string()
)
.to_string()),
}
} else {
Ok(())
}
}

View file

@ -0,0 +1,30 @@
function test_get
response = http_client https://www.rust-lang.org/
found = contains ${response} Rust
assert ${found}
end
function test_get_to_file
file = set ./target/_duckscript_test/http_client/page.html
rm ${file}
response_size = http_client -O ${file} https://www.rust-lang.org/
response = readfile ${file}
found = contains ${response} Rust
assert ${found}
assert ${response_size}
end
function test_post
payload = set {\"login\":\"login\",\"password\":\"password\"}
response = http_client --method=HTTP-POST --post-data=${payload} https://reqbin.com/echo/post/json
found = contains ${response} success
assert ${found}
end