-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
133 lines (111 loc) · 2.35 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
package main
import (
"database/sql"
"fmt"
"io"
"math/rand"
"net/http"
_ "github.com/mattn/go-sqlite3"
)
const urlChars = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
// todo: cache will overfill and is subject to easy DoS attack
var cache map[string]*[]byte
var db *sql.DB
func main() {
cache = make(map[string]*[]byte)
var err error
db, err = sql.Open("sqlite3", "./data.db")
if err != nil {
panic(err)
}
_, err = db.Exec("create table if not exists store (id text, val blob);")
if err != nil {
panic(err)
}
err = http.ListenAndServe("0.0.0.0:3000", http.HandlerFunc(handler))
if err != nil {
panic(err)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
err := recover()
if err != nil {
errType, ok := err.(error)
if ok {
fmt.Printf("request panic: %w\n", errType)
} else {
panic(err)
}
}
}()
if r.Method == "GET" {
handleGet(w, r)
} else if r.Method == "POST" && r.URL.Path == "/" {
handlePost(w, r)
} else {
w.WriteHeader(http.StatusNotFound)
}
}
func handleGet(w http.ResponseWriter, r *http.Request) {
key := r.URL.Path[1:]
println("try get:", key)
// try cache lookup first
res, ok := cache[key]
if ok {
w.WriteHeader(http.StatusOK)
_, err := w.Write(*res)
if err != nil {
panic(err)
}
return
}
rows, err := db.Query("select val from store where id = $1;", key)
if err != nil {
panic(err)
}
defer rows.Close()
next := rows.Next()
if !next {
w.WriteHeader(http.StatusNotFound)
return
}
var val []byte
err = rows.Scan(&val)
if err != nil {
panic(err)
}
w.WriteHeader(http.StatusOK)
_, err = w.Write(val)
if err != nil {
panic(err)
}
cache[key] = &val
}
func handlePost(w http.ResponseWriter, r *http.Request) {
id := randomString(8)
defer r.Body.Close()
data, err := io.ReadAll(r.Body)
if err != nil {
panic(err)
}
res, err := db.Exec("insert into store (id, val) values ($1, $2);", id, data)
if err != nil {
panic(err)
}
if rowsAffected, err := res.RowsAffected(); rowsAffected < 1 {
panic(err)
}
w.WriteHeader(http.StatusCreated)
w.Write([]byte(id))
}
func randomString(length int) string {
if length < 0 {
panic(fmt.Errorf("randomString: invalid length %d", length))
}
val := make([]rune, length)
for i := 0; i < length; i++ {
val[i] = rune(urlChars[rand.Intn(len(urlChars))])
}
return string(val)
}