-
Notifications
You must be signed in to change notification settings - Fork 225
/
main.go.tmpl
212 lines (184 loc) · 5.87 KB
/
main.go.tmpl
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package main
import (
"flag"
"fmt"
"log"
"net/http"
"net/textproto"
"os"
"os/signal"
"strings"
"time"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"golang.org/x/net/context"
"google.golang.org/grpc"
gw "gen/pb-go"
)
type proxyConfig struct {
// The backend gRPC service to listen to.
backend string
// Path to the swagger file to serve.
swagger string
// Value to set for Access-Control-Allow-Origin header.
corsAllowOrigin string
// Value to set for Access-Control-Allow-Credentials header.
corsAllowCredentials string
// Prefix that this gateway is running on. For example, if your API endpoint
// was "/foo/bar" in your protofile, and you wanted to run APIs under "/api",
// set this to "/api/".
apiPrefix string
}
func allowCors(cfg proxyConfig, handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", cfg.corsAllowOrigin)
w.Header().Set("Access-Control-Allow-Credentials", cfg.corsAllowCredentials)
handler.ServeHTTP(w, req)
})
}
// sanitizeApiPrefix forces prefix to be non-empty and end with a slash.
func sanitizeApiPrefix(prefix string) string {
if len(prefix) == 0 || prefix[len(prefix)-1:] != "/" {
return prefix + "/"
}
return prefix
}
// isPermanentHTTPHeader checks whether hdr belongs to the list of
// permenant request headers maintained by IANA.
// http://www.iana.org/assignments/message-headers/message-headers.xml
// From https://github.com/grpc-ecosystem/grpc-gateway/blob/7a2a43655ccd9a488d423ea41a3fc723af103eda/runtime/context.go#L157
func isPermanentHTTPHeader(hdr string) bool {
switch hdr {
case
"Accept",
"Accept-Charset",
"Accept-Language",
"Accept-Ranges",
"Authorization",
"Cache-Control",
"Content-Type",
"Cookie",
"Date",
"Expect",
"From",
"Host",
"If-Match",
"If-Modified-Since",
"If-None-Match",
"If-Schedule-Tag-Match",
"If-Unmodified-Since",
"Max-Forwards",
"Origin",
"Pragma",
"Referer",
"User-Agent",
"Via",
"Warning":
return true
}
return false
}
// isReserved returns whether the key is reserved by gRPC.
func isReserved(key string) bool {
return strings.HasPrefix(key, "Grpc-")
}
// incomingHeaderMatcher converts an HTTP header name on http.Request to
// grpc metadata. Permanent headers (i.e. User-Agent) are prepended with
// "grpc-gateway". Headers that start with start with "Grpc-" (reserved
// by grpc) are prepended with "X-". Other headers are forwarded as is.
func incomingHeaderMatcher(key string) (string, bool) {
key = textproto.CanonicalMIMEHeaderKey(key)
if isPermanentHTTPHeader(key) {
return runtime.MetadataPrefix + key, true
}
if isReserved(key) {
return "X-" + key, true
}
return key, true
}
// outgoingHeaderMatcher transforms outgoing metadata into HTTP headers.
// We return any response metadata as is.
func outgoingHeaderMatcher(metadata string) (string, bool) {
return metadata, true
}
func SetupMux(ctx context.Context, cfg proxyConfig) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/swagger.json", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, cfg.swagger)
})
gwmux := runtime.NewServeMux(
runtime.WithIncomingHeaderMatcher(incomingHeaderMatcher),
runtime.WithOutgoingHeaderMatcher(outgoingHeaderMatcher),
)
fmt.Printf("Proxying requests to gRPC service at '%s'\n", cfg.backend)
opts := []grpc.DialOption{grpc.WithInsecure()}
// If you get a compilation error that gw.Register${SERVICE}HandlerFromEndpoint
// does not exist, it's because you haven't added any google.api.http annotations
// to your proto. Add some!
err := gw.Register${SERVICE}HandlerFromEndpoint(ctx, gwmux, cfg.backend, opts)
if err != nil {
log.Fatalf("Could not register gateway: %v", err)
}
prefix := sanitizeApiPrefix(cfg.apiPrefix)
log.Println("API prefix is", prefix)
mux.Handle(prefix, http.StripPrefix(prefix[:len(prefix)-1], allowCors(cfg, gwmux)))
return mux
}
// SetupViper returns a viper configuration object
func SetupViper() *viper.Viper {
viper.SetConfigName("config")
viper.AddConfigPath(".")
viper.SetEnvPrefix("${SERVICE}")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
flag.String("backend", "", "The gRPC backend service to proxy.")
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
viper.BindPFlags(pflag.CommandLine)
err := viper.ReadInConfig()
if err != nil {
log.Fatalf("Could not read config: %v", err)
}
return viper.GetViper()
}
// SignalRunner runs a runner function until an interrupt signal is received, at which point it
// will call stopper.
func SignalRunner(runner, stopper func()) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, os.Kill)
go func() {
runner()
}()
fmt.Println("hit Ctrl-C to shutdown")
select {
case <-signals:
stopper()
}
}
func main() {
cfg := SetupViper()
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := SetupMux(ctx, proxyConfig{
backend: cfg.GetString("backend"),
swagger: cfg.GetString("swagger.file"),
corsAllowOrigin: cfg.GetString("cors.allow-origin"),
corsAllowCredentials: cfg.GetString("cors.allow-credentials"),
apiPrefix: cfg.GetString("proxy.api-prefix"),
})
addr := fmt.Sprintf(":%v", cfg.GetInt("proxy.port"))
server := &http.Server{Addr: addr, Handler: mux}
SignalRunner(
func() {
fmt.Printf("launching http server on %v\n", server.Addr)
if err := server.ListenAndServe(); err != nil {
log.Fatalf("Could not start http server: %v", err)
}
},
func() {
shutdown, _ := context.WithTimeout(ctx, 10*time.Second)
server.Shutdown(shutdown)
})
}