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.

262 lines
7.1 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("/ip20", h.IP20)
mux.HandleFunc("/ssh", h.Ssh)
mux.HandleFunc("/sftp", h.Sftp)
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
}
}