-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.go
143 lines (129 loc) · 3.69 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
package lobby
import (
"encoding/json"
"errors"
"net"
"net/http"
"strings"
)
var _ http.Handler = (*Server)(nil)
// Server is an HTTP Nox lobby server.
type Server struct {
l Lobby
mux *http.ServeMux
trustAddr bool // trust IP sent by a remote
}
// IPResp represents a response to the address request.
type IPResp struct {
IP string `json:"ip"`
}
// ServerListResp represents a response to the server list request.
type ServerListResp []GameInfo
// Response wraps all other HTTP responses to separate errors from the rest of the response.
type Response struct {
Result interface{} `json:"data,omitempty"`
Err string `json:"error,omitempty"`
}
// NewServer creates a new http.Handler from a Lobby implementation.
func NewServer(l Lobby) *Server {
api := &Server{l: l, mux: http.NewServeMux()}
api.mux.HandleFunc("/api/v0/address", api.Address)
api.mux.HandleFunc("/api/v0/games/list", api.ServersList)
api.mux.HandleFunc("/api/v0/games/register", api.RegisterServer)
return api
}
func (api *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
cntRequests.WithLabelValues(r.Method, r.URL.Path, r.Header.Get("User-Agent")).Inc()
api.mux.ServeHTTP(w, r)
}
// jsonResponse writes response, wrapping it into JSON format.
func (api *Server) jsonResponse(w http.ResponseWriter, code int, data interface{}) {
if code == 0 {
code = http.StatusOK
}
if data == nil {
// result field in JSON is omitempty, but we want to keep it for this case
data = (*struct{})(nil)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(&Response{
Result: data,
})
}
// jsonError writes an error, wrapping it into JSON format.
func (api *Server) jsonError(w http.ResponseWriter, code int, err error) {
if code == 0 {
code = http.StatusInternalServerError
}
if err == nil {
err = errors.New(http.StatusText(code))
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
_ = json.NewEncoder(w).Encode(&Response{
Err: err.Error(),
})
}
func (api *Server) getAddress(r *http.Request) (string, error) {
if r.RemoteAddr == "" {
return "", errors.New("cannot detect IP address")
}
ip, _, err := net.SplitHostPort(r.RemoteAddr)
return ip, err
}
func (api *Server) Address(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet, http.MethodHead:
ip, err := api.getAddress(r)
if err != nil {
api.jsonError(w, http.StatusBadRequest, err)
return
}
api.jsonResponse(w, 0, IPResp{IP: ip})
default:
api.jsonError(w, http.StatusMethodNotAllowed, nil)
}
}
func (api *Server) RegisterServer(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
// set limit to avoid giant requests - 1MB
body := http.MaxBytesReader(w, r.Body, 1024*1024)
var req Game
if err := json.NewDecoder(body).Decode(&req); err != nil {
api.jsonError(w, http.StatusBadRequest, err)
return
}
if !api.trustAddr {
addr, err := api.getAddress(r)
if err != nil {
api.jsonError(w, http.StatusBadRequest, err)
return
}
req.Address = addr
}
req.Map = strings.ToLower(req.Map)
err := api.l.RegisterGame(r.Context(), &req)
if err != nil {
api.jsonError(w, http.StatusBadRequest, err)
return
}
api.jsonResponse(w, 0, nil)
default:
api.jsonError(w, http.StatusMethodNotAllowed, nil)
}
}
func (api *Server) ServersList(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet, http.MethodHead:
list, err := api.l.ListGames(r.Context())
if err != nil {
api.jsonError(w, http.StatusInternalServerError, err)
return
}
api.jsonResponse(w, 0, ServerListResp(list))
default:
api.jsonError(w, http.StatusMethodNotAllowed, nil)
}
}