doggo/cmd/api/handlers.go

143 lines
3.9 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
"git.zio.sh/astra/doggo/internal/app"
"git.zio.sh/astra/doggo/pkg/models"
"git.zio.sh/astra/doggo/pkg/resolvers"
)
type httpResp struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
}
func handleIndexAPI(w http.ResponseWriter, r *http.Request) {
var (
app = r.Context().Value("app").(app.App)
)
sendResponse(w, http.StatusOK, fmt.Sprintf("Welcome to Doggo API. Version: %s", app.Version))
return
}
func handleHealthCheck(w http.ResponseWriter, r *http.Request) {
sendResponse(w, http.StatusOK, "PONG")
return
}
func handleLookup(w http.ResponseWriter, r *http.Request) {
var (
app = r.Context().Value("app").(app.App)
)
// Read body.
b, err := ioutil.ReadAll(r.Body)
defer r.Body.Close()
if err != nil {
app.Logger.WithError(err).Error("error reading request body")
sendErrorResponse(w, fmt.Sprintf("Invalid JSON payload"), http.StatusBadRequest, nil)
return
}
// Prepare query flags.
var qFlags models.QueryFlags
if err := json.Unmarshal(b, &qFlags); err != nil {
app.Logger.WithError(err).Error("error unmarshalling payload")
sendErrorResponse(w, fmt.Sprintf("Invalid JSON payload"), http.StatusBadRequest, nil)
return
}
app.QueryFlags = qFlags
// Load fallbacks.
app.LoadFallbacks()
// Load Questions.
app.PrepareQuestions()
if len(app.Questions) == 0 {
sendErrorResponse(w, fmt.Sprintf("Missing field `query`."), http.StatusBadRequest, nil)
return
}
// Load Nameservers.
err = app.LoadNameservers()
if err != nil {
app.Logger.WithError(err).Error("error loading nameservers")
sendErrorResponse(w, fmt.Sprintf("Error looking up for records."), http.StatusInternalServerError, nil)
return
}
// Load Resolvers.
rslvrs, err := resolvers.LoadResolvers(resolvers.Options{
Nameservers: app.Nameservers,
UseIPv4: app.QueryFlags.UseIPv4,
UseIPv6: app.QueryFlags.UseIPv6,
SearchList: app.ResolverOpts.SearchList,
Ndots: app.ResolverOpts.Ndots,
Timeout: app.QueryFlags.Timeout * time.Second,
Logger: app.Logger,
})
if err != nil {
app.Logger.WithError(err).Error("error loading resolver")
sendErrorResponse(w, fmt.Sprintf("Error looking up for records."), http.StatusInternalServerError, nil)
return
}
app.Resolvers = rslvrs
var responses []resolvers.Response
for _, q := range app.Questions {
for _, rslv := range app.Resolvers {
resp, err := rslv.Lookup(q)
if err != nil {
app.Logger.WithError(err).Error("error looking up DNS records")
sendErrorResponse(w, fmt.Sprintf("Error looking up for records."), http.StatusInternalServerError, nil)
return
}
responses = append(responses, resp)
}
}
sendResponse(w, http.StatusOK, responses)
return
}
// wrap is a middleware that wraps HTTP handlers and injects the "app" context.
func wrap(app app.App, next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "app", app)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// sendResponse sends a JSON envelope to the HTTP response.
func sendResponse(w http.ResponseWriter, code int, data interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
out, err := json.Marshal(httpResp{Status: "success", Data: data})
if err != nil {
sendErrorResponse(w, "Internal Server Error", http.StatusInternalServerError, nil)
return
}
w.Write(out)
}
// sendErrorResponse sends a JSON error envelope to the HTTP response.
func sendErrorResponse(w http.ResponseWriter, message string, code int, data interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
resp := httpResp{Status: "error",
Message: message,
Data: data}
out, _ := json.Marshal(resp)
w.Write(out)
}