mirror of
https://github.com/minio/minio-rs.git
synced 2025-12-06 23:36:52 +08:00
Implement get_bucket_location API
- with unsigned body
This commit is contained in:
parent
393e1645b4
commit
eb35be48d3
@ -8,3 +8,7 @@ edition = "2018"
|
||||
hyper = "0.12.28"
|
||||
ring = "0.14.6"
|
||||
time = "0.1.42"
|
||||
http = "0.1.17"
|
||||
hyper-tls = "0.3.2"
|
||||
futures = "0.1.27"
|
||||
bytes = "0.4.12"
|
||||
|
||||
24
src/main.rs
24
src/main.rs
@ -1,6 +1,24 @@
|
||||
mod minio;
|
||||
|
||||
fn main() {
|
||||
// let r = minio::Settings::new("localhost:9000", "minio", "minio123");
|
||||
// println!("{:?}", r)
|
||||
use futures::future::Future;
|
||||
use hyper::rt;
|
||||
|
||||
fn get_local_default_server() -> minio::Client {
|
||||
match minio::Client::new("http://localhost:9000") {
|
||||
Ok(mut c) => {
|
||||
c.set_credentials(minio::Credentials::new("minio", "minio123"));
|
||||
c
|
||||
}
|
||||
Err(_) => panic!("could not make local client"),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
rt::run(rt::lazy(|| {
|
||||
// let c = get_local_default_server();
|
||||
let c = minio::Client::get_play_client();
|
||||
c.get_bucket_location("txp")
|
||||
.map(|res| println!("{}", res))
|
||||
.map_err(|err| println!("{:?}", err))
|
||||
}));
|
||||
}
|
||||
|
||||
155
src/minio.rs
155
src/minio.rs
@ -1,8 +1,18 @@
|
||||
mod api;
|
||||
mod sign;
|
||||
|
||||
use hyper::{body::Body, header::HeaderMap, Method, Uri};
|
||||
use bytes::Bytes;
|
||||
use futures::future::{self, Future};
|
||||
use futures::stream::Stream;
|
||||
use http;
|
||||
use hyper::header::{HeaderName, HeaderValue};
|
||||
use hyper::{body::Body, client, header, header::HeaderMap, Method, Uri};
|
||||
use hyper_tls::HttpsConnector;
|
||||
use std::collections::HashMap;
|
||||
use std::{env, string::String};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use std::{string, string::String};
|
||||
use time;
|
||||
use time::Tm;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -36,12 +46,40 @@ pub type Region = String;
|
||||
pub enum Err {
|
||||
InvalidUrl(String),
|
||||
InvalidEnv(String),
|
||||
HttpErr(http::Error),
|
||||
HyperErr(hyper::Error),
|
||||
FailStatusCodeErr(hyper::StatusCode, Bytes),
|
||||
Utf8DecodingErr(string::FromUtf8Error),
|
||||
}
|
||||
|
||||
trait ApiClient {
|
||||
fn make_req(&self, req: http::Request<Body>) -> client::ResponseFuture;
|
||||
}
|
||||
|
||||
struct HttpApiClient {
|
||||
c: client::Client<client::HttpConnector, Body>,
|
||||
}
|
||||
|
||||
impl ApiClient for HttpApiClient {
|
||||
fn make_req(&self, req: http::Request<Body>) -> client::ResponseFuture {
|
||||
self.c.request(req)
|
||||
}
|
||||
}
|
||||
|
||||
struct HttpsApiClient {
|
||||
c: client::Client<HttpsConnector<client::HttpConnector>, Body>,
|
||||
}
|
||||
|
||||
impl ApiClient for HttpsApiClient {
|
||||
fn make_req(&self, req: http::Request<Body>) -> client::ResponseFuture {
|
||||
self.c.request(req)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
server: Uri,
|
||||
region: Region,
|
||||
conn_client: Arc<Box<dyn ApiClient + Send + Sync>>,
|
||||
pub credentials: Option<Credentials>,
|
||||
}
|
||||
|
||||
@ -56,8 +94,18 @@ impl Client {
|
||||
Err(Err::InvalidUrl("invalid scheme!".to_string()))
|
||||
} else {
|
||||
Ok(Client {
|
||||
server: s,
|
||||
server: s.clone(),
|
||||
region: String::from(""),
|
||||
conn_client: if s.scheme_str() == Some("http") {
|
||||
Arc::new(Box::new(HttpApiClient {
|
||||
c: client::Client::new(),
|
||||
}))
|
||||
} else {
|
||||
let https = HttpsConnector::new(4).unwrap();
|
||||
Arc::new(Box::new(HttpsApiClient {
|
||||
c: client::Client::builder().build::<_, hyper::Body>(https),
|
||||
}))
|
||||
},
|
||||
credentials: None,
|
||||
})
|
||||
}
|
||||
@ -74,16 +122,90 @@ impl Client {
|
||||
self.region = r;
|
||||
}
|
||||
|
||||
fn add_host_header(&self, h: &mut HeaderMap) {
|
||||
let host_val = match self.server.port_part() {
|
||||
Some(port) => format!("{}:{}", self.server.host().unwrap_or(""), port),
|
||||
None => self.server.host().unwrap_or("").to_string(),
|
||||
};
|
||||
match header::HeaderValue::from_str(&host_val) {
|
||||
Ok(v) => {
|
||||
h.insert(header::HOST, v);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_play_client() -> Client {
|
||||
Client {
|
||||
server: "https://play.min.io:9000".parse::<Uri>().unwrap(),
|
||||
region: String::from(""),
|
||||
conn_client: {
|
||||
let https = HttpsConnector::new(4).unwrap();
|
||||
Arc::new(Box::new(HttpsApiClient {
|
||||
c: client::Client::builder().build::<_, hyper::Body>(https),
|
||||
}))
|
||||
},
|
||||
credentials: Some(Credentials::new(
|
||||
"Q3AM3UQ867SPQQA43P2F",
|
||||
"zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bucket_location(&self, b: &str) -> impl Future<Item = String, Error = Err> {
|
||||
let mut qp = HashMap::new();
|
||||
qp.insert("location".to_string(), None);
|
||||
let mut hmap = HeaderMap::new();
|
||||
|
||||
self.add_host_header(&mut hmap);
|
||||
let body_hash_hdr = (
|
||||
HeaderName::from_static("x-amz-content-sha256"),
|
||||
HeaderValue::from_static("UNSIGNED-PAYLOAD"),
|
||||
);
|
||||
hmap.insert(body_hash_hdr.0.clone(), body_hash_hdr.1.clone());
|
||||
let s3_req = S3Req {
|
||||
method: Method::GET,
|
||||
bucket: Some(b.to_string()),
|
||||
object: None,
|
||||
headers: hmap,
|
||||
query: qp,
|
||||
body: Body::empty(),
|
||||
ts: time::now_utc(),
|
||||
};
|
||||
|
||||
let sign_hdrs = sign::sign_v4(&s3_req, &self);
|
||||
println!("signout: {:?}", sign_hdrs);
|
||||
let req_result = api::mk_request(&s3_req, &self, &sign_hdrs);
|
||||
let conn_client = Arc::clone(&self.conn_client);
|
||||
future::result(req_result)
|
||||
.map_err(|e| Err::HttpErr(e))
|
||||
.and_then(move |req| {
|
||||
conn_client
|
||||
.make_req(req)
|
||||
.map_err(|e| Err::HyperErr(e))
|
||||
.and_then(|resp| {
|
||||
let st = resp.status();
|
||||
resp.into_body()
|
||||
.concat2()
|
||||
.map_err(|err| Err::HyperErr(err))
|
||||
.map(move |chunk| (st, chunk.into_bytes()))
|
||||
})
|
||||
.and_then(|(code, body)| {
|
||||
if code.is_success() {
|
||||
b2s(body)
|
||||
} else {
|
||||
Err(Err::FailStatusCodeErr(code, body))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn b2s(b: Bytes) -> Result<String, Err> {
|
||||
match String::from_utf8(b.iter().map(|x| x.clone()).collect::<Vec<u8>>()) {
|
||||
Err(e) => Err(Err::Utf8DecodingErr(e)),
|
||||
Ok(s) => Ok(s),
|
||||
}
|
||||
}
|
||||
|
||||
pub struct S3Req {
|
||||
@ -95,3 +217,28 @@ pub struct S3Req {
|
||||
body: Body,
|
||||
ts: Tm,
|
||||
}
|
||||
|
||||
impl S3Req {
|
||||
fn mk_path(&self) -> String {
|
||||
let mut res: String = String::from("/");
|
||||
if let Some(s) = &self.bucket {
|
||||
res.push_str(&s);
|
||||
res.push_str("/");
|
||||
if let Some(o) = &self.object {
|
||||
res.push_str(&o);
|
||||
}
|
||||
};
|
||||
res
|
||||
}
|
||||
|
||||
fn mk_query(&self) -> String {
|
||||
self.query
|
||||
.iter()
|
||||
.map(|(x, y)| match y {
|
||||
Some(v) => format!("{}={}", x, v),
|
||||
None => x.to_string(),
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("&")
|
||||
}
|
||||
}
|
||||
|
||||
27
src/minio/api.rs
Normal file
27
src/minio/api.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use crate::minio;
|
||||
use http;
|
||||
use hyper::{header::HeaderName, header::HeaderValue, Body, Request};
|
||||
|
||||
pub fn mk_request(
|
||||
r: &minio::S3Req,
|
||||
c: &minio::Client,
|
||||
sign_hdrs: &Vec<(HeaderName, HeaderValue)>,
|
||||
) -> http::Result<Request<Body>> {
|
||||
let mut request = Request::builder();
|
||||
let svr_str = &c.server.to_string();
|
||||
let uri_str = svr_str.trim_end_matches('/');
|
||||
println!("uri_str: {}", uri_str);
|
||||
let upd_uri = format!("{}{}?{}", uri_str, r.mk_path(), r.mk_query());
|
||||
println!("upd_uri: {}", upd_uri);
|
||||
|
||||
request.uri(&upd_uri).method(&r.method);
|
||||
for hdr in r
|
||||
.headers
|
||||
.iter()
|
||||
.map(|(x, y)| (x.clone(), y.clone()))
|
||||
.chain(sign_hdrs.iter().map(|x| x.clone()))
|
||||
{
|
||||
request.header(hdr.0, hdr.1);
|
||||
}
|
||||
request.body(Body::empty())
|
||||
}
|
||||
@ -11,6 +11,10 @@ fn aws_format_time(t: &Tm) -> String {
|
||||
t.strftime("%Y%m%dT%H%M%SZ").unwrap().to_string()
|
||||
}
|
||||
|
||||
fn aws_format_date(t: &Tm) -> String {
|
||||
t.strftime("%Y%m%d").unwrap().to_string()
|
||||
}
|
||||
|
||||
fn mk_scope(t: &Tm, r: &minio::Region) -> String {
|
||||
let scope_time = t.strftime("%Y%m%d").unwrap().to_string();
|
||||
format!("{}/{}/s3/aws4_request", scope_time, r)
|
||||
@ -53,7 +57,7 @@ fn uri_encode(c: char, encode_slash: bool) -> String {
|
||||
} else {
|
||||
let mut b = [0; 8];
|
||||
let cs = c.encode_utf8(&mut b).as_bytes();
|
||||
cs.iter().map(|x| format!("%{:X}", x)).collect()
|
||||
cs.iter().map(|x| format!("%{:02X}", x)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,32 +70,23 @@ fn get_canonical_querystr(q: &HashMap<String, Option<String>>) -> String {
|
||||
hs.sort();
|
||||
let vs: Vec<String> = hs
|
||||
.drain(..)
|
||||
.map(|(x, y)| match y {
|
||||
Some(s) => uri_encode_str(&x, true) + "=" + &uri_encode_str(&s, true),
|
||||
None => uri_encode_str(&x, true),
|
||||
.map(|(x, y)| {
|
||||
let val_str = match y {
|
||||
Some(s) => uri_encode_str(&s, true),
|
||||
None => "".to_string(),
|
||||
};
|
||||
uri_encode_str(&x, true) + "=" + &val_str
|
||||
})
|
||||
.collect();
|
||||
vs[..].join("&")
|
||||
}
|
||||
|
||||
fn mk_path(r: &minio::S3Req) -> String {
|
||||
let mut res: String = String::from("");
|
||||
if let Some(s) = &r.bucket {
|
||||
res.push_str(&s);
|
||||
if let Some(o) = &r.object {
|
||||
let s1 = format!("/{}", o);
|
||||
res.push_str(&s1);
|
||||
}
|
||||
};
|
||||
res
|
||||
}
|
||||
|
||||
fn get_canonical_request(
|
||||
r: &minio::S3Req,
|
||||
hdrs_to_use: &Vec<(String, String)>,
|
||||
signed_hdrs_str: &str,
|
||||
) -> String {
|
||||
let path_str = mk_path(r);
|
||||
let path_str = r.mk_path();
|
||||
let canonical_qstr = get_canonical_querystr(&r.query);
|
||||
let canonical_hdrs: String = hdrs_to_use
|
||||
.iter()
|
||||
@ -116,7 +111,7 @@ fn string_to_sign(ts: &Tm, scope: &str, canonical_request: &str) -> String {
|
||||
let sha256_digest: String = digest::digest(&digest::SHA256, canonical_request.as_bytes())
|
||||
.as_ref()
|
||||
.iter()
|
||||
.map(|x| format!("{:X}", x))
|
||||
.map(|x| format!("{:02x}", x))
|
||||
.collect();
|
||||
vec![
|
||||
"AWS4-HMAC-SHA256",
|
||||
@ -134,7 +129,7 @@ fn hmac_sha256(msg: &str, key: &[u8]) -> hmac::Signature {
|
||||
|
||||
fn get_signing_key(ts: &Tm, region: &str, secret_key: &str) -> Vec<u8> {
|
||||
let kstr = format!("AWS4{}", secret_key);
|
||||
let s1 = hmac_sha256(&aws_format_time(&ts), kstr.as_bytes());
|
||||
let s1 = hmac_sha256(&aws_format_date(&ts), kstr.as_bytes());
|
||||
let s2 = hmac_sha256(®ion, s1.as_ref());
|
||||
let s3 = hmac_sha256("s3", s2.as_ref());
|
||||
let s4 = hmac_sha256("aws4_request", s3.as_ref());
|
||||
@ -144,12 +139,11 @@ fn get_signing_key(ts: &Tm, region: &str, secret_key: &str) -> Vec<u8> {
|
||||
|
||||
fn compute_sign(str_to_sign: &str, key: &Vec<u8>) -> String {
|
||||
let s1 = hmac_sha256(&str_to_sign, key.as_slice());
|
||||
s1.as_ref().iter().map(|x| format!("{:X}", x)).collect()
|
||||
s1.as_ref().iter().map(|x| format!("{:02x}", x)).collect()
|
||||
}
|
||||
|
||||
pub fn sign_v4(r: &minio::S3Req, c: &minio::Client) -> Option<Vec<(HeaderName, HeaderValue)>> {
|
||||
let creds = c.credentials.clone();
|
||||
creds.map(|creds| {
|
||||
pub fn sign_v4(r: &minio::S3Req, c: &minio::Client) -> Vec<(HeaderName, HeaderValue)> {
|
||||
c.credentials.clone().map_or(Vec::new(), |creds| {
|
||||
let scope = mk_scope(&r.ts, &c.region);
|
||||
let date_hdr = (
|
||||
HeaderName::from_static("x-amz-date"),
|
||||
@ -157,6 +151,7 @@ pub fn sign_v4(r: &minio::S3Req, c: &minio::Client) -> Option<Vec<(HeaderName, H
|
||||
);
|
||||
let mut hmap = r.headers.clone();
|
||||
hmap.insert(date_hdr.0.clone(), date_hdr.1.clone());
|
||||
|
||||
let hs = get_headers_to_sign(&hmap);
|
||||
let signed_hdrs_str: String = hs
|
||||
.iter()
|
||||
@ -164,9 +159,13 @@ pub fn sign_v4(r: &minio::S3Req, c: &minio::Client) -> Option<Vec<(HeaderName, H
|
||||
.collect::<Vec<String>>()
|
||||
.join(";");
|
||||
let cr = get_canonical_request(r, &hs, &signed_hdrs_str);
|
||||
println!("canonicalreq: {}", cr);
|
||||
let s2s = string_to_sign(&r.ts, &scope, &cr);
|
||||
println!("s2s: {}", s2s);
|
||||
let skey = get_signing_key(&r.ts, &c.region, &creds.secret_key);
|
||||
println!("skey: {:?}", skey);
|
||||
let signature = compute_sign(&s2s, &skey);
|
||||
println!("sign: {}", signature);
|
||||
|
||||
let auth_hdr_val = format!(
|
||||
"AWS4-HMAC-SHA256 Credential={}/{}, SignedHeaders={}, Signature={}",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user