bbox_core/
tile_response.rs

1use actix_web::http::header::{
2    self, HeaderMap, HeaderValue, TryIntoHeaderPair, TryIntoHeaderValue,
3};
4use flate2::{read::GzDecoder, read::GzEncoder, Compression as GzCompression};
5use std::io::{Cursor, Read};
6
7/// Tile data compression
8#[derive(Clone, PartialEq, Debug)]
9pub enum Compression {
10    // Unknown,
11    None,
12    Gzip,
13    // Brotli,
14    // Zstd,
15}
16
17/// Tile reader response
18pub struct TileResponse {
19    headers: HeaderMap,
20    pub(crate) body: Box<dyn Read + Send + Sync>,
21}
22
23/// Tile response data
24pub struct TileResponseData {
25    headers: HeaderMap,
26    pub body: Vec<u8>,
27}
28
29impl TileResponse {
30    pub fn new() -> Self {
31        TileResponse {
32            headers: HeaderMap::new(),
33            body: Box::new(std::io::empty()),
34        }
35    }
36    /// Set response content type.
37    pub fn set_content_type<V: TryIntoHeaderValue>(&mut self, value: V) -> &mut Self {
38        if let Ok(value) = value.try_into_value() {
39            self.headers.insert(header::CONTENT_TYPE, value);
40        }
41        self
42    }
43    /// Insert a header, replacing any that were set with an equivalent field name.
44    pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
45        if let Ok((key, value)) = header.try_into_pair() {
46            self.headers.insert(key, value);
47        }
48        self
49    }
50    pub fn set_headers(&mut self, headers: &HeaderMap) -> &mut Self {
51        for (key, value) in headers {
52            self.insert_header((key, value));
53        }
54        self
55    }
56    pub fn with_body(mut self, body: Box<dyn Read + Send + Sync>) -> TileResponse {
57        self.body = body;
58        self
59    }
60    /// Apply optional de-/compression
61    pub fn with_compression(mut self, compression: &Compression) -> TileResponse {
62        match (self.compression(), compression) {
63            (Compression::None, Compression::Gzip) => {
64                let gz = GzEncoder::new(self.body, GzCompression::fast());
65                self.body = Box::new(gz);
66                self.insert_header(("Content-Encoding", "gzip"));
67            }
68            (Compression::Gzip, Compression::None) => {
69                let gz = GzDecoder::new(self.body);
70                self.body = Box::new(gz);
71                self.headers.remove(header::CONTENT_ENCODING);
72            }
73            _ => {}
74        }
75        self
76    }
77    pub fn content_type(&self) -> Option<&HeaderValue> {
78        self.headers.get(header::CONTENT_TYPE)
79    }
80    pub fn compression(&self) -> Compression {
81        match self.headers.get(header::CONTENT_ENCODING) {
82            Some(v) if v == HeaderValue::from_static("gzip") => Compression::Gzip,
83            _ => Compression::None,
84        }
85    }
86    pub fn headers(&self) -> &HeaderMap {
87        &self.headers
88    }
89    /// Read tile body with optional compression
90    pub fn read_bytes(
91        mut self,
92        compression: &Compression,
93    ) -> Result<TileResponseData, std::io::Error> {
94        let mut response = TileResponseData {
95            headers: self.headers,
96            body: Vec::new(),
97        };
98        match compression {
99            Compression::Gzip => {
100                let mut gz = GzEncoder::new(self.body, GzCompression::fast());
101                gz.read_to_end(&mut response.body)?;
102                response.insert_header(("Content-Encoding", "gzip"));
103            }
104            Compression::None => {
105                self.body.read_to_end(&mut response.body)?;
106            }
107        }
108        Ok(response)
109    }
110}
111
112impl Default for TileResponse {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl TileResponseData {
119    /// Insert a header, replacing any that were set with an equivalent field name.
120    pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
121        if let Ok((key, value)) = header.try_into_pair() {
122            self.headers.insert(key, value);
123        }
124        self
125    }
126    pub fn compression(&self) -> Compression {
127        match self.headers.get(header::CONTENT_ENCODING) {
128            Some(v) if v == HeaderValue::from_static("gzip") => Compression::Gzip,
129            _ => Compression::None,
130        }
131    }
132    /// Read tile body with optional compression
133    pub fn as_response(self, compression: &Compression) -> TileResponse {
134        let mut response = TileResponse::new();
135        response.set_headers(&self.headers);
136        match (self.compression(), compression) {
137            (Compression::None, Compression::Gzip) => {
138                let gz = GzEncoder::new(Cursor::new(self.body), GzCompression::fast());
139                response.body = Box::new(gz);
140                response.insert_header(("Content-Encoding", "gzip"));
141            }
142            (Compression::Gzip, Compression::None) => {
143                let gz = GzDecoder::new(Cursor::new(self.body));
144                response.body = Box::new(gz);
145                response.headers.remove(header::CONTENT_ENCODING);
146            }
147            _ => response.body = Box::new(Cursor::new(self.body)),
148        }
149        response
150    }
151}