├── comment └── handler.go ├── go.mod ├── handler └── handler.go ├── invoice └── handler.go ├── main.go ├── middleware ├── auth.go ├── generic.go ├── logging.go └── middleware.go ├── monster ├── handler.go └── monster.go └── router.go /comment/handler.go: -------------------------------------------------------------------------------- 1 | package comment 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type Handler struct{} 9 | 10 | func NewHandler() *Handler { 11 | return &Handler{} 12 | } 13 | 14 | func (h *Handler) Create(w http.ResponseWriter, r *http.Request) { 15 | fmt.Println("Can't create comment, no user ID") 16 | w.WriteHeader(http.StatusInternalServerError) 17 | w.Write([]byte(http.StatusText(http.StatusInternalServerError))) 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dreamsofcode-io/nethttp 2 | 3 | go 1.22.1 4 | -------------------------------------------------------------------------------- /handler/handler.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import "net/http" 4 | 5 | func FindByID(w http.ResponseWriter, r *http.Request) { 6 | if r.Method == http.MethodGet { 7 | // Handle get 8 | } else if r.Method == http.MethodPost { 9 | // Handle post 10 | } else if r.Method == http.MethodPut { 11 | // Handle put 12 | } 13 | } 14 | 15 | func GetLatest(w http.ResponseWriter, r *http.Request) { 16 | } 17 | -------------------------------------------------------------------------------- /invoice/handler.go: -------------------------------------------------------------------------------- 1 | package invoice 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | type Handler struct{} 11 | 12 | func (h *Handler) Create(w http.ResponseWriter, r *http.Request) { 13 | w.WriteHeader(http.StatusCreated) 14 | w.Write([]byte("Invoice created!")) 15 | } 16 | 17 | func (h *Handler) FindByID(w http.ResponseWriter, r *http.Request) { 18 | monster, exists := loadInvoices()[r.PathValue("id")] 19 | if !exists { 20 | w.WriteHeader(http.StatusNotFound) 21 | return 22 | } 23 | json.NewEncoder(w).Encode(monster) 24 | } 25 | 26 | func (h *Handler) UpdateByID(w http.ResponseWriter, r *http.Request) { 27 | w.Write([]byte("Invoice updated!")) 28 | } 29 | 30 | func (h *Handler) DeleteByID(w http.ResponseWriter, r *http.Request) { 31 | w.Write([]byte("Invoice deleted!")) 32 | } 33 | 34 | func (h *Handler) PatchByID(w http.ResponseWriter, r *http.Request) { 35 | } 36 | 37 | func (h *Handler) Options(w http.ResponseWriter, r *http.Request) { 38 | } 39 | 40 | type Invoice struct { 41 | ID uint `json:"id"` 42 | Name string `json:"name"` 43 | Amount uint `json:"amount"` 44 | Timestamp time.Time `json:"timestamp"` 45 | } 46 | 47 | var invoices []Invoice = []Invoice{ 48 | {ID: 1, Name: "Invoice #1", Amount: 100, Timestamp: time.Now().Add(-24 * time.Hour)}, 49 | {ID: 2, Name: "Invoice #2", Amount: 150, Timestamp: time.Now().Add(-48 * time.Hour)}, 50 | {ID: 3, Name: "Invoice #3", Amount: 200, Timestamp: time.Now().Add(-72 * time.Hour)}, 51 | {ID: 4, Name: "Invoice #4", Amount: 75, Timestamp: time.Now().Add(-96 * time.Hour)}, 52 | {ID: 5, Name: "Invoice #5", Amount: 300, Timestamp: time.Now().Add(-120 * time.Hour)}, 53 | } 54 | 55 | func loadInvoices() map[string]Invoice { 56 | res := make(map[string]Invoice, len(invoices)) 57 | 58 | for _, x := range invoices { 59 | res[strconv.Itoa(int(x.ID))] = x 60 | } 61 | 62 | return res 63 | } 64 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func handleOther(w http.ResponseWriter, r *http.Request) { 10 | log.Println("Received a non domain request") 11 | w.Write([]byte("Hello, stranger...")) 12 | } 13 | 14 | func handle(w http.ResponseWriter, r *http.Request) { 15 | log.Println("Received a request at my domain") 16 | w.Write([]byte("Hello, Domain name!")) 17 | } 18 | 19 | func main() { 20 | router := http.NewServeMux() 21 | router.HandleFunc("/", handleOther) 22 | router.HandleFunc("dreamsofcode.foo/", handle) 23 | 24 | server := http.Server{ 25 | Addr: ":8080", 26 | Handler: router, 27 | } 28 | 29 | fmt.Println("Server listening on port :8080") 30 | server.ListenAndServe() 31 | } 32 | -------------------------------------------------------------------------------- /middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | const AuthUserID = "middleware.auth.userID" 11 | 12 | func writeUnauthed(w http.ResponseWriter) { 13 | w.WriteHeader(http.StatusUnauthorized) 14 | w.Write([]byte(http.StatusText(http.StatusUnauthorized))) 15 | } 16 | 17 | func IsAuthenticated(next http.Handler) http.Handler { 18 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 19 | authorization := r.Header.Get("Authorization") 20 | 21 | // Check that the header begins with a prefix of Bearer 22 | if !strings.HasPrefix(authorization, "Bearer ") { 23 | writeUnauthed(w) 24 | return 25 | } 26 | 27 | // Pull out the token 28 | encodedToken := strings.TrimPrefix(authorization, "Bearer ") 29 | 30 | // Decode the token from base 64 31 | token, err := base64.StdEncoding.DecodeString(encodedToken) 32 | if err != nil { 33 | writeUnauthed(w) 34 | return 35 | } 36 | 37 | // We're just assuming a valid base64 token is a valid user id. 38 | userID := string(token) 39 | fmt.Println("userID:", userID) 40 | 41 | next.ServeHTTP(w, r) 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /middleware/generic.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | func EnsureAdmin(next http.Handler) http.Handler { 10 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | log.Println("Checking if user is admin") 12 | if !strings.Contains(r.Header.Get("Authorization"), "Admin") { 13 | w.WriteHeader(http.StatusUnauthorized) 14 | w.Write([]byte(http.StatusText(http.StatusUnauthorized))) 15 | return 16 | } 17 | next.ServeHTTP(w, r) 18 | }) 19 | } 20 | 21 | func LoadUser(next http.Handler) http.Handler { 22 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 | log.Println("Loading user") 24 | next.ServeHTTP(w, r) 25 | }) 26 | } 27 | 28 | func AllowCors(next http.Handler) http.Handler { 29 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 30 | log.Println("Enabling CORS") 31 | next.ServeHTTP(w, r) 32 | }) 33 | } 34 | 35 | func CheckPermissions(next http.Handler) http.Handler { 36 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 | log.Println("Checking Permissions") 38 | next.ServeHTTP(w, r) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /middleware/logging.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | type wrappedWriter struct { 10 | http.ResponseWriter 11 | statusCode int 12 | } 13 | 14 | func (w *wrappedWriter) WriteHeader(statusCode int) { 15 | w.ResponseWriter.WriteHeader(statusCode) 16 | w.statusCode = statusCode 17 | } 18 | 19 | func Logging(next http.Handler) http.Handler { 20 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 21 | start := time.Now() 22 | 23 | wrapped := &wrappedWriter{ 24 | ResponseWriter: w, 25 | statusCode: http.StatusOK, 26 | } 27 | 28 | next.ServeHTTP(wrapped, r) 29 | 30 | log.Println(wrapped.statusCode, r.Method, r.URL.Path, time.Since(start)) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import "net/http" 4 | 5 | type Middleware func(http.Handler) http.Handler 6 | 7 | func CreateStack(xs ...Middleware) Middleware { 8 | return func(next http.Handler) http.Handler { 9 | for i := len(xs) - 1; i >= 0; i-- { 10 | x := xs[i] 11 | next = x(next) 12 | } 13 | 14 | return next 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /monster/handler.go: -------------------------------------------------------------------------------- 1 | package monster 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | type Handler struct{} 10 | 11 | func (h *Handler) Create(w http.ResponseWriter, r *http.Request) { 12 | w.WriteHeader(http.StatusCreated) 13 | log.Println("received request to create a monster") 14 | w.Write([]byte("Monster created!")) 15 | } 16 | 17 | func (h *Handler) FindByID(w http.ResponseWriter, r *http.Request) { 18 | log.Println("handling READ request - Method:", r.Method) 19 | monster, exists := loadMonsters()[r.PathValue("id")] 20 | if !exists { 21 | w.WriteHeader(http.StatusNotFound) 22 | return 23 | } 24 | json.NewEncoder(w).Encode(monster) 25 | } 26 | 27 | func (h *Handler) UpdateByID(w http.ResponseWriter, r *http.Request) { 28 | log.Println("Handling UPDATE request - Method:", r.Method) 29 | } 30 | 31 | func (h *Handler) DeleteByID(w http.ResponseWriter, r *http.Request) { 32 | log.Println("received DELETE request for monster") 33 | } 34 | 35 | func (h *Handler) PatchByID(w http.ResponseWriter, r *http.Request) { 36 | } 37 | 38 | func (h *Handler) Options(w http.ResponseWriter, r *http.Request) { 39 | } 40 | -------------------------------------------------------------------------------- /monster/monster.go: -------------------------------------------------------------------------------- 1 | package monster 2 | 3 | import "strconv" 4 | 5 | type Monster struct { 6 | ID uint `json:"id"` 7 | Name string `json:"name"` 8 | Powers []string `json:"powers"` 9 | } 10 | 11 | func monsters() []Monster { 12 | return []Monster{ 13 | { 14 | ID: 1, 15 | Name: "Dracula", 16 | Powers: []string{"Immortality", "Shape-shifting", "Mind Control"}, 17 | }, 18 | { 19 | ID: 2, 20 | Name: "Frankenstein", 21 | Powers: []string{"Superhuman Strength", "Endurance"}, 22 | }, 23 | { 24 | ID: 3, 25 | Name: "Werewolf", 26 | Powers: []string{"Shape-shifting", "Enhanced Senses", "Regeneration"}, 27 | }, 28 | { 29 | ID: 4, 30 | Name: "Zombie", 31 | Powers: []string{"Undead Physiology", "Immunity to Pain"}, 32 | }, 33 | { 34 | ID: 5, 35 | Name: "Mummy", 36 | Powers: []string{"Immortality", "Control over Sand"}, 37 | }, 38 | } 39 | } 40 | 41 | func loadMonsters() map[string]Monster { 42 | monsters := monsters() 43 | res := make(map[string]Monster, len(monsters)) 44 | 45 | for _, x := range monsters { 46 | res[strconv.Itoa(int(x.ID))] = x 47 | } 48 | 49 | return res 50 | } 51 | -------------------------------------------------------------------------------- /router.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/dreamsofcode-io/nethttp/monster" 7 | ) 8 | 9 | func loadRoutes(router *http.ServeMux) { 10 | handler := &monster.Handler{} 11 | 12 | router.HandleFunc("POST /monster", handler.Create) 13 | router.HandleFunc("PUT /monster/{id}", handler.UpdateByID) 14 | router.HandleFunc("GET /monster/{id}", handler.FindByID) 15 | router.HandleFunc("DELETE /monster/{id}", handler.DeleteByID) 16 | } 17 | --------------------------------------------------------------------------------