aboutsummaryrefslogtreecommitdiff
path: root/src/api/s3_get.rs
blob: 75478e4641d22ab5f900bb03e939f6628ea320cf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::sync::Arc;
use std::time::{Duration, UNIX_EPOCH};

use futures::stream::*;
use hyper::body::Bytes;
use hyper::{Response, StatusCode};

use garage_util::error::Error;

use garage_table::EmptyKey;

use garage_core::garage::Garage;
use garage_core::object_table::*;

use crate::http_util::*;

fn object_headers(version: &ObjectVersion) -> http::response::Builder {
	let date = UNIX_EPOCH + Duration::from_millis(version.timestamp);
	let date_str = httpdate::fmt_http_date(date);

	Response::builder()
		.header("Content-Type", version.mime_type.to_string())
		.header("Content-Length", format!("{}", version.size))
		.header("Last-Modified", date_str)
}

pub async fn handle_head(
	garage: Arc<Garage>,
	bucket: &str,
	key: &str,
) -> Result<Response<BodyType>, Error> {
	let object = match garage
		.object_table
		.get(&bucket.to_string(), &key.to_string())
		.await?
	{
		None => return Err(Error::NotFound),
		Some(o) => o,
	};

	let version = match object
		.versions()
		.iter()
		.rev()
		.filter(|v| v.is_data())
		.next()
	{
		Some(v) => v,
		None => return Err(Error::NotFound),
	};

	let body: BodyType = Box::new(BytesBody::from(vec![]));
	let response = object_headers(&version)
		.status(StatusCode::OK)
		.body(body)
		.unwrap();
	Ok(response)
}

pub async fn handle_get(
	garage: Arc<Garage>,
	bucket: &str,
	key: &str,
) -> Result<Response<BodyType>, Error> {
	let object = match garage
		.object_table
		.get(&bucket.to_string(), &key.to_string())
		.await?
	{
		None => return Err(Error::NotFound),
		Some(o) => o,
	};

	let last_v = match object
		.versions()
		.iter()
		.rev()
		.filter(|v| v.is_complete())
		.next()
	{
		Some(v) => v,
		None => return Err(Error::NotFound),
	};

	let resp_builder = object_headers(&last_v).status(StatusCode::OK);

	match &last_v.data {
		ObjectVersionData::Uploading => Err(Error::Message(format!(
			"Version is_complete() but data is stil Uploading (internal error)"
		))),
		ObjectVersionData::DeleteMarker => Err(Error::NotFound),
		ObjectVersionData::Inline(bytes) => {
			let body: BodyType = Box::new(BytesBody::from(bytes.to_vec()));
			Ok(resp_builder.body(body)?)
		}
		ObjectVersionData::FirstBlock(first_block_hash) => {
			let read_first_block = garage.block_manager.rpc_get_block(&first_block_hash);
			let get_next_blocks = garage.version_table.get(&last_v.uuid, &EmptyKey);

			let (first_block, version) = futures::try_join!(read_first_block, get_next_blocks)?;
			let version = match version {
				Some(v) => v,
				None => return Err(Error::NotFound),
			};

			let mut blocks = version
				.blocks()
				.iter()
				.map(|vb| (vb.hash, None))
				.collect::<Vec<_>>();
			blocks[0].1 = Some(first_block);

			let body_stream = futures::stream::iter(blocks)
				.map(move |(hash, data_opt)| {
					let garage = garage.clone();
					async move {
						if let Some(data) = data_opt {
							Ok(Bytes::from(data))
						} else {
							garage
								.block_manager
								.rpc_get_block(&hash)
								.await
								.map(Bytes::from)
						}
					}
				})
				.buffered(2);
			let body: BodyType = Box::new(StreamBody::new(Box::pin(body_stream)));
			Ok(resp_builder.body(body)?)
		}
	}
}