This repository has been archived by the owner on Jul 12, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 27
/
server.go
149 lines (127 loc) · 4.04 KB
/
server.go
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package gosaas
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"github.com/dstpierre/gosaas/data"
"github.com/dstpierre/gosaas/internal/config"
)
func init() {
if err := config.LoadFromFile(); err != nil {
log.Println(err)
}
if len(config.Current.StripeKey) > 0 {
SetStripeKey(config.Current.StripeKey)
}
if len(config.Current.Plans) > 0 {
for _, p := range config.Current.Plans {
if p.Params == nil {
p.Params = make(map[string]interface{})
}
data.AddPlan(p)
}
}
}
// Server is the starting point of the backend.
//
// Responsible for routing requests to handlers.
type Server struct {
DB *data.DB
Logger func(http.Handler) http.Handler
Authenticator func(http.Handler) http.Handler
Throttler func(http.Handler) http.Handler
RateLimiter func(http.Handler) http.Handler
Cors func(http.Handler) http.Handler
StaticDirectory string
Routes map[string]*Route
}
// NewServer returns a production server with all available middlewares.
// Only the top level routes needs to be passed as parameter.
//
// There's three built-in implementations:
//
// 1. users: for user management (signup, signin, authentication, get detail, etc).
//
// 2. billing: for a fully functional billing process (converting from free to paid, changing plan, get invoices, etc).
//
// 3. webhooks: for allowing users to subscribe to events (you may trigger webhook via gosaas.SendWebhook).
//
// To override default inplementation you simply have to supply your own like so:
//
// routes := make(map[string]*gosaas.Route)
// routes["billing"] = &gosaas.Route{Handler: billing.Route}
//
// This would use your own billing implementation instead of the one supplied by gosaas.
func NewServer(routes map[string]*Route) *Server {
// if users, billing and webhooks are not part
// of the routes, we default to gosaas's implementation.
if _, ok := routes["users"]; !ok {
routes["users"] = newUser()
}
if _, ok := routes["billing"]; !ok {
routes["billing"] = newBilling()
}
if _, ok := routes["webhooks"]; !ok {
routes["webhooks"] = newWebhook()
}
return &Server{
Logger: Logger,
Authenticator: Authenticator,
Throttler: Throttler,
RateLimiter: RateLimiter,
Cors: Cors,
StaticDirectory: "/public/",
Routes: routes,
}
}
// ServeHTTP is where the top level routes get matched with the map[string]*gosaas.Route
// received from the call to NewServer. Middleware are applied based on the found route properties.
//
// If no route can be found an error is returned.
//
// Static files are served from the "/public/" directory by default. To change this
// you may set the StaticDirectory after creating the server like this:
//
// mux := gosaas.NewServer(routes)
// mux.StaticDirectory = "/files/"
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
if strings.HasPrefix(r.URL.Path, s.StaticDirectory) {
http.ServeFile(w, r, r.URL.Path[1:])
return
}
ctx := r.Context()
ctx = context.WithValue(ctx, ContextOriginalPath, r.URL.Path)
isJSON := strings.ToLower(r.Header.Get("Content-Type")) == "application/json"
ctx = context.WithValue(ctx, ContextContentIsJSON, isJSON)
var next *Route
var head string
head, r.URL.Path = ShiftPath(r.URL.Path)
if r, ok := s.Routes[head]; ok {
next = r
} else if catchall, ok := s.Routes["__catchall__"]; ok {
next = catchall
} else {
next = NewError(fmt.Errorf("path not found"), http.StatusNotFound)
}
if next.WithDB {
ctx = context.WithValue(ctx, ContextDatabase, s.DB)
}
ctx = context.WithValue(ctx, ContextMinimumRole, next.MinimumRole)
// make sure we are authenticating all calls
next.Handler = s.Authenticator(next.Handler)
if next.Logger {
next.Handler = s.Logger(next.Handler)
}
if next.EnforceRateLimit {
next.Handler = s.RateLimiter(next.Handler)
next.Handler = s.Throttler(next.Handler)
}
// are we allowing cross-origin requests for this route
if next.AllowCrossOrigin {
next.Handler = s.Cors(next.Handler)
}
next.Handler.ServeHTTP(w, r.WithContext(ctx))
}