mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-03 06:27:47 +00:00
1bb1b621c6
Signed-off-by: deadprogram <ron@hybridgroup.com>
582 lines
19 KiB
Go
582 lines
19 KiB
Go
package http
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
urlpkg "net/url"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
// A Handler responds to an HTTP request.
|
|
//
|
|
// ServeHTTP should write reply headers and data to the ResponseWriter
|
|
// and then return. Returning signals that the request is finished; it
|
|
// is not valid to use the ResponseWriter or read from the
|
|
// Request.Body after or concurrently with the completion of the
|
|
// ServeHTTP call.
|
|
//
|
|
// Depending on the HTTP client software, HTTP protocol version, and
|
|
// any intermediaries between the client and the Go server, it may not
|
|
// be possible to read from the Request.Body after writing to the
|
|
// ResponseWriter. Cautious handlers should read the Request.Body
|
|
// first, and then reply.
|
|
//
|
|
// Except for reading the body, handlers should not modify the
|
|
// provided Request.
|
|
//
|
|
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
|
|
// that the effect of the panic was isolated to the active request.
|
|
// It recovers the panic, logs a stack trace to the server error log,
|
|
// and either closes the network connection or sends an HTTP/2
|
|
// RST_STREAM, depending on the HTTP protocol. To abort a handler so
|
|
// the client sees an interrupted response but the server doesn't log
|
|
// an error, panic with the value ErrAbortHandler.
|
|
type Handler interface {
|
|
ServeHTTP(ResponseWriter, *Request)
|
|
}
|
|
|
|
// A ResponseWriter interface is used by an HTTP handler to
|
|
// construct an HTTP response.
|
|
//
|
|
// A ResponseWriter may not be used after the Handler.ServeHTTP method
|
|
// has returned.
|
|
type ResponseWriter interface {
|
|
// Header returns the header map that will be sent by
|
|
// WriteHeader. The Header map also is the mechanism with which
|
|
// Handlers can set HTTP trailers.
|
|
//
|
|
// Changing the header map after a call to WriteHeader (or
|
|
// Write) has no effect unless the modified headers are
|
|
// trailers.
|
|
//
|
|
// There are two ways to set Trailers. The preferred way is to
|
|
// predeclare in the headers which trailers you will later
|
|
// send by setting the "Trailer" header to the names of the
|
|
// trailer keys which will come later. In this case, those
|
|
// keys of the Header map are treated as if they were
|
|
// trailers. See the example. The second way, for trailer
|
|
// keys not known to the Handler until after the first Write,
|
|
// is to prefix the Header map keys with the TrailerPrefix
|
|
// constant value. See TrailerPrefix.
|
|
//
|
|
// To suppress automatic response headers (such as "Date"), set
|
|
// their value to nil.
|
|
Header() Header
|
|
|
|
// Write writes the data to the connection as part of an HTTP reply.
|
|
//
|
|
// If WriteHeader has not yet been called, Write calls
|
|
// WriteHeader(http.StatusOK) before writing the data. If the Header
|
|
// does not contain a Content-Type line, Write adds a Content-Type set
|
|
// to the result of passing the initial 512 bytes of written data to
|
|
// DetectContentType. Additionally, if the total size of all written
|
|
// data is under a few KB and there are no Flush calls, the
|
|
// Content-Length header is added automatically.
|
|
//
|
|
// Depending on the HTTP protocol version and the client, calling
|
|
// Write or WriteHeader may prevent future reads on the
|
|
// Request.Body. For HTTP/1.x requests, handlers should read any
|
|
// needed request body data before writing the response. Once the
|
|
// headers have been flushed (due to either an explicit Flusher.Flush
|
|
// call or writing enough data to trigger a flush), the request body
|
|
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
|
|
// handlers to continue to read the request body while concurrently
|
|
// writing the response. However, such behavior may not be supported
|
|
// by all HTTP/2 clients. Handlers should read before writing if
|
|
// possible to maximize compatibility.
|
|
Write([]byte) (int, error)
|
|
|
|
// WriteHeader sends an HTTP response header with the provided
|
|
// status code.
|
|
//
|
|
// If WriteHeader is not called explicitly, the first call to Write
|
|
// will trigger an implicit WriteHeader(http.StatusOK).
|
|
// Thus explicit calls to WriteHeader are mainly used to
|
|
// send error codes.
|
|
//
|
|
// The provided code must be a valid HTTP 1xx-5xx status code.
|
|
// Only one header may be written. Go does not currently
|
|
// support sending user-defined 1xx informational headers,
|
|
// with the exception of 100-continue response header that the
|
|
// Server sends automatically when the Request.Body is read.
|
|
WriteHeader(statusCode int)
|
|
}
|
|
|
|
// TimeFormat is the time format to use when generating times in HTTP
|
|
// headers. It is like time.RFC1123 but hard-codes GMT as the time
|
|
// zone. The time being formatted must be in UTC for Format to
|
|
// generate the correct format.
|
|
//
|
|
// For parsing this time format, see ParseTime.
|
|
const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
|
|
|
// The HandlerFunc type is an adapter to allow the use of
|
|
// ordinary functions as HTTP handlers. If f is a function
|
|
// with the appropriate signature, HandlerFunc(f) is a
|
|
// Handler that calls f.
|
|
type HandlerFunc func(ResponseWriter, *Request)
|
|
|
|
// ServeHTTP calls f(w, r).
|
|
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
|
f(w, r)
|
|
}
|
|
|
|
// Helper handlers
|
|
|
|
// Error replies to the request with the specified error message and HTTP code.
|
|
// It does not otherwise end the request; the caller should ensure no further
|
|
// writes are done to w.
|
|
// The error message should be plain text.
|
|
func Error(w ResponseWriter, error string, code int) {
|
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
|
w.WriteHeader(code)
|
|
fmt.Fprintln(w, error)
|
|
}
|
|
|
|
// NotFound replies to the request with an HTTP 404 not found error.
|
|
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }
|
|
|
|
// NotFoundHandler returns a simple request handler
|
|
// that replies to each request with a “404 page not found” reply.
|
|
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }
|
|
|
|
// StripPrefix returns a handler that serves HTTP requests by removing the
|
|
// given prefix from the request URL's Path (and RawPath if set) and invoking
|
|
// the handler h. StripPrefix handles a request for a path that doesn't begin
|
|
// with prefix by replying with an HTTP 404 not found error. The prefix must
|
|
// match exactly: if the prefix in the request contains escaped characters
|
|
// the reply is also an HTTP 404 not found error.
|
|
func StripPrefix(prefix string, h Handler) Handler {
|
|
if prefix == "" {
|
|
return h
|
|
}
|
|
return HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
p := strings.TrimPrefix(r.URL.Path, prefix)
|
|
rp := strings.TrimPrefix(r.URL.RawPath, prefix)
|
|
if len(p) < len(r.URL.Path) && (r.URL.RawPath == "" || len(rp) < len(r.URL.RawPath)) {
|
|
r2 := new(Request)
|
|
*r2 = *r
|
|
r2.URL = new(url.URL)
|
|
*r2.URL = *r.URL
|
|
r2.URL.Path = p
|
|
r2.URL.RawPath = rp
|
|
h.ServeHTTP(w, r2)
|
|
} else {
|
|
NotFound(w, r)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Redirect replies to the request with a redirect to url,
|
|
// which may be a path relative to the request path.
|
|
//
|
|
// The provided code should be in the 3xx range and is usually
|
|
// StatusMovedPermanently, StatusFound or StatusSeeOther.
|
|
//
|
|
// If the Content-Type header has not been set, Redirect sets it
|
|
// to "text/html; charset=utf-8" and writes a small HTML body.
|
|
// Setting the Content-Type header to any value, including nil,
|
|
// disables that behavior.
|
|
func Redirect(w ResponseWriter, r *Request, url string, code int) {
|
|
if u, err := urlpkg.Parse(url); err == nil {
|
|
// If url was relative, make its path absolute by
|
|
// combining with request path.
|
|
// The client would probably do this for us,
|
|
// but doing it ourselves is more reliable.
|
|
// See RFC 7231, section 7.1.2
|
|
if u.Scheme == "" && u.Host == "" {
|
|
oldpath := r.URL.Path
|
|
if oldpath == "" { // should not happen, but avoid a crash if it does
|
|
oldpath = "/"
|
|
}
|
|
|
|
// no leading http://server
|
|
if url == "" || url[0] != '/' {
|
|
// make relative path absolute
|
|
olddir, _ := path.Split(oldpath)
|
|
url = olddir + url
|
|
}
|
|
|
|
var query string
|
|
if i := strings.Index(url, "?"); i != -1 {
|
|
url, query = url[:i], url[i:]
|
|
}
|
|
|
|
// clean up but preserve trailing slash
|
|
trailing := strings.HasSuffix(url, "/")
|
|
url = path.Clean(url)
|
|
if trailing && !strings.HasSuffix(url, "/") {
|
|
url += "/"
|
|
}
|
|
url += query
|
|
}
|
|
}
|
|
|
|
h := w.Header()
|
|
|
|
// RFC 7231 notes that a short HTML body is usually included in
|
|
// the response because older user agents may not understand 301/307.
|
|
// Do it only if the request didn't already have a Content-Type header.
|
|
_, hadCT := h["Content-Type"]
|
|
|
|
h.Set("Location", hexEscapeNonASCII(url))
|
|
if !hadCT && (r.Method == "GET" || r.Method == "HEAD") {
|
|
h.Set("Content-Type", "text/html; charset=utf-8")
|
|
}
|
|
w.WriteHeader(code)
|
|
|
|
// Shouldn't send the body for POST or HEAD; that leaves GET.
|
|
if !hadCT && r.Method == "GET" {
|
|
body := "<a href=\"" + htmlEscape(url) + "\">" + statusText[code] + "</a>.\n"
|
|
fmt.Fprintln(w, body)
|
|
}
|
|
}
|
|
|
|
var htmlReplacer = strings.NewReplacer(
|
|
"&", "&",
|
|
"<", "<",
|
|
">", ">",
|
|
// """ is shorter than """.
|
|
`"`, """,
|
|
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
|
"'", "'",
|
|
)
|
|
|
|
func htmlEscape(s string) string {
|
|
return htmlReplacer.Replace(s)
|
|
}
|
|
|
|
// Redirect to a fixed URL
|
|
type redirectHandler struct {
|
|
url string
|
|
code int
|
|
}
|
|
|
|
func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
|
|
Redirect(w, r, rh.url, rh.code)
|
|
}
|
|
|
|
// RedirectHandler returns a request handler that redirects
|
|
// each request it receives to the given url using the given
|
|
// status code.
|
|
//
|
|
// The provided code should be in the 3xx range and is usually
|
|
// StatusMovedPermanently, StatusFound or StatusSeeOther.
|
|
func RedirectHandler(url string, code int) Handler {
|
|
return &redirectHandler{url, code}
|
|
}
|
|
|
|
// ServeMux is an HTTP request multiplexer.
|
|
// It matches the URL of each incoming request against a list of registered
|
|
// patterns and calls the handler for the pattern that
|
|
// most closely matches the URL.
|
|
//
|
|
// Patterns name fixed, rooted paths, like "/favicon.ico",
|
|
// or rooted subtrees, like "/images/" (note the trailing slash).
|
|
// Longer patterns take precedence over shorter ones, so that
|
|
// if there are handlers registered for both "/images/"
|
|
// and "/images/thumbnails/", the latter handler will be
|
|
// called for paths beginning "/images/thumbnails/" and the
|
|
// former will receive requests for any other paths in the
|
|
// "/images/" subtree.
|
|
//
|
|
// Note that since a pattern ending in a slash names a rooted subtree,
|
|
// the pattern "/" matches all paths not matched by other registered
|
|
// patterns, not just the URL with Path == "/".
|
|
//
|
|
// If a subtree has been registered and a request is received naming the
|
|
// subtree root without its trailing slash, ServeMux redirects that
|
|
// request to the subtree root (adding the trailing slash). This behavior can
|
|
// be overridden with a separate registration for the path without
|
|
// the trailing slash. For example, registering "/images/" causes ServeMux
|
|
// to redirect a request for "/images" to "/images/", unless "/images" has
|
|
// been registered separately.
|
|
//
|
|
// Patterns may optionally begin with a host name, restricting matches to
|
|
// URLs on that host only. Host-specific patterns take precedence over
|
|
// general patterns, so that a handler might register for the two patterns
|
|
// "/codesearch" and "codesearch.google.com/" without also taking over
|
|
// requests for "http://www.google.com/".
|
|
//
|
|
// ServeMux also takes care of sanitizing the URL request path and the Host
|
|
// header, stripping the port number and redirecting any request containing . or
|
|
// .. elements or repeated slashes to an equivalent, cleaner URL.
|
|
type ServeMux struct {
|
|
mu sync.RWMutex
|
|
m map[string]muxEntry
|
|
es []muxEntry // slice of entries sorted from longest to shortest.
|
|
hosts bool // whether any patterns contain hostnames
|
|
}
|
|
|
|
type muxEntry struct {
|
|
h Handler
|
|
pattern string
|
|
}
|
|
|
|
// NewServeMux allocates and returns a new ServeMux.
|
|
func NewServeMux() *ServeMux { return new(ServeMux) }
|
|
|
|
// DefaultServeMux is the default ServeMux used by Serve.
|
|
var DefaultServeMux = &defaultServeMux
|
|
|
|
var defaultServeMux ServeMux
|
|
|
|
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
|
func cleanPath(p string) string {
|
|
if p == "" {
|
|
return "/"
|
|
}
|
|
if p[0] != '/' {
|
|
p = "/" + p
|
|
}
|
|
np := path.Clean(p)
|
|
// path.Clean removes trailing slash except for root;
|
|
// put the trailing slash back if necessary.
|
|
if p[len(p)-1] == '/' && np != "/" {
|
|
// Fast path for common case of p being the string we want:
|
|
if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
|
|
np = p
|
|
} else {
|
|
np += "/"
|
|
}
|
|
}
|
|
return np
|
|
}
|
|
|
|
// stripHostPort returns h without any trailing ":<port>".
|
|
func stripHostPort(h string) string {
|
|
// If no port on host, return unchanged
|
|
if strings.IndexByte(h, ':') == -1 {
|
|
return h
|
|
}
|
|
host, _, err := net.SplitHostPort(h)
|
|
if err != nil {
|
|
return h // on error, return unchanged
|
|
}
|
|
return host
|
|
}
|
|
|
|
// Find a handler on a handler map given a path string.
|
|
// Most-specific (longest) pattern wins.
|
|
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
|
|
// Check for exact match first.
|
|
v, ok := mux.m[path]
|
|
if ok {
|
|
return v.h, v.pattern
|
|
}
|
|
|
|
// Check for longest valid match. mux.es contains all patterns
|
|
// that end in / sorted from longest to shortest.
|
|
for _, e := range mux.es {
|
|
if strings.HasPrefix(path, e.pattern) {
|
|
return e.h, e.pattern
|
|
}
|
|
}
|
|
return nil, ""
|
|
}
|
|
|
|
// redirectToPathSlash determines if the given path needs appending "/" to it.
|
|
// This occurs when a handler for path + "/" was already registered, but
|
|
// not for path itself. If the path needs appending to, it creates a new
|
|
// URL, setting the path to u.Path + "/" and returning true to indicate so.
|
|
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
|
|
mux.mu.RLock()
|
|
shouldRedirect := mux.shouldRedirectRLocked(host, path)
|
|
mux.mu.RUnlock()
|
|
if !shouldRedirect {
|
|
return u, false
|
|
}
|
|
path = path + "/"
|
|
u = &url.URL{Path: path, RawQuery: u.RawQuery}
|
|
return u, true
|
|
}
|
|
|
|
// shouldRedirectRLocked reports whether the given path and host should be redirected to
|
|
// path+"/". This should happen if a handler is registered for path+"/" but
|
|
// not path -- see comments at ServeMux.
|
|
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
|
|
p := []string{path, host + path}
|
|
|
|
for _, c := range p {
|
|
if _, exist := mux.m[c]; exist {
|
|
return false
|
|
}
|
|
}
|
|
|
|
n := len(path)
|
|
if n == 0 {
|
|
return false
|
|
}
|
|
for _, c := range p {
|
|
if _, exist := mux.m[c+"/"]; exist {
|
|
return path[n-1] != '/'
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Handler returns the handler to use for the given request,
|
|
// consulting r.Method, r.Host, and r.URL.Path. It always returns
|
|
// a non-nil handler. If the path is not in its canonical form, the
|
|
// handler will be an internally-generated handler that redirects
|
|
// to the canonical path. If the host contains a port, it is ignored
|
|
// when matching handlers.
|
|
//
|
|
// The path and host are used unchanged for CONNECT requests.
|
|
//
|
|
// Handler also returns the registered pattern that matches the
|
|
// request or, in the case of internally-generated redirects,
|
|
// the pattern that will match after following the redirect.
|
|
//
|
|
// If there is no registered handler that applies to the request,
|
|
// Handler returns a “page not found” handler and an empty pattern.
|
|
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
|
|
|
|
// CONNECT requests are not canonicalized.
|
|
if r.Method == "CONNECT" {
|
|
// If r.URL.Path is /tree and its handler is not registered,
|
|
// the /tree -> /tree/ redirect applies to CONNECT requests
|
|
// but the path canonicalization does not.
|
|
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
|
|
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
|
|
}
|
|
|
|
return mux.handler(r.Host, r.URL.Path)
|
|
}
|
|
|
|
// All other requests have any port stripped and path cleaned
|
|
// before passing to mux.handler.
|
|
host := stripHostPort(r.Host)
|
|
path := cleanPath(r.URL.Path)
|
|
|
|
// If the given path is /tree and its handler is not registered,
|
|
// redirect for /tree/.
|
|
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
|
|
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
|
|
}
|
|
|
|
if path != r.URL.Path {
|
|
_, pattern = mux.handler(host, path)
|
|
url := *r.URL
|
|
url.Path = path
|
|
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
|
|
}
|
|
|
|
return mux.handler(host, r.URL.Path)
|
|
}
|
|
|
|
// handler is the main implementation of Handler.
|
|
// The path is known to be in canonical form, except for CONNECT methods.
|
|
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
|
|
mux.mu.RLock()
|
|
defer mux.mu.RUnlock()
|
|
|
|
// Host-specific pattern takes precedence over generic ones
|
|
if mux.hosts {
|
|
h, pattern = mux.match(host + path)
|
|
}
|
|
if h == nil {
|
|
h, pattern = mux.match(path)
|
|
}
|
|
if h == nil {
|
|
h, pattern = NotFoundHandler(), ""
|
|
}
|
|
return
|
|
}
|
|
|
|
// ServeHTTP dispatches the request to the handler whose
|
|
// pattern most closely matches the request URL.
|
|
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
|
|
if r.RequestURI == "*" {
|
|
if r.ProtoAtLeast(1, 1) {
|
|
w.Header().Set("Connection", "close")
|
|
}
|
|
w.WriteHeader(StatusBadRequest)
|
|
return
|
|
}
|
|
h, _ := mux.Handler(r)
|
|
h.ServeHTTP(w, r)
|
|
}
|
|
|
|
// Handle registers the handler for the given pattern.
|
|
// If a handler already exists for pattern, Handle panics.
|
|
func (mux *ServeMux) Handle(pattern string, handler Handler) {
|
|
mux.mu.Lock()
|
|
defer mux.mu.Unlock()
|
|
|
|
if pattern == "" {
|
|
panic("http: invalid pattern")
|
|
}
|
|
if handler == nil {
|
|
panic("http: nil handler")
|
|
}
|
|
if _, exist := mux.m[pattern]; exist {
|
|
panic("http: multiple registrations for " + pattern)
|
|
}
|
|
|
|
if mux.m == nil {
|
|
mux.m = make(map[string]muxEntry)
|
|
}
|
|
e := muxEntry{h: handler, pattern: pattern}
|
|
mux.m[pattern] = e
|
|
if pattern[len(pattern)-1] == '/' {
|
|
mux.es = appendSorted(mux.es, e)
|
|
}
|
|
|
|
if pattern[0] != '/' {
|
|
mux.hosts = true
|
|
}
|
|
}
|
|
|
|
func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
|
|
n := len(es)
|
|
i := sort.Search(n, func(i int) bool {
|
|
return len(es[i].pattern) < len(e.pattern)
|
|
})
|
|
if i == n {
|
|
return append(es, e)
|
|
}
|
|
// we now know that i points at where we want to insert
|
|
es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
|
|
copy(es[i+1:], es[i:]) // Move shorter entries down
|
|
es[i] = e
|
|
return es
|
|
}
|
|
|
|
// HandleFunc registers the handler function for the given pattern.
|
|
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
|
if handler == nil {
|
|
panic("http: nil handler")
|
|
}
|
|
mux.Handle(pattern, HandlerFunc(handler))
|
|
}
|
|
|
|
// Handle registers the handler for the given pattern
|
|
// in the DefaultServeMux.
|
|
// The documentation for ServeMux explains how patterns are matched.
|
|
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
|
|
|
|
// HandleFunc registers the handler function for the given pattern
|
|
// in the DefaultServeMux.
|
|
// The documentation for ServeMux explains how patterns are matched.
|
|
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
|
|
DefaultServeMux.HandleFunc(pattern, handler)
|
|
}
|
|
|
|
// ListenAndServe listens on the TCP network address addr and then calls
|
|
// Serve with handler to handle requests on incoming connections.
|
|
// Accepted connections are configured to enable TCP keep-alives.
|
|
//
|
|
// The handler is typically nil, in which case the DefaultServeMux is used.
|
|
//
|
|
// ListenAndServe always returns a non-nil error.
|
|
func ListenAndServe(addr string, handler Handler) error {
|
|
return ActiveDevice.ListenAndServe(addr, handler)
|
|
}
|