This repository has been archived by the owner on Nov 30, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.go
161 lines (139 loc) · 4.03 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package main
import (
"context"
"database/sql"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
_ "github.com/mattn/go-sqlite3"
)
func main() {
if err := run(context.Background()); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func run(ctx context.Context) error {
// Parse command line flags.
dsn := flag.String("dsn", "", "datasource name")
addr := flag.String("addr", ":8080", "bind address")
flag.Parse()
if *dsn == "" {
return fmt.Errorf("flag required: -dsn DSN")
}
// Ensure app name, primary region & current region are both set via env vars.
if os.Getenv("FLY_APP_NAME") == "" {
return fmt.Errorf("FLY_APP_NAME must be set")
} else if os.Getenv("FLY_REGION") == "" {
return fmt.Errorf("FLY_REGION must be set")
} else if os.Getenv("FLY_PRIMARY_REGION") == "" {
return fmt.Errorf("FLY_PRIMARY_REGION must be set")
}
// Set "readonly" mode if this is a replica.
if !isPrimary() {
*dsn += "?mode=ro"
}
// Open local SQLite database connection.
db, err := sql.Open("sqlite3", *dsn)
if err != nil {
return fmt.Errorf("open database: %w", err)
}
defer db.Close()
// Build schema if this is the primary node.
if isPrimary() {
if _, err := db.Exec(`CREATE TABLE IF NOT EXISTS records (id INTEGER PRIMARY KEY, value TEXT NOT NULL)`); err != nil {
return fmt.Errorf("create table: %w", err)
}
}
// Start HTP server.
log.Printf("listening on %s", *addr)
return http.ListenAndServe(*addr, &Handler{db: db})
}
// Handler represents an HTTP handler.
type Handler struct {
db *sql.DB
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// We only support the root path.
if r.URL.Path != "/" {
http.Error(w, "Not found", http.StatusNotFound)
return
}
// Our endpoint only supports a GET to read data and POST to write data.
switch r.Method {
case "GET":
h.serveGet(w, r)
case "POST":
h.servePost(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
// serveGet returns a list of all records that have been created.
func (h *Handler) serveGet(w http.ResponseWriter, r *http.Request) {
// Fetch all records from the database.
rows, err := h.db.QueryContext(r.Context(), `SELECT id, value FROM records ORDER BY id`)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
// Read each row and print it to the response body.
var n int
for ; rows.Next(); n++ {
var id int
var value string
if err := rows.Scan(&id, &value); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Record #%d: %q\n", id, value)
}
if err := rows.Close(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Return a message if no records are found.
if n == 0 {
fmt.Fprintf(w, "No records found. Please POST to add one.\n")
return
}
}
// servePost creates a new record using the contents of the request body.
func (h *Handler) servePost(w http.ResponseWriter, r *http.Request) {
// If this is not the primary, redirect to the primary.
if !isPrimary() {
log.Printf("redirecting to primary")
w.Header().Set("fly-replay", "region="+os.Getenv("FLY_PRIMARY_REGION"))
return
}
// Read body from HTTP request.
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
} else if len(body) == 0 {
http.Error(w, "request body required", http.StatusBadRequest)
return
}
// Insert record into the database.
result, err := h.db.ExecContext(r.Context(), `INSERT INTO records (value) VALUES (?)`, string(body))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Report the record ID back to the user.
id, err := result.LastInsertId()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "Record #%d successfully inserted.\n", id)
}
// isPrimary returns true if the current region is the primary region.
func isPrimary() bool {
return os.Getenv("FLY_PRIMARY_REGION") == os.Getenv("FLY_REGION")
}