Browse Source

Restructure, document, and license the crate

dev-2.0
Jens Pitkänen 4 years ago
parent
commit
5d97db764a
9 changed files with 225 additions and 116 deletions
  1. +15
    -0
      COPYING.md
  2. +1
    -0
      Cargo.toml
  3. +11
    -0
      README.md
  4. +9
    -18
      src/connection.rs
  5. +58
    -20
      src/http.rs
  6. +77
    -1
      src/lib.rs
  7. +21
    -66
      src/requests.rs
  8. +22
    -11
      src/tests/mod.rs
  9. +11
    -0
      src/tests/setup.rs

+ 15
- 0
COPYING.md View File

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2018, Jens Pitkanen
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

+ 1
- 0
Cargo.toml View File

@ -2,6 +2,7 @@
name = "minreq"
version = "0.1.0"
authors = ["Jens Pitkanen <jens@neon.moe>"]
license = "ISC"
[dependencies]


+ 11
- 0
README.md View File

@ -0,0 +1,11 @@
# minreq
[![Crates.io](https://img.shields.io/crates/d/minreq.svg)](https://crates.io/crates/minreq)
[![Documentation](https://docs.rs/minreq/badge.svg)](https://docs.rs/minreq)
[![CI](https://img.shields.io/travis/neonmoe/minreq.svg)](https://travis-ci.org/neonmoe/minreq)
Simple, minimal-dependency HTTP client.
## [Documentation](https://docs.rs/minreq)
## License
This crate is distributed under the terms of the [ISC license](COPYING.md).

+ 9
- 18
src/connection.rs View File

@ -17,27 +17,18 @@ impl Connection {
/// [`Request`](struct.Request.html) for specifics about *what* is
/// being sent.
pub(crate) fn new(request: Request) -> Connection {
let timeout = env::var("MINREQ_TIMEOUT")
.unwrap_or("5".to_string())
.parse::<u64>()
.unwrap_or(5);
let timeout;
if let Some(t) = request.timeout {
timeout = t;
} else {
timeout = env::var("MINREQ_TIMEOUT")
.unwrap_or("5".to_string()) // Not defined -> 5
.parse::<u64>()
.unwrap_or(5); // NaN -> 5
}
Connection { request, timeout }
}
/// Sets how long it takes to timeout (in seconds) for this
/// connection. Usage:
/// ```no_run
/// use minreq::Method;
///
/// minreq::create_connection(Method::Get, "https://httpbin.org/ip", None)
/// .with_timeout(10)
/// .send();
/// ```
pub fn with_timeout(mut self, timeout: u64) -> Connection {
self.timeout = timeout;
self
}
/// Sends the [`Request`](struct.Request.html), consumes this
/// connection, and returns a [`Response`](struct.Response.html).
pub fn send(self) -> Result<Response, Error> {


+ 58
- 20
src/http.rs View File

@ -1,21 +1,35 @@
use std::collections::HashMap;
use std::fmt;
use std::str::Lines;
use std::io::Error;
use connection::Connection;
/// A URL type for requests.
pub type URL = String;
/// An HTTP request method.
pub enum Method {
/// The GET method
Get,
/// The HEAD method
Head,
/// The POST method
Post,
/// The PUT method
Put,
/// The DELETE method
Delete,
/// The CONNECT method
Connect,
/// The OPTIONS method
Options,
/// The TRACE method
Trace,
/// The PATCH method
Patch,
/// A custom method, use with care: the string will be embedded in
/// your request as-is.
Custom(String),
}
impl fmt::Display for Method {
@ -32,44 +46,65 @@ impl fmt::Display for Method {
&Method::Options => write!(f, "OPTIONS"),
&Method::Trace => write!(f, "TRACE"),
&Method::Patch => write!(f, "PATCH"),
&Method::Custom(ref s) => write!(f, "{}", s),
}
}
}
/// An HTTP request.
pub struct Request {
/// The HTTP request method.
pub method: Method,
/// The HTTP request's "Host" field.
pub host: URL,
/// The requested resource.
pub resource: URL,
/// The additional headers.
pub headers: HashMap<String, String>,
/// The optional body of the request.
pub body: Option<String>,
method: Method,
pub(crate) host: URL,
resource: URL,
headers: HashMap<String, String>,
body: Option<String>,
pub(crate) timeout: Option<u64>,
}
impl Request {
/// Creates a new HTTP `Request`.
///
/// This is only the request's data, it is not sent here. For
/// sending the request, see [`get`](fn.get.html).
pub fn new<T: Into<URL>>(method: Method, url: T, body: Option<String>) -> Request {
/// This is only the request's data, it is not sent yet. For
/// sending the request, see [`send`](struct.Request.html#method.send).
pub fn new<T: Into<URL>>(method: Method, url: T) -> Request {
let (host, resource) = parse_url(url.into());
let mut headers = HashMap::new();
if let Some(ref body) = body {
headers.insert("Content-Length".to_string(), format!("{}", body.len()));
}
Request {
method,
host,
resource,
headers,
body,
headers: HashMap::new(),
body: None,
timeout: None,
}
}
/// Adds a header to the request this is called on. Use this
/// function to add headers to your requests.
pub fn with_header<T: Into<String>, U: Into<String>>(mut self, key: T, value: U) -> Request {
self.headers.insert(key.into(), value.into());
self
}
/// Sets the request body.
pub fn with_body<T: Into<String>>(mut self, body: T) -> Request {
let body = body.into();
let body_length = body.len();
self.body = Some(body);
self.with_header("Content-Length", format!("{}", body_length))
}
/// Sets the request timeout.
pub fn with_timeout(mut self, timeout: u64) -> Request {
self.timeout = Some(timeout);
self
}
/// Sends this request to the host.
pub fn send(self) -> Result<Response, Error> {
let connection = Connection::new(self);
connection.send()
}
/// Returns the HTTP request as a `String`, ready to be sent to
/// the server.
pub(crate) fn into_string(self) -> String {
@ -129,10 +164,13 @@ fn parse_url(url: URL) -> (URL, URL) {
} else if slashes == 2 {
first.push(c);
}
if slashes == 3 {
if slashes >= 3 {
second.push(c);
}
}
if !first.contains(":") {
first += ":80";
}
(first, second)
}


+ 77
- 1
src/lib.rs View File

@ -1,3 +1,80 @@
//! # Minreq
//! Simple, minimal-dependency HTTP client.
//! The library has a very minimal API, so you'll probably know
//! everything you need to after reading a few examples.
//!
//! # Examples
//!
//! ## Get
//! ```no_run
//! // This is a simple example of sending a GET request and
//! // printing out the response.
//! if let Ok(response) = minreq::get("http://httpbin.org/ip").send() {
//! println!("{}", response.body);
//! }
//! ```
//!
//! ## Body
//! ```no_run
//! // To include a body, add .with_body("") before .send().
//! if let Ok(response) = minreq::post("http://httpbin.org/post")
//! .with_body("Pong!")
//! .send()
//! {
//! println!("{}", response.body);
//! }
//! ```
//!
//! ## Headers
//! ```no_run
//! // To add a header, add .with_header("Key", "Value") before .send().
//! if let Ok(response) = minreq::get("http://httpbin.org/headers")
//! .with_header("Accept", "text/plain")
//! .with_header("Something", "Interesting")
//! .send()
//! {
//! println!("{}", response.body);
//! }
//! ```
//!
//! ## Timeouts
//! ```no_run
//! // To avoid timing out, or limit the request's response time even more,
//! // use .with_timeout(n) before .send(). The given value is in seconds.
//! // NOTE: The default timeout is 5 seconds.
//! if let Ok(response) = minreq::post("http://httpbin.org/delay/6")
//! .with_timeout(10)
//! .send()
//! {
//! println!("{}", response.body);
//! }
//! ```
//!
//! # Timeouts
//! The timeout of the created request is 5 seconds by default. You
//! can change this in two ways:
//! - Use this function (`create_request`) and call
//! [`with_timeout`](struct.Request.html#method.with_timeout)
//! on it to set the timeout per-request like so:
//! ```
//! minreq::get("/").with_timeout(8).send();
//! ```
//! - Set the environment variable `MINREQ_TIMEOUT` to the desired
//! amount of seconds until timeout. Ie. if you have a program called
//! `foo` that uses minreq, and you want all the requests made by that
//! program to timeout in 8 seconds, you launch the program like so:
//! ```text,ignore
//! $ MINREQ_TIMEOUT=8 ./foo
//! ```
//! Or add the following somewhere before the requests in the code.
//! ```
//! use std::env;
//!
//! env::set_var("MINREQ_TIMEOUT", "8");
//! ```
#![deny(missing_docs)]
#[cfg(test)]
extern crate tiny_http;
@ -9,4 +86,3 @@ mod tests;
pub use requests::*;
pub use http::*;
pub use connection::*;

+ 21
- 66
src/requests.rs View File

@ -1,6 +1,4 @@
use std::io::Error;
use http::{Method, Request, Response, URL};
use connection::Connection;
use http::{Method, Request, URL};
/// Sends a request to `url` with `method`, returns the response or
/// an [`Error`](https://doc.rust-lang.org/std/io/struct.Error.html).
@ -13,103 +11,60 @@ use connection::Connection;
/// [`patch`](fn.patch.html). They omit the `method` parameter, since
/// it is implied in the name, and the body is as optional as it is on
/// [Wikipedia](https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Summary_table).
///
/// The timeout of the created request is 5 seconds by default. You
/// can change this in two ways:
/// - Use this function (`create_connection`) and call
/// [`with_timeout`](struct.Connection.html#method.with_timeout)
/// on it to set the timeout per-request.
/// - Set the environment variable `MINREQ_TIMEOUT` to the desired
/// amount of seconds until timeout. Ie. if you have a program called
/// `foo` that uses minreq, and you want all the requests made by that
/// program to timeout in 8 seconds, you launch the program like so:
/// ```text,ignore
/// $ MINREQ_TIMEOUT=8 ./foo
/// ```
/// Or add the following somewhere before the requests in the code.
/// ```
/// use std::env;
///
/// env::set_var("MINREQ_TIMEOUT", "8");
/// ```
///
/// # Examples
///
/// ### Using `minreq::send`
///
/// ```no_run
/// use minreq::Method;
///
/// // This application prints out your public IP. (Or an error.)
/// match minreq::create_connection(Method::Get, "https://httpbin.org/ip", None).send() {
/// Ok(response) => println!("Your public IP: {}", response.body),
/// Err(err) => println!("[ERROR]: {}", err),
/// }
/// ```
///
/// ### Using the aliases ie. how you'll actually probably use this crate
///
/// ```no_run
/// // This is the same as above, except less elaborate.
/// if let Ok(response) = minreq::get("https://httpbin.org/ip", None) {
/// println!("Your public IP: {}", response.body);
/// }
/// ```
pub fn create_connection<T: Into<URL>>(method: Method, url: T, body: Option<String>) -> Connection {
let request = Request::new(method, url.into(), body);
Connection::new(request)
pub fn create_request<T: Into<URL>>(method: Method, url: T) -> Request {
Request::new(method, url.into())
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Get](enum.Method.html).
pub fn get<T: Into<URL>>(url: T, body: Option<String>) -> Result<Response, Error> {
create_connection(Method::Get, url, body).send()
pub fn get<T: Into<URL>>(url: T) -> Request {
create_request(Method::Get, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Head](enum.Method.html).
pub fn head<T: Into<URL>>(url: T) -> Result<Response, Error> {
create_connection(Method::Head, url, None).send()
pub fn head<T: Into<URL>>(url: T) -> Request {
create_request(Method::Head, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Post](enum.Method.html).
pub fn post<T: Into<URL>>(url: T, body: String) -> Result<Response, Error> {
create_connection(Method::Post, url, Some(body)).send()
pub fn post<T: Into<URL>>(url: T) -> Request {
create_request(Method::Post, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Put](enum.Method.html).
pub fn put<T: Into<URL>>(url: T, body: String) -> Result<Response, Error> {
create_connection(Method::Put, url, Some(body)).send()
pub fn put<T: Into<URL>>(url: T) -> Request {
create_request(Method::Put, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Delete](enum.Method.html).
pub fn delete<T: Into<URL>>(url: T) -> Result<Response, Error> {
create_connection(Method::Delete, url, None).send()
pub fn delete<T: Into<URL>>(url: T) -> Request {
create_request(Method::Delete, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Connect](enum.Method.html).
pub fn connect<T: Into<URL>>(url: T, body: String) -> Result<Response, Error> {
create_connection(Method::Connect, url, Some(body)).send()
pub fn connect<T: Into<URL>>(url: T) -> Request {
create_request(Method::Connect, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Options](enum.Method.html).
pub fn options<T: Into<URL>>(url: T, body: Option<String>) -> Result<Response, Error> {
create_connection(Method::Options, url, body).send()
pub fn options<T: Into<URL>>(url: T) -> Request {
create_request(Method::Options, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Trace](enum.Method.html).
pub fn trace<T: Into<URL>>(url: T) -> Result<Response, Error> {
create_connection(Method::Trace, url, None).send()
pub fn trace<T: Into<URL>>(url: T) -> Request {
create_request(Method::Trace, url)
}
/// Alias for [send](fn.send.html) with `method` set to
/// [Method::Patch](enum.Method.html).
pub fn patch<T: Into<URL>>(url: T, body: String) -> Result<Response, Error> {
create_connection(Method::Patch, url, Some(body)).send()
pub fn patch<T: Into<URL>>(url: T) -> Request {
create_request(Method::Patch, url)
}

+ 22
- 11
src/tests/mod.rs View File

@ -1,76 +1,87 @@
mod setup;
use requests;
use http::Method;
use self::setup::*;
#[test]
fn test_latency() {
setup();
let body = get_body(
requests::create_connection(Method::Get, url("/slow_a"), Some("Q".to_string()))
requests::get(url("/slow_a"))
.with_body("Q".to_string())
.with_timeout(1)
.send(),
);
assert_ne!(body, "j: Q");
}
#[test]
fn test_headers() {
setup();
let body = get_body(
requests::get(url("/header_pong"))
.with_header("Ping", "Qwerty")
.send(),
);
assert_eq!("Qwerty", body);
}
#[test]
fn test_get() {
setup();
let body = get_body(requests::get(url("/a"), Some("Q".to_string())));
let body = get_body(requests::get(url("/a")).with_body("Q").send());
assert_eq!(body, "j: Q");
}
#[test]
fn test_head() {
setup();
assert_eq!(get_status_code(requests::head(url("/b"))), 420);
assert_eq!(get_status_code(requests::head(url("/b")).send()), 420);
}
#[test]
fn test_post() {
setup();
let body = get_body(requests::post(url("/c"), "E".to_string()));
let body = get_body(requests::post(url("/c")).with_body("E").send());
assert_eq!(body, "l: E");
}
#[test]
fn test_put() {
setup();
let body = get_body(requests::put(url("/d"), "R".to_string()));
let body = get_body(requests::put(url("/d")).with_body("R").send());
assert_eq!(body, "m: R");
}
#[test]
fn test_delete() {
setup();
assert_eq!(get_body(requests::delete(url("/e"))), "n:");
assert_eq!(get_body(requests::delete(url("/e")).send()), "n:");
}
#[test]
fn test_trace() {
setup();
assert_eq!(get_body(requests::trace(url("/f"))), "o:");
assert_eq!(get_body(requests::trace(url("/f")).send()), "o:");
}
#[test]
fn test_options() {
setup();
let body = get_body(requests::options(url("/g"), Some("U".to_string())));
let body = get_body(requests::options(url("/g")).with_body("U").send());
assert_eq!(body, "p: U");
}
#[test]
fn test_connect() {
setup();
let body = get_body(requests::connect(url("/h"), "I".to_string()));
let body = get_body(requests::connect(url("/h")).with_body("I").send());
assert_eq!(body, "q: I");
}
#[test]
fn test_patch() {
setup();
let body = get_body(requests::patch(url("/i"), "O".to_string()));
let body = get_body(requests::patch(url("/i")).with_body("O").send());
assert_eq!(body, "r: O");
}

+ 11
- 0
src/tests/setup.rs View File

@ -18,9 +18,20 @@ pub(crate) fn setup() {
let mut request = server.recv().unwrap();
let mut content = String::new();
request.as_reader().read_to_string(&mut content).ok();
let headers = Vec::from(request.headers());
let url = String::from(request.url());
match request.method() {
&Method::Get if url == "/header_pong" => {
for header in headers {
if header.field.as_str() == "Ping" {
let response = Response::from_string(format!("{}", header.value));
request.respond(response).ok();
return;
}
}
request.respond(Response::from_string("No header!")).ok();
}
&Method::Get if url == "/slow_a" => {
thread::sleep(Duration::from_secs(2));
let response = Response::from_string(format!("j: {}", content));


Loading…
Cancel
Save