Log requests
[litoprism.git] / src / main.rs
1 use std::convert::From;
2 use std::env;
3 use std::io::Cursor;
4 use std::fmt::Display;
5 use std::fmt;
6 use std::borrow::BorrowMut;
7 use std::sync::{Arc, Mutex};
8 use log::LevelFilter;
9 use num_traits::Zero;
10 use num_traits::cast::AsPrimitive;
11 use percent_encoding::percent_decode_str;
12 use actix_web::{middleware::Logger, get, web, App, HttpServer, HttpRequest, HttpResponse, Responder, ResponseError, Result};
13 use actix_web::body::BoxBody;
14 use actix_web::http::StatusCode;
15 use image::{ImageBuffer, ColorType, Rgb, RgbImage, write_buffer_with_format};
16 use image::ImageOutputFormat::Png;
17 use image::imageops::{FilterType, overlay, resize};
18 use image::error::{LimitError, LimitErrorKind};
19 use actix_web::cookie::time::OffsetDateTime;
20
21
22 const TIMEAVATAR_SIZE_U32: u32 = 6;
23 const TIMEAVATAR_SIZE_I64: i64 = TIMEAVATAR_SIZE_U32 as i64;
24
25 #[derive(Debug)]
26 struct ImageError(image::ImageError);
27
28 impl ImageError {
29 fn dimension_error() -> ImageError {
30 ImageError(image::ImageError::Limits(LimitError::from_kind(LimitErrorKind::DimensionError)))
31 }
32 }
33
34 impl Display for ImageError {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(f, "{}", &self.0)
37 }
38 }
39
40 impl ResponseError for ImageError {
41 fn status_code(&self) -> StatusCode {
42 StatusCode::INTERNAL_SERVER_ERROR
43 }
44
45 fn error_response(&self) -> HttpResponse<BoxBody> {
46 HttpResponse::InternalServerError().body(format!("{}\n", &self))
47 }
48 }
49
50 #[derive(Debug)]
51 enum CanvasError {
52 NotExists,
53 DimensionMismatch((u32, u32)),
54 ImageError(ImageError)
55 }
56
57 impl Display for CanvasError {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 write!(f, "Canvas Error: see error_response() for more details")
60 }
61 }
62
63 impl ResponseError for CanvasError {
64 fn status_code(&self) -> StatusCode {
65 match &self {
66 CanvasError::NotExists => StatusCode::NOT_FOUND,
67 CanvasError::DimensionMismatch(_) => StatusCode::BAD_REQUEST,
68 CanvasError::ImageError(e) => e.status_code()
69 }
70 }
71
72 fn error_response(&self) -> HttpResponse<BoxBody> {
73 match &self {
74 CanvasError::NotExists => HttpResponse::NotFound().body("Canvas does not exist."),
75 CanvasError::DimensionMismatch(dim) =>
76 HttpResponse::BadRequest().body(format!("Supplied dimensions don't match the dimensions of existing canvas {:?}.", dim)),
77 CanvasError::ImageError(e) => e.error_response()
78 }
79 }
80 }
81
82 impl From<ImageError> for CanvasError {
83 fn from(e: ImageError) -> CanvasError {
84 CanvasError::ImageError(e)
85 }
86 }
87
88 struct ToRgbIter<'a, T> where T: AsPrimitive<u8> {
89 iter: &'a mut dyn Iterator<Item = T>
90 }
91
92 impl<'a, T: AsPrimitive<u8>> ToRgbIter<'a, T> {
93 fn new(iter: &'a mut (dyn Iterator<Item = T>)) -> ToRgbIter<T> {
94 ToRgbIter { iter }
95 }
96 }
97
98 impl<T: AsPrimitive<u8> + Zero> Iterator for ToRgbIter<'_, T> {
99 type Item = Rgb<u8>;
100 fn next(&mut self) -> Option<Rgb<u8>> {
101 if let Some(r) = self.iter.next() {
102 let g = self.iter.next().unwrap_or_else(T::zero);
103 let b = self.iter.next().unwrap_or_else(T::zero);
104 Some(Rgb([r.as_(), g.as_(), b.as_()]))
105 }
106 else {
107 None
108 }
109 }
110 }
111
112 fn to_imageresult<T> (result: Result<T, image::ImageError>) -> Result<T, ImageError> {
113 match result {
114 Ok(v) => Ok(v),
115 Err(e) => Err(ImageError(e))
116 }
117 }
118
119 fn to_canvasresult<T: std::fmt::Debug> (result: Result<T, ImageError>) -> Result<T, CanvasError> {
120 match result {
121 Ok(v) => Ok(v),
122 Err(e) => Err(CanvasError::ImageError(e))
123 }
124 }
125
126 #[derive(Debug)]
127 struct Canvas {
128 pen: usize,
129 img: RgbImage
130 }
131
132 impl Canvas {
133 fn new(width: u32, height: u32) -> Canvas {
134 Canvas { pen: 0, img: ImageBuffer::new(width, height) }
135 }
136
137 fn from_image(img: RgbImage) -> Canvas {
138 Canvas { pen: 0, img }
139 }
140
141 /*fn replace(&self, img: RgbImage, pen: usize) -> Canvas {
142 self.img = img;
143 self.pen = pen;
144 self
145 }*/
146
147 fn advance_pen(mut self, offset: usize) -> Canvas {
148 //Canvas { pen: self.pen + offset, img: self.img }
149 self.pen += offset;
150 self.pen %= self.img.width() as usize * self.img.height() as usize;
151 self
152 }
153 }
154
155 struct Canvas0(Option<Canvas>);
156 struct Canvas1(Option<Canvas>);
157 struct Canvas2(Option<Canvas>);
158
159 #[get("/hello/{name}")]
160 async fn greet(name: web::Path<String>, req: HttpRequest) -> impl Responder {
161 println!("{:?}", req);
162 format!("Hello {name}!\n{:?}", req.headers().get("user-agent"))
163 }
164
165 fn rgb_encode_to_canvas(mut canvas: Canvas, data: &mut dyn Iterator<Item = u8>) -> Result<Canvas, ImageError> {
166 let mut pixels = canvas.img.pixels_mut().skip(canvas.pen);
167 let mut counter = 0;
168
169 for sp in ToRgbIter::new(data) {
170 let mut dp = match pixels.next() {
171 Some(pixel) => pixel,
172 None => {
173 pixels = canvas.img.pixels_mut().skip(0);
174 pixels.next().ok_or_else(ImageError::dimension_error)?
175 }
176 };
177 dp.0 = sp.0;
178 counter += 1;
179 }
180
181 Ok(canvas.advance_pen(counter))
182 }
183
184 fn rgb_encode(img: RgbImage, data: &mut dyn Iterator<Item = u8>) -> Result<RgbImage, ImageError> {
185 Ok(rgb_encode_to_canvas(Canvas::from_image(img), data)?.img)
186 }
187
188 fn apply_to_canvas<'a, F>(f: F, canvas_option: &'a mut Option<Canvas>, dimensions: Option<(u32, u32)>, data: &str) -> Result<&'a mut Canvas, CanvasError>
189 where F: Fn(Canvas, &mut dyn Iterator<Item = u8>) -> Result<Canvas, CanvasError>
190 {
191 let canvas = match canvas_option.take() {
192 Some(canvas) => match dimensions {
193 Some(dimensions) => {
194 if dimensions == canvas.img.dimensions() {
195 Ok(canvas)
196 }
197 else {
198 Err(CanvasError::DimensionMismatch(canvas_option.insert(canvas).img.dimensions()))
199 }
200 },
201 None =>
202 Ok(canvas)
203 },
204 None => match dimensions {
205 Some((dim_x, dim_y)) =>
206 Ok(Canvas::new(dim_x, dim_y)),
207 None =>
208 Err(CanvasError::NotExists)
209 }
210 }?;
211
212 Ok(canvas_option.insert(f(canvas, percent_decode_str(data).into_iter().borrow_mut())?))
213 }
214
215 fn make_png(dim_x: u32, dim_y: u32, scale: u32, data: &mut dyn Iterator<Item = u8>) -> Result<Cursor<Vec<u8>>, ImageError> {
216 // Image must not be larger than 1 megapixel
217 if dim_x * dim_y * scale > 1048576 {
218 return Err(ImageError::dimension_error())
219 }
220
221 let img: RgbImage = rgb_encode(ImageBuffer::new(dim_x, dim_y), data)?;
222 let cursor = image_to_cursor(img, scale)?;
223 Ok(cursor)
224 }
225
226 fn image_to_cursor(img: RgbImage, scale: u32) -> Result<Cursor<Vec<u8>>, ImageError> {
227 let (dim_x, dim_y) = img.dimensions();
228 let (dim_x, dim_y) = (dim_x * scale, dim_y * scale);
229
230 // Image must not be larger than 1 megapixel
231 if dim_x * dim_y > 1048576 {
232 return Err(ImageError::dimension_error())
233 }
234
235 let mut cursor = Cursor::new(Vec::new());
236 let img = resize(&img, dim_x, dim_y, FilterType::Nearest);
237 to_imageresult(write_buffer_with_format(&mut cursor, &img, dim_x, dim_y, ColorType::Rgb8, Png))?;
238 Ok(cursor)
239 }
240
241
242 #[get("/gen/0/{data}")]
243 async fn img_gen0(req: HttpRequest) -> Result<impl Responder> {
244 let data = req.uri().path().split('/').nth(3).unwrap();
245 let cursor = make_png(32, 32, 16, percent_decode_str(data).into_iter().borrow_mut())?;
246 Ok(HttpResponse::build(StatusCode::OK)
247 .content_type("image/png")
248 .body(cursor.into_inner()))
249 }
250
251 #[get("/gen/1/{dim_x}/{dim_y}/{scale}/{data}")]
252 async fn img_gen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
253 let (dim_x, dim_y, scale) = path.into_inner();
254 let data = req.uri().path().split('/').nth(6).unwrap();
255 let cursor = make_png(dim_x, dim_y, scale, percent_decode_str(data).into_iter().borrow_mut())?;
256 Ok(HttpResponse::build(StatusCode::OK)
257 .content_type("image/png")
258 .body(cursor.into_inner()))
259 }
260
261 #[get("/gen/2/{dim_x}/{dim_y}/{scale}")]
262 async fn img_gen2(req: HttpRequest, path: web::Path<(u32, u32, u32)>) -> Result<impl Responder> {
263 let (dim_x, dim_y, scale) = path.into_inner();
264 let data = match req.headers().get("user-agent") {
265 Some(header) => header.to_str().unwrap_or(""),
266 _ => ""
267 };
268 let cursor = make_png(dim_x, dim_y, scale, &mut data.bytes())?;
269 Ok(HttpResponse::build(StatusCode::OK)
270 .content_type("image/png")
271 .body(cursor.into_inner()))
272 }
273
274 #[get("/gen/3/{scale}")]
275 async fn img_gen3(req: HttpRequest, path: web::Path<u32>) -> Result<impl Responder> {
276 let scale = path.into_inner();
277 let data = match req.headers().get("user-agent") {
278 Some(header) => header.to_str().unwrap_or(""),
279 _ => ""
280 };
281 let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
282 let mut resimg: RgbImage = ImageBuffer::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32);
283 let time = OffsetDateTime::now_utc().time();
284 let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
285 overlay(&mut resimg, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
286 let cursor = image_to_cursor(resimg, scale)?;
287 Ok(HttpResponse::build(StatusCode::OK)
288 .content_type("image/png")
289 .body(cursor.into_inner()))
290 }
291
292 #[get("/pgen/0/{data}")]
293 async fn img_pgen0(req: HttpRequest, canvas0: web::Data<Arc<Mutex<Canvas0>>>) -> Result<impl Responder> {
294 let data = req.uri().path().split('/').nth(3).unwrap();
295 let scale = 16;
296 let img: RgbImage = ({
297 let canvas_option = &mut *canvas0.lock().unwrap();
298 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, None, data)?.img.clone())
299 } as Result<RgbImage, CanvasError>)?;
300 let cursor = image_to_cursor(img, scale)?;
301 Ok(HttpResponse::build(StatusCode::OK)
302 .content_type("image/png")
303 .body(cursor.into_inner()))
304 }
305
306 #[get("/pgen/1/{dim_x}/{dim_y}/{scale}/{data}")]
307 async fn img_pgen1(req: HttpRequest, path: web::Path<(u32, u32, u32)>, canvas1: web::Data<Arc<Mutex<Canvas1>>>) -> Result<impl Responder> {
308 let (dim_x, dim_y, scale) = path.into_inner();
309 let data = req.uri().path().split('/').nth(6).unwrap();
310 let img: RgbImage = ({
311 let canvas_option = &mut *canvas1.lock().unwrap();
312 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, Some((dim_x, dim_y)), data)?.img.clone())
313 } as Result<RgbImage, CanvasError>)?;
314 let cursor = image_to_cursor(img, scale)?;
315 Ok(HttpResponse::build(StatusCode::OK)
316 .content_type("image/png")
317 .body(cursor.into_inner()))
318 }
319
320 #[get("/pgen/2/{dim_x}/{dim_y}/{scale}")]
321 async fn img_pgen2(req: HttpRequest, path: web::Path<(u32, u32, u32)>, canvas1: web::Data<Arc<Mutex<Canvas1>>>) -> Result<impl Responder> {
322 let (dim_x, dim_y, scale) = path.into_inner();
323 let data = match req.headers().get("user-agent") {
324 Some(header) => header.to_str().unwrap_or(""),
325 _ => ""
326 };
327 let img: RgbImage = ({
328 let canvas_option = &mut *canvas1.lock().unwrap();
329 Ok(apply_to_canvas( |c, d| to_canvasresult(rgb_encode_to_canvas(c, d)), &mut canvas_option.0, Some((dim_x, dim_y)), data)?.img.clone())
330 } as Result<RgbImage, CanvasError>)?;
331 let cursor = image_to_cursor(img, scale)?;
332 Ok(HttpResponse::build(StatusCode::OK)
333 .content_type("image/png")
334 .body(cursor.into_inner()))
335 }
336
337 #[get("/pgen/3/{scale}")]
338 async fn img_pgen3(req: HttpRequest, path: web::Path<u32>, canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
339 let scale = path.into_inner();
340 let data = match req.headers().get("user-agent") {
341 Some(header) => header.to_str().unwrap_or(""),
342 _ => ""
343 };
344 let time = OffsetDateTime::now_utc().time();
345 let (hour, minute): (i64, i64) = (time.hour().into(), time.minute().into());
346 let rgbimg: RgbImage = rgb_encode(ImageBuffer::new(TIMEAVATAR_SIZE_U32, TIMEAVATAR_SIZE_U32), &mut data.bytes())?;
347 let resimg: RgbImage = ({
348 let canvas_option = &mut *canvas2.lock().unwrap();
349 let mut canvas = match canvas_option.0.take() {
350 Some(canvas) => canvas,
351 None => Canvas::new(60 * TIMEAVATAR_SIZE_U32, 24 * TIMEAVATAR_SIZE_U32)
352 };
353 overlay(&mut canvas.img, &rgbimg, minute * TIMEAVATAR_SIZE_I64, hour * TIMEAVATAR_SIZE_I64);
354 Ok(canvas_option.0.insert(canvas).img.clone())
355 } as Result<RgbImage, CanvasError>)?;
356 let cursor = image_to_cursor(resimg, scale)?;
357 Ok(HttpResponse::build(StatusCode::OK)
358 .content_type("image/png")
359 .body(cursor.into_inner()))
360 }
361
362 #[get("/pdrop/{canvas_id}")]
363 async fn pdrop(canvas_id: web::Path<u8>,
364 canvas0: web::Data<Arc<Mutex<Canvas0>>>,
365 canvas1: web::Data<Arc<Mutex<Canvas1>>>,
366 canvas2: web::Data<Arc<Mutex<Canvas2>>>) -> Result<impl Responder> {
367
368 let canvas_id = canvas_id.into_inner();
369 match canvas_id {
370 0 => canvas0.lock().unwrap().0.take(),
371 1 => canvas1.lock().unwrap().0.take(),
372 2 => canvas2.lock().unwrap().0.take(),
373 _ => None
374 }.ok_or(CanvasError::NotExists)?;
375 Ok(HttpResponse::build(StatusCode::OK)
376 .body(format!("Canvas {} dropped.", canvas_id)))
377 }
378
379
380 #[actix_web::main] // or #[tokio::main]
381 async fn main() -> std::io::Result<()> {
382 env_logger::builder()
383 .filter_module("actix_web", LevelFilter::Info)
384 .parse_default_env()
385 .init();
386
387 let listenaddress = env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8080".to_string());
388 let canvas0 = Arc::new(Mutex::new(Canvas0(Some(Canvas::new(32, 32)))));
389 let canvas1 = Arc::new(Mutex::new(Canvas1(None)));
390 let canvas2 = Arc::new(Mutex::new(Canvas2(None)));
391
392 HttpServer::new(move || {
393 App::new()
394 .wrap(Logger::default())
395 .service(greet)
396 .service(img_gen0)
397 .service(img_gen1)
398 .service(img_gen2)
399 .service(img_gen3)
400 .service(img_pgen0)
401 .service(img_pgen1)
402 .service(img_pgen2)
403 .service(img_pgen3)
404 .service(pdrop)
405 .app_data(web::Data::new(canvas0.clone()))
406 .app_data(web::Data::new(canvas1.clone()))
407 .app_data(web::Data::new(canvas2.clone()))
408 })
409 .bind(listenaddress)?
410 .run()
411 .await
412 }