forked from FactomProject/factomd
-
Notifications
You must be signed in to change notification settings - Fork 1
/
server.go
148 lines (126 loc) · 4.36 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
package wsapi
import (
"context"
"crypto/tls"
"fmt"
"net/http"
"strconv"
"time"
"github.com/FactomProject/factomd/common/interfaces"
"github.com/gorilla/mux"
"github.com/rs/cors"
)
type Server struct {
State interfaces.IState
httpServer *http.Server
router *mux.Router
tlsEnabled bool
certFile string
keyFile string
Port string
}
type Middleware func(http.HandlerFunc) http.HandlerFunc
func InitServer(state interfaces.IState) *Server {
tlsIsEnabled, keyFile, certFile := state.GetTlsInfo()
address := fmt.Sprintf(":%d", state.GetPort())
router := mux.NewRouter()
port := strconv.Itoa(state.GetPort())
server := Server{State: state, router: router, tlsEnabled: tlsIsEnabled, certFile: certFile, keyFile: keyFile, Port: port}
if tlsIsEnabled {
router.Schemes("HTTPS")
wsLog.Info("Starting encrypted API server")
if !fileExists(keyFile) && !fileExists(certFile) {
err := genCertPair(certFile, keyFile, state.GetFactomdLocations())
if err != nil {
panic(fmt.Sprintf("could not start encrypted API server with error: %v", err))
}
}
keypair, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
panic(fmt.Sprintf("could not create TLS keypair with error: %v", err))
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{keypair},
MinVersion: tls.VersionTLS12,
}
server.httpServer = &http.Server{Addr: address, Handler: router, TLSConfig: tlsConfig}
} else {
server.httpServer = &http.Server{Addr: address, Handler: router}
}
wsLog.Infof("Init API server at: %s\n", address)
return &server
}
func (server *Server) Start() {
wsLog.Info("Starting API server")
go func() {
// returns ErrServerClosed on graceful close
if server.tlsEnabled {
if err := server.httpServer.ListenAndServeTLS(server.certFile, server.keyFile); err != http.ErrServerClosed {
wsLog.Errorf("ListenAndServeTLS %v", err)
}
} else {
if err := server.httpServer.ListenAndServe(); err != http.ErrServerClosed {
wsLog.Errorf("ListenAndServe %v", err)
}
}
}()
}
func (server *Server) Stop() {
// close the server gracefully ("shutdown")
wsLog.Info("closing wsapi server")
if err := server.httpServer.Shutdown(context.Background()); err != nil {
panic(err) // failure/timeout shutting down the server gracefully
}
}
// Logging logs all requests with its path and the time it took to process
func APILogger() Middleware {
return func(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
wsLog.Debugf("%s\t%s\t%s\n", r.Method, r.RequestURI, time.Since(start))
// Call the next middleware/handler in chain
f(w, r)
}
}
}
// IDInjector injects the server's ID into every request as http header
func IDInjector(server *Server) Middleware {
return func(f http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.Header.Set("factomd-port", server.Port)
f(w, r)
}
}
}
// add route and Chain applies middlewares to a http.HandlerFunc
func (server *Server) addRoute(path string, f func(http.ResponseWriter, *http.Request), middlewares ...Middleware) *mux.Route {
middlewares = append(middlewares, APILogger())
middlewares = append(middlewares, IDInjector(server))
for _, m := range middlewares {
f = m(f)
}
return server.router.HandleFunc(path, f)
}
func (server *Server) AddRootEndpoints() {
state := server.State
if len(state.GetCorsDomains()) > 0 {
c := cors.New(cors.Options{
AllowedOrigins: state.GetCorsDomains(),
})
server.router.Use(c.Handler)
}
// for the v1 endpoints the default behavior of a not allowed method behaviour is different.
// for v2 and debug endpoints this isn't applicable as all methods accept both gets, and posts
server.router.MethodNotAllowedHandler = methodNotAllowedHandler()
// start the debugging api if we are not on the main network
if state.GetNetworkName() != "MAIN" {
server.addRoute("/debug", HandleDebug).Methods("GET", "POST")
}
}
// methodNotAllowed replies to the request with an HTTP status code 404 instead of default 405.
func methodNotAllowed(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
// methodNotAllowedHandler returns a simple request handler
// that replies to each request with a status code 404 instead of the default 405.
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }