Skip to content

Commit 643f58a

Browse files
committed
chore(allsrv): add implementation of allsrv server
This server represents a server with minimum abstraction. It takes the popular convention from the Matt Ryer post and implements it in an intensely terse manner. Explore this allsrv pkg and answer the following questions: Question: What stands out? Question: What do you find favorable? Question: What do you find nasueating? Refs: [How I write HTTP Services After 8 Years - Matt Ryer](https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html)
0 parents  commit 643f58a

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

allsrv/server.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package allsrv
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"log"
7+
"net/http"
8+
9+
"github.com/gofrs/uuid"
10+
)
11+
12+
type Server struct {
13+
db *inmemDB
14+
15+
user, pass string
16+
}
17+
18+
func NewServer(db *inmemDB, user, pass string) *Server {
19+
s := Server{
20+
db: db,
21+
user: user,
22+
pass: pass,
23+
}
24+
s.routes()
25+
return &s
26+
}
27+
28+
func (s *Server) routes() {
29+
http.Handle("POST /foo", http.HandlerFunc(s.createFoo))
30+
http.Handle("GET /foo", http.HandlerFunc(s.readFoo))
31+
http.Handle("PUT /foo", http.HandlerFunc(s.updateFoo))
32+
http.Handle("DELETE /foo", http.HandlerFunc(s.delFoo))
33+
}
34+
35+
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
36+
http.DefaultServeMux.ServeHTTP(w, r)
37+
}
38+
39+
type Foo struct {
40+
ID string `json:"id" gorm:"id"`
41+
Name string `json:"name" gorm:"name"`
42+
Note string `json:"note" gorm:"note"`
43+
}
44+
45+
func (s *Server) createFoo(w http.ResponseWriter, r *http.Request) {
46+
if user, pass, ok := r.BasicAuth(); !(ok && user == s.user && pass == s.pass) {
47+
w.WriteHeader(http.StatusUnauthorized)
48+
return
49+
}
50+
51+
var f Foo
52+
if err := json.NewDecoder(r.Body).Decode(&f); err != nil {
53+
w.WriteHeader(http.StatusForbidden)
54+
return
55+
}
56+
57+
newFooID, err := s.db.createFoo(f)
58+
if err != nil {
59+
w.WriteHeader(http.StatusInternalServerError)
60+
return
61+
}
62+
63+
f, err = s.db.readFoo(newFooID)
64+
if err != nil {
65+
w.WriteHeader(http.StatusNotFound)
66+
return
67+
}
68+
69+
w.WriteHeader(http.StatusCreated)
70+
if err := json.NewEncoder(w).Encode(f); err != nil {
71+
log.Printf("unexpected error writing json value to response body: " + err.Error())
72+
}
73+
}
74+
75+
func (s *Server) readFoo(w http.ResponseWriter, r *http.Request) {
76+
if user, pass, ok := r.BasicAuth(); !(ok && user == s.user && pass == s.pass) {
77+
w.WriteHeader(http.StatusUnauthorized)
78+
return
79+
}
80+
81+
f, err := s.db.readFoo(r.URL.Query().Get("id"))
82+
if err != nil {
83+
w.WriteHeader(http.StatusNotFound)
84+
return
85+
}
86+
87+
if err := json.NewEncoder(w).Encode(f); err != nil {
88+
log.Printf("unexpected error writing json value to response body: " + err.Error())
89+
}
90+
}
91+
92+
func (s *Server) updateFoo(w http.ResponseWriter, r *http.Request) {
93+
if user, pass, ok := r.BasicAuth(); !(ok && user == s.user && pass == s.pass) {
94+
w.WriteHeader(http.StatusUnauthorized)
95+
return
96+
}
97+
98+
var f Foo
99+
if err := json.NewDecoder(r.Body).Decode(&f); err != nil {
100+
w.WriteHeader(http.StatusForbidden)
101+
return
102+
}
103+
104+
if err := s.db.updateFoo(f); err != nil {
105+
w.WriteHeader(http.StatusInternalServerError)
106+
return
107+
}
108+
}
109+
110+
func (s *Server) delFoo(w http.ResponseWriter, r *http.Request) {
111+
if user, pass, ok := r.BasicAuth(); !(ok && user == s.user && pass == s.pass) {
112+
w.WriteHeader(http.StatusUnauthorized)
113+
return
114+
}
115+
116+
if err := s.db.delFoo(r.URL.Query().Get("id")); err != nil {
117+
w.WriteHeader(http.StatusNotFound)
118+
return
119+
}
120+
}
121+
122+
type inmemDB struct {
123+
m []Foo
124+
}
125+
126+
func (db *inmemDB) createFoo(f Foo) (string, error) {
127+
f.ID = uuid.Must(uuid.NewV4()).String()
128+
129+
for _, existing := range db.m {
130+
if f.Name == existing.Name {
131+
return "", errors.New("foo " + f.Name + " exists")
132+
}
133+
}
134+
135+
db.m = append(db.m, f)
136+
137+
return f.ID, nil
138+
}
139+
140+
func (db *inmemDB) readFoo(id string) (Foo, error) {
141+
for _, f := range db.m {
142+
if id == f.ID {
143+
return f, nil
144+
}
145+
}
146+
return Foo{}, errors.New("foo not found for id: " + id)
147+
}
148+
149+
func (db *inmemDB) updateFoo(f Foo) error {
150+
for i, existing := range db.m {
151+
if f.ID == existing.ID {
152+
db.m[i] = f
153+
return nil
154+
}
155+
}
156+
return errors.New("foo not found for id: " + f.ID)
157+
}
158+
159+
func (db *inmemDB) delFoo(id string) error {
160+
for i, f := range db.m {
161+
if id == f.ID {
162+
db.m = append(db.m[:i], db.m[i+1:]...)
163+
}
164+
}
165+
return errors.New("foo not found for id: " + id)
166+
}

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module mess
2+
3+
go 1.22
4+
5+
require github.com/gofrs/uuid v4.4.0+incompatible

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
2+
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=

0 commit comments

Comments
 (0)