159 lines
3.9 KiB
Go
159 lines
3.9 KiB
Go
|
//
|
||
|
// Versions
|
||
|
// ========
|
||
|
// This example demonstrates the use of the render subpackage, with
|
||
|
// a quick concept for how to support multiple api versions.
|
||
|
//
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"math/rand"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
|
||
|
"github.com/go-chi/chi"
|
||
|
"github.com/go-chi/chi/_examples/versions/data"
|
||
|
"github.com/go-chi/chi/_examples/versions/presenter/v1"
|
||
|
"github.com/go-chi/chi/_examples/versions/presenter/v2"
|
||
|
"github.com/go-chi/chi/_examples/versions/presenter/v3"
|
||
|
"github.com/go-chi/chi/middleware"
|
||
|
"github.com/go-chi/render"
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
r := chi.NewRouter()
|
||
|
|
||
|
r.Use(middleware.RequestID)
|
||
|
r.Use(middleware.Logger)
|
||
|
r.Use(middleware.Recoverer)
|
||
|
|
||
|
// API version 3.
|
||
|
r.Route("/v3", func(r chi.Router) {
|
||
|
r.Use(apiVersionCtx("v3"))
|
||
|
r.Mount("/articles", articleRouter())
|
||
|
})
|
||
|
|
||
|
// API version 2.
|
||
|
r.Route("/v2", func(r chi.Router) {
|
||
|
r.Use(apiVersionCtx("v2"))
|
||
|
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(apiVersionCtx("v1"))
|
||
|
r.Mount("/articles", articleRouter())
|
||
|
})
|
||
|
|
||
|
http.ListenAndServe(":3333", r)
|
||
|
}
|
||
|
|
||
|
func apiVersionCtx(version string) func(next http.Handler) http.Handler {
|
||
|
return func(next http.Handler) http.Handler {
|
||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
r = r.WithContext(context.WithValue(r.Context(), "api.version", version))
|
||
|
next.ServeHTTP(w, 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 render.Renderer, 5)
|
||
|
|
||
|
// Load data asynchronously into the channel (simulate slow storage):
|
||
|
go func() {
|
||
|
for i := 1; i <= 10; i++ {
|
||
|
article := &data.Article{
|
||
|
ID: i,
|
||
|
Title: fmt.Sprintf("Article #%v", i),
|
||
|
Data: []string{"one", "two", "three", "four"},
|
||
|
CustomDataForAuthUsers: "secret data for auth'd users only",
|
||
|
}
|
||
|
|
||
|
apiVersion := r.Context().Value("api.version").(string)
|
||
|
switch apiVersion {
|
||
|
case "v1":
|
||
|
articles <- v1.NewArticleResponse(article)
|
||
|
case "v2":
|
||
|
articles <- v2.NewArticleResponse(article)
|
||
|
default:
|
||
|
articles <- v3.NewArticleResponse(article)
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
|
||
|
var payload render.Renderer
|
||
|
|
||
|
apiVersion := r.Context().Value("api.version").(string)
|
||
|
switch apiVersion {
|
||
|
case "v1":
|
||
|
payload = v1.NewArticleResponse(article)
|
||
|
case "v2":
|
||
|
payload = v2.NewArticleResponse(article)
|
||
|
default:
|
||
|
payload = v3.NewArticleResponse(article)
|
||
|
}
|
||
|
|
||
|
render.Render(w, r, payload)
|
||
|
}
|
||
|
|
||
|
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)
|
||
|
})
|
||
|
}
|