-
Notifications
You must be signed in to change notification settings - Fork 3
/
server.go
152 lines (130 loc) · 3.59 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
150
151
152
// Package joehttp contains an HTTP server integrations for the Joe bot library
// https://github.com/go-joe/joe
package joehttp
import (
"context"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/go-joe/joe"
"go.uber.org/zap"
)
// RequestEvent corresponds to an HTTP request that was received by the server.
type RequestEvent struct {
Header http.Header
Method string
URL *url.URL
RemoteAddr string
Body []byte
}
type server struct {
http *http.Server
logger *zap.Logger
conf config
events joe.EventEmitter
}
// Server returns a joe Module that runs an HTTP server to receive HTTP requests
// and emit them as events. This Module is mainly meant to be used to integrate
// a Bot with other systems that send events via HTTP (e.g. pull requests on GitHub).
func Server(path string, opts ...Option) joe.Module {
return joe.ModuleFunc(func(joeConf *joe.Config) error {
conf, err := newConf(path, joeConf, opts)
if err != nil {
return err
}
events := joeConf.EventEmitter()
server := newServer(conf, events)
server.logger.Info("Starting HTTP server", zap.String("addr", server.http.Addr))
started := make(chan bool)
go func() {
started <- true
server.Run()
}()
<-started
joeConf.RegisterHandler(func(joe.ShutdownEvent) {
server.Shutdown()
})
return nil
})
}
func newServer(conf config, events joe.EventEmitter) *server {
srv := &server{
logger: conf.logger,
events: events,
conf: conf,
}
srv.http = &http.Server{
Addr: conf.listenAddr,
Handler: http.HandlerFunc(srv.HTTPHandler),
ErrorLog: zap.NewStdLog(conf.logger),
TLSConfig: conf.tlsConf,
ReadTimeout: conf.readTimeout,
WriteTimeout: conf.writeTimeout,
}
return srv
}
// Run starts the HTTP server to handle any incoming requests on the listen
// address that was configured.
func (s *server) Run() {
var err error
if s.conf.certFile == "" {
err = s.http.ListenAndServe()
} else {
err = s.http.ListenAndServeTLS(s.conf.certFile, s.conf.keyFile)
}
if err != nil && err != http.ErrServerClosed {
s.logger.Error("Failed to listen and serve requests", zap.Error(err))
}
}
// HTTPHandler receives any incoming requests and emits them as events to the
// bots Brain.
func (s *server) HTTPHandler(_ http.ResponseWriter, r *http.Request) {
clientIP := s.clientAddress(r)
s.logger.Debug("Received HTTP request",
zap.String("method", r.Method),
zap.Stringer("url", r.URL),
zap.String("remote_addr", clientIP),
)
event := RequestEvent{
Header: r.Header,
Method: r.Method,
URL: r.URL,
RemoteAddr: clientIP,
}
var err error
if r.Body != nil {
event.Body, err = ioutil.ReadAll(r.Body)
if err != nil {
s.logger.Error("Failed to read request body")
}
}
s.events.Emit(event)
}
// Shutdown gracefully shuts down the HTTP server without interrupting any
// active connections.
func (s *server) Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
s.logger.Info("HTTP server is shutting down gracefully")
err := s.http.Shutdown(ctx)
if err != nil {
s.logger.Error("Failed to shutdown server", zap.Error(err))
}
}
func (s *server) clientAddress(req *http.Request) string {
rip, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
s.logger.Error("Error parsing RemoteAddr", zap.String("RemoteAddr", req.RemoteAddr))
return req.RemoteAddr
}
ips := req.Header.Get(s.conf.trustedHeader)
if ips == "" {
return rip
}
// The n parameter for SplitN is the number of substrings, not how many
// to split.
return strings.SplitN(ips, ", ", 2)[0]
}