aboutsummaryrefslogtreecommitdiffstats
path: root/http
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2019-12-26 19:23:15 +0100
committerMartin Polden <mpolden@mpolden.no>2019-12-26 19:23:15 +0100
commit82214806d0fe1ff73e4cf443465e75c99e028222 (patch)
treeda015eb2d075f7dfe1598b4d154b84a8e7abf16d /http
parentf031924851c4121117b70d97d7d507e5bd593fb1 (diff)
Add /cache/v1/ path
Diffstat (limited to 'http')
-rw-r--r--http/http.go78
-rw-r--r--http/http_test.go69
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)
}
}
}