Allow multiple formats for response, add html and json response

• 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
master
Buddy Sandidge 8 years ago
parent ba99b61aa0
commit b0b44a992f

20
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
}

@ -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
}
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) ServeHTTP(w http.ResponseWriter, req *http.Request) {
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 (s *Server) sendResponse(resp http.ResponseWriter, ip *IP) {
io.WriteString(resp, ip.String()+"\n")
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, 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) {

@ -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 = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>What is My IP?</title>
<style> .ip { text-align: center; } </style>
</head>
<body>
<div class="ip">{{.IP}}</div>
</body>
</html>`

Loading…
Cancel
Save