Commit e73acc1b authored by Russ Cox's avatar Russ Cox

flesh out http server.

convert to uppercase names.

R=r
DELTA=613  (460 added, 61 deleted, 92 changed)
OCL=24139
CL=24145
parent f61639d4
// 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();
}
......@@ -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
......
This diff is collapsed.
......@@ -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())
}
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment