Skip to content

Instantly share code, notes, and snippets.

@win-t
Last active March 17, 2019 01:38
Show Gist options
  • Save win-t/8a243301bd227cca6135374cf94d9e98 to your computer and use it in GitHub Desktop.
Save win-t/8a243301bd227cca6135374cf94d9e98 to your computer and use it in GitHub Desktop.
Example usage of go-router and go-middleware
package main
import (
"encoding/json"
"net/http"
"sort"
"strconv"
"sync"
"github.com/payfazz/go-middleware"
"github.com/payfazz/go-middleware/common"
"github.com/payfazz/go-middleware/common/kv"
"github.com/payfazz/go-router/method"
"github.com/payfazz/go-router/path"
"github.com/payfazz/go-router/segment"
)
func main() {
handler := middleware.Compile(
common.BasicPack(),
// force clean path
path.WithTrailingSlash,
path.H{
"/phonebooks": method.H{
"GET": getAllHandler(),
"POST": postHandler(),
}.C(),
"/phonebooks/:id": middleware.Compile(
// don't forget to use segment.MustEnd
// because we only want to handle last part of segment
// so request like phonebooks/10/a/b/c/d will not go here
segment.MustEnd,
method.H{
"GET": getByIDHandler(),
"PUT": putHandler(),
"DELETE": deleteByIDHandler(),
}.C(),
),
}.C(),
)
if err := http.ListenAndServe(":8080", handler); err != nil {
panic(err)
}
}
// handlers
func getAllHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
storage.mu.RLock()
defer storage.mu.RUnlock()
ret := make([]*Data, 0)
for _, v := range storage.data {
ret = append(ret, v)
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].ID < ret[j].ID
})
writeJSON(w, http.StatusOK, ret)
}
}
func getByIDHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ret := func() *Data {
storage.mu.RLock()
defer storage.mu.RUnlock()
idStr := segment.Param(r, "id")
id, err := strconv.Atoi(idStr)
if err != nil {
return nil
}
return storage.data[id]
}()
if ret == nil {
w.WriteHeader(http.StatusNotFound)
return
}
writeJSON(w, http.StatusOK, ret)
}
}
func postHandler() http.HandlerFunc {
return middleware.Compile(
authMiddleware(),
dataParserMiddleware(),
func(w http.ResponseWriter, r *http.Request) {
id := func() int {
storage.mu.RLock()
defer storage.mu.RUnlock()
for {
id := nextID
nextID++
if _, found := storage.data[id]; id != 0 && !found {
return id
}
}
}()
storage.mu.Lock()
defer storage.mu.Unlock()
data := kv.MustGet(r, "data").(*Data)
data.ID = id
storage.data[id] = data
writeJSON(w, http.StatusCreated, data)
},
)
}
func putHandler() http.HandlerFunc {
return middleware.Compile(
authMiddleware(),
dataParserMiddleware(),
func(w http.ResponseWriter, r *http.Request) {
id := func() int {
idStr := segment.Param(r, "id")
id, _ := strconv.Atoi(idStr)
return id
}()
if id == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
storage.mu.Lock()
defer storage.mu.Unlock()
data := kv.MustGet(r, "data").(*Data)
data.ID = id
storage.data[id] = data
writeJSON(w, http.StatusCreated, data)
},
)
}
func deleteByIDHandler() http.HandlerFunc {
return middleware.Compile(
authMiddleware(),
func(w http.ResponseWriter, r *http.Request) {
id := func() int {
idStr := segment.Param(r, "id")
id, _ := strconv.Atoi(idStr)
return id
}()
if id == 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
storage.mu.Lock()
defer storage.mu.Unlock()
if _, found := storage.data[id]; !found {
w.WriteHeader(http.StatusNotFound)
return
}
delete(storage.data, id)
w.WriteHeader(http.StatusNoContent)
},
)
}
// middlewares
// simple authMiddleware that only accept
// user "admin" with password "passwd"
func authMiddleware() func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, _ := r.BasicAuth()
if user == "admin" && pass == "passwd" {
next(w, r)
} else {
w.WriteHeader(http.StatusUnauthorized)
w.Header().Set("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
}
}
}
}
func dataParserMiddleware() func(http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data := func() *Data {
var data *Data
err := json.NewDecoder(r.Body).Decode(&data)
if err != nil {
return nil
}
return data
}()
if data == nil {
w.WriteHeader(http.StatusUnprocessableEntity)
} else {
kv.Set(r, "data", data)
next(w, r)
}
}
}
}
// data
type Data struct {
ID int
Name string
PhoneNumber string
}
var (
nextID = 1
storage = struct {
mu sync.RWMutex
data map[int]*Data
}{
data: map[int]*Data{
1: &Data{
ID: 1,
Name: "Alice",
PhoneNumber: "000-001",
},
2: &Data{
ID: 2,
Name: "Bob",
PhoneNumber: "000-002",
},
3: &Data{
ID: 3,
Name: "Charlie",
PhoneNumber: "000-003",
},
},
}
)
// helper
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
encoder.Encode(data)
}
@win-t
Copy link
Author

win-t commented May 10, 2018

NOTE, I don't handler race condition on storage map, in production, you should use mutex when there are multiple goroutine writer/reader to map

EDIT:
I already update it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment