Implemented basic webserver
This commit is contained in:
parent
b2662dadb7
commit
e9d0580f7e
4 changed files with 149 additions and 56 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -97,6 +97,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum-core",
|
"axum-core",
|
||||||
|
"axum-macros",
|
||||||
"bytes",
|
"bytes",
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|
@ -145,6 +146,17 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-macros"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.76"
|
version = "0.3.76"
|
||||||
|
|
@ -413,6 +425,12 @@ version = "0.3.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
|
@ -470,6 +488,25 @@ version = "0.32.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"http",
|
||||||
|
"indexmap",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "half"
|
name = "half"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
|
@ -548,6 +585,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"h2",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"httparse",
|
"httparse",
|
||||||
|
|
@ -1412,6 +1450,19 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.7.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.23"
|
version = "0.8.23"
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
image = {version = "0.25.8", features = ["png"]}
|
image = {version = "0.25.8", features = ["png"]}
|
||||||
|
|
||||||
axum = { version = "0.8.4", features = ["multipart"] }
|
# axum = { version = "0.8.4", features = ["multipart", "debug"] }
|
||||||
|
axum = { version = "0.8.4", features = ["multipart", "http2", "macros"] }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
tower-http = { version = "0.6.1", features = ["limit", "trace"] }
|
tower-http = { version = "0.6.1", features = ["limit", "trace"] }
|
||||||
|
|
|
||||||
|
|
@ -108,30 +108,41 @@ impl Job {
|
||||||
return Err(EscPosError::InvalidBitmapMode);
|
return Err(EscPosError::InvalidBitmapMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (w, h) = (img.width(), img.height());
|
let (iw, ih) = (img.width(), img.height());
|
||||||
let swap = matches!(orientation, Some(ImageOrientation::Largest))
|
let mw = self.max_width as u32;
|
||||||
&& h.min(self.max_width as u32) * w > w.min(self.max_width as u32) * h;
|
|
||||||
|
|
||||||
let scale = if swap {
|
let rotate = matches!(orientation, Some(ImageOrientation::Largest)) && {
|
||||||
self.max_width as f32 / h as f32
|
let scale_a = mw as f32 / iw as f32;
|
||||||
} else {
|
let out_h_a = (ih as f32 * scale_a).round() as u32;
|
||||||
self.max_width as f32 / w as f32
|
let area_a = (mw as u64) * (out_h_a as u64);
|
||||||
|
let scale_b = mw as f32 / ih as f32;
|
||||||
|
let out_h_b = (iw as f32 * scale_b).round() as u32;
|
||||||
|
let area_b = (mw as u64) * (out_h_b as u64);
|
||||||
|
area_b > area_a
|
||||||
};
|
};
|
||||||
|
|
||||||
let (w, h) = if swap {
|
let (pw, ph) = if rotate { (ih, iw) } else { (iw, ih) };
|
||||||
((h as f32 * scale) as u32, self.max_width as u32)
|
let target_w = mw;
|
||||||
} else {
|
let target_h = (ph as f32 * (mw as f32 / pw as f32)).round() as u32;
|
||||||
(self.max_width as u32, (h as f32 * scale) as u32)
|
|
||||||
};
|
|
||||||
|
|
||||||
let img =
|
let mut rotated: Option<DynamicImage> = None;
|
||||||
DynamicImage::ImageRgba8(image::imageops::resize(img, w, h, FilterType::Triangle));
|
if rotate {
|
||||||
|
rotated = Some(img.rotate90());
|
||||||
|
}
|
||||||
|
let src: &DynamicImage = rotated.as_ref().unwrap_or(img);
|
||||||
|
let resized = DynamicImage::ImageRgba8(image::imageops::resize(
|
||||||
|
src,
|
||||||
|
target_w,
|
||||||
|
target_h,
|
||||||
|
FilterType::Triangle,
|
||||||
|
));
|
||||||
|
|
||||||
let img = if swap { img.rotate90() } else { img };
|
let mut gray = resized.to_luma8();
|
||||||
let mut gray = img.to_luma8();
|
|
||||||
auto_brighten(&mut gray);
|
auto_brighten(&mut gray);
|
||||||
|
|
||||||
let mono = dither(&mut gray);
|
let mono = dither(&mut gray);
|
||||||
|
|
||||||
|
let w = target_w;
|
||||||
|
let h = target_h;
|
||||||
let width_bytes = ((w + 7) >> 3) as u16;
|
let width_bytes = ((w + 7) >> 3) as u16;
|
||||||
|
|
||||||
let buf = &mut self.content;
|
let buf = &mut self.content;
|
||||||
|
|
@ -143,10 +154,10 @@ impl Job {
|
||||||
0x76,
|
0x76,
|
||||||
0x30,
|
0x30,
|
||||||
mode,
|
mode,
|
||||||
width_bytes as u8,
|
(width_bytes & 0xFF) as u8,
|
||||||
(width_bytes >> 8) as u8,
|
((width_bytes >> 8) & 0xFF) as u8,
|
||||||
h as u8,
|
(h & 0xFF) as u8,
|
||||||
(h >> 8) as u8,
|
((h >> 8) & 0xFF) as u8,
|
||||||
]);
|
]);
|
||||||
buf.extend_from_slice(&mono);
|
buf.extend_from_slice(&mono);
|
||||||
|
|
||||||
|
|
|
||||||
104
src/main.rs
104
src/main.rs
|
|
@ -5,60 +5,90 @@ use std::{io::Write, sync::Arc};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
Router,
|
Router,
|
||||||
|
body::{Body, Bytes},
|
||||||
extract::{Extension, Multipart},
|
extract::{Extension, Multipart},
|
||||||
http::StatusCode,
|
http::{Response, StatusCode, header},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::post,
|
routing::{get, post},
|
||||||
};
|
};
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::escpos::printer::Printer;
|
use crate::escpos::{errors::EscPosError, job::ImageOrientation, printer::Printer};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let printer = Arc::new(Mutex::new(Printer::new(380)));
|
let printer = Arc::new(Mutex::new(Printer::new(380)));
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", post(upload))
|
.route("/upload", post(upload))
|
||||||
|
.route("/print", get(print))
|
||||||
.layer(Extension(printer));
|
.layer(Extension(printer));
|
||||||
|
|
||||||
println!("Listening on http://127.0.0.1:8000");
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
|
||||||
axum::Server::bind(&"192.168.1.112:8000".parse().unwrap())
|
axum::serve(listener, app).await.unwrap();
|
||||||
.serve(app.into_make_service())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
async fn print(
|
||||||
|
Extension(printer): Extension<Arc<Mutex<Printer>>>,
|
||||||
|
) -> impl axum::response::IntoResponse {
|
||||||
|
let mut printer = printer.lock().await;
|
||||||
|
let bytes = match printer.export_job() {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(EscPosError::EmptyQueue) => {
|
||||||
|
println!("requested but no queue");
|
||||||
|
return (
|
||||||
|
StatusCode::NO_CONTENT,
|
||||||
|
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||||
|
Vec::new(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||||
|
Vec::new(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
StatusCode::OK,
|
||||||
|
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||||
|
bytes,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
async fn upload(
|
async fn upload(
|
||||||
|
Extension(printer): Extension<Arc<Mutex<Printer>>>,
|
||||||
mut multipart: Multipart,
|
mut multipart: Multipart,
|
||||||
Extension(state): Extension<Arc<Mutex<Printer>>>,
|
) -> Result<Response<Body>, StatusCode> {
|
||||||
) -> Result<impl IntoResponse, (StatusCode, &'static str)> {
|
let field = multipart
|
||||||
let mut field = match multipart.next_field().await {
|
.next_field()
|
||||||
Ok(Some(f)) => f,
|
.await
|
||||||
Ok(None) => return (StatusCode::BAD_REQUEST, "no multipart field").into_response(),
|
.map_err(|_| StatusCode::BAD_REQUEST)?
|
||||||
Err(_) => return (StatusCode::BAD_REQUEST, "multipart parse error").into_response(),
|
.ok_or(StatusCode::BAD_REQUEST)?;
|
||||||
};
|
let data = field
|
||||||
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
let data = match field.bytes().await {
|
let img: DynamicImage =
|
||||||
Ok(b) => b,
|
image::load_from_memory(&data).map_err(|_| StatusCode::UNSUPPORTED_MEDIA_TYPE)?;
|
||||||
Err(_) => {
|
|
||||||
return (StatusCode::INTERNAL_SERVER_ERROR, "read field bytes error").into_response();
|
println!("{}x{}", img.width(), img.height());
|
||||||
}
|
|
||||||
};
|
let mut printer = printer.lock().await;
|
||||||
|
let job = printer.new_job().unwrap();
|
||||||
let img: DynamicImage = match image::load_from_memory(data.chunk()) {
|
job.write_bitmap(&img, Some(ImageOrientation::Largest), None)
|
||||||
Ok(i) => i,
|
.unwrap();
|
||||||
Err(_) => {
|
job.ready = true;
|
||||||
return (StatusCode::UNSUPPORTED_MEDIA_TYPE, "image decode error").into_response();
|
|
||||||
}
|
// let mut file = std::fs::File::create("/dev/usb/lp0").unwrap();
|
||||||
};
|
// printer.print_job(&mut file).unwrap();
|
||||||
|
|
||||||
let s = state.lock().await;
|
Ok((StatusCode::OK).into_response())
|
||||||
let mut job = s.new_job().unwrap();
|
|
||||||
job.write_bitmap(&img, None, None);
|
|
||||||
|
|
||||||
let mut file = std::fs::File::create("/dev/usb/lp0").unwrap();
|
|
||||||
s.print_job(&mut file);
|
|
||||||
(StatusCode::OK).into_response()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue