flesh out http server.

convert to uppercase names.

R=r
DELTA=613  (460 added, 61 deleted, 92 changed)
OCL=24139
CL=24145
This commit is contained in:
Russ Cox 2009-02-02 18:01:32 -08:00
parent f61639d4e2
commit e73acc1b35
5 changed files with 544 additions and 146 deletions

View file

@ -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();
}

View file

@ -9,14 +9,15 @@ package http
import ( import (
"bufio"; "bufio";
"http"; "http";
"io";
"os"; "os";
"strings" "strings"
) )
const ( const (
_MaxLineLength = 1024; // assumed < bufio.DefaultBufSize maxLineLength = 1024; // assumed < bufio.DefaultBufSize
_MaxValueLength = 1024; maxValueLength = 1024;
_MaxHeaderLines = 1024; maxHeaderLines = 1024;
) )
var ( var (
@ -30,30 +31,36 @@ var (
// HTTP Request // HTTP Request
type Request struct { type Request struct {
method string; // GET, PUT,etc. Method string; // GET, PUT,etc.
rawurl string; RawUrl string;
url *URL; // URI after GET, PUT etc. Url *URL; // URI after GET, PUT etc.
proto string; // "HTTP/1.0" Proto string; // "HTTP/1.0"
pmajor int; // 1 ProtoMajor int; // 1
pminor int; // 0 ProtoMinor int; // 0
header map[string] string; Header map[string] string;
close bool; Close bool;
host string; Host string;
referer string; Referer string; // referer [sic]
useragent string; 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. // 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 returned bytes are a pointer into storage in
// the bufio, so they are only valid until the next bufio read. // the bufio, so they are only valid until the next bufio read.
func readLineBytes(b *bufio.BufRead) (p []byte, err *os.Error) { func readLineBytes(b *bufio.BufRead) (p []byte, err *os.Error) {
if p, err = b.ReadLineSlice('\n'); err != nil { if p, err = b.ReadLineSlice('\n'); err != nil {
return nil, err return nil, err
} }
if len(p) >= _MaxLineLength { if len(p) >= maxLineLength {
return nil, LineTooLong return nil, LineTooLong
} }
@ -132,7 +139,7 @@ func readKeyValue(b *bufio.BufRead) (key, value string, err *os.Error) {
} }
value += " " + string(line); value += " " + string(line);
if len(value) >= _MaxValueLength { if len(value) >= maxValueLength {
return "", "", ValueTooLong return "", "", ValueTooLong
} }
} }
@ -179,6 +186,37 @@ func parseHTTPVersion(vers string) (int, int, bool) {
return major, minor, true 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. // Read and parse a request from b.
func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) { func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
req = new(Request); 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 { if f = strings.Split(s, " "); len(f) != 3 {
return nil, BadRequest 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; 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 return nil, BadHTTPVersion
} }
if req.url, err = ParseURL(req.rawurl); err != nil { if req.Url, err = ParseURL(req.RawUrl); err != nil {
return nil, err return nil, err
} }
// Subsequent lines: Key: value. // Subsequent lines: Key: value.
nheader := 0; nheader := 0;
req.header = make(map[string] string); req.Header = make(map[string] string);
for { for {
var key, value string; var key, value string;
if key, value, err = readKeyValue(b); err != nil { if key, value, err = readKeyValue(b); err != nil {
@ -214,18 +252,20 @@ func ReadRequest(b *bufio.BufRead) (req *Request, err *os.Error) {
if key == "" { if key == "" {
break break
} }
if nheader++; nheader >= _MaxHeaderLines { if nheader++; nheader >= maxHeaderLines {
return nil, HeaderTooLong return nil, HeaderTooLong
} }
key = CanonicalHeaderKey(key);
// RFC 2616 says that if you send the same header key // RFC 2616 says that if you send the same header key
// multiple times, it has to be semantically equivalent // multiple times, it has to be semantically equivalent
// to concatenating the values separated by commas. // to concatenating the values separated by commas.
oldvalue, present := req.header[key]; oldvalue, present := req.Header[key];
if present { if present {
req.header[key] = oldvalue+","+value req.Header[key] = oldvalue+","+value
} else { } 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 // GET http://www.google.com/index.html HTTP/1.1
// Host: doesntmatter // Host: doesntmatter
// the same. In the second case, any Host line is ignored. // the same. In the second case, any Host line is ignored.
if v, have := req.header["Host"]; have && req.url.host == "" { if v, present := req.Header["Host"]; present && req.Url.Host == "" {
req.host = v req.Host = v
} }
// RFC2616: Should treat // RFC2616: Should treat
// Pragma: no-cache // Pragma: no-cache
// like // like
// Cache-control: no-cache // Cache-Control: no-cache
if v, have := req.header["Pragma"]; have && v == "no-cache" { if v, present := req.Header["Pragma"]; present && v == "no-cache" {
if cc, havecc := req.header["Cache-control"]; !havecc { if cc, presentcc := req.Header["Cache-Control"]; !presentcc {
req.header["Cache-control"] = "no-cache" req.Header["Cache-Control"] = "no-cache"
} }
} }
// Determine whether to hang up after sending the reply. // Determine whether to hang up after sending the reply.
if req.pmajor < 1 || (req.pmajor == 1 && req.pminor < 1) { if req.ProtoMajor < 1 || (req.ProtoMajor == 1 && req.ProtoMinor < 1) {
req.close = true req.Close = true
} else if v, have := req.header["Connection"]; have { } else if v, present := req.Header["Connection"]; present {
// TODO: Should split on commas, toss surrounding white space, // TODO: Should split on commas, toss surrounding white space,
// and check each field. // and check each field.
if v == "close" { if v == "close" {
req.close = true req.Close = true
} }
} }
// Pull out useful fields as a convenience to clients. // Pull out useful fields as a convenience to clients.
if v, have := req.header["Referer"]; have { if v, present := req.Header["Referer"]; present {
req.referer = v req.Referer = v
} }
if v, have := req.header["User-Agent"]; have { if v, present := req.Header["User-Agent"]; present {
req.useragent = v req.UserAgent = v
} }
// TODO: Parse specific header values: // TODO: Parse specific header values:
// Accept // Accept
// Accept-Encoding // Accept-Encoding

View file

@ -2,64 +2,448 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 package http
import ( import (
"io"; "bufio";
"os"; "fmt";
"net";
"http"; "http";
"io";
"net";
"os";
"strconv"; "strconv";
) )
// Serve a new connection. var ErrWriteAfterFlush = os.NewError("Conn.Write called after Flush")
func serveConnection(fd net.Conn, raddr string, f func(*Conn, *Request)) {
c, err := NewConn(fd); type Conn struct
if err != nil {
// 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 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 { for {
req, err := c.ReadRequest(); req, err := c.readRequest();
if err != nil { if err != nil {
break 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 { if c.close {
break break;
} }
} }
c.Close(); c.Close();
} }
// Web server: already listening on l, call f for each request. // Adapter: can use RequestFunction(f) as Handler
func Serve(l net.Listener, f func(*Conn, *Request)) *os.Error { type handlerFunc struct {
// TODO: Make this unnecessary f func(*Conn, *Request)
s, e := os.Getenv("GOMAXPROCS"); }
if n, ok := strconv.Atoi(s); n < 3 { func (h handlerFunc) ServeHTTP(c *Conn, req *Request) {
print("Warning: $GOMAXPROCS needs to be at least 3.\n"); 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 { for {
rw, raddr, e := l.Accept(); rw, raddr, e := l.Accept();
if e != nil { if e != nil {
return e return e
} }
go serveConnection(rw, raddr, f) c, err := newConn(rw, raddr, handler);
if err != nil {
continue;
}
go c.serve();
} }
panic("not reached") panic("not reached")
} }
// Web server: listen on address, call f for each request. // 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); l, e := net.Listen("tcp", addr);
if e != nil { if e != nil {
return e return e
} }
e = Serve(l, f); e = Serve(l, handler);
l.Close(); l.Close();
return e return e
} }

View file

@ -5,24 +5,52 @@
package main package main
import ( import (
"io";
"bufio"; "bufio";
"os"; "flag";
"fmt";
"http";
"io";
"net"; "net";
"http" "os";
) )
func Echo(conn *http.Conn, req *http.Request) {
fd := conn.bw; // hello world, the web server
conn.close = true; func HelloServer(c *http.Conn, req *http.Request) {
io.WriteString(fd, "HTTP/1.1 200 OK\r\n" io.WriteString(c, "hello, world!\n");
"Content-Type: text/plain\r\n" }
"\r\n");
io.WriteString(fd, req.method+" "+req.rawurl+" "+req.proto+"\r\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() { 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 { if err != nil {
panic("ListenAndServe: ", err.String()) panic("ListenAndServe: ", err.String())
} }

View file

@ -77,15 +77,15 @@ func URLUnescape(s string) (string, *os.Error) {
} }
type URL struct { type URL struct {
raw string; Raw string;
scheme string; Scheme string;
rawpath string; RawPath string;
authority string; Authority string;
userinfo string; Userinfo string;
host string; Host string;
path string; Path string;
query string; Query string;
fragment string; Fragment string;
} }
// Maybe rawurl is of the form scheme:path. // Maybe rawurl is of the form scheme:path.
@ -132,39 +132,39 @@ func ParseURL(rawurl string) (url *URL, err *os.Error) {
return nil, BadURL return nil, BadURL
} }
url = new(URL); url = new(URL);
url.raw = rawurl; url.Raw = rawurl;
// split off possible leading "http:", "mailto:", etc. // split off possible leading "http:", "mailto:", etc.
var path string; var path string;
if url.scheme, path, err = getscheme(rawurl); err != nil { if url.Scheme, path, err = getscheme(rawurl); err != nil {
return nil, err return nil, err
} }
url.rawpath = path; url.RawPath = path;
// RFC 2396: a relative URI (no scheme) has a ?query, // RFC 2396: a relative URI (no scheme) has a ?query,
// but absolute URIs only have query if path begins with / // but absolute URIs only have query if path begins with /
if url.scheme == "" || len(path) > 0 && path[0] == '/' { if url.Scheme == "" || len(path) > 0 && path[0] == '/' {
path, url.query = split(path, '?', true); path, url.Query = split(path, '?', true);
if url.query, err = URLUnescape(url.query); err != nil { if url.Query, err = URLUnescape(url.Query); err != nil {
return nil, err return nil, err
} }
} }
// Maybe path is //authority/path // Maybe path is //authority/path
if len(path) > 2 && path[0:2] == "//" { 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 there's no @, split's default is wrong. Check explicitly.
if strings.Index(url.authority, "@") < 0 { if strings.Index(url.Authority, "@") < 0 {
url.host = url.authority; url.Host = url.Authority;
} else { } else {
url.userinfo, url.host = split(url.authority, '@', true); url.Userinfo, url.Host = split(url.Authority, '@', true);
} }
// What's left is the path. // What's left is the path.
// TODO: Canonicalize (remove . and ..)? // TODO: Canonicalize (remove . and ..)?
if url.path, err = URLUnescape(path); err != nil { if url.Path, err = URLUnescape(path); err != nil {
return nil, err return nil, err
} }
@ -178,7 +178,7 @@ func ParseURLReference(rawurlref string) (url *URL, err *os.Error) {
if url, err = ParseURL(rawurl); err != nil { if url, err = ParseURL(rawurl); err != nil {
return nil, err return nil, err
} }
if url.fragment, err = URLUnescape(frag); err != nil { if url.Fragment, err = URLUnescape(frag); err != nil {
return nil, err return nil, err
} }
return url, nil return url, nil