81 lines
3.6 KiB
Markdown
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'.
|