import (
"bufio";
"http";
+ "io";
"os";
"strings"
)
const (
- _MaxLineLength = 1024; // assumed < bufio.DefaultBufSize
- _MaxValueLength = 1024;
- _MaxHeaderLines = 1024;
+ maxLineLength = 1024; // assumed < bufio.DefaultBufSize
+ maxValueLength = 1024;
+ maxHeaderLines = 1024;
)
var (
// HTTP Request
type Request struct {
- method string; // GET, PUT,etc.
- rawurl string;
- url *URL; // URI after GET, PUT etc.
- proto string; // "HTTP/1.0"
- pmajor int; // 1
- pminor int; // 0
-
- header map[string] string;
-
- close bool;
- host string;
- referer string;
- useragent string;
+ Method string; // GET, PUT,etc.
+ RawUrl string;
+ Url *URL; // URI after GET, PUT etc.
+ Proto string; // "HTTP/1.0"
+ ProtoMajor int; // 1
+ ProtoMinor int; // 0
+
+ Header map[string] string;
+
+ Close bool;
+ Host string;
+ Referer string; // referer [sic]
+ UserAgent string;
}
+func (r *Request) ProtoAtLeast(major, minor int) bool {
+ return r.ProtoMajor > major ||
+ r.ProtoMajor == major && r.ProtoMinor >= minor
+}
+
+
// Read a line of bytes (up to \n) from b.
-// Give up if the line exceeds _MaxLineLength.
+// Give up if the line exceeds maxLineLength.
// The returned bytes are a pointer into storage in
// the bufio, so they are only valid until the next bufio read.
func readLineBytes(b *bufio.BufRead) (p []byte, err *os.Error) {
if p, err = b.ReadLineSlice('\n'); err != nil {
return nil, err
}
- if len(p) >= _MaxLineLength {
+ if len(p) >= maxLineLength {
return nil, LineTooLong
}
}
value += " " + string(line);
- if len(value) >= _MaxValueLength {
+ if len(value) >= maxValueLength {
return "", "", ValueTooLong
}
}
return major, minor, true
}
+var cmap = make(map[string]string)
+
+func CanonicalHeaderKey(s string) string {
+ if t, ok := cmap[s]; ok {
+ return t;
+ }
+
+ // canonicalize: first letter upper case
+ // and upper case after each dash.
+ // (Host, User-Agent, If-Modified-Since).
+ // HTTP headers are ASCII only, so no Unicode issues.
+ a := io.StringBytes(s);
+ upper := true;
+ for i,v := range a {
+ if upper && 'a' <= v && v <= 'z' {
+ a[i] = v + 'A' - 'a';
+ }
+ if !upper && 'A' <= v && v <= 'Z' {
+ a[i] = v + 'a' - 'A';
+ }
+ upper = false;
+ if v == '-' {
+ upper = true;
+ }
+ }
+ t := string(a);
+ cmap[s] = t;
+ return t;
+}
+
+
// Read and parse a request from b.
func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
req = new(Request);
if f = strings.Split(s, " "); len(f) != 3 {
return nil, BadRequest
}
- req.method, req.rawurl, req.proto = f[0], f[1], f[2];
+ req.Method, req.RawUrl, req.Proto = f[0], f[1], f[2];
var ok bool;
- if req.pmajor, req.pminor, ok = parseHTTPVersion(req.proto); !ok {
+ if req.ProtoMajor, req.ProtoMinor, ok = parseHTTPVersion(req.Proto); !ok {
return nil, BadHTTPVersion
}
- if req.url, err = ParseURL(req.rawurl); err != nil {
+ if req.Url, err = ParseURL(req.RawUrl); err != nil {
return nil, err
}
// Subsequent lines: Key: value.
nheader := 0;
- req.header = make(map[string] string);
+ req.Header = make(map[string] string);
for {
var key, value string;
if key, value, err = readKeyValue(b); err != nil {
if key == "" {
break
}
- if nheader++; nheader >= _MaxHeaderLines {
+ if nheader++; nheader >= maxHeaderLines {
return nil, HeaderTooLong
}
+ key = CanonicalHeaderKey(key);
+
// RFC 2616 says that if you send the same header key
// multiple times, it has to be semantically equivalent
// to concatenating the values separated by commas.
- oldvalue, present := req.header[key];
+ oldvalue, present := req.Header[key];
if present {
- req.header[key] = oldvalue+","+value
+ req.Header[key] = oldvalue+","+value
} else {
- req.header[key] = value
+ req.Header[key] = value
}
}
// GET http://www.google.com/index.html HTTP/1.1
// Host: doesntmatter
// the same. In the second case, any Host line is ignored.
- if v, have := req.header["Host"]; have && req.url.host == "" {
- req.host = v
+ if v, present := req.Header["Host"]; present && req.Url.Host == "" {
+ req.Host = v
}
// RFC2616: Should treat
// Pragma: no-cache
// like
- // Cache-control: no-cache
- if v, have := req.header["Pragma"]; have && v == "no-cache" {
- if cc, havecc := req.header["Cache-control"]; !havecc {
- req.header["Cache-control"] = "no-cache"
+ // Cache-Control: no-cache
+ if v, present := req.Header["Pragma"]; present && v == "no-cache" {
+ if cc, presentcc := req.Header["Cache-Control"]; !presentcc {
+ req.Header["Cache-Control"] = "no-cache"
}
}
// Determine whether to hang up after sending the reply.
- if req.pmajor < 1 || (req.pmajor == 1 && req.pminor < 1) {
- req.close = true
- } else if v, have := req.header["Connection"]; have {
+ if req.ProtoMajor < 1 || (req.ProtoMajor == 1 && req.ProtoMinor < 1) {
+ req.Close = true
+ } else if v, present := req.Header["Connection"]; present {
// TODO: Should split on commas, toss surrounding white space,
// and check each field.
if v == "close" {
- req.close = true
+ req.Close = true
}
}
// Pull out useful fields as a convenience to clients.
- if v, have := req.header["Referer"]; have {
- req.referer = v
+ if v, present := req.Header["Referer"]; present {
+ req.Referer = v
}
- if v, have := req.header["User-Agent"]; have {
- req.useragent = v
+ if v, present := req.Header["User-Agent"]; present {
+ req.UserAgent = v
}
-
// TODO: Parse specific header values:
// Accept
// Accept-Encoding
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Trivial HTTP server
+// HTTP server. See RFC 2616.
-// TODO: Routines for writing responses.
+// TODO(rsc):
+// logging
+// cgi support
+// post support
package http
import (
+ "bufio";
+ "fmt";
+ "http";
"io";
- "os";
"net";
- "http";
+ "os";
"strconv";
)
-// Serve a new connection.
-func serveConnection(fd net.Conn, raddr string, f func(*Conn, *Request)) {
- c, err := NewConn(fd);
- if err != nil {
+var ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush")
+
+type Conn struct
+
+// Interface implemented by servers using this library.
+type Handler interface {
+ ServeHTTP(*Conn, *Request);
+}
+
+// Active HTTP connection (server side).
+type Conn struct {
+ Fd io.ReadWriteClose;
+ RemoteAddr string;
+ Req *Request;
+ Br *bufio.BufRead;
+
+ br *bufio.BufRead;
+ bw *bufio.BufWrite;
+ close bool;
+ chunking bool;
+ flushed bool;
+ header map[string] string;
+ wroteHeader bool;
+ handler Handler;
+}
+
+// HTTP response codes.
+// TODO(rsc): Maybe move these to their own file, so that
+// clients can use them too.
+
+const (
+ StatusContinue = 100;
+ StatusSwitchingProtocols = 101;
+
+ StatusOK = 200;
+ StatusCreated = 201;
+ StatusAccepted = 202;
+ StatusNonAuthoritativeInfo = 203;
+ StatusNoContent = 204;
+ StatusResetContent = 205;
+ StatusPartialContent = 206;
+
+ StatusMultipleChoices = 300;
+ StatusMovedPermanently = 301;
+ StatusFound = 302;
+ StatusSeeOther = 303;
+ StatusNotModified = 304;
+ StatusUseProxy = 305;
+ StatusTemporaryRedirect = 307;
+
+ StatusBadRequest = 400;
+ StatusUnauthorized = 401;
+ StatusPaymentRequired = 402;
+ StatusForbidden = 403;
+ StatusNotFound = 404;
+ StatusMethodNotAllowed = 405;
+ StatusNotAcceptable = 406;
+ StatusProxyAuthRequired = 407;
+ StatusRequestTimeout = 408;
+ StatusConflict = 409;
+ StatusGone = 410;
+ StatusLengthRequired = 411;
+ StatusPreconditionFailed = 412;
+ StatusRequestEntityTooLarge = 413;
+ StatusRequestURITooLong = 414;
+ StatusUnsupportedMediaType = 415;
+ StatusRequestedRangeNotSatisfiable = 416;
+ StatusExpectationFailed = 417;
+
+ StatusInternalServerError = 500;
+ StatusNotImplemented = 501;
+ StatusBadGateway = 502;
+ StatusServiceUnavailable = 503;
+ StatusGatewayTimeout = 504;
+ StatusHTTPVersionNotSupported = 505;
+)
+
+var statusText = map[int]string {
+ StatusContinue: "Continue",
+ StatusSwitchingProtocols: "Switching Protocols",
+
+ StatusOK: "OK",
+ StatusCreated: "Created",
+ StatusAccepted: "Accepted",
+ StatusNonAuthoritativeInfo: "Non-Authoritative Information",
+ StatusNoContent: "No Content",
+ StatusResetContent: "Reset Content",
+ StatusPartialContent: "Partial Content",
+
+ StatusMultipleChoices: "Multiple Choices",
+ StatusMovedPermanently: "Moved Permanently",
+ StatusFound: "Found",
+ StatusSeeOther: "See Other",
+ StatusNotModified: "Not Modified",
+ StatusUseProxy: "Use Proxy",
+ StatusTemporaryRedirect: "Temporary Redirect",
+
+ StatusBadRequest: "Bad Request",
+ StatusUnauthorized: "Unauthorized",
+ StatusPaymentRequired: "Payment Required",
+ StatusForbidden: "Forbidden",
+ StatusNotFound: "Not Found",
+ StatusMethodNotAllowed: "Method Not Allowed",
+ StatusNotAcceptable: "Not Acceptable",
+ StatusProxyAuthRequired: "Proxy Authentication Required",
+ StatusRequestTimeout: "Request Timeout",
+ StatusConflict: "Conflict",
+ StatusGone: "Gone",
+ StatusLengthRequired: "Length Required",
+ StatusPreconditionFailed: "Precondition Failed",
+ StatusRequestEntityTooLarge: "Request Entity Too Large",
+ StatusRequestURITooLong: "Request URI Too Long",
+ StatusUnsupportedMediaType: "Unsupported Media Type",
+ StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable",
+ StatusExpectationFailed: "Expectation Failed",
+
+ StatusInternalServerError: "Internal Server Error",
+ StatusNotImplemented: "Not Implemented",
+ StatusBadGateway: "Bad Gateway",
+ StatusServiceUnavailable: "Service Unavailable",
+ StatusGatewayTimeout: "Gateway Timeout",
+ StatusHTTPVersionNotSupported: "HTTP Version Not Supported",
+}
+
+// Create new connection from rwc.
+func newConn(rwc io.ReadWriteClose, raddr string, handler Handler) (c *Conn, err *os.Error) {
+ c = new(Conn);
+ c.Fd = rwc;
+ c.RemoteAddr = raddr;
+ c.handler = handler;
+ if c.br, err = bufio.NewBufRead(rwc.(io.Read)); err != nil {
+ return nil, err
+ }
+c.Br = c.br;
+ if c.bw, err = bufio.NewBufWrite(rwc); err != nil {
+ return nil, err
+ }
+ 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 req, err = ReadRequest(c.br); err != nil {
+ return nil, err
+ }
+
+ // Reset per-request connection state.
+ c.header = make(map[string] string);
+ c.wroteHeader = false;
+ c.flushed = 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.close = true;
+ c.chunking = false;
+ }
+
+ return req, nil
+}
+
+func (c *Conn) SetHeader(hdr, val string) {
+ c.header[CanonicalHeaderKey(hdr)] = val;
+}
+
+// Write header.
+func (c *Conn) WriteHeader(code int) {
+ if c.wroteHeader {
+ // TODO(rsc): log
+ return
+ }
+ c.wroteHeader = true;
+ 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.bw, proto + " " + codestring + " " + text + "\r\n");
+ for k,v := range c.header {
+ io.WriteString(c.bw, k + ": " + v + "\r\n");
+ }
+ io.WriteString(c.bw, "\r\n");
+}
+
+// TODO(rsc): BUG in 6g: must return "nn int" not "n int"
+// so that the implicit struct assignment in
+// return c.bw.Write(data) works. oops
+func (c *Conn) Write(data []byte) (nn int, err *os.Error) {
+ if c.flushed {
+ return 0, ErrWriteAfterFlush
+ }
+ if !c.wroteHeader {
+ c.WriteHeader(StatusOK);
+ }
+ if len(data) == 0 {
+ return 0, nil
+ }
+
+ // TODO(rsc): if chunking happened after the buffering,
+ // then there would be fewer chunk headers
+ if c.chunking {
+ fmt.Fprintf(c.bw, "%x\r\n", len(data)); // TODO(rsc): use strconv not fmt
+ }
+ return c.bw.Write(data);
+}
+
+func (c *Conn) Flush() {
+ if c.flushed {
return
}
+ if !c.wroteHeader {
+ c.WriteHeader(StatusOK);
+ }
+ if c.chunking {
+ io.WriteString(c.bw, "0\r\n");
+ // trailer key/value pairs, followed by blank line
+ io.WriteString(c.bw, "\r\n");
+ }
+ c.bw.Flush();
+ c.flushed = true;
+}
+
+// Close the connection.
+func (c *Conn) Close() {
+ if c.bw != nil {
+ c.bw.Flush();
+ c.bw = nil;
+ }
+ if c.Fd != nil {
+ c.Fd.Close();
+ c.Fd = nil;
+ }
+}
+
+// Serve a new connection.
+func (c *Conn) serve() {
for {
- req, err := c.ReadRequest();
+ req, err := c.readRequest();
if err != nil {
break
}
- f(c, req);
+ // 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 thread.
+ c.handler.ServeHTTP(c, req);
+ if c.Fd == nil {
+ // Handler took over the connection.
+ return;
+ }
+ if !c.flushed {
+ c.Flush();
+ }
if c.close {
- break
+ break;
}
}
c.Close();
}
-// Web server: already listening on l, call f for each request.
-func Serve(l net.Listener, f func(*Conn, *Request)) *os.Error {
- // TODO: Make this unnecessary
- s, e := os.Getenv("GOMAXPROCS");
- if n, ok := strconv.Atoi(s); n < 3 {
- print("Warning: $GOMAXPROCS needs to be at least 3.\n");
+// Adapter: can use RequestFunction(f) as Handler
+type handlerFunc struct {
+ f func(*Conn, *Request)
+}
+func (h handlerFunc) ServeHTTP(c *Conn, req *Request) {
+ h.f(c, req)
+}
+func HandlerFunc(f func(*Conn, *Request)) Handler {
+ return handlerFunc{f}
+}
+
+/* simpler version of above, not accepted by 6g:
+
+type HandlerFunc func(*Conn, *Request)
+func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) {
+ f(c, req);
+}
+*/
+
+// Helper handlers
+
+// 404 not found
+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");
+}
+
+var NotFoundHandler = HandlerFunc(notFound)
+
+// Redirect to a fixed URL
+type redirectHandler struct {
+ to string;
+}
+func (h *redirectHandler) ServeHTTP(c *Conn, req *Request) {
+ c.SetHeader("Location", h.to);
+ c.WriteHeader(StatusMovedPermanently);
+}
+
+func RedirectHandler(to string) Handler {
+ return &redirectHandler{to};
+}
+
+// Path-based HTTP request multiplexer.
+// Patterns name fixed paths, like "/favicon.ico",
+// or subtrees, like "/images/".
+// For now, patterns must begin with /.
+// Eventually, might want to allow host name
+// at beginning of pattern, so that you could register
+// /codesearch
+// codesearch.google.com/
+// but not take over /.
+
+type ServeMux struct {
+ m map[string] Handler
+}
+
+func NewServeMux() *ServeMux {
+ return &ServeMux{make(map[string] Handler)};
+}
+
+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;
+}
+
+func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) {
+ // 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);
+}
+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 redirect for /tree.
+ n := len(pattern);
+ if n > 0 && pattern[n-1] == '/' {
+ mux.m[pattern[0:n-1]] = RedirectHandler(pattern);
+ }
+}
+
+func Handle(pattern string, h Handler) {
+ DefaultServeMux.Handle(pattern, h);
+}
+
+
+// Web server: listening on l, call handler.ServeHTTP for each request.
+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
}
- go serveConnection(rw, raddr, f)
+ c, err := newConn(rw, raddr, handler);
+ if err != nil {
+ continue;
+ }
+ go c.serve();
}
panic("not reached")
}
// Web server: listen on address, call f for each request.
-func ListenAndServe(addr string, f func(*Conn, *Request)) *os.Error {
+func ListenAndServe(addr string, handler Handler) *os.Error {
l, e := net.Listen("tcp", addr);
if e != nil {
return e
}
- e = Serve(l, f);
+ e = Serve(l, handler);
l.Close();
return e
}
+