diff options
Diffstat (limited to 'src/pkg/http/server.go')
-rw-r--r-- | src/pkg/http/server.go | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/src/pkg/http/server.go b/src/pkg/http/server.go new file mode 100644 index 000000000..c1de5de78 --- /dev/null +++ b/src/pkg/http/server.go @@ -0,0 +1,575 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// HTTP server. See RFC 2616. + +// TODO(rsc): +// logging +// cgi support +// post support + +package http + +import ( + "bufio"; + "fmt"; + "http"; + "io"; + "log"; + "net"; + "os"; + "path"; + "strconv"; + "strings"; +) + +// Errors introduced by the HTTP server. +var ( + ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush"); + ErrHijacked = os.NewError("Conn has been hijacked"); +) + +type Conn struct + +// Objects implemeting the Handler interface can be +// registered to serve a particular path or subtree +// in the HTTP server. +type Handler interface { + ServeHTTP(*Conn, *Request); +} + +// A Conn represents the server side of a single active HTTP connection. +type Conn struct { + RemoteAddr string; // network address of remote side + Req *Request; // current HTTP request + + rwc io.ReadWriteCloser; // i/o connection + buf *bufio.ReadWriter; // buffered rwc + handler Handler; // request handler + hijacked bool; // connection has been hijacked by handler + + // state for the current reply + closeAfterReply bool; // close connection after this reply + chunking bool; // using chunked transfer encoding for reply body + wroteHeader bool; // reply header has been written + header map[string] string; // reply header parameters + written int64; // number of bytes written in body + status int; // status code passed to WriteHeader +} + +// Create new connection from rwc. +func newConn(rwc io.ReadWriteCloser, raddr string, handler Handler) (c *Conn, err os.Error) { + c = new(Conn); + c.RemoteAddr = raddr; + c.handler = handler; + c.rwc = rwc; + br := bufio.NewReader(rwc); + bw := bufio.NewWriter(rwc); + c.buf = bufio.NewReadWriter(br, bw); + return c, nil +} + +func (c *Conn) SetHeader(hdr, val string) + +// Read next request from connection. +func (c *Conn) readRequest() (req *Request, err os.Error) { + if c.hijacked { + return nil, ErrHijacked + } + if req, err = ReadRequest(c.buf.Reader); err != nil { + return nil, err + } + + // Reset per-request connection state. + c.header = make(map[string] string); + c.wroteHeader = false; + c.Req = req; + + // Default output is HTML encoded in UTF-8. + c.SetHeader("Content-Type", "text/html; charset=utf-8"); + + if req.ProtoAtLeast(1, 1) { + // HTTP/1.1 or greater: use chunked transfer encoding + // to avoid closing the connection at EOF. + c.chunking = true; + c.SetHeader("Transfer-Encoding", "chunked"); + } else { + // HTTP version < 1.1: cannot do chunked transfer + // encoding, so signal EOF by closing connection. + // Could avoid closing the connection if there is + // a Content-Length: header in the response, + // but everyone who expects persistent connections + // does HTTP/1.1 now. + c.closeAfterReply = true; + c.chunking = false; + } + + return req, nil +} + +// SetHeader sets a header line in the eventual reply. +// For example, SetHeader("Content-Type", "text/html; charset=utf-8") +// will result in the header line +// +// Content-Type: text/html; charset=utf-8 +// +// being sent. UTF-8 encoded HTML is the default setting for +// Content-Type in this library, so users need not make that +// particular call. Calls to SetHeader after WriteHeader (or Write) +// are ignored. +func (c *Conn) SetHeader(hdr, val string) { + c.header[CanonicalHeaderKey(hdr)] = val; +} + +// WriteHeader sends an HTTP response header with 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. +func (c *Conn) WriteHeader(code int) { + if c.hijacked { + log.Stderr("http: Conn.WriteHeader on hijacked connection"); + return + } + if c.wroteHeader { + log.Stderr("http: multiple Conn.WriteHeader calls"); + return + } + c.wroteHeader = true; + c.status = code; + c.written = 0; + if !c.Req.ProtoAtLeast(1, 0) { + return + } + proto := "HTTP/1.0"; + if c.Req.ProtoAtLeast(1, 1) { + proto = "HTTP/1.1"; + } + codestring := strconv.Itoa(code); + text, ok := statusText[code]; + if !ok { + text = "status code " + codestring; + } + io.WriteString(c.buf, proto + " " + codestring + " " + text + "\r\n"); + for k,v := range c.header { + io.WriteString(c.buf, k + ": " + v + "\r\n"); + } + io.WriteString(c.buf, "\r\n"); +} + +// 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. +func (c *Conn) Write(data []byte) (n int, err os.Error) { + if c.hijacked { + log.Stderr("http: Conn.Write on hijacked connection"); + return 0, ErrHijacked + } + if !c.wroteHeader { + c.WriteHeader(StatusOK); + } + if len(data) == 0 { + return 0, nil + } + + c.written += int64(len(data)); // ignoring errors, for errorKludge + + // TODO(rsc): if chunking happened after the buffering, + // then there would be fewer chunk headers. + // On the other hand, it would make hijacking more difficult. + if c.chunking { + fmt.Fprintf(c.buf, "%x\r\n", len(data)); // TODO(rsc): use strconv not fmt + } + n, err = c.buf.Write(data); + if err == nil && c.chunking { + if n != len(data) { + err = io.ErrShortWrite; + } + if err == nil { + io.WriteString(c.buf, "\r\n"); + } + } + + return n, err; +} + +// If this is an error reply (4xx or 5xx) +// and the handler wrote some data explaining the error, +// some browsers (i.e., Chrome, Internet Explorer) +// will show their own error instead unless the error is +// long enough. The minimum lengths used in those +// browsers are in the 256-512 range. +// Pad to 1024 bytes. +func errorKludge(c *Conn, req *Request) { + const min = 1024; + + // Is this an error? + if kind := c.status/100; kind != 4 && kind != 5 { + return; + } + + // Did the handler supply any info? Enough? + if c.written == 0 || c.written >= min { + return; + } + + // Is it text? ("Content-Type" is always in the map) + if s := c.header["Content-Type"]; len(s) < 5 || s[0:5] != "text/" { + return; + } + + // Is it a broken browser? + var msg string; + switch agent := req.UserAgent; { + case strings.Index(agent, "MSIE") >= 0: + msg = "Internet Explorer"; + case strings.Index(agent, "Chrome/") >= 0: + msg = "Chrome"; + default: + return; + } + msg += " would ignore this error page if this text weren't here.\n"; + io.WriteString(c, "\n"); + for c.written < min { + io.WriteString(c, msg); + } +} + +func (c *Conn) flush() { + if !c.wroteHeader { + c.WriteHeader(StatusOK); + } + errorKludge(c, c.Req); + if c.chunking { + io.WriteString(c.buf, "0\r\n"); + // trailer key/value pairs, followed by blank line + io.WriteString(c.buf, "\r\n"); + } + c.buf.Flush(); +} + +// Close the connection. +func (c *Conn) close() { + if c.buf != nil { + c.buf.Flush(); + c.buf = nil; + } + if c.rwc != nil { + c.rwc.Close(); + c.rwc = nil; + } +} + +// Serve a new connection. +func (c *Conn) serve() { + for { + req, err := c.readRequest(); + if err != nil { + break + } + // HTTP cannot have multiple simultaneous active requests. + // Until the server replies to this request, it can't read another, + // so we might as well run the handler in this goroutine. + c.handler.ServeHTTP(c, req); + if c.hijacked { + return; + } + c.flush(); + if c.closeAfterReply { + break; + } + } + c.close(); +} + +// Hijack lets the caller take over the connection. +// After a call to c.Hijack(), the HTTP server library +// will not do anything else with the connection. +// It becomes the caller's responsibility to manage +// and close the connection. +func (c *Conn) Hijack() (rwc io.ReadWriteCloser, buf *bufio.ReadWriter, err os.Error) { + if c.hijacked { + return nil, nil, ErrHijacked; + } + c.hijacked = true; + rwc = c.rwc; + buf = c.buf; + c.rwc = nil; + c.buf = nil; + return; +} + +// 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 object that calls f. +type HandlerFunc func(*Conn, *Request) + +// ServeHTTP calls f(c, req). +func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) { + f(c, req); +} + +// Helper handlers + +// NotFound replies to the request with an HTTP 404 not found error. +func NotFound(c *Conn, req *Request) { + c.SetHeader("Content-Type", "text/plain; charset=utf-8"); + c.WriteHeader(StatusNotFound); + io.WriteString(c, "404 page not found\n"); +} + +// NotFoundHandler returns a simple request handler +// that replies to each request with a ``404 page not found'' reply. +func NotFoundHandler() Handler { + return HandlerFunc(NotFound) +} + +// Redirect replies to the request with a redirect to url, +// which may be a path relative to the request path. +func Redirect(c *Conn, url string, code int) { + // RFC2616 recommends that a short note "SHOULD" be included in the + // response because older user agents may not understand 301/307. + note := "<a href=\"%v\">" + statusText[code] + "</a>.\n"; + if c.Req.Method == "POST" { + note = ""; + } + + u, err := ParseURL(url); + if err != nil { + goto finish + } + + // If url was relative, make absolute by + // combining with request path. + // The browser would probably do this for us, + // but doing it ourselves is more reliable. + + // NOTE(rsc): RFC 2616 says that the Location + // line must be an absolute URI, like + // "http://www.google.com/redirect/", + // not a path like "/redirect/". + // Unfortunately, we don't know what to + // put in the host name section to get the + // client to connect to us again, so we can't + // know the right absolute URI to send back. + // Because of this problem, no one pays attention + // to the RFC; they all send back just a new path. + // So do we. + oldpath := c.Req.Url.Path; + if oldpath == "" { // should not happen, but avoid a crash if it does + oldpath = "/" + } + if u.Scheme == "" { + // no leading http://server + if url == "" || url[0] != '/' { + // make relative path absolute + olddir, oldfile := path.Split(oldpath); + url = olddir + url; + } + + // clean up but preserve trailing slash + trailing := url[len(url) - 1] == '/'; + url = path.Clean(url); + if trailing && url[len(url) - 1] != '/' { + url += "/"; + } + } + +finish: + c.SetHeader("Location", url); + c.WriteHeader(code); + fmt.Fprintf(c, note, url); +} + +// Redirect to a fixed URL +type redirectHandler struct { + url string; + code int; +} +func (rh *redirectHandler) ServeHTTP(c *Conn, req *Request) { + Redirect(c, rh.url, rh.code); +} + +// RedirectHandler returns a request handler that redirects +// each request it receives to the given url using the given +// status code. +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 named fixed paths, like "/favicon.ico", +// or subtrees, like "/images/" (note the trailing slash). +// Patterns must begin with /. +// 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 receiver requests for any other paths in the +// "/images/" subtree. +// +// In the future, the pattern syntax may be relaxed to allow +// an optional host-name at the beginning of the pattern, +// so that a handler might register for the two patterns +// "/codesearch" and "codesearch.google.com/" +// without taking over requests for http://www.google.com/. +// +// ServeMux also takes care of sanitizing the URL request path, +// redirecting any request containing . or .. elements to an +// equivalent .- and ..-free URL. +type ServeMux struct { + m map[string] Handler +} + +// NewServeMux allocates and returns a new ServeMux. +func NewServeMux() *ServeMux { + return &ServeMux{make(map[string] Handler)}; +} + +// DefaultServeMux is the default ServeMux used by Serve. +var DefaultServeMux = NewServeMux(); + +// Does path match pattern? +func pathMatch(pattern, path string) bool { + if len(pattern) == 0 { + // should not happen + return false + } + n := len(pattern); + if pattern[n-1] != '/' { + return pattern == path + } + return len(path) >= n && path[0:n] == pattern; +} + +// Return 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 != "/" { + np += "/"; + } + return np; +} + +// ServeHTTP dispatches the request to the handler whose +// pattern most closely matches the request URL. +func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) { + // Clean path to canonical form and redirect. + if p := cleanPath(req.Url.Path); p != req.Url.Path { + c.SetHeader("Location", p); + c.WriteHeader(StatusMovedPermanently); + return; + } + + // Most-specific (longest) pattern wins. + var h Handler; + var n = 0; + for k, v := range mux.m { + if !pathMatch(k, req.Url.Path) { + continue; + } + if h == nil || len(k) > n { + n = len(k); + h = v; + } + } + if h == nil { + h = NotFoundHandler(); + } + h.ServeHTTP(c, req); +} + +// Handle registers the handler for the given pattern. +func (mux *ServeMux) Handle(pattern string, handler Handler) { + if pattern == "" || pattern[0] != '/' { + panicln("http: invalid pattern", pattern); + } + + mux.m[pattern] = handler; + + // Helpful behavior: + // If pattern is /tree/, insert permanent redirect for /tree. + n := len(pattern); + if n > 0 && pattern[n-1] == '/' { + mux.m[pattern[0:n-1]] = RedirectHandler(pattern, StatusMovedPermanently); + } +} + +// Handle registers the handler for the given pattern +// in the DefaultServeMux. +func Handle(pattern string, handler Handler) { + DefaultServeMux.Handle(pattern, handler); +} + +// Serve accepts incoming HTTP connections on the listener l, +// creating a new service thread for each. The service threads +// read requests and then call handler to reply to them. +// Handler is typically nil, in which case the DefaultServeMux is used. +func Serve(l net.Listener, handler Handler) os.Error { + if handler == nil { + handler = DefaultServeMux; + } + for { + rw, raddr, e := l.Accept(); + if e != nil { + return e + } + c, err := newConn(rw, raddr, handler); + if err != nil { + continue; + } + go c.serve(); + } + panic("not reached") +} + +// ListenAndServe listens on the TCP network address addr +// and then calls Serve with handler to handle requests +// on incoming connections. Handler is typically nil, +// in which case the DefaultServeMux is used. +// +// A trivial example server is: +// +// package main +// +// import ( +// "http"; +// "io"; +// ) +// +// // hello world, the web server +// func HelloServer(c *http.Conn, req *http.Request) { +// io.WriteString(c, "hello, world!\n"); +// } +// +// func main() { +// http.Handle("/hello", http.HandlerFunc(HelloServer)); +// err := http.ListenAndServe(":12345", nil); +// if err != nil { +// panic("ListenAndServe: ", err.String()) +// } +// } +func ListenAndServe(addr string, handler Handler) os.Error { + l, e := net.Listen("tcp", addr); + if e != nil { + return e + } + e = Serve(l, handler); + l.Close(); + return e +} + |