summaryrefslogtreecommitdiff
path: root/src/lib/http/request.go
diff options
context:
space:
mode:
authorRuss Cox <rsc@golang.org>2008-09-24 15:26:55 -0700
committerRuss Cox <rsc@golang.org>2008-09-24 15:26:55 -0700
commitd7958892a5a7a352826380421b090e37c98b0111 (patch)
tree923d12ecfd9480dafbb8120aae8791032131ec06 /src/lib/http/request.go
parent19650c539ee3f8f9516f633c12a0280485737256 (diff)
downloadgolang-d7958892a5a7a352826380421b090e37c98b0111.tar.gz
early HTTP library and trivial server
R=r OCL=15777 CL=15791
Diffstat (limited to 'src/lib/http/request.go')
-rw-r--r--src/lib/http/request.go300
1 files changed, 300 insertions, 0 deletions
diff --git a/src/lib/http/request.go b/src/lib/http/request.go
new file mode 100644
index 000000000..bed911eb4
--- /dev/null
+++ b/src/lib/http/request.go
@@ -0,0 +1,300 @@
+// 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;
+}