diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..0e2595e --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,18 @@ +Copyright (c) 2026 Elf M. Sternberg + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..84bc783 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# 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'.