Haio
0.2.X
Homebrew Asset IO/CDN
Toggle main menu visibility
Loading...
Searching...
No Matches
haio_cdn.hpp
Go to the documentation of this file.
1
#pragma once
2
3
#include "
haio.hpp
"
4
#include "
haio_cache.hpp
"
5
6
#include <boost/asio/awaitable.hpp>
7
8
#include <chrono>
9
#include <filesystem>
10
#include <map>
11
#include <set>
12
#include <string_view>
13
14
/**
15
* @defgroup cdn CDN
16
* serving images over http.
17
*
18
* one toml file, named on the command line or handed over whole in HAIO_CDN_TOML.
19
* every setting lives in a table; a key outside one is refused.
20
*
21
* @code{.toml}
22
* [cdn]
23
* host = "0.0.0.0" # address to listen on
24
* port = 8080
25
* @endcode
26
*/
27
28
/**
29
* @defgroup cdn_bucket Bucket
30
* @ingroup cdn
31
* where the bytes come from. the scheme of the url picks the reader, so there is no
32
* type to set. `url` is the only key a bucket takes.
33
*
34
* ## Bucket File
35
*
36
* @code{.toml}
37
* [bucket.assets]
38
* url = "file://assets" # relative to the working directory
39
* url = "file:///srv/img" # absolute
40
* @endcode
41
*
42
* ".." and a segment starting with "~" are refused.
43
*
44
* ## Bucket Zip
45
*
46
* a path may name a file inside an archive, as in `/cdn/assets/pack.zip/logo.png`,
47
* when `allow_unzip` is on. the archive is read once and kept in memory, so asking
48
* for a second picture out of it costs no fetch.
49
*
50
* an entry larger than `max_unzip`, or one that expands more than two hundred times,
51
* is refused: real image data barely compresses, so anything near that is an attempt
52
* to spend memory rather than an archive.
53
*
54
* ## Bucket Http
55
*
56
* @code{.toml}
57
* [bucket.upstream]
58
* url = "https://host/prefix" # everything under one prefix
59
* url = "s3://s3.eu-west-1.amazonaws.com/bucket" # signed, region from the host
60
* url = "s3://minio.local:9000/bucket?region=us-east-1" # or said outright
61
* url = "https://*" # open: the request names the host
62
* @endcode
63
*
64
* redirects are followed, downgrades included. an open bucket fetches whatever host
65
* the caller names, warns at startup, and is meant for testing.
66
*
67
* ## Bucket S3
68
*
69
* an `s3://` bucket is https with a signature. the region is settled while the config
70
* loads, from `?region=` if it says so, else from an amazon host, else from
71
* `AWS_DEFAULT_REGION`. a bucket whose region nobody can work out is refused at
72
* startup rather than at the first request.
73
*
74
* @code{.toml}
75
* [bucket.orders]
76
* url = "s3://bucket.s3.us-east-1.amazonaws.com/prefix"
77
* access_key = "AKIA..."
78
* secret_key = "..."
79
* session_token = "..." # only for temporary credentials
80
* @endcode
81
*
82
* the keys may be left out, and then AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and
83
* AWS_SESSION_TOKEN are read instead. with neither the request goes out unsigned,
84
* which is what a public bucket wants.
85
*
86
* a config holding a secret is a file worth guarding, and haio says so at startup if
87
* anybody but its owner can read it.
88
*
89
* an s3 bucket cannot be open: a signature is made for one host, and there is nothing
90
* to sign for a host that arrives with the request.
91
*/
92
93
/**
94
* @defgroup cdn_cache Cache
95
* @ingroup cdn
96
* keeping the answer, so the fetch and the conversion behind it happen once.
97
*
98
* @code{.toml}
99
* [cache]
100
* ttl = 5000 # in seconds, since the entry was last wanted
101
* max_usage = 50 # in mb, wherever the entries are kept
102
* url = "redis://127.0.0.1:6379" # optional; without it, kept in this process
103
* @endcode
104
*
105
* ## Cache Key
106
*
107
* the md5 of the bucket, the path and the query, in the order written. the query is
108
* not sorted: it will spell a pipeline once filters chain, and a sorted key would
109
* serve one picture when another was asked for. the method is left out, so a HEAD
110
* finds what a GET stored.
111
*
112
* ## Cache Where
113
*
114
* | url | kept in | bounded by |
115
* | ---------------- | ---------------- | ------------------- |
116
* | absent | this process | what it holds |
117
* | `file://dir` | one file per key | what the files take |
118
* | `redis://host` | a shared redis | an index haio keeps |
119
*
120
* a redis that cannot be reached reads as a miss: losing the cache costs latency and
121
* nothing else.
122
*
123
* ## Cache Flow
124
*
125
* deduplication is not part of caching. with no `[cache]` nothing is kept, and a
126
* burst on one key still reaches the producer once.
127
*
128
* @startuml
129
* start
130
* if (cache on?) then (yes)
131
* :look in the store;
132
* if (hit?) then (yes)
133
* :renew the ttl;
134
* stop
135
* endif
136
* endif
137
* if (already being produced?) then (yes)
138
* :wait for the leader;
139
* :take their answer;
140
* stop
141
* else (no)
142
* :become the leader;
143
* endif
144
* :fetch and convert;
145
* if (cache on and within quota?) then (yes)
146
* :store;
147
* endif
148
* :wake the waiters;
149
* stop
150
* @enduml
151
*/
152
153
/**
154
* @defgroup cdn_security Security
155
* @ingroup cdn
156
* every limit in one table, so reviewing what protects a deployment is one page.
157
*
158
* @code{.toml}
159
* [security]
160
* timeout = 10 # in seconds, one conversion
161
* max_size_pixel = 4096 # in pixels, what a resize may ask for outright
162
* max_size_percent = 500 # in percent, what a resize may ask for as a share
163
* max_cache_entries_by_ip = 30 # entries one address may create per ttl window
164
* max_requests_by_ip = 10 # per second, as a leaky bucket
165
* allow_unzip = false # whether a path may reach inside a zip
166
* max_unzip = 64 # in mb, one extracted file and the archives kept
167
* @endcode
168
*
169
* ## Security Defaults
170
*
171
* each has a default and the server starts without them. the two counted per address
172
* default to no limit on purpose: a number picked blind shuts out an office behind
173
* one NAT. either way the server says at startup which ones it chose for itself.
174
*
175
* ## Security Why
176
*
177
* | key | answers |
178
* | ------------------------- | ------------------------------------------------ |
179
* | `max_size_pixel` | a url asking for an allocation |
180
* | `max_size_percent` | the same, asked for as a share of the picture |
181
* | `timeout` | a url asking for work |
182
* | `max_cache_entries_by_ip` | one caller evicting everyone else's entries |
183
* | `max_requests_by_ip` | a flood |
184
* | `allow_unzip` | work on behalf of whoever wrote the file |
185
* | `max_unzip` | a small zip that expands into a large one |
186
*
187
* over the cache quota the answer is still produced and still correct, it is simply
188
* not stored: the limit is on eviction, not on access.
189
*/
190
191
namespace
Haio::Cdn
{
192
193
/**
194
* a bucket is described entirely by the scheme of its url:
195
*
196
* file://relative/dir file:///absolute/dir
197
* http://host/prefix https://host/prefix s3://host/bucket
198
* https://\* open, the request names the host
199
* //\* open, the request names the scheme and the host
200
*/
201
struct
BucketConfig
{
202
std::string
name
;
203
std::string
url
;
204
205
// derived from the url while the config loads, so a bad one fails at startup
206
std::string
scheme
;
207
bool
open
=
false
;
208
std::filesystem::path
root
;
209
210
/** s3 only: from ?region=, then the host, then AWS_DEFAULT_REGION */
211
std::string
region
;
212
213
/**
214
* s3 only, and the reason a config file deserves careful permissions. empty
215
* leaves the request unsigned, which is what a public bucket wants.
216
*/
217
std::string
accessKey
;
218
std::string
secretKey
;
219
std::string
sessionToken
;
220
};
221
222
/**
223
* every limit the server has, in one table, so that reviewing what protects this
224
* deployment is reading one page of the config rather than hunting for it.
225
*
226
* each one is safe when left out, and the server says at startup which ones it chose
227
* for itself; see @ref cdn_security.
228
*/
229
struct
SecurityConfig
{
230
/** seconds one conversion may run before it is given up on */
231
std::chrono::seconds
timeout
{10};
232
233
/** the largest width or height a resize may ask for outright */
234
int
maxSizePixel
= 4096;
235
236
/**
237
* the largest share a resize may ask for, as a percentage.
238
*
239
* it is a separate number because it bounds a different thing: a picture scaled
240
* by a share is bounded by what it was, so five hundred percent of a thumbnail is
241
* still small, while five hundred percent of something large is not.
242
*/
243
int
maxSizePercent
= 500;
244
245
/** entries one address may create per cache window; zero is no limit */
246
size_t
maxCacheEntriesByIp
= 0;
247
248
/** requests one address may make per second; zero is no limit */
249
size_t
maxRequestsByIp
= 0;
250
251
/** whether a path may reach inside a zip; off unless asked for */
252
bool
allowUnzip
=
false
;
253
254
/**
255
* bytes one file out of a zip may take, and what the archives kept in memory may
256
* take altogether. megabytes in the config.
257
*/
258
size_t
maxUnzip
= 64u << 20;
259
};
260
261
struct
Config
{
262
std::string
host
=
"0.0.0.0"
;
263
unsigned
short
port
= 8080;
264
265
SecurityConfig
security
;
266
CacheConfig
cache
;
267
std::map<std::string, BucketConfig>
buckets
;
268
269
/**
270
* the keys the file actually spelled out, as "max_size" or "cache.max_ip".
271
*
272
* the safety limits all have a default, so leaving one out is safe rather than
273
* open; this is what lets the server say which ones it chose for itself instead
274
* of letting an operator believe they had configured something.
275
*/
276
std::set<std::string>
declared
;
277
};
278
279
Config
parseConfig
(std::string_view text);
280
Config
loadConfig
(
const
std::filesystem::path& path);
281
namespace
Bucket
{
class
ZipArchives
; }
282
283
boost::asio::awaitable<Result<Blob>>
fetchBucket
(
const
Config
& config,
Bucket::ZipArchives
& archives, std::string bucket, std::string path);
284
boost::asio::awaitable<void>
runServer
(
Config
config);
285
286
}
Haio::Cdn::Bucket::ZipArchives
Definition
bucket.hpp:98
haio.hpp
haio_cache.hpp
Haio::Cdn::Bucket
Definition
haio_cdn.hpp:281
Haio::Cdn
Definition
haio_cache.hpp:16
Haio::Cdn::SecurityConfig::maxSizePercent
int maxSizePercent
Definition
haio_cdn.hpp:243
Haio::Cdn::BucketConfig::region
std::string region
Definition
haio_cdn.hpp:211
Haio::Cdn::SecurityConfig::maxSizePixel
int maxSizePixel
Definition
haio_cdn.hpp:234
Haio::Cdn::Config::security
SecurityConfig security
Definition
haio_cdn.hpp:265
Haio::Cdn::SecurityConfig::maxRequestsByIp
size_t maxRequestsByIp
Definition
haio_cdn.hpp:249
Haio::Cdn::Config::cache
CacheConfig cache
Definition
haio_cdn.hpp:266
Haio::Cdn::runServer
boost::asio::awaitable< void > runServer(Config config)
Definition
server.cpp:431
Haio::Cdn::BucketConfig::url
std::string url
Definition
haio_cdn.hpp:203
Haio::Cdn::BucketConfig::scheme
std::string scheme
Definition
haio_cdn.hpp:206
Haio::Cdn::SecurityConfig::maxCacheEntriesByIp
size_t maxCacheEntriesByIp
Definition
haio_cdn.hpp:246
Haio::Cdn::Config::buckets
std::map< std::string, BucketConfig > buckets
Definition
haio_cdn.hpp:267
Haio::Cdn::SecurityConfig::allowUnzip
bool allowUnzip
Definition
haio_cdn.hpp:252
Haio::Cdn::BucketConfig::accessKey
std::string accessKey
Definition
haio_cdn.hpp:217
Haio::Cdn::BucketConfig::open
bool open
Definition
haio_cdn.hpp:207
Haio::Cdn::Config::host
std::string host
Definition
haio_cdn.hpp:262
Haio::Cdn::BucketConfig::name
std::string name
Definition
haio_cdn.hpp:202
Haio::Cdn::BucketConfig::secretKey
std::string secretKey
Definition
haio_cdn.hpp:218
Haio::Cdn::parseConfig
Config parseConfig(std::string_view text)
Definition
config.cpp:190
Haio::Cdn::Config::port
unsigned short port
Definition
haio_cdn.hpp:263
Haio::Cdn::Config::declared
std::set< std::string > declared
Definition
haio_cdn.hpp:276
Haio::Cdn::fetchBucket
boost::asio::awaitable< Result< Blob > > fetchBucket(const Config &config, Bucket::ZipArchives &archives, std::string bucket, std::string path)
Definition
bucket.cpp:15
Haio::Cdn::SecurityConfig::timeout
std::chrono::seconds timeout
Definition
haio_cdn.hpp:231
Haio::Cdn::SecurityConfig::maxUnzip
size_t maxUnzip
Definition
haio_cdn.hpp:258
Haio::Cdn::BucketConfig::sessionToken
std::string sessionToken
Definition
haio_cdn.hpp:219
Haio::Cdn::BucketConfig::root
std::filesystem::path root
Definition
haio_cdn.hpp:208
Haio::Cdn::loadConfig
Config loadConfig(const std::filesystem::path &path)
Definition
config.cpp:287
Haio::Cdn::BucketConfig
Definition
haio_cdn.hpp:201
Haio::Cdn::CacheConfig
Definition
haio_cache.hpp:28
Haio::Cdn::Config
Definition
haio_cdn.hpp:261
Haio::Cdn::SecurityConfig
Definition
haio_cdn.hpp:229
include
haio_cdn.hpp
Generated by
1.18.0