From b0b44a992ff65b2e3cdaf9c24f86869991e22a35 Mon Sep 17 00:00:00 2001 From: Buddy Sandidge Date: Sat, 12 Nov 2016 16:50:22 -0800 Subject: [PATCH] Allow multiple formats for response, add html and json response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • If a request comes in to the root path, look at accept header and parse it. Check for hmtl, json, and text. If no known accept header comes in, return text/plain • Add route for /json that always returns json response • Add route for /text that always returns text response • Add route for /html that always returns html response --- ip.go | 20 ++++++++--- server.go | 94 ++++++++++++++++++++++++++++++++++++++++++++---- what-is-my-ip.go | 28 ++++++++++++++- 3 files changed, 130 insertions(+), 12 deletions(-) diff --git a/ip.go b/ip.go index 136dbdc..62030bb 100644 --- a/ip.go +++ b/ip.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "errors" "net" ) @@ -11,10 +12,6 @@ type IP struct { version string } -func (ip *IP) isV6() bool { - return ip.To4() == nil -} - // NewIP returns a new instance of an IP address func NewIP(addr string) (*IP, error) { parsedIP := net.ParseIP(addr) @@ -31,3 +28,18 @@ func NewIP(addr string) (*IP, error) { return result, nil } + +// MarshalJSON for marshalling ip to json for response +func (ip *IP) MarshalJSON() ([]byte, error) { + return json.Marshal(&struct { + IP string `json:"ip"` + Version string `json:"version"` + }{ + IP: ip.String(), + Version: ip.version, + }) +} + +func (ip *IP) isV6() bool { + return ip.To4() == nil +} diff --git a/server.go b/server.go index 2277639..f9cbcbc 100644 --- a/server.go +++ b/server.go @@ -1,18 +1,37 @@ package main import ( + "bytes" + "html/template" "io" "net" "net/http" + "strings" + + "github.com/pkg/errors" ) // Server that handles http responses type Server struct { headerNames []string logger LoggerHandler + tmpl *template.Template } -func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { +type responseFormat int + +const ( + unknownResponse responseFormat = iota + jsonResponse + textResponse + htmlResponse +) + +func (s *Server) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + s.handleHTTP(resp, req, s.getResponseType(req)) +} + +func (s *Server) handleHTTP(resp http.ResponseWriter, req *http.Request, responseType responseFormat) { for _, headerName := range s.headerNames { possibleIP := req.Header.Get(headerName) if possibleIP == "" { @@ -24,28 +43,89 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { continue } - s.sendResponse(w, ip) + s.sendResponse(resp, ip, responseType) return } addr, _, err := net.SplitHostPort(req.RemoteAddr) if err != nil { - s.handleError(w, http.StatusBadRequest, err, "Could not get IP address from request") + s.handleError(resp, http.StatusBadRequest, err, "Could not get IP address from request") return } ip, err := NewIP(addr) if err != nil { - s.handleError(w, http.StatusBadRequest, err, "Could not parse IP address"+addr) + s.handleError(resp, http.StatusBadRequest, err, "Could not parse IP address"+addr) return } - s.sendResponse(w, ip) + s.sendResponse(resp, ip, responseType) +} + +func hasType(accepts, valid []string) bool { + for _, accept := range accepts { + value := strings.Trim(strings.Split(accept, ";")[0], " ") + for _, check := range valid { + if check == value { + return true + } + } + } + return false +} + +func (s *Server) getResponseType(req *http.Request) responseFormat { + accepts := strings.Split(req.Header.Get("Accept"), ",") + if hasType(accepts, []string{"text/html", "application/xhtml+xml"}) { + return htmlResponse + } + if hasType(accepts, []string{"application/json", "text/json", "text/javascript"}) { + return jsonResponse + } + if hasType(accepts, []string{"text/plain"}) { + return textResponse + } + return unknownResponse } -func (s *Server) sendResponse(resp http.ResponseWriter, ip *IP) { - io.WriteString(resp, ip.String()+"\n") +func (s *Server) sendResponse(resp http.ResponseWriter, ip *IP, responseType responseFormat) { s.logger.Printf("Request from %s %s\n", ip.version, ip) + + var body []byte + var contentType = "text/plain" + var err error + + switch responseType { + + case jsonResponse: + jsonBody, marshalErr := ip.MarshalJSON() + if marshalErr != nil { + err = errors.Wrap(marshalErr, "could not marshal json") + } + contentType = "application/json" + body = jsonBody + + case htmlResponse: + buffer := bytes.NewBuffer([]byte{}) + exeErr := s.tmpl.Execute(buffer, map[string]string{"IP": ip.String()}) + if exeErr != nil { + err = errors.Wrap(exeErr, "could not get html") + } + contentType = "text/html" + body = buffer.Bytes() + + default: + body = []byte(ip.String()) + } + + if err != nil { + const errorMessage = "could not send response" + s.handleError(resp, http.StatusInternalServerError, errors.Wrap(err, errorMessage), errorMessage) + return + } + + resp.Header().Set("Content-Type", contentType) + resp.Write(body) } func (s *Server) handleError(resp http.ResponseWriter, status int, err error, message string) { diff --git a/what-is-my-ip.go b/what-is-my-ip.go index bfc9b7b..d898acd 100644 --- a/what-is-my-ip.go +++ b/what-is-my-ip.go @@ -2,6 +2,7 @@ package main import ( "errors" + "html/template" "net/http" "os" @@ -18,7 +19,7 @@ const ( func main() { app := cli.NewApp() app.Name = appName - app.Version = "0.3.0" + app.Version = "0.4.0" app.Usage = "Webapp that gives the IP address for incoming requests" app.Action = action @@ -67,6 +68,7 @@ func getHandler(headers []string) (http.Handler, *Server) { service := &Server{ headerNames: headers, logger: NewLogger(), + tmpl: template.Must(template.New("html").Parse(htmlTemplate)), } recover := negroni.NewRecovery() @@ -74,6 +76,18 @@ func getHandler(headers []string) (http.Handler, *Server) { router := pat.New() + router.Get("/json", func(resp http.ResponseWriter, req *http.Request) { + service.handleHTTP(resp, req, jsonResponse) + }) + + router.Get("/text", func(resp http.ResponseWriter, req *http.Request) { + service.handleHTTP(resp, req, textResponse) + }) + + router.Get("/html", func(resp http.ResponseWriter, req *http.Request) { + service.handleHTTP(resp, req, htmlResponse) + }) + router.Get("/", func(resp http.ResponseWriter, req *http.Request) { if req.URL.Path == "/" { service.ServeHTTP(resp, req) @@ -90,3 +104,15 @@ func getHandler(headers []string) (http.Handler, *Server) { return n, service } + +const htmlTemplate = ` + + + + What is My IP? + + + +
{{.IP}}
+ +`