# Ratchet An example web application in Golang. https://lets-go.alexedwards.net/sample/02.09-serving-static-files.html ## Project Structure Loosely inspired by the organization of [WTFDial](https://github.com/benbjohnson/wtf?tab=readme-ov-file#project-structure), - Application domain types reside in the project root (User, UserService, etc) - Implementations of the application domain reside in the subpackages `sqlite`, `http`, etc. - Everything is tied together in the `cmd` subpackages ### Application Domain This is project is an Indie reader inspired by [Feedi](https://github.com/facundoolano/feedi). ### Implementation Subpackages The subpackages function as an adapter between our domain and the technology used to implement the domain. For example `sqlite.FeedService` implements the `ratchet.FeedService` using SQLite. Subpackages ideally **SHOULD NOT** know about one another and **SHOULD** communicate in terms of the application domain. A special `mock` packages can be used to create simple mocks for each application domain interface. This will allow each subpackages unit tests to share a common set of mocks so layers can be tested in isolatation. ### Binary Packages With loosely coupledc subpackages the application is wired together in the `cmd` subpackages to produce a final binary. The `cmd` packages are ultimately the interface between the application domain and the operator. Configuration of types and any CLI flags *SHOULD* live in these packages. ## Development You can build `ratchet` locally by cloning the respository, then run `make` `go install ./cmd/...` The `ratchetd` cmd binary uses Oauth so you will need to create a new Oauth App. The vlaue of the authorization callback must be the hostname and IP at which clients can access the `ratchetd` server. ## Additional Resources - [Content Range Requests](https://web.archive.org/web/20230918195519/https://benramsey.com/blog/2008/05/206-partial-content-and-range-requests/) - [HTTP 204 and 205 Status Codes](https://web.archive.org/web/20230918193536/https://benramsey.com/blog/2008/05/http-status-204-no-content-and-205-reset-content/) - [How to Disable FileServer Directory Listings](https://www.alexedwards.net/blog/disable-http-fileserver-directory-listings) - [Understand Mutexs in Go](https://www.alexedwards.net/blog/understanding-mutexes) - [Structured Logging in Go with log/slog](https://pkg.go.dev/log/slog) - [Exit Codes with special meaning](https://tldp.org/LDP/abs/html/exitcodes.html) - [Organizing Database Access in Go](https://www.alexedwards.net/blog/organising-database-access) ### Go http.FileServer Supports If-Modified-Since and Last-Modified headers ``` curl -i -H "If-Modified-Since: Thu, 04 May 2017 13:07:52 GMT" http://localhost:5001/static/img/logo.png HTTP/1.1 304 Not Modified Last-Modified: Thu, 04 May 2017 13:07:52 GMT Date: Sun, 12 Jan 2025 14:26:06 GMT ``` Supports Range Requests and 206 Partial Content responses. ``` curl -i -H "Range: bytes=100-199" --output - http://localhost:5001/static/img/logo.png HTTP/1.1 206 Partial Content Accept-Ranges: bytes Content-Length: 100 Content-Range: bytes 100-199/1075 Content-Type: image/png Last-Modified: Thu, 04 May 2017 13:07:52 GMT Date: Sun, 12 Jan 2025 14:18:32 GMT ``` - The `Content-Type` is automatically set from the file extension using the `mime.TypeByExtension()` function. You can add your own custom extensions and content types using the `mime.AddExtensionType()` function if necessary. - Sometimes you might want to serve a single file from within a handler. For this there’s the `http.ServeFile()` function, which you can use like so: ```go func downloadHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "./ui/static/file.zip") } ``` Warning: http.ServeFile() does not automatically sanitize the file path. If you’re constructing a file path from untrusted user input, to avoid directory traversal attacks you must sanitize the input with filepath.Clean() before using it.