Skip to content

Commit

Permalink
Add /hostname endpoint
Browse files Browse the repository at this point in the history
This adds a new /hostname endpoint as originally proposed in #66. In
this implementation, it exposes a dummy hostname by default, and only
exposes the real hostname (via `os.Hostname()`) if the
`-expose-real-hostname` flag is given on the command line.
  • Loading branch information
mccutchen committed Jul 3, 2022
1 parent 6a245c4 commit 4b8a6b9
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 15 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ TOOL_BIN_DIR ?= $(shell go env GOPATH)/bin
TOOL_GOLINT := $(TOOL_BIN_DIR)/golint
TOOL_STATICCHECK := $(TOOL_BIN_DIR)/staticcheck

GO_SOURCES = $(wildcard **/*.go)
GO_SOURCES = $(shell find . -name *.go)


# =============================================================================
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ variables:
```
$ go-httpbin --help
Usage of go-httpbin:
-expose-real-hostname
Expose real hostname as reported by os.Hostname() in the /hostname endpoint
-host string
Host to listen on (default "0.0.0.0")
Host to listen on (default "0.0.0.0")
-https-cert-file string
HTTPS Server certificate file
HTTPS Server certificate file
-https-key-file string
HTTPS Server private key file
HTTPS Server private key file
-max-body-size int
Maximum size of request or response, in bytes (default 1048576)
Maximum size of request or response, in bytes (default 1048576)
-max-duration duration
Maximum duration a response may take (default 10s)
Maximum duration a response may take (default 10s)
-port int
Port to listen on (default 8080)
Port to listen on (default 8080)
```

Examples:
Expand Down
27 changes: 19 additions & 8 deletions cmd/go-httpbin/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ const (
)

var (
host string
port int
maxBodySize int64
maxDuration time.Duration
httpsCertFile string
httpsKeyFile string
host string
port int
maxBodySize int64
maxDuration time.Duration
httpsCertFile string
httpsKeyFile string
exposeRealHostname bool
)

func main() {
flag.BoolVar(&exposeRealHostname, "expose-real-hostname", false, "Expose value of os.Hostname() in the /hostname endpoint instead of dummy value")
flag.StringVar(&host, "host", defaultHost, "Host to listen on")
flag.IntVar(&port, "port", defaultPort, "Port to listen on")
flag.StringVar(&httpsCertFile, "https-cert-file", "", "HTTPS Server certificate file")
Expand Down Expand Up @@ -101,11 +103,20 @@ func main() {
logger.Printf(logFmt, time.Now().Format(dateFmt), fmt.Sprintf(msg, args...))
}

h := httpbin.New(
opts := []httpbin.OptionFunc{
httpbin.WithMaxBodySize(maxBodySize),
httpbin.WithMaxDuration(maxDuration),
httpbin.WithObserver(httpbin.StdLogObserver(logger)),
)
}
if exposeRealHostname {
hostname, err := os.Hostname()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: -expose-real-hostname=true but hostname lookup failed: %s\n", err)
os.Exit(1)
}
opts = append(opts, httpbin.WithHostname(hostname))
}
h := httpbin.New(opts...)

listenAddr := net.JoinHostPort(host, strconv.Itoa(port))

Expand Down
8 changes: 8 additions & 0 deletions httpbin/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1004,3 +1004,11 @@ func (h *HTTPBin) Bearer(w http.ResponseWriter, r *http.Request) {
})
writeJSON(w, body, http.StatusOK)
}

// Hostname - returns the hostname.
func (h *HTTPBin) Hostname(w http.ResponseWriter, r *http.Request) {
body, _ := json.Marshal(hostnameResponse{
Hostname: h.hostname,
})
writeJSON(w, body, http.StatusOK)
}
46 changes: 46 additions & 0 deletions httpbin/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2571,3 +2571,49 @@ func TestNotImplemented(t *testing.T) {
})
}
}

func TestHostname(t *testing.T) {
t.Parallel()

loadResponse := func(t *testing.T, bodyBytes []byte) hostnameResponse {
var resp hostnameResponse
err := json.Unmarshal(bodyBytes, &resp)
if err != nil {
t.Fatalf("failed to unmarshal body %q from JSON: %s", string(bodyBytes), err)
}
return resp
}

t.Run("default hostname", func(t *testing.T) {
t.Parallel()

var (
handler = New().Handler()
r, _ = http.NewRequest("GET", "/hostname", nil)
w = httptest.NewRecorder()
)
handler.ServeHTTP(w, r)
assertStatusCode(t, w, http.StatusOK)
resp := loadResponse(t, w.Body.Bytes())
if resp.Hostname != DefaultHostname {
t.Errorf("expected hostname %q, got %q", DefaultHostname, resp.Hostname)
}
})

t.Run("real hostname", func(t *testing.T) {
t.Parallel()

var (
realHostname = "real-hostname"
handler = New(WithHostname(realHostname)).Handler()
r, _ = http.NewRequest("GET", "/hostname", nil)
w = httptest.NewRecorder()
)
handler.ServeHTTP(w, r)
assertStatusCode(t, w, http.StatusOK)
resp := loadResponse(t, w.Body.Bytes())
if resp.Hostname != realHostname {
t.Errorf("expected hostname %q, got %q", realHostname, resp.Hostname)
}
})
}
17 changes: 17 additions & 0 deletions httpbin/httpbin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
const (
DefaultMaxBodySize int64 = 1024 * 1024
DefaultMaxDuration = 10 * time.Second
DefaultHostname = "go-httpbin"
)

const (
Expand Down Expand Up @@ -87,6 +88,10 @@ type bearerResponse struct {
Token string `json:"token"`
}

type hostnameResponse struct {
Hostname string `json:"hostname"`
}

// HTTPBin contains the business logic
type HTTPBin struct {
// Max size of an incoming request generated response body, in bytes
Expand All @@ -101,6 +106,9 @@ type HTTPBin struct {

// Default parameter values
DefaultParams DefaultParams

// The hostname to expose via /hostname.
hostname string
}

// DefaultParams defines default parameter values
Expand Down Expand Up @@ -137,6 +145,7 @@ func (h *HTTPBin) Handler() http.Handler {
mux.HandleFunc("/user-agent", h.UserAgent)
mux.HandleFunc("/headers", h.Headers)
mux.HandleFunc("/response-headers", h.ResponseHeaders)
mux.HandleFunc("/hostname", h.Hostname)

mux.HandleFunc("/status/", h.Status)
mux.HandleFunc("/unstable", h.Unstable)
Expand Down Expand Up @@ -222,6 +231,7 @@ func New(opts ...OptionFunc) *HTTPBin {
MaxBodySize: DefaultMaxBodySize,
MaxDuration: DefaultMaxDuration,
DefaultParams: DefaultDefaultParams,
hostname: DefaultHostname,
}
for _, opt := range opts {
opt(h)
Expand Down Expand Up @@ -254,6 +264,13 @@ func WithMaxDuration(d time.Duration) OptionFunc {
}
}

// WithHostname sets the hostname to return via the /hostname endpoint.
func WithHostname(s string) OptionFunc {
return func(h *HTTPBin) {
h.hostname = s
}
}

// WithObserver sets the request observer callback
func WithObserver(o Observer) OptionFunc {
return func(h *HTTPBin) {
Expand Down
1 change: 1 addition & 0 deletions httpbin/static/index.html

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 4b8a6b9

Please sign in to comment.