diff options
author | Martin Polden <mpolden@mpolden.no> | 2019-12-26 19:23:15 +0100 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2019-12-26 19:23:15 +0100 |
commit | 82214806d0fe1ff73e4cf443465e75c99e028222 (patch) | |
tree | da015eb2d075f7dfe1598b4d154b84a8e7abf16d /http | |
parent | f031924851c4121117b70d97d7d507e5bd593fb1 (diff) |
Add /cache/v1/ path
Diffstat (limited to 'http')
-rw-r--r-- | http/http.go | 78 | ||||
-rw-r--r-- | http/http_test.go | 69 |
2 files changed, 100 insertions, 47 deletions
diff --git a/http/http.go b/http/http.go index dac035c..98ca90d 100644 --- a/http/http.go +++ b/http/http.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/mpolden/zdns/cache" "github.com/mpolden/zdns/dns" "github.com/mpolden/zdns/log" ) @@ -14,13 +15,15 @@ import ( // A Server defines paramaters for running an HTTP server. The HTTP server serves an API for inspecting cache contents // and request log. type Server struct { - server *http.Server + cache *cache.Cache logger *log.Logger + server *http.Server } -type logEntry struct { +type entry struct { Time string `json:"time"` - RemoteAddr net.IP `json:"remote_addr"` + TTL int64 `json:"ttl,omitempty"` + RemoteAddr net.IP `json:"remote_addr,omitempty"` Qtype string `json:"type"` Question string `json:"question"` Answers []string `json:"answers"` @@ -32,18 +35,22 @@ type httpError struct { Message string `json:"message"` } -// NewServer creates a new HTTP server, serving logs from the given logger and listening on listenAddr. -func NewServer(logger *log.Logger, listenAddr string) *Server { - server := &http.Server{Addr: listenAddr} - s := &Server{logger: logger, server: server} +// NewServer creates a new HTTP server, serving logs from the given logger and listening on addr. +func NewServer(logger *log.Logger, cache *cache.Cache, addr string) *Server { + server := &http.Server{Addr: addr} + s := &Server{ + cache: cache, + logger: logger, + server: server, + } s.server.Handler = s.handler() return s } func (s *Server) handler() http.Handler { mux := http.NewServeMux() + mux.Handle("/cache/v1/", appHandler(s.cacheHandler)) mux.Handle("/log/v1/", appHandler(s.logHandler)) - //mux.Handle("/cache/v1") mux.Handle("/", appHandler(notFoundHandler)) return requestFilter(mux) } @@ -85,6 +92,33 @@ func notFoundHandler(w http.ResponseWriter, r *http.Request) (interface{}, *http } } +func (s *Server) cacheHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { + cacheValues := s.cache.List(100) + entries := make([]entry, 0, len(cacheValues)) + for _, v := range cacheValues { + entries = append(entries, entry{ + Time: v.CreatedAt.UTC().Format(time.RFC3339), + TTL: int64(v.TTL().Truncate(time.Second).Seconds()), + Qtype: qtype(v.Qtype), + Question: v.Question, + Answers: v.Answers, + }) + } + return entries, nil +} + +func qtype(qtype uint16) string { + switch qtype { + case dns.TypeA: + return "A" + case dns.TypeAAAA: + return "AAAA" + case dns.TypeMX: + return "MX" + } + return "" +} + func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { logEntries, err := s.logger.Get(100) if err != nil { @@ -93,25 +127,15 @@ func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{} Status: http.StatusInternalServerError, } } - entries := make([]logEntry, 0, len(logEntries)) - for _, entry := range logEntries { - dnsType := "" - switch entry.Qtype { - case dns.TypeA: - dnsType = "A" - case dns.TypeAAAA: - dnsType = "AAAA" - case dns.TypeMX: - dnsType = "MX" - } - e := logEntry{ - Time: entry.Time.UTC().Format(time.RFC3339), - RemoteAddr: entry.RemoteAddr, - Qtype: dnsType, - Question: entry.Question, - Answers: entry.Answers, - } - entries = append(entries, e) + entries := make([]entry, 0, len(logEntries)) + for _, le := range logEntries { + entries = append(entries, entry{ + Time: le.Time.UTC().Format(time.RFC3339), + RemoteAddr: le.RemoteAddr, + Qtype: qtype(le.Qtype), + Question: le.Question, + Answers: le.Answers, + }) } return entries, nil } diff --git a/http/http_test.go b/http/http_test.go index 94708f8..fc100aa 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -5,22 +5,39 @@ import ( "net" "net/http" "net/http/httptest" + "regexp" + "strings" "testing" "time" + "github.com/miekg/dns" + "github.com/mpolden/zdns/cache" "github.com/mpolden/zdns/log" ) -func testServer() (*httptest.Server, *log.Logger) { - logger, err := log.New(ioutil.Discard, "", log.RecordOptions{ - Database: ":memory:", - }) - logger.Now = func() time.Time { return time.Date(2006, 1, 2, 15, 4, 5, 0, time.UTC) } +func newA(name string, ttl uint32, ipAddr ...net.IP) *dns.Msg { + m := dns.Msg{} + m.Id = dns.Id() + m.SetQuestion(dns.Fqdn(name), dns.TypeA) + rr := make([]dns.RR, 0, len(ipAddr)) + for _, ip := range ipAddr { + rr = append(rr, &dns.A{ + A: ip, + Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: ttl}, + }) + } + m.Answer = rr + return &m +} + +func testServer() (*httptest.Server, *Server) { + logger, err := log.New(ioutil.Discard, "", log.RecordOptions{Database: ":memory:"}) if err != nil { panic(err) } - server := Server{logger: logger} - return httptest.NewServer(server.handler()), logger + cache := cache.New(10, time.Minute) + server := Server{logger: logger, cache: cache} + return httptest.NewServer(server.handler()), &server } func httpGet(url string) (string, int, error) { @@ -37,13 +54,17 @@ func httpGet(url string) (string, int, error) { } func TestRequests(t *testing.T) { - server, logger := testServer() - defer server.Close() - logger.Record(net.IPv4(127, 0, 0, 42), 1, "example.com.", "192.0.2.100", "192.0.2.101") - logger.Record(net.IPv4(127, 0, 0, 254), 28, "example.com.", "2001:db8::1") + httpSrv, srv := testServer() + defer httpSrv.Close() + srv.logger.Record(net.IPv4(127, 0, 0, 42), 1, "example.com.", "192.0.2.100", "192.0.2.101") + srv.logger.Record(net.IPv4(127, 0, 0, 254), 28, "example.com.", "2001:db8::1") + srv.cache.Set(1, newA("1.example.com.", 60, net.IPv4(192, 0, 2, 200))) + srv.cache.Set(2, newA("2.example.com.", 30, net.IPv4(192, 0, 2, 201))) - var logResponse = "[{\"time\":\"2006-01-02T15:04:05Z\",\"remote_addr\":\"127.0.0.254\",\"type\":\"AAAA\",\"question\":\"example.com.\",\"answers\":[\"2001:db8::1\"]}," + - "{\"time\":\"2006-01-02T15:04:05Z\",\"remote_addr\":\"127.0.0.42\",\"type\":\"A\",\"question\":\"example.com.\",\"answers\":[\"192.0.2.101\",\"192.0.2.100\"]}]" + var cacheResponse = "[{\"time\":\"RFC3339\",\"ttl\":30,\"type\":\"A\",\"question\":\"2.example.com.\",\"answers\":[\"192.0.2.201\"]}," + + "{\"time\":\"RFC3339\",\"ttl\":60,\"type\":\"A\",\"question\":\"1.example.com.\",\"answers\":[\"192.0.2.200\"]}]" + var logResponse = "[{\"time\":\"RFC3339\",\"remote_addr\":\"127.0.0.254\",\"type\":\"AAAA\",\"question\":\"example.com.\",\"answers\":[\"2001:db8::1\"]}," + + "{\"time\":\"RFC3339\",\"remote_addr\":\"127.0.0.42\",\"type\":\"A\",\"question\":\"example.com.\",\"answers\":[\"192.0.2.101\",\"192.0.2.100\"]}]" var tests = []struct { method string @@ -52,12 +73,12 @@ func TestRequests(t *testing.T) { response string status int }{ - // Unknown resources {http.MethodGet, "", "/not-found", `{"status":404,"message":"Resource not found"}`, 404}, {http.MethodGet, "", "/log/v1/", logResponse, 200}, + {http.MethodGet, "", "/cache/v1/", cacheResponse, 200}, } - for _, tt := range tests { + for i, tt := range tests { var ( data string status int @@ -65,18 +86,26 @@ func TestRequests(t *testing.T) { ) switch tt.method { case http.MethodGet: - data, status, err = httpGet(server.URL + tt.url) + data, status, err = httpGet(httpSrv.URL + tt.url) default: - t.Fatal("invalid method: " + tt.method) + t.Fatalf("#%d: invalid method: %s", i, tt.method) } if err != nil { t.Fatal(err) } if got := status; status != tt.status { - t.Errorf("want status %d for %q, got %d", tt.status, tt.url, got) + t.Errorf("#%d: %s %s returned status %d, want %d", i, tt.method, tt.url, got, tt.status) + } + + got := string(data) + want := regexp.QuoteMeta(tt.response) + want = strings.ReplaceAll(want, "RFC3339", `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`) + matched, err := regexp.MatchString(want, got) + if err != nil { + t.Fatal(err) } - if got := string(data); got != tt.response { - t.Errorf("want response %q for %s, got %q", tt.response, tt.url, got) + if !matched { + t.Errorf("#%d: %s %s returned response %s, want %s", i, tt.method, tt.url, got, want) } } } |