You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ratchet/README.md

87 lines
3.9 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 theres 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 youre constructing a file path from untrusted user input, to avoid directory traversal attacks you must sanitize the input with filepath.Clean() before using it.