use hyper::StatusCode;
use hyper::header::ByteRangeSpec;
use hyper::header::ByteRangeSpec::FromTo;
use hyper::header::Range::Bytes;
use hyper::header::{ContentLength, ContentRange, ContentRangeSpec};
use reqwest;
use reqwest::Client;
use reqwest::Error;
use reqwest::header;
use reader::Reader;
use buffer::Buffer;
use std::io::SeekFrom;
use std::cmp;
use std::time::Instant;
fn get_head(filename: &String) -> Result<reqwest::Response, Error> {
if filename.contains("s3.amazonaws.com") {
let range = vec![FromTo(0, 0)];
let mut headers = header::Headers::new();
headers.set(Bytes(range));
let client = reqwest::Client::builder()
.default_headers(headers)
.build()
.unwrap();
client.get(filename).send()
} else {
let client = Client::new();
client.head(filename).send()
}
}
#[derive(Debug)]
struct ResponseData {
body_data: Vec<u8>,
file_size: u64
}
fn get_data(filename: &String, range: Vec<ByteRangeSpec>) -> Result<ResponseData, String> {
let mut headers = header::Headers::new();
headers.set(Bytes(range));
let client = reqwest::Client::builder()
.default_headers(headers)
.build()
.unwrap();
let mut response = client.get(filename).send().unwrap();
let status = response.status();
if !(status == StatusCode::Ok || status == StatusCode::PartialContent) {
println!("ERROR {:?}", response);
return Err("bad response status".to_string());
}
let mut body: Vec<u8> = vec![];
response.copy_to(&mut body).unwrap();
let file_size =
match get_content_range(&response) {
Ok(length) => length.unwrap(),
Err(_msg) => return Err("bad response header".to_string()),
};
Ok(ResponseData{
body_data: body,
file_size: file_size
})
}
fn get_content_length(response: &reqwest::Response) -> Option<u64> {
match response.headers().get::<ContentRange>() {
Some(&ContentRange(ContentRangeSpec::Bytes{range: _range, instance_length})) => {
instance_length
},
_ => {
match response.headers().get::<ContentLength>() {
Some(length) => Some(**length as u64),
None => None
}
},
}
}
fn get_content_range(response: &reqwest::Response) -> Result<Option<u64>, String> {
match response.headers().get::<ContentRange>() {
Some(content_range) => {
match content_range.clone() {
ContentRange(ContentRangeSpec::Bytes{
range: _range,
instance_length: length
}) => {
Ok(length)
},
ContentRange(ContentRangeSpec::Unregistered{unit: _unit, resp: _resp}) => {
Err("Unregistered, actually unsupported".to_string())
}
}
},
None =>
Err("Missing content_range".to_string()),
}
}
#[derive(Debug)]
pub struct HttpReader {
pub filename: String,
pub file_size: Option<u64>,
pub position: u64,
pub buffer: Buffer
}
pub fn exists(filename: &String) -> bool {
match get_head(filename) {
Ok(resp) => {
resp.status().is_success()
},
Err(_msg) => {
false
}
}
}
fn get_data_range(position: u64, size: usize, max_end_position: Option<u64>) -> Vec<ByteRangeSpec> {
let start = position;
let end =
match (position, size) {
(0, 0) => 0,
(_, _) => {
match max_end_position {
Some(max) => {
let max_size = max - position;
position + cmp::min((size - 1) as u64, max_size)
},
None => position + (size - 1) as u64,
}
},
};
vec![FromTo(start, end)]
}
fn load_data(reader: &mut HttpReader, size: usize) -> Result<Option<Vec<u8>>, String> {
let start = Instant::now();
info!("make HTTP request with request {:?} bytes", size);
let position =
match reader.buffer.size {
Some(_) => reader.buffer.position,
None => reader.position,
};
match reader.file_size {
Some(total_file_size) => {
if position >= total_file_size {
info!("request range out of range: {} > {}", position, total_file_size);
return Ok(None)
}
},
None => (),
};
let range = get_data_range(position, size, reader.buffer.max_end_position);
let response = get_data(&reader.filename, range).map_err(|e| e.to_string()).unwrap();
let elapsed = start.elapsed();
if elapsed.as_secs() > 0 {
println!("WARNING: Request duration {} seconds", elapsed.as_secs());
}
let new_position = position + response.body_data.len() as u64;
match reader.buffer.size {
Some(_) => {
reader.buffer.position = new_position;
},
None => {
reader.position = new_position;
}
};
Ok(Some(response.body_data))
}
impl Reader for HttpReader {
fn open(filename: &String) -> HttpReader {
match get_head(filename) {
Err(msg) => panic!(msg.to_string()),
Ok(response) => {
let content_length =
match get_content_range(&response) {
Ok(length) => length,
_ => get_content_length(&response),
};
HttpReader {
filename: filename.to_string(),
file_size: content_length,
position: 0,
buffer: Buffer {
size: None,
position: 0,
max_end_position: None,
buffer: vec![]
}
}
}
}
}
fn get_position(&mut self) -> Result<u64, String> {
Ok(self.position)
}
fn get_cache_size(&self) -> Option<usize> {
self.buffer.size
}
fn set_cache_size(&mut self, cache_size: Option<usize>) {
self.buffer.size = cache_size;
}
fn get_max_end_position(&self) -> Option<u64> {
self.buffer.max_end_position
}
fn set_max_end_position(&mut self, max_end_position: Option<u64>) {
self.buffer.max_end_position = max_end_position;
}
fn get_size(&mut self) -> Result<u64, String> {
match self.file_size {
Some(length) => Ok(length),
None => Err("No length detected".to_string()),
}
}
fn read(&mut self, size: usize) -> Result<Vec<u8>, String> {
if self.buffer.get_cached_size() >= size {
self.position += size as u64;
Ok(self.buffer.get_data(size))
} else {
match self.buffer.size {
Some(buffer_size) => {
match load_data(self, buffer_size) {
Err(msg) => Err(msg),
Ok(some_data) => {
match some_data {
Some(data) => {
self.buffer.append_data(&data.to_vec());
self.position += size as u64;
Ok(self.buffer.get_data(size))
},
None => Ok(Vec::new()),
}
}
}
},
None => {
match load_data(self, size) {
Err(msg) => Err(msg),
Ok(some_data) => {
match some_data {
Some(data) => {
match data.len() >= size {
true => {
Ok(data)
},
false => Ok(Vec::new()),
}
},
None => Ok(Vec::new()),
}
}
}
}
}
}
}
fn seek(&mut self, seek: SeekFrom) -> Result<u64, String> {
match seek {
SeekFrom::Current(offset) => {
self.position = self.position + offset as u64;
if self.buffer.size.is_some() {
if offset > 0 && self.buffer.get_cached_size() > offset as usize {
let _skiped_data = self.buffer.get_data(offset as usize);
} else {
self.buffer.reset();
}
}
},
SeekFrom::Start(offset) => {
self.buffer.reset();
self.position = offset;
if self.buffer.size.is_some() {
self.buffer.position = self.position;
}
},
SeekFrom::End(offset) => {
self.buffer.reset();
match self.file_size {
Some(size) => {
self.position = size - offset as u64;
if self.buffer.size.is_some() {
self.buffer.position = self.position;
}
},
None => return Err("Missing file size".to_string())
}
}
}
Ok(self.position)
}
}