|
|
# 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. |