diff options
Diffstat (limited to 'web/utils.go')
-rw-r--r-- | web/utils.go | 117 |
1 files changed, 117 insertions, 0 deletions
diff --git a/web/utils.go b/web/utils.go new file mode 100644 index 0000000..5467132 --- /dev/null +++ b/web/utils.go | |||
@@ -0,0 +1,117 @@ | |||
1 | package web | ||
2 | |||
3 | import ( | ||
4 | "context" | ||
5 | "fmt" | ||
6 | "log" | ||
7 | "net" | ||
8 | "net/http" | ||
9 | "net/url" | ||
10 | "os" | ||
11 | "os/signal" | ||
12 | "regexp" | ||
13 | |||
14 | "github.com/gin-gonic/gin" | ||
15 | ) | ||
16 | |||
17 | // Parses an IPv4 or IPv6 address with an optional port on the end. Returns | ||
18 | // match groups for the addresses. The first match is the IPv6 address and the | ||
19 | // second the IPv4 address. | ||
20 | var ipRegexp = regexp.MustCompile(`(?:\[([0-9a-f:]+)\]|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))(?::\d+)?`) | ||
21 | |||
22 | // TODO: use from a common package | ||
23 | func ParseIP(s string) net.IP { | ||
24 | ips := ipRegexp.FindStringSubmatch(s) | ||
25 | if ips == nil { | ||
26 | return nil | ||
27 | } | ||
28 | |||
29 | if v6, v4 := ips[1], ips[2]; v6 != "" { | ||
30 | return net.ParseIP(v6) | ||
31 | } else { | ||
32 | return net.ParseIP(v4) | ||
33 | } | ||
34 | } | ||
35 | |||
36 | // TODO: use from a common package | ||
37 | func GetRequestIP(c *gin.Context) net.IP { | ||
38 | if xff := c.Request.Header.Get("X-Forwarded-For"); xff != "" { | ||
39 | return ParseIP(xff) | ||
40 | } | ||
41 | return ParseIP(c.Request.RemoteAddr) | ||
42 | } | ||
43 | |||
44 | // TODO: use from a common package | ||
45 | func MakeURL(r *http.Request, path string, subs ...interface{}) *url.URL { | ||
46 | scheme := "https" | ||
47 | if r.TLS == nil { | ||
48 | scheme = "http" | ||
49 | } | ||
50 | |||
51 | // Always defer to whatever the proxy told us it was doing because this | ||
52 | // could be a mullet-VIP in either direction. | ||
53 | if fwProto := r.Header.Get("X-Forwarded-Proto"); fwProto != "" { | ||
54 | scheme = fwProto | ||
55 | } | ||
56 | |||
57 | return &url.URL{ | ||
58 | Scheme: scheme, | ||
59 | Host: r.Host, | ||
60 | Path: fmt.Sprintf(path, subs...), | ||
61 | } | ||
62 | } | ||
63 | |||
64 | // TODO: use from a common package | ||
65 | // Copied from: https://github.com/gin-gonic/gin/blob/59ab588bf597f9f41faee4f217b5659893c2e925/utils.go#L137 | ||
66 | func resolveAddress(addr []string) string { | ||
67 | switch len(addr) { | ||
68 | case 0: | ||
69 | if port := os.Getenv("PORT"); port != "" { | ||
70 | log.Printf("Environment variable PORT=\"%s\"", port) | ||
71 | return ":" + port | ||
72 | } | ||
73 | log.Printf("Environment variable PORT is undefined. Using port :8080 by default") | ||
74 | return ":8080" | ||
75 | case 1: | ||
76 | return addr[0] | ||
77 | default: | ||
78 | panic("too many parameters") | ||
79 | } | ||
80 | } | ||
81 | |||
82 | // TODO: use from a common package | ||
83 | // Runs a gin.Engine instance in a way that can be canceled by an SIGINT | ||
84 | func GinRun(e *gin.Engine, debug bool, a ...string) { | ||
85 | if debug { | ||
86 | gin.SetMode(gin.DebugMode) | ||
87 | } else { | ||
88 | gin.SetMode(gin.ReleaseMode) | ||
89 | } | ||
90 | |||
91 | srv := &http.Server{ | ||
92 | Addr: resolveAddress(a), | ||
93 | Handler: e, | ||
94 | } | ||
95 | |||
96 | idleConnsClosed := make(chan struct{}) | ||
97 | |||
98 | go func() { | ||
99 | sigint := make(chan os.Signal, 1) | ||
100 | signal.Notify(sigint, os.Interrupt) | ||
101 | <-sigint | ||
102 | |||
103 | log.Println("Caught SIGINT, shutting down") | ||
104 | if err := srv.Shutdown(context.Background()); err != nil { | ||
105 | log.Printf("HTTP server Shutdown: %v", err) | ||
106 | } | ||
107 | |||
108 | close(idleConnsClosed) | ||
109 | }() | ||
110 | |||
111 | log.Printf("Listening and serving HTTP on %s\n", srv.Addr) | ||
112 | if err := srv.ListenAndServe(); err != http.ErrServerClosed { | ||
113 | log.Fatalf("HTTP server ListenAndServe: %v", err) | ||
114 | } | ||
115 | |||
116 | <-idleConnsClosed | ||
117 | } | ||