diff options
Diffstat (limited to 'src/pkg/http/server.go')
-rw-r--r-- | src/pkg/http/server.go | 542 |
1 files changed, 331 insertions, 211 deletions
diff --git a/src/pkg/http/server.go b/src/pkg/http/server.go index 81ce98229..b8783da28 100644 --- a/src/pkg/http/server.go +++ b/src/pkg/http/server.go @@ -13,6 +13,8 @@ package http import ( "bufio" + "crypto/rand" + "crypto/tls" "fmt" "io" "log" @@ -21,6 +23,7 @@ import ( "path" "strconv" "strings" + "time" ) // Errors introduced by the HTTP server. @@ -34,42 +37,93 @@ var ( // registered to serve a particular path or subtree // in the HTTP server. // -// ServeHTTP should write reply headers and data to the Conn +// ServeHTTP should write reply headers and data to the ResponseWriter // and then return. Returning signals that the request is finished // and that the HTTP server can move on to the next request on // the connection. type Handler interface { - ServeHTTP(*Conn, *Request) + ServeHTTP(ResponseWriter, *Request) } -// A Conn represents the server side of a single active HTTP connection. -type Conn struct { - RemoteAddr string // network address of remote side - Req *Request // current HTTP request +// A ResponseWriter interface is used by an HTTP handler to +// construct an HTTP response. +type ResponseWriter interface { + // RemoteAddr returns the address of the client that sent the current request + RemoteAddr() string + + // UsingTLS returns true if the client is connected using TLS + UsingTLS() bool + + // SetHeader sets a header line in the eventual response. + // For example, SetHeader("Content-Type", "text/html; charset=utf-8") + // will result in the header line + // + // Content-Type: text/html; charset=utf-8 + // + // being sent. UTF-8 encoded HTML is the default setting for + // Content-Type in this library, so users need not make that + // particular call. Calls to SetHeader after WriteHeader (or Write) + // are ignored. + SetHeader(string, string) + + // Write writes the data to the connection as part of an HTTP reply. + // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) + // before writing the data. + Write([]byte) (int, os.Error) + + // WriteHeader sends an HTTP response header with status code. + // If WriteHeader is not called explicitly, the first call to Write + // will trigger an implicit WriteHeader(http.StatusOK). + // Thus explicit calls to WriteHeader are mainly used to + // send error codes. + WriteHeader(int) + + // Flush sends any buffered data to the client. + Flush() + + // Hijack lets the caller take over the connection. + // After a call to Hijack(), the HTTP server library + // will not do anything else with the connection. + // It becomes the caller's responsibility to manage + // and close the connection. + Hijack() (io.ReadWriteCloser, *bufio.ReadWriter, os.Error) +} - rwc io.ReadWriteCloser // i/o connection - buf *bufio.ReadWriter // buffered rwc - handler Handler // request handler - hijacked bool // connection has been hijacked by handler +// A conn represents the server side of an HTTP connection. +type conn struct { + remoteAddr string // network address of remote side + handler Handler // request handler + rwc io.ReadWriteCloser // i/o connection + buf *bufio.ReadWriter // buffered rwc + hijacked bool // connection has been hijacked by handler + usingTLS bool // a flag indicating connection over TLS +} - // state for the current reply - closeAfterReply bool // close connection after this reply - chunking bool // using chunked transfer encoding for reply body - wroteHeader bool // reply header has been written - wroteContinue bool // 100 Continue response was written - header map[string]string // reply header parameters - written int64 // number of bytes written in body - status int // status code passed to WriteHeader +// A response represents the server side of an HTTP response. +type response struct { + conn *conn + req *Request // request for this response + chunking bool // using chunked transfer encoding for reply body + wroteHeader bool // reply header has been written + wroteContinue bool // 100 Continue response was written + header map[string]string // reply header parameters + written int64 // number of bytes written in body + status int // status code passed to WriteHeader + + // close connection after this reply. set on request and + // updated after response from handler if there's a + // "Connection: keep-alive" response header and a + // Content-Length. + closeAfterReply bool } // Create new connection from rwc. -func newConn(rwc net.Conn, handler Handler) (c *Conn, err os.Error) { - c = new(Conn) - if a := rwc.RemoteAddr(); a != nil { - c.RemoteAddr = a.String() - } +func newConn(rwc net.Conn, handler Handler) (c *conn, err os.Error) { + c = new(conn) + c.remoteAddr = rwc.RemoteAddr().String() c.handler = handler c.rwc = rwc + _, c.usingTLS = rwc.(*tls.Conn) br := bufio.NewReader(rwc) bw := bufio.NewWriter(rwc) c.buf = bufio.NewReadWriter(br, bw) @@ -79,17 +133,15 @@ func newConn(rwc net.Conn, handler Handler) (c *Conn, err os.Error) { // wrapper around io.ReaderCloser which on first read, sends an // HTTP/1.1 100 Continue header type expectContinueReader struct { - conn *Conn + resp *response readCloser io.ReadCloser } func (ecr *expectContinueReader) Read(p []byte) (n int, err os.Error) { - if !ecr.conn.wroteContinue && !ecr.conn.hijacked { - ecr.conn.wroteContinue = true - if ecr.conn.Req.ProtoAtLeast(1, 1) { - io.WriteString(ecr.conn.buf, "HTTP/1.1 100 Continue\r\n\r\n") - ecr.conn.buf.Flush() - } + if !ecr.resp.wroteContinue && !ecr.resp.conn.hijacked { + ecr.resp.wroteContinue = true + io.WriteString(ecr.resp.conn.buf, "HTTP/1.1 100 Continue\r\n\r\n") + ecr.resp.conn.buf.Flush() } return ecr.readCloser.Read(p) } @@ -98,88 +150,88 @@ func (ecr *expectContinueReader) Close() os.Error { return ecr.readCloser.Close() } +// TimeFormat is the time format to use with +// time.Parse and time.Time.Format when parsing +// or generating times in HTTP headers. +// It is like time.RFC1123 but hard codes GMT as the time zone. +const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" + // Read next request from connection. -func (c *Conn) readRequest() (req *Request, err os.Error) { +func (c *conn) readRequest() (w *response, err os.Error) { if c.hijacked { return nil, ErrHijacked } + var req *Request if req, err = ReadRequest(c.buf.Reader); err != nil { return nil, err } - // Reset per-request connection state. - c.header = make(map[string]string) - c.wroteHeader = false - c.wroteContinue = false - c.Req = req + w = new(response) + w.conn = c + w.req = req + w.header = make(map[string]string) // Expect 100 Continue support - if req.expectsContinue() { + if req.expectsContinue() && req.ProtoAtLeast(1, 1) { // Wrap the Body reader with one that replies on the connection - req.Body = &expectContinueReader{readCloser: req.Body, conn: c} + req.Body = &expectContinueReader{readCloser: req.Body, resp: w} } // Default output is HTML encoded in UTF-8. - c.SetHeader("Content-Type", "text/html; charset=utf-8") + w.SetHeader("Content-Type", "text/html; charset=utf-8") + w.SetHeader("Date", time.UTC().Format(TimeFormat)) 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") + w.chunking = true + w.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.closeAfterReply = true - c.chunking = false + // Will be overridden if the HTTP handler ends up + // writing a Content-Length and the client requested + // "Connection: keep-alive" + w.closeAfterReply = true } - return req, nil + return w, nil } -// SetHeader sets a header line in the eventual reply. -// For example, SetHeader("Content-Type", "text/html; charset=utf-8") -// will result in the header line -// -// Content-Type: text/html; charset=utf-8 -// -// being sent. UTF-8 encoded HTML is the default setting for -// Content-Type in this library, so users need not make that -// particular call. Calls to SetHeader after WriteHeader (or Write) -// are ignored. -func (c *Conn) SetHeader(hdr, val string) { c.header[CanonicalHeaderKey(hdr)] = val } - -// WriteHeader sends an HTTP response header with status code. -// If WriteHeader is not called explicitly, the first call to Write -// will trigger an implicit WriteHeader(http.StatusOK). -// Thus explicit calls to WriteHeader are mainly used to -// send error codes. -func (c *Conn) WriteHeader(code int) { - if c.hijacked { - log.Stderr("http: Conn.WriteHeader on hijacked connection") +// UsingTLS implements the ResponseWriter.UsingTLS +func (w *response) UsingTLS() bool { + return w.conn.usingTLS +} + +// RemoteAddr implements the ResponseWriter.RemoteAddr method +func (w *response) RemoteAddr() string { return w.conn.remoteAddr } + +// SetHeader implements the ResponseWriter.SetHeader method +func (w *response) SetHeader(hdr, val string) { w.header[CanonicalHeaderKey(hdr)] = val } + +// WriteHeader implements the ResponseWriter.WriteHeader method +func (w *response) WriteHeader(code int) { + if w.conn.hijacked { + log.Print("http: response.WriteHeader on hijacked connection") return } - if c.wroteHeader { - log.Stderr("http: multiple Conn.WriteHeader calls") + if w.wroteHeader { + log.Print("http: multiple response.WriteHeader calls") return } - c.wroteHeader = true - c.status = code + w.wroteHeader = true + w.status = code if code == StatusNotModified { // Must not have body. - c.header["Content-Type"] = "", false - c.header["Transfer-Encoding"] = "", false + w.header["Content-Type"] = "", false + w.header["Transfer-Encoding"] = "", false + w.chunking = false } - c.written = 0 - if !c.Req.ProtoAtLeast(1, 0) { + if !w.req.ProtoAtLeast(1, 0) { return } proto := "HTTP/1.0" - if c.Req.ProtoAtLeast(1, 1) { + if w.req.ProtoAtLeast(1, 1) { proto = "HTTP/1.1" } codestring := strconv.Itoa(code) @@ -187,48 +239,55 @@ func (c *Conn) WriteHeader(code int) { if !ok { text = "status code " + codestring } - io.WriteString(c.buf, proto+" "+codestring+" "+text+"\r\n") - for k, v := range c.header { - io.WriteString(c.buf, k+": "+v+"\r\n") + io.WriteString(w.conn.buf, proto+" "+codestring+" "+text+"\r\n") + for k, v := range w.header { + io.WriteString(w.conn.buf, k+": "+v+"\r\n") } - io.WriteString(c.buf, "\r\n") + io.WriteString(w.conn.buf, "\r\n") } -// Write writes the data to the connection as part of an HTTP reply. -// If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) -// before writing the data. -func (c *Conn) Write(data []byte) (n int, err os.Error) { - if c.hijacked { - log.Stderr("http: Conn.Write on hijacked connection") +// Write implements the ResponseWriter.Write method +func (w *response) Write(data []byte) (n int, err os.Error) { + if w.conn.hijacked { + log.Print("http: response.Write on hijacked connection") return 0, ErrHijacked } - if !c.wroteHeader { - c.WriteHeader(StatusOK) + if !w.wroteHeader { + if w.req.wantsHttp10KeepAlive() { + _, hasLength := w.header["Content-Length"] + if hasLength { + _, connectionHeaderSet := w.header["Connection"] + if !connectionHeaderSet { + w.header["Connection"] = "keep-alive" + } + } + } + w.WriteHeader(StatusOK) } if len(data) == 0 { return 0, nil } - if c.status == StatusNotModified { + if w.status == StatusNotModified { // Must not have body. return 0, ErrBodyNotAllowed } - c.written += int64(len(data)) // ignoring errors, for errorKludge + w.written += int64(len(data)) // ignoring errors, for errorKludge // TODO(rsc): if chunking happened after the buffering, // then there would be fewer chunk headers. // On the other hand, it would make hijacking more difficult. - if c.chunking { - fmt.Fprintf(c.buf, "%x\r\n", len(data)) // TODO(rsc): use strconv not fmt + if w.chunking { + fmt.Fprintf(w.conn.buf, "%x\r\n", len(data)) // TODO(rsc): use strconv not fmt } - n, err = c.buf.Write(data) - if err == nil && c.chunking { + n, err = w.conn.buf.Write(data) + if err == nil && w.chunking { if n != len(data) { err = io.ErrShortWrite } if err == nil { - io.WriteString(c.buf, "\r\n") + io.WriteString(w.conn.buf, "\r\n") } } @@ -242,25 +301,25 @@ func (c *Conn) Write(data []byte) (n int, err os.Error) { // long enough. The minimum lengths used in those // browsers are in the 256-512 range. // Pad to 1024 bytes. -func errorKludge(c *Conn, req *Request) { +func errorKludge(w *response) { const min = 1024 // Is this an error? - if kind := c.status / 100; kind != 4 && kind != 5 { + if kind := w.status / 100; kind != 4 && kind != 5 { return } // Did the handler supply any info? Enough? - if c.written == 0 || c.written >= min { + if w.written == 0 || w.written >= min { return } // Is it a broken browser? var msg string - switch agent := req.UserAgent; { - case strings.Index(agent, "MSIE") >= 0: + switch agent := w.req.UserAgent; { + case strings.Contains(agent, "MSIE"): msg = "Internet Explorer" - case strings.Index(agent, "Chrome/") >= 0: + case strings.Contains(agent, "Chrome/"): msg = "Chrome" default: return @@ -268,45 +327,54 @@ func errorKludge(c *Conn, req *Request) { msg += " would ignore this error page if this text weren't here.\n" // Is it text? ("Content-Type" is always in the map) - baseType := strings.Split(c.header["Content-Type"], ";", 2)[0] + baseType := strings.Split(w.header["Content-Type"], ";", 2)[0] switch baseType { case "text/html": - io.WriteString(c, "<!-- ") - for c.written < min { - io.WriteString(c, msg) + io.WriteString(w, "<!-- ") + for w.written < min { + io.WriteString(w, msg) } - io.WriteString(c, " -->") + io.WriteString(w, " -->") case "text/plain": - io.WriteString(c, "\n") - for c.written < min { - io.WriteString(c, msg) + io.WriteString(w, "\n") + for w.written < min { + io.WriteString(w, msg) } } } -func (c *Conn) finishRequest() { - if !c.wroteHeader { - c.WriteHeader(StatusOK) +func (w *response) finishRequest() { + // If this was an HTTP/1.0 request with keep-alive and we sent a Content-Length + // back, we can make this a keep-alive response ... + if w.req.wantsHttp10KeepAlive() { + _, sentLength := w.header["Content-Length"] + if sentLength && w.header["Connection"] == "keep-alive" { + w.closeAfterReply = false + } + } + if !w.wroteHeader { + w.WriteHeader(StatusOK) } - errorKludge(c, c.Req) - if c.chunking { - io.WriteString(c.buf, "0\r\n") + errorKludge(w) + if w.chunking { + io.WriteString(w.conn.buf, "0\r\n") // trailer key/value pairs, followed by blank line - io.WriteString(c.buf, "\r\n") + io.WriteString(w.conn.buf, "\r\n") } - c.buf.Flush() + w.conn.buf.Flush() + w.req.Body.Close() } -// Flush sends any buffered data to the client. -func (c *Conn) Flush() { - if !c.wroteHeader { - c.WriteHeader(StatusOK) +// Flush implements the ResponseWriter.Flush method. +func (w *response) Flush() { + if !w.wroteHeader { + w.WriteHeader(StatusOK) } - c.buf.Flush() + w.conn.buf.Flush() } // Close the connection. -func (c *Conn) close() { +func (c *conn) close() { if c.buf != nil { c.buf.Flush() c.buf = nil @@ -318,41 +386,39 @@ func (c *Conn) close() { } // Serve a new connection. -func (c *Conn) serve() { +func (c *conn) serve() { for { - req, err := c.readRequest() + w, err := c.readRequest() if err != nil { break } - // HTTP cannot have multiple simultaneous active requests. + // 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 goroutine. - c.handler.ServeHTTP(c, req) + // [*] Not strictly true: HTTP pipelining. We could let them all process + // in parallel even if their responses need to be serialized. + c.handler.ServeHTTP(w, w.req) if c.hijacked { return } - c.finishRequest() - if c.closeAfterReply { + w.finishRequest() + if w.closeAfterReply { break } } c.close() } -// Hijack lets the caller take over the connection. -// After a call to c.Hijack(), the HTTP server library -// will not do anything else with the connection. -// It becomes the caller's responsibility to manage -// and close the connection. -func (c *Conn) Hijack() (rwc io.ReadWriteCloser, buf *bufio.ReadWriter, err os.Error) { - if c.hijacked { +// Hijack impements the ResponseWriter.Hijack method. +func (w *response) Hijack() (rwc io.ReadWriteCloser, buf *bufio.ReadWriter, err os.Error) { + if w.conn.hijacked { return nil, nil, ErrHijacked } - c.hijacked = true - rwc = c.rwc - buf = c.buf - c.rwc = nil - c.buf = nil + w.conn.hijacked = true + rwc = w.conn.rwc + buf = w.conn.buf + w.conn.rwc = nil + w.conn.buf = nil return } @@ -360,24 +426,24 @@ func (c *Conn) Hijack() (rwc io.ReadWriteCloser, buf *bufio.ReadWriter, err os.E // ordinary functions as HTTP handlers. If f is a function // with the appropriate signature, HandlerFunc(f) is a // Handler object that calls f. -type HandlerFunc func(*Conn, *Request) +type HandlerFunc func(ResponseWriter, *Request) -// ServeHTTP calls f(c, req). -func (f HandlerFunc) ServeHTTP(c *Conn, req *Request) { - f(c, req) +// ServeHTTP calls f(w, req). +func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) } // Helper handlers // Error replies to the request with the specified error message and HTTP code. -func Error(c *Conn, error string, code int) { - c.SetHeader("Content-Type", "text/plain; charset=utf-8") - c.WriteHeader(code) - fmt.Fprintln(c, error) +func Error(w ResponseWriter, error string, code int) { + w.SetHeader("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(code) + fmt.Fprintln(w, error) } // NotFound replies to the request with an HTTP 404 not found error. -func NotFound(c *Conn, req *Request) { Error(c, "404 page not found", StatusNotFound) } +func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) } // NotFoundHandler returns a simple request handler // that replies to each request with a ``404 page not found'' reply. @@ -385,59 +451,64 @@ func NotFoundHandler() Handler { return HandlerFunc(NotFound) } // Redirect replies to the request with a redirect to url, // which may be a path relative to the request path. -func Redirect(c *Conn, url string, code int) { - // RFC2616 recommends that a short note "SHOULD" be included in the - // response because older user agents may not understand 301/307. - note := "<a href=\"%v\">" + statusText[code] + "</a>.\n" - if c.Req.Method == "POST" { - note = "" +func Redirect(w ResponseWriter, r *Request, url string, code int) { + if u, err := ParseURL(url); err == nil { + // If url was relative, make absolute by + // combining with request path. + // The browser would probably do this for us, + // but doing it ourselves is more reliable. + + // NOTE(rsc): RFC 2616 says that the Location + // line must be an absolute URI, like + // "http://www.google.com/redirect/", + // not a path like "/redirect/". + // Unfortunately, we don't know what to + // put in the host name section to get the + // client to connect to us again, so we can't + // know the right absolute URI to send back. + // Because of this problem, no one pays attention + // to the RFC; they all send back just a new path. + // So do we. + oldpath := r.URL.Path + if oldpath == "" { // should not happen, but avoid a crash if it does + oldpath = "/" + } + if u.Scheme == "" { + // no leading http://server + if url == "" || url[0] != '/' { + // make relative path absolute + olddir, _ := path.Split(oldpath) + url = olddir + url + } + + // clean up but preserve trailing slash + trailing := url[len(url)-1] == '/' + url = path.Clean(url) + if trailing && url[len(url)-1] != '/' { + url += "/" + } + } } - u, err := ParseURL(url) - if err != nil { - goto finish - } - - // If url was relative, make absolute by - // combining with request path. - // The browser would probably do this for us, - // but doing it ourselves is more reliable. - - // NOTE(rsc): RFC 2616 says that the Location - // line must be an absolute URI, like - // "http://www.google.com/redirect/", - // not a path like "/redirect/". - // Unfortunately, we don't know what to - // put in the host name section to get the - // client to connect to us again, so we can't - // know the right absolute URI to send back. - // Because of this problem, no one pays attention - // to the RFC; they all send back just a new path. - // So do we. - oldpath := c.Req.URL.Path - if oldpath == "" { // should not happen, but avoid a crash if it does - oldpath = "/" - } - if u.Scheme == "" { - // no leading http://server - if url == "" || url[0] != '/' { - // make relative path absolute - olddir, _ := path.Split(oldpath) - url = olddir + url - } + w.SetHeader("Location", url) + w.WriteHeader(code) - // clean up but preserve trailing slash - trailing := url[len(url)-1] == '/' - url = path.Clean(url) - if trailing && url[len(url)-1] != '/' { - url += "/" - } + // RFC2616 recommends that a short note "SHOULD" be included in the + // response because older user agents may not understand 301/307. + note := "<a href=\"" + htmlEscape(url) + "\">" + statusText[code] + "</a>.\n" + if r.Method == "POST" { + note = "" } + fmt.Fprintln(w, note) +} -finish: - c.SetHeader("Location", url) - c.WriteHeader(code) - fmt.Fprintf(c, note, url) +func htmlEscape(s string) string { + s = strings.Replace(s, "&", "&", -1) + s = strings.Replace(s, "<", "<", -1) + s = strings.Replace(s, ">", ">", -1) + s = strings.Replace(s, "\"", """, -1) + s = strings.Replace(s, "'", "'", -1) + return s } // Redirect to a fixed URL @@ -446,8 +517,8 @@ type redirectHandler struct { code int } -func (rh *redirectHandler) ServeHTTP(c *Conn, req *Request) { - Redirect(c, rh.url, rh.code) +func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) { + Redirect(w, r, rh.url, rh.code) } // RedirectHandler returns a request handler that redirects @@ -523,11 +594,11 @@ func cleanPath(p string) string { // ServeHTTP dispatches the request to the handler whose // pattern most closely matches the request URL. -func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) { +func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { // Clean path to canonical form and redirect. - if p := cleanPath(req.URL.Path); p != req.URL.Path { - c.SetHeader("Location", p) - c.WriteHeader(StatusMovedPermanently) + if p := cleanPath(r.URL.Path); p != r.URL.Path { + w.SetHeader("Location", p) + w.WriteHeader(StatusMovedPermanently) return } @@ -535,7 +606,7 @@ func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) { var h Handler var n = 0 for k, v := range mux.m { - if !pathMatch(k, req.URL.Path) { + if !pathMatch(k, r.URL.Path) { continue } if h == nil || len(k) > n { @@ -546,7 +617,7 @@ func (mux *ServeMux) ServeHTTP(c *Conn, req *Request) { if h == nil { h = NotFoundHandler() } - h.ServeHTTP(c, req) + h.ServeHTTP(w, r) } // Handle registers the handler for the given pattern. @@ -566,7 +637,7 @@ func (mux *ServeMux) Handle(pattern string, handler Handler) { } // HandleFunc registers the handler function for the given pattern. -func (mux *ServeMux) HandleFunc(pattern string, handler func(*Conn, *Request)) { +func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { mux.Handle(pattern, HandlerFunc(handler)) } @@ -576,7 +647,7 @@ func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, h // HandleFunc registers the handler function for the given pattern // in the DefaultServeMux. -func HandleFunc(pattern string, handler func(*Conn, *Request)) { +func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } @@ -618,8 +689,8 @@ func Serve(l net.Listener, handler Handler) os.Error { // ) // // // hello world, the web server -// func HelloServer(c *http.Conn, req *http.Request) { -// io.WriteString(c, "hello, world!\n") +// func HelloServer(w http.ResponseWriter, req *http.Request) { +// io.WriteString(w, "hello, world!\n") // } // // func main() { @@ -638,3 +709,52 @@ func ListenAndServe(addr string, handler Handler) os.Error { l.Close() return e } + +// ListenAndServeTLS acts identically to ListenAndServe, except that it +// expects HTTPS connections. Additionally, files containing a certificate and +// matching private key for the server must be provided. +// +// A trivial example server is: +// +// import ( +// "http" +// "log" +// ) +// +// func handler(w http.ResponseWriter, req *http.Request) { +// w.SetHeader("Content-Type", "text/plain") +// w.Write([]byte("This is an example server.\n")) +// } +// +// func main() { +// http.HandleFunc("/", handler) +// log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/") +// err := http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil) +// if err != nil { +// log.Exit(err) +// } +// } +// +// One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem. +func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) os.Error { + config := &tls.Config{ + Rand: rand.Reader, + Time: time.Seconds, + NextProtos: []string{"http/1.1"}, + } + + var err os.Error + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + + conn, err := net.Listen("tcp", addr) + if err != nil { + return err + } + + tlsListener := tls.NewListener(conn, config) + return Serve(tlsListener, handler) +} |