main.go
3.31 KB
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
//
// Versions
// ========
// This example demonstrates the use of the render subpackage and its
// render.Presenter interface to transform a handler response to easily
// handle API versioning.
//
package main
import (
"context"
"errors"
"fmt"
"math/rand"
"net/http"
"time"
"github.com/meiqia/chi"
"github.com/meiqia/chi/_examples/versions/data"
"github.com/meiqia/chi/_examples/versions/presenter/v1"
"github.com/meiqia/chi/_examples/versions/presenter/v2"
"github.com/meiqia/chi/_examples/versions/presenter/v3"
"github.com/meiqia/chi/middleware"
"github.com/meiqia/chi/render"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(render.UsePresenter(v3.Presenter)) // API version 3 (latest) by default.
// Redirect for Example convenience.
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/v3/articles/1", 302)
})
// API version 3.
r.Route("/v3", func(r chi.Router) {
r.Mount("/articles", articleRouter())
})
// API version 2.
r.Route("/v2", func(r chi.Router) {
r.Use(render.UsePresenter(v2.Presenter))
r.Mount("/articles", articleRouter())
})
// API version 1.
r.Route("/v1", func(r chi.Router) {
r.Use(randomErrorMiddleware) // Simulate random error, ie. version 1 is buggy.
r.Use(render.UsePresenter(v1.Presenter))
r.Mount("/articles", articleRouter())
})
http.ListenAndServe(":3333", r)
}
func articleRouter() http.Handler {
r := chi.NewRouter()
r.Get("/", listArticles)
r.Route("/:articleID", func(r chi.Router) {
r.Get("/", getArticle)
// r.Put("/", updateArticle)
// r.Delete("/", deleteArticle)
})
return r
}
func listArticles(w http.ResponseWriter, r *http.Request) {
articles := make(chan *data.Article, 5)
// Load data asynchronously into the channel (simulate slow storage):
go func() {
for i := 1; i <= 10; i++ {
articles <- &data.Article{
ID: i,
Title: fmt.Sprintf("Article #%v", i),
Data: []string{"one", "two", "three", "four"},
CustomDataForAuthUsers: "secret data for auth'd users only",
}
time.Sleep(100 * time.Millisecond)
}
close(articles)
}()
// Start streaming data from the channel.
render.Respond(w, r, articles)
}
func getArticle(w http.ResponseWriter, r *http.Request) {
// Load article.
if chi.URLParam(r, "articleID") != "1" {
render.Respond(w, r, data.ErrNotFound)
return
}
article := &data.Article{
ID: 1,
Title: "Article #1",
Data: []string{"one", "two", "three", "four"},
CustomDataForAuthUsers: "secret data for auth'd users only",
}
// Simulate some context values:
// 1. ?auth=true simluates authenticated session/user.
// 2. ?error=true simulates random error.
if r.URL.Query().Get("auth") != "" {
r = r.WithContext(context.WithValue(r.Context(), "auth", true))
}
if r.URL.Query().Get("error") != "" {
render.Respond(w, r, errors.New("error"))
return
}
render.Respond(w, r, article)
}
func randomErrorMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().Unix())
// One in three chance of random error.
if rand.Int31n(3) == 0 {
errors := []error{data.ErrUnauthorized, data.ErrForbidden, data.ErrNotFound}
render.Respond(w, r, errors[rand.Intn(len(errors))])
return
}
next.ServeHTTP(w, r)
})
}