Skip to content
Snippets Groups Projects
Commit 20dd618a authored by Will McCutchen's avatar Will McCutchen
Browse files

Support cmd/go-httpbin (:thumbsup:) and cmd/go_httpbin (:thumbsdown:)

In #17, we switched to deploying httpbingo.org on Google App Engine. To
meet their naming requirements, we had to rename cmd/go-httpbin to
cmd/go_httpbin, but this broke any existing uses of, e.g.,

    go get github.com/mccutchen/go-httpbin/cmd/go-httpbin

as suggested in the README and as used in various places (including my
employer).

Here's a dumb ass fix for that dumb ass problem; I'm not happy about it,
but it seems to work.
parent 87418bff
No related branches found
No related tags found
No related merge requests found
......@@ -10,14 +10,16 @@ BIN_DIR := $(GOPATH)/bin
GOLINT := $(BIN_DIR)/golint
GOBINDATA := $(BIN_DIR)/go-bindata
GO_SOURCES = $(wildcard **/*.go)
# =============================================================================
# build
# =============================================================================
build: dist/go-httpbin
dist/go-httpbin: assets cmd/go_httpbin/*.go httpbin/*.go go.mod
dist/go-httpbin: assets $(GO_SOURCES)
mkdir -p dist
go build -o dist/go-httpbin ./cmd/go_httpbin
go build -o dist/go-httpbin ./cmd/go-httpbin
assets: $(GENERATED_ASSETS_PATH)
......@@ -67,7 +69,7 @@ run: build
# =============================================================================
# docker images
# =============================================================================
image: assets cmd/go_httpbin/*.go httpbin/*.go
image: build
docker build -t mccutchen/go-httpbin:$(VERSION) .
imagepush: image
......
......@@ -88,7 +88,7 @@ func TestSlowResponse(t *testing.T) {
## Installation
```
go get github.com/mccutchen/go-httpbin/...
go get github.com/mccutchen/go-httpbin/cmd/go-httpbin
```
......
# What is going on here?
## TL;DR
* `cmd/maincmd` package exposes all of this app's command line functionality in a `Main()`
* `cmd/go-httpbin` and `cmd/go_httpbin` build identical binaries using the
`maincmd` package for backwards compatibility reasons explained below
## Why tho
Originally, this project exposed only one command:
cmd/go-httpbin/main.go
But the dash in that path was incompatible with Google App Engine's naming
restrictions, so in [moving httpbingo.org onto Google App Engine][pr17], that
path was (carelessly) renamed to
cmd/go_httpbin/main.go
_That_ change had a number of unintended consequences:
* It broke existing workflows built around `go get github.com/mccutchen/go-httpbin/cmd/go-httpbin`,
as suggested in the README
* It broke the Makefile, which was still looking for `cmd/go-httpbin`
* It broke the absolute aesthetic truth that CLI binaries should use dashes
instead of underscores for word separators
So, to restore the former behavior while maintaining support for deploying to
App Engine, the actual main functionality was extracted into the `cmd/maincmd`
package here and shared between the other two.
(This is pretty dumb, I know, but it seems to work.)
[pr17]: https://github.com/mccutchen/go-httpbin/pull/17
package main
import "github.com/mccutchen/go-httpbin/cmd/maincmd"
func main() {
maincmd.Main()
}
package main
import (
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"strconv"
"time"
"github.com/mccutchen/go-httpbin/httpbin"
)
const defaultHost = "0.0.0.0"
const defaultPort = 8080
var (
host string
port int
maxBodySize int64
maxDuration time.Duration
httpsCertFile string
httpsKeyFile string
)
import "github.com/mccutchen/go-httpbin/cmd/maincmd"
func main() {
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")
flag.StringVar(&httpsKeyFile, "https-key-file", "", "HTTPS Server private key file")
flag.Int64Var(&maxBodySize, "max-body-size", httpbin.DefaultMaxBodySize, "Maximum size of request or response, in bytes")
flag.DurationVar(&maxDuration, "max-duration", httpbin.DefaultMaxDuration, "Maximum duration a response may take")
flag.Parse()
// Command line flags take precedence over environment vars, so we only
// check for environment vars if we have default values for our command
// line flags.
var err error
if maxBodySize == httpbin.DefaultMaxBodySize && os.Getenv("MAX_BODY_SIZE") != "" {
maxBodySize, err = strconv.ParseInt(os.Getenv("MAX_BODY_SIZE"), 10, 64)
if err != nil {
fmt.Printf("invalid value %#v for env var MAX_BODY_SIZE: %s\n", os.Getenv("MAX_BODY_SIZE"), err)
flag.Usage()
os.Exit(1)
}
}
if maxDuration == httpbin.DefaultMaxDuration && os.Getenv("MAX_DURATION") != "" {
maxDuration, err = time.ParseDuration(os.Getenv("MAX_DURATION"))
if err != nil {
fmt.Printf("invalid value %#v for env var MAX_DURATION: %s\n", os.Getenv("MAX_DURATION"), err)
flag.Usage()
os.Exit(1)
}
}
if host == defaultHost && os.Getenv("HOST") != "" {
host = os.Getenv("HOST")
}
if port == defaultPort && os.Getenv("PORT") != "" {
port, err = strconv.Atoi(os.Getenv("PORT"))
if err != nil {
fmt.Printf("invalid value %#v for env var PORT: %s\n", os.Getenv("PORT"), err)
flag.Usage()
os.Exit(1)
}
}
if httpsCertFile == "" && os.Getenv("HTTPS_CERT_FILE") != "" {
httpsCertFile = os.Getenv("HTTPS_CERT_FILE")
}
if httpsKeyFile == "" && os.Getenv("HTTPS_KEY_FILE") != "" {
httpsKeyFile = os.Getenv("HTTPS_KEY_FILE")
}
logger := log.New(os.Stderr, "", 0)
h := httpbin.New(
httpbin.WithMaxBodySize(maxBodySize),
httpbin.WithMaxDuration(maxDuration),
httpbin.WithObserver(httpbin.StdLogObserver(logger)),
)
listenAddr := net.JoinHostPort(host, strconv.Itoa(port))
server := &http.Server{
Addr: listenAddr,
Handler: h.Handler(),
}
var listenErr error
if httpsCertFile != "" && httpsKeyFile != "" {
cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile)
if err != nil {
logger.Fatal("Failed to generate https key pair: ", err)
}
server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}
logger.Printf("go-httpbin listening on https://%s", listenAddr)
listenErr = server.ListenAndServeTLS("", "")
} else {
logger.Printf("go-httpbin listening on http://%s", listenAddr)
listenErr = server.ListenAndServe()
}
if listenErr != nil {
logger.Fatalf("Failed to listen: %s", listenErr)
}
maincmd.Main()
}
package maincmd
import (
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"strconv"
"time"
"github.com/mccutchen/go-httpbin/httpbin"
)
const defaultHost = "0.0.0.0"
const defaultPort = 8080
var (
host string
port int
maxBodySize int64
maxDuration time.Duration
httpsCertFile string
httpsKeyFile string
)
// Main implements the go-httpbin CLI's main() function in a reusable way
func Main() {
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")
flag.StringVar(&httpsKeyFile, "https-key-file", "", "HTTPS Server private key file")
flag.Int64Var(&maxBodySize, "max-body-size", httpbin.DefaultMaxBodySize, "Maximum size of request or response, in bytes")
flag.DurationVar(&maxDuration, "max-duration", httpbin.DefaultMaxDuration, "Maximum duration a response may take")
flag.Parse()
// Command line flags take precedence over environment vars, so we only
// check for environment vars if we have default values for our command
// line flags.
var err error
if maxBodySize == httpbin.DefaultMaxBodySize && os.Getenv("MAX_BODY_SIZE") != "" {
maxBodySize, err = strconv.ParseInt(os.Getenv("MAX_BODY_SIZE"), 10, 64)
if err != nil {
fmt.Printf("invalid value %#v for env var MAX_BODY_SIZE: %s\n", os.Getenv("MAX_BODY_SIZE"), err)
flag.Usage()
os.Exit(1)
}
}
if maxDuration == httpbin.DefaultMaxDuration && os.Getenv("MAX_DURATION") != "" {
maxDuration, err = time.ParseDuration(os.Getenv("MAX_DURATION"))
if err != nil {
fmt.Printf("invalid value %#v for env var MAX_DURATION: %s\n", os.Getenv("MAX_DURATION"), err)
flag.Usage()
os.Exit(1)
}
}
if host == defaultHost && os.Getenv("HOST") != "" {
host = os.Getenv("HOST")
}
if port == defaultPort && os.Getenv("PORT") != "" {
port, err = strconv.Atoi(os.Getenv("PORT"))
if err != nil {
fmt.Printf("invalid value %#v for env var PORT: %s\n", os.Getenv("PORT"), err)
flag.Usage()
os.Exit(1)
}
}
if httpsCertFile == "" && os.Getenv("HTTPS_CERT_FILE") != "" {
httpsCertFile = os.Getenv("HTTPS_CERT_FILE")
}
if httpsKeyFile == "" && os.Getenv("HTTPS_KEY_FILE") != "" {
httpsKeyFile = os.Getenv("HTTPS_KEY_FILE")
}
logger := log.New(os.Stderr, "", 0)
h := httpbin.New(
httpbin.WithMaxBodySize(maxBodySize),
httpbin.WithMaxDuration(maxDuration),
httpbin.WithObserver(httpbin.StdLogObserver(logger)),
)
listenAddr := net.JoinHostPort(host, strconv.Itoa(port))
server := &http.Server{
Addr: listenAddr,
Handler: h.Handler(),
}
var listenErr error
if httpsCertFile != "" && httpsKeyFile != "" {
cert, err := tls.LoadX509KeyPair(httpsCertFile, httpsKeyFile)
if err != nil {
logger.Fatal("Failed to generate https key pair: ", err)
}
server.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
}
logger.Printf("go-httpbin listening on https://%s", listenAddr)
listenErr = server.ListenAndServeTLS("", "")
} else {
logger.Printf("go-httpbin listening on http://%s", listenAddr)
listenErr = server.ListenAndServe()
}
if listenErr != nil {
logger.Fatalf("Failed to listen: %s", listenErr)
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment