|
|
||
|---|---|---|
| demo | ||
| serve_zip | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE.md | ||
| README.md | ||
| rustfmt.toml | ||
README.md
tower-http-servezip
ServeZip is a Rust
Tower leaf service that serves files from a
given Zip file. The Zip file can either be specified by a Pathbuf or a static &[u8]; the latter is intended to allow you to embed your Zip file directly into
the binary, creating a complete standalone solution for a deployable web
service. You just need to supply the logic.
Usage
The demo folder contains a somewhat complete implementation, including a demo
zip file. TODO Full indexing is not currently implemented. The demo can be started via:
cargo run -p demo -- --zip demo/assets/demo.zip
Valid targets are: http://localhost:8001/index.html, hello.txt, and
docs/just-a-file.txt. These demonstrate the basics of mime-types,
content-length, and file lookup. Any other file should return a simple 404.
Internals
Interally, this is a mess. It was a project-based learning exercise, and like so much of my career now I'm not sure I could reimplement it without the book open and the lots of example code.
I'm mostly unhappy with the fact that I have to make a copy of whatever stream I'm sending out over the wire, rather than pulling it sequentially from the Zip file while it's decompressing. This is mostly intended to supply a PWA and its back-end logic in a single package; it shouldn't be doing work that often, but it still annoys me that I have to do it more than once while (briefly) wasting memory. I wish I understond Rust well enough to say "Either let me keep wasting the memory and keep the performance, or let me take the performance hit without wasting the memory."
But I'm not that good at Rust quite yet.
Lessons learned
There's a lot going on here, and I'm still not entirely thrilled with how it
went down. I am proud of figuring out how to use the NewType pattern to create
an implementation of ServeZip that works with Arc[u8], which is the data type
I pass around containing the compresed data. Reading
ServeDir's
source code opened my eyes to a lot of the little bits, like separating the
Config, the Builder, the Service; I have some old habits about keeping
everything really close at hand, but Rust makes the implentation so smooth and
efficient that I'm really starting to appreciate how it works.
This implementation just unpacks the Zip file every time. I was hoping for something smarter, maybe a way to just keep separate ZipCursors alive and re-usable, but for now this will have to do. It's not bad, it just feels like there's a lot of Rust to learn before it's much more idiomatic.
Huge shoutout to @bearcave [Bearcove]] for the sans-io implementation of Zip in Rust. I learned a lot about sans-io in the process, even if I didn't end up using much of it this time.
Todo
- Implement full indexing
- Implement ServeDir's use of http::body, instead of axum
- Read ServeDir much more closely in order to understand what it's doing internally; all the security and safety issues, plus performance
- Reduce the amount of copying. Oy, the copying
- Put an upper limit on Zip size, configurable from the command line
- Provide a
build.rsfile in the demo to show howinclude_bytes!works
License
Tower-http-servezip is Copyright Elf M. Sternberg (c) 2026, and licensed under the original MIT License. A copy of the license file is included in the root folder.
An all-human effort.
Every last line of this code I typed myself. No AI-provided code included. I wouldn't have learned anything otherwise. Just sayin'.