-
Notifications
You must be signed in to change notification settings - Fork 5
/
http.go
141 lines (122 loc) · 4.39 KB
/
http.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
package infrabin
import (
"context"
"log"
"net/http"
"os"
"strings"
"github.com/gorilla/handlers"
grpc_health_v1 "github.com/maruina/go-infrabin/pkg/grpc/health/v1"
"github.com/spf13/viper"
"google.golang.org/grpc/health"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
)
type HTTPServerOption func(ctx context.Context, s *HTTPServer)
func RegisterHealth(pattern string, healthService *health.Server) HTTPServerOption {
return func(ctx context.Context, s *HTTPServer) {
// Register the handler to call local instance, i.e. no network calls
mux := newGatewayMux()
if err := grpc_health_v1.RegisterHealthHandlerServer(ctx, mux, healthService); err != nil {
log.Fatalf("gRPC server failed to register: %v", err)
}
var handler http.Handler
if p := strings.TrimSuffix(pattern, "/"); len(p) < len(pattern) {
handler = http.StripPrefix(p, mux)
} else {
handler = mux
}
s.Server.Handler.(*http.ServeMux).Handle(pattern, handler)
}
}
func RegisterInfrabin(pattern string, infrabinService InfrabinServer) HTTPServerOption {
return func(ctx context.Context, s *HTTPServer) {
// Register the handler to call local instance, i.e. no network calls
mux := newGatewayMux()
if err := RegisterInfrabinHandlerServer(ctx, mux, infrabinService); err != nil {
log.Fatalf("gRPC server failed to register: %v", err)
}
var handler http.Handler
if p := strings.TrimSuffix(pattern, "/"); len(p) < len(pattern) {
handler = http.StripPrefix(p, mux)
} else {
handler = mux
}
s.Server.Handler.(*http.ServeMux).Handle(pattern, handler)
}
}
func RegisterHandler(pattern string, handler http.Handler) HTTPServerOption {
return func(ctx context.Context, s *HTTPServer) {
if p := strings.TrimSuffix(pattern, "/"); len(p) < len(pattern) {
handler = http.StripPrefix(p, handler)
}
s.Server.Handler.(*http.ServeMux).Handle(pattern, handler)
}
}
type HTTPServer struct {
Name string
Server *http.Server
}
func (s *HTTPServer) ListenAndServe() {
// Wrap handler now that everything is registered
s.Server.Handler = handlers.RecoveryHandler()(handlers.ProxyHeaders(handlers.CombinedLoggingHandler(os.Stdout, s.Server.Handler)))
log.Printf("Starting %s server on %s", s.Name, s.Server.Addr)
if err := s.Server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal("HTTP server crashed", err)
}
}
func (s *HTTPServer) Shutdown() {
drainTimeout := viper.GetDuration("drainTimeout")
log.Printf("Shutting down %s server with %s graceful shutdown", s.Name, drainTimeout)
ctx, cancel := context.WithTimeout(context.Background(), drainTimeout)
defer cancel()
if err := s.Server.Shutdown(ctx); err != nil {
log.Fatalf("HTTP %s server graceful shutdown failed: %v", s.Name, err)
} else {
log.Printf("HTTP %s server stopped", s.Name)
}
}
func NewHTTPServer(name string, opts ...HTTPServerOption) *HTTPServer {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
addr := viper.GetString(name+".host") + ":" + viper.GetString(name+".port")
// A standard http.Server
server := &http.Server{
Handler: http.NewServeMux(),
Addr: addr,
// Good practice: enforce timeouts
WriteTimeout: viper.GetDuration("httpWriteTimeout"),
ReadTimeout: viper.GetDuration("httpReadTimeout"),
IdleTimeout: viper.GetDuration("httpIdleTimeout"),
ReadHeaderTimeout: viper.GetDuration("httpReadHeaderTimeout"),
}
s := &HTTPServer{Name: name, Server: server}
for _, opt := range opts {
opt(ctx, s)
}
return s
}
func newGatewayMux() *runtime.ServeMux {
mux := runtime.NewServeMux(
runtime.WithIncomingHeaderMatcher(passThroughHeaderMatcher),
)
// Set default marshaller options
marshaler, _ := runtime.MarshalerForRequest(mux, &http.Request{})
jsonMarshaler := marshaler.(*runtime.HTTPBodyMarshaler).Marshaler.(*runtime.JSONPb)
jsonMarshaler.EmitUnpopulated = false
jsonMarshaler.UseProtoNames = true
return mux
}
// Keep the standard "Grpc-Metadata-" and well known behaviour
// All other headers are passed, also with grpcgateway- prefix
func passThroughHeaderMatcher(key string) (string, bool) {
if grpcKey, ok := runtime.DefaultHeaderMatcher(key); ok {
return grpcKey, ok
} else {
return runtime.MetadataPrefix + key, true
}
}
// Workaround for not being able to specify root as a path
// See https://github.com/grpc-ecosystem/grpc-gateway/issues/1500
func init() {
pattern_Infrabin_Root_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{""}, ""))
}