tower-http-servezip/README.md

81 lines
3.6 KiB
Markdown

# tower-http-servezip
ServeZip is a [Rust](https://rust-lang.org/)
[Tower](https://github.com/tower-rs/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:
``` sh
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](https://github.com/tower-rs/tower-http/tree/main/tower-http/src/services/fs/serve_dir)
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](https://github.com/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.rs` file in the demo to show how `include_bytes!` works
## License
Tower-http-servezip is Copyright [Elf M. Sternberg](https://elfsternberg.com)
(c) 2026, and licensed under the original [MIT License](./LICENSE.md). 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'.