aboutsummaryrefslogtreecommitdiff
path: root/http.go
diff options
context:
space:
mode:
Diffstat (limited to 'http.go')
-rw-r--r--http.go124
1 files changed, 124 insertions, 0 deletions
diff --git a/http.go b/http.go
new file mode 100644
index 0000000..93bf545
--- /dev/null
+++ b/http.go
@@ -0,0 +1,124 @@
1package main
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net"
8 "net/http"
9 "reflect"
10 "strings"
11 "time"
12
13 jww "github.com/spf13/jwalterweatherman"
14)
15
16const (
17 APP_CTX_KEY = "app"
18)
19
20// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
21// connections. It's used by ListenAndServe and ListenAndServeTLS so
22// dead TCP connections (e.g. closing laptop mid-download) eventually
23// go away.
24type tcpKeepAliveListener struct {
25 *net.TCPListener
26}
27
28func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
29 tc, err := ln.AcceptTCP()
30 if err != nil {
31 return
32 }
33 tc.SetKeepAlive(true)
34 tc.SetKeepAlivePeriod(3 * time.Minute)
35 return tc, nil
36}
37
38// Do the same thing ListenAndServe does but allow passing in the listener
39// instead of the address so that we can bind privileged ports before dropping
40// permissions to start the server itself.
41func ListenAndServeRaw(ln net.Listener, handler http.Handler) error {
42 server := &http.Server{Addr: ln.Addr().String(), Handler: handler}
43 return server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
44}
45
46// Handler that pushes the application context onto the request context stack
47// so that it's available to all other handlers in the stack.
48type ContextAwareHandler struct {
49 ctx *appContext
50 handler http.HandlerFunc
51}
52
53func (h ContextAwareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
54 defaultHeaders(w)
55 h.handler(w, r.WithContext(context.WithValue(r.Context(), APP_CTX_KEY, h.ctx)))
56}
57
58// Handler that is able to inspect the application context and print out the
59// value of specific fields.
60type ContextPrintingHandler struct {
61 ctx *appContext
62 field string
63}
64
65func (h ContextPrintingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
66 defaultHeaders(w)
67
68 c := reflect.ValueOf(h.ctx)
69 f := reflect.Indirect(c).FieldByName(h.field)
70
71 switch i := f.Interface().(type) {
72 case fmt.Stringer:
73 fmt.Fprintln(w, i.String())
74 case *string:
75 if i == nil {
76 fmt.Fprintln(w, "")
77 } else {
78 fmt.Fprintln(w, *i)
79 }
80 default:
81 fmt.Fprintln(w, i)
82 }
83}
84
85// Print default headers for JSON data
86func defaultHeaders(w http.ResponseWriter) {
87 w.Header().Set("Content-Type", "text/plain")
88 w.Header().Set("Server", "EC2ws")
89 w.Header().Set("Connection", "close")
90}
91
92// Write out JSON data in formatted form or an error
93func writeHTTPJson(w http.ResponseWriter, data interface{}, name string) {
94 jd, err := json.MarshalIndent(data, "", " ")
95 if err != nil {
96 jww.ERROR.Printf("Error marshaling json in %s: %s", name, err)
97 http.Error(w, err.Error(), http.StatusInternalServerError)
98 }
99 fmt.Fprintf(w, string(jd))
100}
101
102// Get the application context from the request context
103func getAppCtx(r *http.Request) *appContext {
104 return r.Context().Value(APP_CTX_KEY).(*appContext)
105}
106
107// Handler that will reject non-local requests in case the daemon gets bound
108// incorrectly to a public interface
109type SecurityHandler struct {
110 handler http.Handler
111}
112
113func (h SecurityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
114 // String is ip:port formatted
115 ip := strings.Split(r.RemoteAddr, ":")[0]
116
117 if ip != "169.254.169.254" && ip != "127.0.0.1" {
118 jww.ERROR.Printf("Non-local metadata request from %s!", ip)
119 http.NotFound(w, r)
120 return
121 } else {
122 h.handler.ServeHTTP(w, r)
123 }
124}