From 5faf40c2ab3c70e6b1553bbb4bced106e07b1c9f Mon Sep 17 00:00:00 2001 From: Justin Hawkins Date: Mon, 10 Mar 2025 14:46:06 +1030 Subject: [PATCH] Initial checkin --- README.md | 14 +++++++ common/db/db.go | 22 +++++++++++ go.mod | 3 ++ pathvalues/1-basic/main.go | 36 ++++++++++++++++++ pathvalues/2-slightly-less-basic/main.go | 42 +++++++++++++++++++++ pathvalues/3-middleware/main.go | 48 ++++++++++++++++++++++++ 6 files changed, 165 insertions(+) create mode 100644 README.md create mode 100644 common/db/db.go create mode 100644 go.mod create mode 100644 pathvalues/1-basic/main.go create mode 100644 pathvalues/2-slightly-less-basic/main.go create mode 100644 pathvalues/3-middleware/main.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..0077ca7 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +## path values + +The three examples to go with the blog post at https://hawkins.id.au/posts/go-mux/path-values + +These should all work identically, each just shows a different way of achieving +the same thing - extracing a user id from the path and making the user struct +(instantiated from some sort of database, faked in common/db/db.go for this +example) available to the route handler. + + go run go-mux/pathvalues/1-basic + + go run go-mux/pathvalues/2-slightly-less-basic + + go run go-mux/pathvalues/3-middleware \ No newline at end of file diff --git a/common/db/db.go b/common/db/db.go new file mode 100644 index 0000000..7e61f5d --- /dev/null +++ b/common/db/db.go @@ -0,0 +1,22 @@ +package db + +import "fmt" + +type User struct { + ID int +} + +func LoadUser(id int) (User, error) { + if id < 0 { + return User{}, fmt.Errorf("invalid negative user id '%d'", id) + } + if id > 100 { + return User{}, fmt.Errorf("invalid user id '%d'", id) + } + + return User{ID: id}, nil +} + +func (u User) String() string { + return fmt.Sprintf("this user is id: %d", u.ID) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..10407d8 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module go-mux + +go 1.24.1 diff --git a/pathvalues/1-basic/main.go b/pathvalues/1-basic/main.go new file mode 100644 index 0000000..5c0da78 --- /dev/null +++ b/pathvalues/1-basic/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "go-mux/common/db" + "log/slog" + "net/http" + "strconv" +) + +func main() { + http.HandleFunc("/user/{userId}/view", viewUser) + + slog.Info("starting web service on :8080") + panic(http.ListenAndServe(":8080", nil)) +} + +func viewUser(w http.ResponseWriter, r *http.Request) { + userID := r.PathValue("userId") + userIDint, err := strconv.Atoi(userID) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("bad user id")) + return + } + user, err := db.LoadUser(userIDint) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf("could not load user: %s", err.Error()))) + return + } + + slog.Info("loaded user") + // show the user id + w.Write([]byte(fmt.Sprintf("loaded user %s", user))) +} diff --git a/pathvalues/2-slightly-less-basic/main.go b/pathvalues/2-slightly-less-basic/main.go new file mode 100644 index 0000000..ac29987 --- /dev/null +++ b/pathvalues/2-slightly-less-basic/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "go-mux/common/db" + "log/slog" + "net/http" + "strconv" +) + +func main() { + http.HandleFunc("/user/{userId}/view", viewUser) + + slog.Info("starting web service on :8080") + panic(http.ListenAndServe(":8080", nil)) +} + +func viewUser(w http.ResponseWriter, r *http.Request) { + user, err := loadUserFromRequest(r) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf("could not load user: %s", err.Error()))) + return + } + + slog.Info("loaded user") + // show the user id + w.Write([]byte(fmt.Sprintf("loaded user %s", user))) +} + +func loadUserFromRequest(r *http.Request) (db.User, error) { + userID := r.PathValue("userId") + userIDint, err := strconv.Atoi(userID) + if err != nil { + return db.User{}, err + } + user, err := db.LoadUser(userIDint) + if err != nil { + return db.User{}, err + } + return user, nil +} diff --git a/pathvalues/3-middleware/main.go b/pathvalues/3-middleware/main.go new file mode 100644 index 0000000..876e343 --- /dev/null +++ b/pathvalues/3-middleware/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + "fmt" + "go-mux/common/db" + "log/slog" + "net/http" + "strconv" +) + +var userContextKey = "user" + +func main() { + http.Handle("/user/{userId}/view", getUserFromPath(http.HandlerFunc(viewUser))) + + slog.Info("starting web service on :8080") + panic(http.ListenAndServe(":8080", nil)) +} + +func viewUser(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value(userContextKey).(db.User) + + slog.Info("loaded user") + // show the user id + w.Write([]byte(fmt.Sprintf("loaded user %s", user.String()))) +} + +func getUserFromPath(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.PathValue("userId") + userIDint, err := strconv.Atoi(userID) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("bad user id")) + return + } + user, err := db.LoadUser(userIDint) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(fmt.Sprintf("could not load user: %s", err.Error()))) + return + } + newContext := context.WithValue(r.Context(), userContextKey, user) + r = r.Clone(newContext) + next.ServeHTTP(w, r) + }) +}