You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
7.0 KiB
Go
260 lines
7.0 KiB
Go
package httpbin
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
)
|
|
|
|
// Default configuration values
|
|
const (
|
|
DefaultMaxBodySize int64 = 1024 * 1024
|
|
DefaultMaxDuration = 10 * time.Second
|
|
)
|
|
|
|
const jsonContentType = "application/json; encoding=utf-8"
|
|
const htmlContentType = "text/html; charset=utf-8"
|
|
|
|
type headersResponse struct {
|
|
Headers http.Header `json:"headers"`
|
|
}
|
|
|
|
type ipResponse struct {
|
|
Origin string `json:"origin"`
|
|
}
|
|
|
|
type userAgentResponse struct {
|
|
UserAgent string `json:"user-agent"`
|
|
}
|
|
|
|
type getResponse struct {
|
|
Args url.Values `json:"args"`
|
|
Headers http.Header `json:"headers"`
|
|
Origin string `json:"origin"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// A generic response for any incoming request that might contain a body
|
|
type bodyResponse struct {
|
|
Args url.Values `json:"args"`
|
|
Headers http.Header `json:"headers"`
|
|
Origin string `json:"origin"`
|
|
URL string `json:"url"`
|
|
|
|
Data string `json:"data"`
|
|
Files map[string][]string `json:"files"`
|
|
Form map[string][]string `json:"form"`
|
|
JSON interface{} `json:"json"`
|
|
}
|
|
|
|
type cookiesResponse map[string]string
|
|
|
|
type authResponse struct {
|
|
Authorized bool `json:"authorized"`
|
|
User string `json:"user"`
|
|
}
|
|
|
|
type gzipResponse struct {
|
|
Headers http.Header `json:"headers"`
|
|
Origin string `json:"origin"`
|
|
Gzipped bool `json:"gzipped"`
|
|
}
|
|
|
|
type deflateResponse struct {
|
|
Headers http.Header `json:"headers"`
|
|
Origin string `json:"origin"`
|
|
Deflated bool `json:"deflated"`
|
|
}
|
|
|
|
// An actual stream response body will be made up of one or more of these
|
|
// structs, encoded as JSON and separated by newlines
|
|
type streamResponse struct {
|
|
ID int `json:"id"`
|
|
Args url.Values `json:"args"`
|
|
Headers http.Header `json:"headers"`
|
|
Origin string `json:"origin"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type uuidResponse struct {
|
|
UUID string `json:"uuid"`
|
|
}
|
|
|
|
type bearerResponse struct {
|
|
Authenticated bool `json:"authenticated"`
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
// HTTPBin contains the business logic
|
|
type HTTPBin struct {
|
|
// Max size of an incoming request generated response body, in bytes
|
|
MaxBodySize int64
|
|
|
|
// Max duration of a request, for those requests that allow user control
|
|
// over timing (e.g. /delay)
|
|
MaxDuration time.Duration
|
|
|
|
// Observer called with the result of each handled request
|
|
Observer Observer
|
|
|
|
// Default parameter values
|
|
DefaultParams DefaultParams
|
|
}
|
|
|
|
// DefaultParams defines default parameter values
|
|
type DefaultParams struct {
|
|
DripDuration time.Duration
|
|
DripDelay time.Duration
|
|
DripNumBytes int64
|
|
}
|
|
|
|
// DefaultDefaultParams defines the DefaultParams that are used by default. In
|
|
// general, these should match the original httpbin.org's defaults.
|
|
var DefaultDefaultParams = DefaultParams{
|
|
DripDuration: 2 * time.Second,
|
|
DripDelay: 2 * time.Second,
|
|
DripNumBytes: 10,
|
|
}
|
|
|
|
// Handler returns an http.Handler that exposes all HTTPBin endpoints
|
|
func (h *HTTPBin) Handler() http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
mux.HandleFunc("/", methods(h.Index, "GET"))
|
|
mux.HandleFunc("/forms/post", methods(h.FormsPost, "GET"))
|
|
mux.HandleFunc("/encoding/utf8", methods(h.UTF8, "GET"))
|
|
|
|
mux.HandleFunc("/get", methods(h.Get, "GET"))
|
|
mux.HandleFunc("/post", methods(h.RequestWithBody, "POST"))
|
|
mux.HandleFunc("/put", methods(h.RequestWithBody, "PUT"))
|
|
mux.HandleFunc("/patch", methods(h.RequestWithBody, "PATCH"))
|
|
mux.HandleFunc("/delete", methods(h.RequestWithBody, "DELETE"))
|
|
|
|
mux.HandleFunc("/ip", h.IP)
|
|
mux.HandleFunc("/ip10", h.IP10)
|
|
mux.HandleFunc("/user-agent", h.UserAgent)
|
|
mux.HandleFunc("/headers", h.Headers)
|
|
mux.HandleFunc("/response-headers", h.ResponseHeaders)
|
|
|
|
mux.HandleFunc("/status/", h.Status)
|
|
|
|
mux.HandleFunc("/redirect/", h.Redirect)
|
|
mux.HandleFunc("/relative-redirect/", h.RelativeRedirect)
|
|
mux.HandleFunc("/absolute-redirect/", h.AbsoluteRedirect)
|
|
mux.HandleFunc("/redirect-to", h.RedirectTo)
|
|
|
|
mux.HandleFunc("/cookies", h.Cookies)
|
|
mux.HandleFunc("/cookies/set", h.SetCookies)
|
|
mux.HandleFunc("/cookies/delete", h.DeleteCookies)
|
|
|
|
mux.HandleFunc("/basic-auth/", h.BasicAuth)
|
|
mux.HandleFunc("/hidden-basic-auth/", h.HiddenBasicAuth)
|
|
mux.HandleFunc("/digest-auth/", h.DigestAuth)
|
|
mux.HandleFunc("/bearer", h.Bearer)
|
|
|
|
mux.HandleFunc("/deflate", h.Deflate)
|
|
mux.HandleFunc("/gzip", h.Gzip)
|
|
|
|
mux.HandleFunc("/stream/", h.Stream)
|
|
mux.HandleFunc("/delay/", h.Delay)
|
|
mux.HandleFunc("/drip", h.Drip)
|
|
|
|
mux.HandleFunc("/range/", h.Range)
|
|
mux.HandleFunc("/bytes/", h.Bytes)
|
|
mux.HandleFunc("/stream-bytes/", h.StreamBytes)
|
|
|
|
mux.HandleFunc("/html", h.HTML)
|
|
mux.HandleFunc("/robots.txt", h.Robots)
|
|
mux.HandleFunc("/deny", h.Deny)
|
|
|
|
mux.HandleFunc("/cache", h.Cache)
|
|
mux.HandleFunc("/cache/", h.CacheControl)
|
|
mux.HandleFunc("/etag/", h.ETag)
|
|
|
|
mux.HandleFunc("/links/", h.Links)
|
|
|
|
mux.HandleFunc("/image", h.ImageAccept)
|
|
mux.HandleFunc("/image/", h.Image)
|
|
mux.HandleFunc("/xml", h.XML)
|
|
mux.HandleFunc("/json", h.JSON)
|
|
|
|
mux.HandleFunc("/uuid", h.UUID)
|
|
mux.HandleFunc("/base64/", h.Base64)
|
|
|
|
// existing httpbin endpoints that we do not support
|
|
mux.HandleFunc("/brotli", notImplementedHandler)
|
|
|
|
// Make sure our ServeMux doesn't "helpfully" redirect these invalid
|
|
// endpoints by adding a trailing slash. See the ServeMux docs for more
|
|
// info: https://golang.org/pkg/net/http/#ServeMux
|
|
mux.HandleFunc("/absolute-redirect", http.NotFound)
|
|
mux.HandleFunc("/basic-auth", http.NotFound)
|
|
mux.HandleFunc("/delay", http.NotFound)
|
|
mux.HandleFunc("/digest-auth", http.NotFound)
|
|
mux.HandleFunc("/hidden-basic-auth", http.NotFound)
|
|
mux.HandleFunc("/redirect", http.NotFound)
|
|
mux.HandleFunc("/relative-redirect", http.NotFound)
|
|
mux.HandleFunc("/status", http.NotFound)
|
|
mux.HandleFunc("/stream", http.NotFound)
|
|
mux.HandleFunc("/bytes", http.NotFound)
|
|
mux.HandleFunc("/stream-bytes", http.NotFound)
|
|
mux.HandleFunc("/links", http.NotFound)
|
|
|
|
// Apply global middleware
|
|
var handler http.Handler
|
|
handler = mux
|
|
handler = limitRequestSize(h.MaxBodySize, handler)
|
|
handler = preflight(handler)
|
|
handler = autohead(handler)
|
|
if h.Observer != nil {
|
|
handler = observe(h.Observer, handler)
|
|
}
|
|
|
|
return handler
|
|
}
|
|
|
|
// New creates a new HTTPBin instance
|
|
func New(opts ...OptionFunc) *HTTPBin {
|
|
h := &HTTPBin{
|
|
MaxBodySize: DefaultMaxBodySize,
|
|
MaxDuration: DefaultMaxDuration,
|
|
DefaultParams: DefaultDefaultParams,
|
|
}
|
|
for _, opt := range opts {
|
|
opt(h)
|
|
}
|
|
return h
|
|
}
|
|
|
|
// OptionFunc uses the "functional options" pattern to customize an HTTPBin
|
|
// instance
|
|
type OptionFunc func(*HTTPBin)
|
|
|
|
// WithDefaultParams sets the default params handlers will use
|
|
func WithDefaultParams(defaultParams DefaultParams) OptionFunc {
|
|
return func(h *HTTPBin) {
|
|
h.DefaultParams = defaultParams
|
|
}
|
|
}
|
|
|
|
// WithMaxBodySize sets the maximum amount of memory
|
|
func WithMaxBodySize(m int64) OptionFunc {
|
|
return func(h *HTTPBin) {
|
|
h.MaxBodySize = m
|
|
}
|
|
}
|
|
|
|
// WithMaxDuration sets the maximum amount of time httpbin may take to respond
|
|
func WithMaxDuration(d time.Duration) OptionFunc {
|
|
return func(h *HTTPBin) {
|
|
h.MaxDuration = d
|
|
}
|
|
}
|
|
|
|
// WithObserver sets the request observer callback
|
|
func WithObserver(o Observer) OptionFunc {
|
|
return func(h *HTTPBin) {
|
|
h.Observer = o
|
|
}
|
|
}
|