summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2009-02-02 18:01:32 -0800
committerRuss Cox <rsc@golang.org>2009-02-02 18:01:32 -0800
commitda3f38645968be6dc2b0448d25b6104e674b774f (patch)
treedf06c126f93df7bc143f41dbb5cbb3c43dc05e7a
parent6f02f374aeeb27111e13ca1fa11079cae591eb89 (diff)
downloadgolang-da3f38645968be6dc2b0448d25b6104e674b774f.tar.gz
flesh out http server.
convert to uppercase names. R=r DELTA=613 (460 added, 61 deleted, 92 changed) OCL=24139 CL=24145
-rw-r--r--src/lib/http/conn.go53
-rw-r--r--src/lib/http/request.go123
-rw-r--r--src/lib/http/server.go424
-rw-r--r--src/lib/http/triv.go50
-rw-r--r--src/lib/http/url.go42
5 files changed, 545 insertions, 147 deletions
diff --git a/src/lib/http/conn.go b/src/lib/http/conn.go
deleted file mode 100644
index 909863ef5..000000000
--- a/src/lib/http/conn.go
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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.
-
-package http
-
-import (
- "io";
- "bufio";
- "http";
- "os"
-)
-
-// Active HTTP connection (server side).
-type Conn struct {
- rwc io.ReadWriteClose;
- br *bufio.BufRead;
- bw *bufio.BufWrite;
- close bool;
- chunking bool;
-}
-
-// Create new connection from rwc.
-func NewConn(rwc io.ReadWriteClose) (c *Conn, err *os.Error) {
- c = new(Conn);
- c.rwc = rwc;
- if c.br, err = bufio.NewBufRead(rwc); err != nil {
- return nil, err
- }
- if c.bw, err = bufio.NewBufWrite(rwc); err != nil {
- return nil, err
- }
- return c, nil
-}
-
-// 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
- }
-
- // TODO: Proper handling of (lack of) Connection: close,
- // and chunked transfer encoding on output.
- c.close = true;
- return req, nil
-}
-
-// Close the connection.
-func (c *Conn) Close() {
- c.bw.Flush();
- c.rwc.Close();
-}
-
diff --git a/src/lib/http/request.go b/src/lib/http/request.go
index ba1a7a694..5d1fd67d7 100644
--- a/src/lib/http/request.go
+++ b/src/lib/http/request.go
@@ -9,14 +9,15 @@ package http
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 (
@@ -30,30 +31,36 @@ 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
}
@@ -132,7 +139,7 @@ func readKeyValue(b *bufio.BufRead) (key, value string, err *os.Error) {
}
value += " " + string(line);
- if len(value) >= _MaxValueLength {
+ if len(value) >= maxValueLength {
return "", "", ValueTooLong
}
}
@@ -179,6 +186,37 @@ func parseHTTPVersion(vers string) (int, int, bool) {
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);
@@ -193,19 +231,19 @@ func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
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 {
@@ -214,18 +252,20 @@ func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
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
}
}
@@ -236,40 +276,39 @@ func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
// 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
diff --git a/src/lib/http/server.go b/src/lib/http/server.go
index 855eb98a5..970cd7e38 100644
--- a/src/lib/http/server.go
+++ b/src/lib/http/server.go
@@ -2,64 +2,448 @@
// 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
}
+
diff --git a/src/lib/http/triv.go b/src/lib/http/triv.go
index a7eb35aa2..136100135 100644
--- a/src/lib/http/triv.go
+++ b/src/lib/http/triv.go
@@ -5,24 +5,52 @@
package main
import (
- "io";
"bufio";
- "os";
+ "flag";
+ "fmt";
+ "http";
+ "io";
"net";
- "http"
+ "os";
)
-func Echo(conn *http.Conn, req *http.Request) {
- fd := conn.bw;
- conn.close = true;
- io.WriteString(fd, "HTTP/1.1 200 OK\r\n"
- "Content-Type: text/plain\r\n"
- "\r\n");
- io.WriteString(fd, req.method+" "+req.rawurl+" "+req.proto+"\r\n")
+
+// hello world, the web server
+func HelloServer(c *http.Conn, req *http.Request) {
+ io.WriteString(c, "hello, world!\n");
+}
+
+// simple counter server
+type Counter struct {
+ n int;
+}
+
+func (ctr *Counter) ServeHTTP(c *http.Conn, req *http.Request) {
+ fmt.Fprintf(c, "counter = %d\n", ctr.n);
+ ctr.n++;
+}
+
+// simple file server
+var webroot = flag.String("root", "/home/rsc", "web root directory")
+func FileServer(c *http.Conn, req *http.Request) {
+ c.SetHeader("content-type", "text/plain; charset=utf-8");
+ path := *webroot + req.Url.Path; // TODO: insecure: use os.CleanName
+ fd, err := os.Open(path, os.O_RDONLY, 0);
+ if err != nil {
+ c.WriteHeader(http.StatusNotFound);
+ fmt.Fprintf(c, "open %s: %v\n", path, err);
+ return;
+ }
+ n, err1 := io.Copy(fd, c);
+ fmt.Fprintf(c, "[%d bytes]\n", n);
}
func main() {
- err := http.ListenAndServe("0.0.0.0:12345", &Echo);
+ flag.Parse();
+ http.Handle("/counter", new(Counter));
+ http.Handle("/go/", http.HandlerFunc(FileServer));
+ http.Handle("/go/hello", http.HandlerFunc(HelloServer));
+ err := http.ListenAndServe(":12345", nil);
if err != nil {
panic("ListenAndServe: ", err.String())
}
diff --git a/src/lib/http/url.go b/src/lib/http/url.go
index 9c1a94e2b..f0a94d68b 100644
--- a/src/lib/http/url.go
+++ b/src/lib/http/url.go
@@ -77,15 +77,15 @@ func URLUnescape(s string) (string, *os.Error) {
}
type URL struct {
- raw string;
- scheme string;
- rawpath string;
- authority string;
- userinfo string;
- host string;
- path string;
- query string;
- fragment string;
+ Raw string;
+ Scheme string;
+ RawPath string;
+ Authority string;
+ Userinfo string;
+ Host string;
+ Path string;
+ Query string;
+ Fragment string;
}
// Maybe rawurl is of the form scheme:path.
@@ -132,39 +132,39 @@ func ParseURL(rawurl string) (url *URL, err *os.Error) {
return nil, BadURL
}
url = new(URL);
- url.raw = rawurl;
+ url.Raw = rawurl;
// split off possible leading "http:", "mailto:", etc.
var path string;
- if url.scheme, path, err = getscheme(rawurl); err != nil {
+ if url.Scheme, path, err = getscheme(rawurl); err != nil {
return nil, err
}
- url.rawpath = path;
+ url.RawPath = path;
// RFC 2396: a relative URI (no scheme) has a ?query,
// but absolute URIs only have query if path begins with /
- if url.scheme == "" || len(path) > 0 && path[0] == '/' {
- path, url.query = split(path, '?', true);
- if url.query, err = URLUnescape(url.query); err != nil {
+ if url.Scheme == "" || len(path) > 0 && path[0] == '/' {
+ path, url.Query = split(path, '?', true);
+ if url.Query, err = URLUnescape(url.Query); err != nil {
return nil, err
}
}
// Maybe path is //authority/path
if len(path) > 2 && path[0:2] == "//" {
- url.authority, path = split(path[2:len(path)], '/', false);
+ url.Authority, path = split(path[2:len(path)], '/', false);
}
// If there's no @, split's default is wrong. Check explicitly.
- if strings.Index(url.authority, "@") < 0 {
- url.host = url.authority;
+ if strings.Index(url.Authority, "@") < 0 {
+ url.Host = url.Authority;
} else {
- url.userinfo, url.host = split(url.authority, '@', true);
+ url.Userinfo, url.Host = split(url.Authority, '@', true);
}
// What's left is the path.
// TODO: Canonicalize (remove . and ..)?
- if url.path, err = URLUnescape(path); err != nil {
+ if url.Path, err = URLUnescape(path); err != nil {
return nil, err
}
@@ -178,7 +178,7 @@ func ParseURLReference(rawurlref string) (url *URL, err *os.Error) {
if url, err = ParseURL(rawurl); err != nil {
return nil, err
}
- if url.fragment, err = URLUnescape(frag); err != nil {
+ if url.Fragment, err = URLUnescape(frag); err != nil {
return nil, err
}
return url, nil