--- /dev/null
+# 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.
+
+# DO NOT EDIT. Automatically generated by gobuild.
+# gobuild -m http conn.go request.go server.go url.go
+O=6
+GC=$(O)g
+CC=$(O)c -w
+AS=$(O)a
+AR=$(O)ar
+
+PKG=$(GOROOT)/pkg/http.a
+
+install: $(PKG)
+
+nuke: clean
+ rm -f $(PKG)
+
+clean:
+ rm -f *.$O *.a
+
+%.$O: %.go
+ $(GC) $*.go
+
+%.$O: %.c
+ $(CC) $*.c
+
+%.$O: %.s
+ $(AS) $*.s
+
+
+O1=\
+ url.$O\
+
+O2=\
+ request.$O\
+
+O3=\
+ conn.$O\
+
+O4=\
+ server.$O\
+
+$(PKG): a1 a2 a3 a4
+a1: $(O1)
+ $(AR) grc $(PKG) $(O1)
+a2: $(O2)
+ $(AR) grc $(PKG) $(O2)
+a3: $(O3)
+ $(AR) grc $(PKG) $(O3)
+a4: $(O4)
+ $(AR) grc $(PKG) $(O4)
+
+$(O1): nuke
+$(O2): a1
+$(O3): a2
+$(O4): a3
+
--- /dev/null
+// 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"
+)
+
+// Read/write/close interface.
+type RWC interface {
+ Read(p *[]byte) (n int, err *os.Error);
+ Write(p *[]byte) (n int, err *os.Error);
+ Close() *os.Error
+}
+
+// Active HTTP connection (server side).
+export type Conn struct {
+ rwc RWC;
+ br *bufio.BufRead;
+ bw *bufio.BufWrite;
+ close bool;
+ chunking bool;
+}
+
+// Create new connection from rwc.
+export func NewConn(rwc RWC) (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();
+}
+
--- /dev/null
+// 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 Request reading and parsing.
+
+package http
+
+import (
+ "bufio";
+ "http";
+ "os";
+ "strings"
+)
+
+const (
+ MaxLineLength = 1024; // assumed < bufio.DefaultBufSize
+ MaxValueLength = 1024;
+ MaxHeaderLines = 1024;
+)
+
+export var (
+ LineTooLong = os.NewError("http header line too long");
+ ValueTooLong = os.NewError("http header value too long");
+ HeaderTooLong = os.NewError("http header too long");
+ BadHeader = os.NewError("malformed http header");
+ BadRequest = os.NewError("invalid http request");
+ BadHTTPVersion = os.NewError("unsupported http version");
+)
+
+// HTTP Request
+export 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;
+}
+
+// Read a line of bytes (up to \n) from b.
+// 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 {
+ return nil, LineTooLong
+ }
+
+ // Chop off trailing white space.
+ var i int
+ for i = len(p); i > 0; i-- {
+ if c := p[i-1]; c != ' ' && c != '\r' && c != '\t' && c != '\n' {
+ break
+ }
+ }
+ return p[0:i], nil
+}
+
+// ReadLineByte, but convert the bytes into a string.
+func ReadLine(b *bufio.BufRead) (s string, err *os.Error) {
+ p, e := ReadLineBytes(b)
+ if e != nil {
+ return "", e
+ }
+ return string(p), nil
+}
+
+// Read a key/value pair from b.
+// A key/value has the form Key: Value\r\n
+// and the Value can continue on multiple lines if each continuation line
+// starts with a space.
+func ReadKeyValue(b *bufio.BufRead) (key, value string, err *os.Error) {
+ line, e := ReadLineBytes(b)
+ if e != nil {
+ return "", "", e
+ }
+ if len(line) == 0 {
+ return "", "", nil
+ }
+
+ // Scan first line for colon.
+ for i := 0; i < len(line); i++ {
+ switch line[i] {
+ case ' ':
+ // Key field has space - no good.
+ return "", "", BadHeader
+ case ':':
+ key = string(line[0:i]);
+ // Skip initial space before value.
+ for i++; i < len(line); i++ {
+ if line[i] != ' ' {
+ break
+ }
+ }
+ value = string(line[i:len(line)])
+
+ // Look for extension lines, which must begin with space.
+ for {
+ var c byte;
+
+ if c, e = b.ReadByte(); e != nil {
+ return "", "", e
+ }
+ if c != ' ' {
+ // Not leading space; stop.
+ b.UnreadByte()
+ break
+ }
+
+ // Eat leading space.
+ for c == ' ' {
+ if c, e = b.ReadByte(); e != nil {
+ return "", "", e
+ }
+ }
+ b.UnreadByte()
+
+ // Read the rest of the line and add to value.
+ if line, e = ReadLineBytes(b); e != nil {
+ return "", "", e
+ }
+ value += " " + string(line)
+
+ if len(value) >= MaxValueLength {
+ return "", "", ValueTooLong
+ }
+ }
+ return key, value, nil
+ }
+ }
+
+ // Line ended before space or colon.
+ return "", "", BadHeader;
+}
+
+// Convert decimal at s[i:len(s)] to integer,
+// returning value, string position where the digits stopped,
+// and whether there was a valid number (digits, not too big).
+func atoi(s string, i int) (n, i1 int, ok bool) {
+ const Big = 1000000
+ if i >= len(s) || s[i] < '0' || s[i] > '9' {
+ return 0, 0, false
+ }
+ n = 0
+ for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
+ n = n*10 + int(s[i]-'0')
+ if n > Big {
+ return 0, 0, false
+ }
+ }
+ return n, i, true
+}
+
+// Parse HTTP version: "HTTP/1.2" -> (1, 2, true).
+func ParseHTTPVersion(vers string) (int, int, bool) {
+ if vers[0:5] != "HTTP/" {
+ return 0, 0, false
+ }
+ major, i, ok := atoi(vers, 5)
+ if !ok || i >= len(vers) || vers[i] != '.' {
+ return 0, 0, false
+ }
+ var minor int;
+ minor, i, ok = atoi(vers, i+1)
+ if !ok || i != len(vers) {
+ return 0, 0, false
+ }
+ return major, minor, true
+}
+
+// Read and parse a request from b.
+export func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
+ req = new(Request);
+
+ // First line: GET /index.html HTTP/1.0
+ var s string
+ if s, err = ReadLine(b); err != nil {
+ return nil, err
+ }
+
+ var f *[]string
+ if f = strings.split(s, " "); len(f) != 3 {
+ return nil, BadRequest
+ }
+ 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 {
+ return nil, BadHTTPVersion
+ }
+
+ if req.url, err = ParseURL(req.rawurl); err != nil {
+ return nil, err
+ }
+
+ // Subsequent lines: Key: value.
+ nheader := 0;
+ req.header = new(map[string] string)
+ for {
+ var key, value string
+ if key, value, err = ReadKeyValue(b); err != nil {
+ return nil, err
+ }
+ if key == "" {
+ break
+ }
+ if nheader++; nheader >= MaxHeaderLines {
+ return nil, HeaderTooLong
+ }
+
+ // 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]
+ if present {
+ req.header[key] = oldvalue+","+value
+ } else {
+ req.header[key] = value
+ }
+ }
+
+ // RFC2616: Must treat
+ // GET /index.html HTTP/1.1
+ // Host: www.google.com
+ // and
+ // 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
+ }
+
+ // 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"
+ }
+ }
+
+ // 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 {
+ // TODO: Should split on commas, toss surrounding white space,
+ // and check each field.
+ if v == "close" {
+ req.close = true
+ }
+ }
+
+ // Pull out useful fields as a convenience to clients.
+ if v, have := req.header["Referer"]; have {
+ req.referer = v
+ }
+ if v, have := req.header["User-Agent"]; have {
+ req.useragent = v
+ }
+
+
+ // TODO: Parse specific header values:
+ // Accept
+ // Accept-Encoding
+ // Accept-Language
+ // Authorization
+ // Cache-Control
+ // Connection
+ // Date
+ // Expect
+ // From
+ // If-Match
+ // If-Modified-Since
+ // If-None-Match
+ // If-Range
+ // If-Unmodified-Since
+ // Max-Forwards
+ // Proxy-Authorization
+ // Referer [sic]
+ // TE (transfer-codings)
+ // Trailer
+ // Transfer-Encoding
+ // Upgrade
+ // User-Agent
+ // Via
+ // Warning
+
+ return req, nil;
+}
--- /dev/null
+// 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.
+
+// Trivial HTTP server
+
+// TODO: Routines for writing responses.
+
+package http
+
+import (
+ "io";
+ "os";
+ "net";
+ "http";
+ "strings"
+)
+
+// Serve a new connection.
+func ServeConnection(fd net.Conn, raddr string, f *(*Conn, *Request)) {
+ c, err := NewConn(fd)
+ if err != nil {
+ return
+ }
+ for {
+ req, err := c.ReadRequest()
+ if err != nil {
+ break
+ }
+ f(c, req)
+ if c.close {
+ break
+ }
+ }
+ c.Close();
+}
+
+// Web server: already listening on l, call f for each request.
+export func Serve(l net.Listener, f *(*Conn, *Request)) *os.Error {
+ // TODO: Make this unnecessary
+ s, e := os.Getenv("GOMAXPROCS");
+ if n, ok := strings.atoi(s); n < 3 {
+ print("Warning: $GOMAXPROCS needs to be at least 3.\n");
+ }
+
+ for {
+ rw, raddr, e := l.Accept()
+ if e != nil {
+ return e
+ }
+ go ServeConnection(rw, raddr, f)
+ }
+ panic("not reached")
+}
+
+// Web server: listen on address, call f for each request.
+export func ListenAndServe(addr string, f *(*Conn, *Request)) *os.Error {
+ l, e := net.Listen("tcp", addr)
+ if e != nil {
+ return e
+ }
+ e = Serve(l, f);
+ l.Close()
+ return e
+}
--- /dev/null
+// 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 main
+
+import (
+ "io";
+ "bufio";
+ "os";
+ "net";
+ "http"
+)
+
+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")
+}
+
+func main() {
+ err := http.ListenAndServe("0.0.0.0:12345", &Echo)
+ if err != nil {
+ panic("ListenAndServe: ", err.String())
+ }
+}
+
--- /dev/null
+// 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.
+
+// Parse URLs (actually URIs, but that seems overly pedantic).
+
+package http
+
+import (
+ "os"
+)
+
+export var (
+ BadURL = os.NewError("bad url syntax")
+)
+
+func IsHex(c byte) bool {
+ switch {
+ case '0' <= c && c <= '9':
+ return true
+ case 'a' <= c && c <= 'f':
+ return true
+ case 'A' <= c && c <= 'F':
+ return true
+ }
+ return false
+}
+
+func UnHex(c byte) byte {
+ switch {
+ case '0' <= c && c <= '9':
+ return c - '0'
+ case 'a' <= c && c <= 'f':
+ return c - 'a' + 10
+ case 'A' <= c && c <= 'F':
+ return c - 'A' + 10
+ }
+ return 0
+}
+
+// Unescape %xx into hex.
+export func URLUnescape(s string) (string, *os.Error) {
+ // Count %, check that they're well-formed.
+ n := 0
+ for i := 0; i < len(s); {
+ if s[i] == '%' {
+ n++
+ if !IsHex(s[i+1]) || !IsHex(s[i+2]) {
+ return "", BadURL
+ }
+ i += 3
+ } else {
+ i++
+ }
+ }
+
+ if n == 0 {
+ return s, nil
+ }
+
+ t := new([]byte, len(s)-2*n);
+ j := 0
+ for i := 0; i < len(s); {
+ if s[i] == '%' {
+ t[j] = UnHex(s[i+1]) << 4 | UnHex(s[i+2]);
+ j++
+ i += 3
+ } else {
+ t[j] = s[i];
+ j++
+ i++
+ }
+ }
+ return string(t), nil
+}
+
+export type URL struct {
+ 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.
+// (Scheme must be [a-zA-Z][a-zA-Z0-9+-.]*)
+// If so, return scheme, path; else return "", rawurl.
+func GetScheme(rawurl string) (scheme, path string, err *os.Error) {
+ for i := 0; i < len(rawurl); i++ {
+ c := rawurl[i];
+ switch {
+ case 'a' <= c && c <= 'z' ||'A' <= c && c <= 'Z':
+ // do nothing
+ case '0' <= c && c <= '9' || c == '+' || c == '-' || c == '.':
+ if i == 0 {
+ return "", rawurl, nil
+ }
+ case c == ':':
+ if i == 0 {
+ return "", "", BadURL
+ }
+ return rawurl[0:i], rawurl[i+1:len(rawurl)], nil
+ }
+ }
+ return "", rawurl, nil
+}
+
+// Maybe s is of the form t c u.
+// If so, return t, c u (or t, u if cutc == true).
+// If not, return s, "".
+func Split(s string, c byte, cutc bool) (string, string) {
+ for i := 0; i < len(s); i++ {
+ if s[i] == c {
+ if cutc {
+ return s[0:i], s[i+1:len(s)]
+ }
+ return s[0:i], s[i:len(s)]
+ }
+ }
+ return s, ""
+}
+
+// Parse rawurl into a URL structure.
+export func ParseURL(rawurl string) (url *URL, err *os.Error) {
+ if rawurl == "" {
+ return nil, BadURL
+ }
+ url = new(URL);
+ url.raw = rawurl
+
+ // Split off possible leading "http:", "mailto:", etc.
+ var path string
+ if url.scheme, path, err = GetScheme(rawurl); err != nil {
+ return nil, err
+ }
+ 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 {
+ 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.userinfo, url.host = Split(url.authority, '@', true);
+
+ // What's left is the path.
+ // TODO: Canonicalize (remove . and ..)?
+ if url.path, err = URLUnescape(url.path); err != nil {
+ return nil, err
+ }
+
+ return url, nil
+}
+
+// A URL reference is a URL with #frag potentially added. Parse it.
+export func ParseURLReference(rawurlref string) (url *URL, err *os.Error) {
+ // Cut off #frag.
+ rawurl, frag := Split(rawurlref, '#', true);
+ if url, err = ParseURL(rawurl); err != nil {
+ return nil, err
+ }
+ if url.fragment, err = URLUnescape(frag); err != nil {
+ return nil, err
+ }
+ return url, nil
+}
+