#![doc(html_root_url="https://ghmlee.github.io/rust-request/doc")]
extern crate openssl;
mod url;
pub mod response;
use std::io::{self, Write, Read, Result, ErrorKind};
use std::error::Error;
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::net::TcpStream;
use url::{Protocol, Url};
use response::Response;
use openssl::ssl::{SslStream, SslContext};
use openssl::ssl::SslMethod::Sslv23;
pub fn post(url: &str,
headers: &mut HashMap<String, String>,
body: &[u8]) -> Result<Response> {
return connect("POST", &try!(Url::new(url)), headers, body);
}
pub fn get(url: &str,
headers: &mut HashMap<String, String>) -> Result<Response> {
return connect("GET", &try!(Url::new(url)), headers, "".as_bytes());
}
pub fn put(url: &str,
headers: &mut HashMap<String, String>,
body: &[u8]) -> Result<Response> {
return connect("PUT", &try!(Url::new(url)), headers, body);
}
pub fn delete(url: &str,
headers: &mut HashMap<String, String>) -> Result<Response> {
return connect("DELETE", &try!(Url::new(url)), headers, "".as_bytes());
}
pub fn options(url: &str,
headers: &mut HashMap<String, String>) -> Result<Response> {
return connect("OPTIONS", &try!(Url::new(url)), headers, "".as_bytes());
}
pub fn head(url: &str,
headers: &mut HashMap<String, String>) -> Result<Response> {
return connect("HEAD", &try!(Url::new(url)), headers, "".as_bytes());
}
fn connect(method: &str,
url: &Url,
headers: &mut HashMap<String, String>,
body: &[u8]) -> Result<Response> {
let addr = format!("{}:{}", url.host, url.port);
let host = match url.protocol {
Protocol::HTTP => {
match url.port {
80 => url.host.clone(),
_ => format!("{}:{}", url.host, url.port)
}
}
Protocol::HTTPS => {
match url.port {
443 => url.host.clone(),
_ => format!("{}:{}", url.host, url.port)
}
}
};
match headers.entry("Content-Length".to_string()) {
Occupied(entry) => { entry.remove(); }
Vacant(_) => { }
}
headers.insert("Content-Length".to_string(), format!("{}", body.len()).to_string());
let mut http_headers = String::new();
for header in headers.iter() {
let key = header.0;
let value = header.1;
http_headers.push_str(&format!("\r\n{}: {}", key, value));
}
http_headers.push_str("\r\n\r\n");
let http_content = format!("{} {} HTTP/1.1\r\nHost: {}{}",
method,
url.path,
host,
http_headers);
let mut buf: Vec<u8> = Vec::new();
for x in http_content.as_bytes() { buf.push(*x); }
for x in body { buf.push(*x); }
let mut stream = match TcpStream::connect(&*addr) {
Ok(stream) => stream,
Err(e) => {
let err = io::Error::new(ErrorKind::NotConnected,
e.description());
return Err(err);
}
};
let raw = match url.protocol {
Protocol::HTTP => {
let _ = stream.write(&*buf);
let raw = try!(read(&mut stream));
raw
}
Protocol::HTTPS => {
let context = match SslContext::new(Sslv23) {
Ok(context) => context,
Err(e) => {
let err = io::Error::new(ErrorKind::NotConnected,
e.description());
return Err(err);
}
};
let mut ssl_stream = match SslStream::new(&context, stream) {
Ok(stream) => stream,
Err(e) => {
let err = io::Error::new(ErrorKind::NotConnected,
e.description());
return Err(err);
}
};
let _ = ssl_stream.write(&*buf);
let raw = try!(read(&mut ssl_stream));
raw
}
};
let response = try!(get_response(&raw));
if response.status_code / 100 == 3 {
let location = match response.headers.get("Location") {
Some(location) => location,
None => {
let err = io::Error::new(ErrorKind::NotConnected,
"Server returns an invalid response.");
return Err(err);
}
};
return connect(method, &try!(Url::new(&location)), headers, body);
}
return Ok(response);
}
fn read<S: Read>(stream: &mut S) -> Result<String> {
const BUFFER_SIZE: usize = 1024;
let mut buffer: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
let mut raw = String::new();
loop {
let len = match stream.read(&mut buffer) {
Ok(size) => size,
Err(e) => {
let err = io::Error::new(ErrorKind::NotConnected,
e.description());
return Err(err);
}
};
if len <= 0 { break; }
match std::str::from_utf8(&buffer[0 .. len]) {
Ok(buf) => raw.push_str(buf),
Err(e) => {
let err = io::Error::new(ErrorKind::NotConnected,
e.description());
return Err(err);
}
}
}
return Ok(raw);
}
fn get_response(raw: &str) -> Result<Response> {
let http_response: Vec<&str> = raw.split("\r\n\r\n").collect();
if http_response.len() < 2 {
let err = io::Error::new(ErrorKind::InvalidInput,
"Server returns an invalid response.");
return Err(err);
}
let http_header = http_response[0];
let http_body = http_response[1];
let chunked_content_body: Vec<&str> = http_body.split("\r\n").collect();
let mut content_body = String::new();
if chunked_content_body.len() == 1 {
content_body.push_str(http_body);
} else {
let mut index: i64 = 0;
for chunk in chunked_content_body.iter() {
index = index + 1;
if index % 2 != 0 { continue; }
content_body.push_str(chunk);
}
}
let response = Response::new(http_header, &content_body);
return Ok(response);
}
#[test]
fn it_works() {
}