From: Stephen Ma Date: Wed, 29 Sep 2010 04:30:12 +0000 (+1000) Subject: http: revised http Handler interface X-Git-Tag: weekly.2010-09-29~13 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=fd9a5d22c69e877afd9daacc335806520c40c0e2;p=gostls13.git http: revised http Handler interface R=rsc CC=golang-dev https://golang.org/cl/1993043 --- diff --git a/src/cmd/godoc/codewalk.go b/src/cmd/godoc/codewalk.go index 02417bfefb..442ba7b1dd 100644 --- a/src/cmd/godoc/codewalk.go +++ b/src/cmd/godoc/codewalk.go @@ -31,26 +31,26 @@ import ( // Handler for /doc/codewalk/ and below. -func codewalk(c *http.Conn, r *http.Request) { +func codewalk(w http.ResponseWriter, r *http.Request) { relpath := r.URL.Path[len("/doc/codewalk/"):] abspath := absolutePath(r.URL.Path[1:], *goroot) r.ParseForm() if f := r.FormValue("fileprint"); f != "" { - codewalkFileprint(c, r, f) + codewalkFileprint(w, r, f) return } // If directory exists, serve list of code walks. dir, err := os.Lstat(abspath) if err == nil && dir.IsDirectory() { - codewalkDir(c, r, relpath, abspath) + codewalkDir(w, r, relpath, abspath) return } // If file exists, serve using standard file server. if err == nil { - serveFile(c, r) + serveFile(w, r) return } @@ -59,17 +59,17 @@ func codewalk(c *http.Conn, r *http.Request) { cw, err := loadCodewalk(abspath + ".xml") if err != nil { log.Stderr(err) - serveError(c, r, relpath, err) + serveError(w, r, relpath, err) return } // Canonicalize the path and redirect if changed - if redirect(c, r) { + if redirect(w, r) { return } b := applyTemplate(codewalkHTML, "codewalk", cw) - servePage(c, "Codewalk: "+cw.Title, "", "", b) + servePage(w, "Codewalk: "+cw.Title, "", "", b) } @@ -178,7 +178,7 @@ func loadCodewalk(file string) (*Codewalk, os.Error) { // codewalkDir serves the codewalk directory listing. // It scans the directory for subdirectories or files named *.xml // and prepares a table. -func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) { +func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string) { type elem struct { Name string Title string @@ -187,7 +187,7 @@ func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) { dir, err := ioutil.ReadDir(abspath) if err != nil { log.Stderr(err) - serveError(c, r, relpath, err) + serveError(w, r, relpath, err) return } var v vector.Vector @@ -204,7 +204,7 @@ func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) { } b := applyTemplate(codewalkdirHTML, "codewalkdir", v) - servePage(c, "Codewalks", "", "", b) + servePage(w, "Codewalks", "", "", b) } @@ -214,12 +214,12 @@ func codewalkDir(c *http.Conn, r *http.Request, relpath, abspath string) { // in the response. This format is used for the middle window pane // of the codewalk pages. It is a separate iframe and does not get // the usual godoc HTML wrapper. -func codewalkFileprint(c *http.Conn, r *http.Request, f string) { +func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) { abspath := absolutePath(f, *goroot) data, err := ioutil.ReadFile(abspath) if err != nil { log.Stderr(err) - serveError(c, r, f, err) + serveError(w, r, f, err) return } lo, _ := strconv.Atoi(r.FormValue("lo")) @@ -243,17 +243,17 @@ func codewalkFileprint(c *http.Conn, r *http.Request, f string) { } } - io.WriteString(c, `
`)
-	template.HTMLEscape(c, data[0:mark])
-	io.WriteString(c, "")
-	template.HTMLEscape(c, data[mark:lo])
+	io.WriteString(w, `
`)
+	template.HTMLEscape(w, data[0:mark])
+	io.WriteString(w, "")
+	template.HTMLEscape(w, data[mark:lo])
 	if lo < hi {
-		io.WriteString(c, "
") - template.HTMLEscape(c, data[lo:hi]) - io.WriteString(c, "
") + io.WriteString(w, "
") + template.HTMLEscape(w, data[lo:hi]) + io.WriteString(w, "
") } - template.HTMLEscape(c, data[hi:]) - io.WriteString(c, "
") + template.HTMLEscape(w, data[hi:]) + io.WriteString(w, "
") } diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index d85cb024e0..a101eb24c5 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -792,7 +792,7 @@ func readTemplates() { // ---------------------------------------------------------------------------- // Generic HTML wrapper -func servePage(c *http.Conn, title, subtitle, query string, content []byte) { +func servePage(w http.ResponseWriter, title, subtitle, query string, content []byte) { type Data struct { Title string Subtitle string @@ -813,15 +813,15 @@ func servePage(c *http.Conn, title, subtitle, query string, content []byte) { Content: content, } - if err := godocHTML.Execute(&d, c); err != nil { + if err := godocHTML.Execute(&d, w); err != nil { log.Stderrf("godocHTML.Execute: %s", err) } } -func serveText(c *http.Conn, text []byte) { - c.SetHeader("Content-Type", "text/plain; charset=utf-8") - c.Write(text) +func serveText(w http.ResponseWriter, text []byte) { + w.SetHeader("Content-Type", "text/plain; charset=utf-8") + w.Write(text) } @@ -844,19 +844,19 @@ func extractString(src []byte, rx *regexp.Regexp) (s string) { } -func serveHTMLDoc(c *http.Conn, r *http.Request, abspath, relpath string) { +func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) { // get HTML body contents src, err := ioutil.ReadFile(abspath) if err != nil { log.Stderrf("ioutil.ReadFile: %s", err) - serveError(c, r, relpath, err) + serveError(w, r, relpath, err) return } // if it begins with "") - servePage(c, "Text file "+relpath, "", "", buf.Bytes()) + servePage(w, "Text file "+relpath, "", "", buf.Bytes()) } -func serveDirectory(c *http.Conn, r *http.Request, abspath, relpath string) { - if redirect(c, r) { +func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath string) { + if redirect(w, r) { return } list, err := ioutil.ReadDir(abspath) if err != nil { log.Stderrf("ioutil.ReadDir: %s", err) - serveError(c, r, relpath, err) + serveError(w, r, relpath, err) return } @@ -1007,23 +1007,23 @@ func serveDirectory(c *http.Conn, r *http.Request, abspath, relpath string) { } contents := applyTemplate(dirlistHTML, "dirlistHTML", list) - servePage(c, "Directory "+relpath, "", "", contents) + servePage(w, "Directory "+relpath, "", "", contents) } -func serveFile(c *http.Conn, r *http.Request) { +func serveFile(w http.ResponseWriter, r *http.Request) { relpath := r.URL.Path[1:] // serveFile URL paths start with '/' abspath := absolutePath(relpath, *goroot) // pick off special cases and hand the rest to the standard file server switch r.URL.Path { case "/": - serveHTMLDoc(c, r, pathutil.Join(*goroot, "doc/root.html"), "doc/root.html") + serveHTMLDoc(w, r, pathutil.Join(*goroot, "doc/root.html"), "doc/root.html") return case "/doc/root.html": // hide landing page from its real name - http.Redirect(c, "/", http.StatusMovedPermanently) + http.Redirect(w, r, "/", http.StatusMovedPermanently) return } @@ -1032,42 +1032,42 @@ func serveFile(c *http.Conn, r *http.Request) { if strings.HasSuffix(abspath, "/index.html") { // We'll show index.html for the directory. // Use the dir/ version as canonical instead of dir/index.html. - http.Redirect(c, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently) + http.Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len("index.html")], http.StatusMovedPermanently) return } - serveHTMLDoc(c, r, abspath, relpath) + serveHTMLDoc(w, r, abspath, relpath) return case ".go": - serveGoSource(c, r, abspath, relpath) + serveGoSource(w, r, abspath, relpath) return } dir, err := os.Lstat(abspath) if err != nil { log.Stderr(err) - serveError(c, r, relpath, err) + serveError(w, r, relpath, err) return } if dir != nil && dir.IsDirectory() { - if redirect(c, r) { + if redirect(w, r) { return } if index := abspath + "/index.html"; isTextFile(index) { - serveHTMLDoc(c, r, index, relativePath(index)) + serveHTMLDoc(w, r, index, relativePath(index)) return } - serveDirectory(c, r, abspath, relpath) + serveDirectory(w, r, abspath, relpath) return } if isTextFile(abspath) { - serveTextFile(c, r, abspath, relpath) + serveTextFile(w, r, abspath, relpath) return } - fileServer.ServeHTTP(c, r) + fileServer.ServeHTTP(w, r) } @@ -1243,8 +1243,8 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf } -func (h *httpHandler) ServeHTTP(c *http.Conn, r *http.Request) { - if redirect(c, r) { +func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if redirect(w, r) { return } @@ -1257,13 +1257,13 @@ func (h *httpHandler) ServeHTTP(c *http.Conn, r *http.Request) { info := h.getPageInfo(abspath, relpath, r.FormValue("p"), mode) if info.Err != nil { log.Stderr(info.Err) - serveError(c, r, relpath, info.Err) + serveError(w, r, relpath, info.Err) return } if r.FormValue("f") == "text" { contents := applyTemplate(packageText, "packageText", info) - serveText(c, contents) + serveText(w, contents) return } @@ -1290,7 +1290,7 @@ func (h *httpHandler) ServeHTTP(c *http.Conn, r *http.Request) { } contents := applyTemplate(packageHTML, "packageHTML", info) - servePage(c, title, subtitle, "", contents) + servePage(w, title, subtitle, "", contents) } @@ -1319,13 +1319,13 @@ func lookup(query string) (result SearchResult) { } -func search(c *http.Conn, r *http.Request) { +func search(w http.ResponseWriter, r *http.Request) { query := strings.TrimSpace(r.FormValue("q")) result := lookup(query) if r.FormValue("f") == "text" { contents := applyTemplate(searchText, "searchText", result) - serveText(c, contents) + serveText(w, contents) return } @@ -1337,7 +1337,7 @@ func search(c *http.Conn, r *http.Request) { } contents := applyTemplate(searchHTML, "searchHTML", result) - servePage(c, title, "", query, contents) + servePage(w, title, "", query, contents) } diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index c13979968a..4a913a0172 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -64,14 +64,14 @@ var ( ) -func serveError(c *http.Conn, r *http.Request, relpath string, err os.Error) { +func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) { contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path! - c.WriteHeader(http.StatusNotFound) - servePage(c, "File "+relpath, "", "", contents) + w.WriteHeader(http.StatusNotFound) + servePage(w, "File "+relpath, "", "", contents) } -func exec(c *http.Conn, args []string) (status int) { +func exec(rw http.ResponseWriter, args []string) (status int) { r, w, err := os.Pipe() if err != nil { log.Stderrf("os.Pipe(): %v\n", err) @@ -109,18 +109,18 @@ func exec(c *http.Conn, args []string) (status int) { if *verbose { os.Stderr.Write(buf.Bytes()) } - if c != nil { - c.SetHeader("content-type", "text/plain; charset=utf-8") - c.Write(buf.Bytes()) + if rw != nil { + rw.SetHeader("content-type", "text/plain; charset=utf-8") + rw.Write(buf.Bytes()) } return } -func dosync(c *http.Conn, r *http.Request) { +func dosync(w http.ResponseWriter, r *http.Request) { args := []string{"/bin/sh", "-c", *syncCmd} - switch exec(c, args) { + switch exec(w, args) { case 0: // sync succeeded and some files have changed; // update package tree. @@ -150,9 +150,9 @@ func usage() { func loggingHandler(h http.Handler) http.Handler { - return http.HandlerFunc(func(c *http.Conn, req *http.Request) { - log.Stderrf("%s\t%s", c.RemoteAddr, req.URL) - h.ServeHTTP(c, req) + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + log.Stderrf("%s\t%s", w.RemoteAddr(), req.URL) + h.ServeHTTP(w, req) }) } diff --git a/src/pkg/expvar/expvar.go b/src/pkg/expvar/expvar.go index 4017027b77..9435481dbc 100644 --- a/src/pkg/expvar/expvar.go +++ b/src/pkg/expvar/expvar.go @@ -210,18 +210,18 @@ func Iter() <-chan KeyValue { return c } -func expvarHandler(c *http.Conn, req *http.Request) { - c.SetHeader("content-type", "application/json; charset=utf-8") - fmt.Fprintf(c, "{\n") +func expvarHandler(w http.ResponseWriter, r *http.Request) { + w.SetHeader("content-type", "application/json; charset=utf-8") + fmt.Fprintf(w, "{\n") first := true for name, value := range vars { if !first { - fmt.Fprintf(c, ",\n") + fmt.Fprintf(w, ",\n") } first = false - fmt.Fprintf(c, "%q: %s", name, value) + fmt.Fprintf(w, "%q: %s", name, value) } - fmt.Fprintf(c, "\n}\n") + fmt.Fprintf(w, "\n}\n") } func memstats() string { diff --git a/src/pkg/http/fs.go b/src/pkg/http/fs.go index db80b2f4df..b3047f1827 100644 --- a/src/pkg/http/fs.go +++ b/src/pkg/http/fs.go @@ -43,8 +43,8 @@ func isText(b []byte) bool { return true } -func dirList(c *Conn, f *os.File) { - fmt.Fprintf(c, "
\n")
+func dirList(w ResponseWriter, f *os.File) {
+	fmt.Fprintf(w, "
\n")
 	for {
 		dirs, err := f.Readdir(100)
 		if err != nil || len(dirs) == 0 {
@@ -56,25 +56,25 @@ func dirList(c *Conn, f *os.File) {
 				name += "/"
 			}
 			// TODO htmlescape
-			fmt.Fprintf(c, "%s\n", name, name)
+			fmt.Fprintf(w, "%s\n", name, name)
 		}
 	}
-	fmt.Fprintf(c, "
\n") + fmt.Fprintf(w, "
\n") } -func serveFile(c *Conn, r *Request, name string, redirect bool) { +func serveFile(w ResponseWriter, r *Request, name string, redirect bool) { const indexPage = "/index.html" // redirect .../index.html to .../ if strings.HasSuffix(r.URL.Path, indexPage) { - Redirect(c, r.URL.Path[0:len(r.URL.Path)-len(indexPage)+1], StatusMovedPermanently) + Redirect(w, r, r.URL.Path[0:len(r.URL.Path)-len(indexPage)+1], StatusMovedPermanently) return } f, err := os.Open(name, os.O_RDONLY, 0) if err != nil { // TODO expose actual error? - NotFound(c, r) + NotFound(w, r) return } defer f.Close() @@ -82,7 +82,7 @@ func serveFile(c *Conn, r *Request, name string, redirect bool) { d, err1 := f.Stat() if err1 != nil { // TODO expose actual error? - NotFound(c, r) + NotFound(w, r) return } @@ -92,22 +92,22 @@ func serveFile(c *Conn, r *Request, name string, redirect bool) { url := r.URL.Path if d.IsDirectory() { if url[len(url)-1] != '/' { - Redirect(c, url+"/", StatusMovedPermanently) + Redirect(w, r, url+"/", StatusMovedPermanently) return } } else { if url[len(url)-1] == '/' { - Redirect(c, url[0:len(url)-1], StatusMovedPermanently) + Redirect(w, r, url[0:len(url)-1], StatusMovedPermanently) return } } } if t, _ := time.Parse(TimeFormat, r.Header["If-Modified-Since"]); t != nil && d.Mtime_ns/1e9 <= t.Seconds() { - c.WriteHeader(StatusNotModified) + w.WriteHeader(StatusNotModified) return } - c.SetHeader("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat)) + w.SetHeader("Last-Modified", time.SecondsToUTC(d.Mtime_ns/1e9).Format(TimeFormat)) // use contents of index.html for directory, if present if d.IsDirectory() { @@ -125,7 +125,7 @@ func serveFile(c *Conn, r *Request, name string, redirect bool) { } if d.IsDirectory() { - dirList(c, f) + dirList(w, f) return } @@ -133,25 +133,25 @@ func serveFile(c *Conn, r *Request, name string, redirect bool) { // use extension to find content type. ext := path.Ext(name) if ctype := mime.TypeByExtension(ext); ctype != "" { - c.SetHeader("Content-Type", ctype) + w.SetHeader("Content-Type", ctype) } else { // read first chunk to decide between utf-8 text and binary var buf [1024]byte n, _ := io.ReadFull(f, buf[0:]) b := buf[0:n] if isText(b) { - c.SetHeader("Content-Type", "text-plain; charset=utf-8") + w.SetHeader("Content-Type", "text-plain; charset=utf-8") } else { - c.SetHeader("Content-Type", "application/octet-stream") // generic binary + w.SetHeader("Content-Type", "application/octet-stream") // generic binary } - c.Write(b) + w.Write(b) } - io.Copy(c, f) + io.Copy(w, f) } // ServeFile replies to the request with the contents of the named file or directory. -func ServeFile(c *Conn, r *Request, name string) { - serveFile(c, r, name, false) +func ServeFile(w ResponseWriter, r *Request, name string) { + serveFile(w, r, name, false) } type fileHandler struct { @@ -165,12 +165,12 @@ type fileHandler struct { // looking up the file name in the file system. func FileServer(root, prefix string) Handler { return &fileHandler{root, prefix} } -func (f *fileHandler) ServeHTTP(c *Conn, r *Request) { +func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) { path := r.URL.Path if !strings.HasPrefix(path, f.prefix) { - NotFound(c, r) + NotFound(w, r) return } path = path[len(f.prefix):] - serveFile(c, r, f.root+"/"+path, true) + serveFile(w, r, f.root+"/"+path, true) } diff --git a/src/pkg/http/pprof/pprof.go b/src/pkg/http/pprof/pprof.go index 38d91afbf8..f7db9aab93 100644 --- a/src/pkg/http/pprof/pprof.go +++ b/src/pkg/http/pprof/pprof.go @@ -40,28 +40,28 @@ func init() { // Cmdline responds with the running program's // command line, with arguments separated by NUL bytes. // The package initialization registers it as /debug/pprof/cmdline. -func Cmdline(c *http.Conn, r *http.Request) { - c.SetHeader("content-type", "text/plain; charset=utf-8") - fmt.Fprintf(c, strings.Join(os.Args, "\x00")) +func Cmdline(w http.ResponseWriter, r *http.Request) { + w.SetHeader("content-type", "text/plain; charset=utf-8") + fmt.Fprintf(w, strings.Join(os.Args, "\x00")) } // Heap responds with the pprof-formatted heap profile. // The package initialization registers it as /debug/pprof/heap. -func Heap(c *http.Conn, r *http.Request) { - c.SetHeader("content-type", "text/plain; charset=utf-8") - pprof.WriteHeapProfile(c) +func Heap(w http.ResponseWriter, r *http.Request) { + w.SetHeader("content-type", "text/plain; charset=utf-8") + pprof.WriteHeapProfile(w) } // Symbol looks up the program counters listed in the request, // responding with a table mapping program counters to function names. // The package initialization registers it as /debug/pprof/symbol. -func Symbol(c *http.Conn, r *http.Request) { - c.SetHeader("content-type", "text/plain; charset=utf-8") +func Symbol(w http.ResponseWriter, r *http.Request) { + w.SetHeader("content-type", "text/plain; charset=utf-8") // We don't know how many symbols we have, but we // do have symbol information. Pprof only cares whether // this number is 0 (no symbols available) or > 0. - fmt.Fprintf(c, "num_symbols: 1\n") + fmt.Fprintf(w, "num_symbols: 1\n") var b *bufio.Reader if r.Method == "POST" { @@ -71,15 +71,15 @@ func Symbol(c *http.Conn, r *http.Request) { } for { - w, err := b.ReadSlice('+') + word, err := b.ReadSlice('+') if err == nil { - w = w[0 : len(w)-1] // trim + + word = word[0 : len(word)-1] // trim + } - pc, _ := strconv.Btoui64(string(w), 0) + pc, _ := strconv.Btoui64(string(word), 0) if pc != 0 { f := runtime.FuncForPC(uintptr(pc)) if f != nil { - fmt.Fprintf(c, "%#x %s\n", pc, f.Name()) + fmt.Fprintf(w, "%#x %s\n", pc, f.Name()) } } diff --git a/src/pkg/http/server.go b/src/pkg/http/server.go index 99d5f6b517..4404dda978 100644 --- a/src/pkg/http/server.go +++ b/src/pkg/http/server.go @@ -37,32 +37,78 @@ 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) -} - -// 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 - - rwc io.ReadWriteCloser // i/o connection - buf *bufio.ReadWriter // buffered rwc - handler Handler // request handler - hijacked bool // connection has been hijacked by handler - - // state for the current reply + ServeHTTP(ResponseWriter, *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) +} + +// 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 +} + +// 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 - usingTLS bool // a flag indicating connection over TLS // close connection after this reply. set on request and // updated after response from handler if there's a @@ -72,11 +118,9 @@ type Conn struct { } // 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) @@ -89,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) } @@ -115,92 +157,80 @@ func (ecr *expectContinueReader) Close() os.Error { 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") - c.SetHeader("Date", time.UTC().Format(TimeFormat)) + 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. // Will be overridden if the HTTP handler ends up // writing a Content-Length and the client requested // "Connection: keep-alive" - c.closeAfterReply = true - c.chunking = false + w.closeAfterReply = true } - return req, nil + return w, nil } -// UsingTLS returns true if the connection uses transport layer security (TLS). -func (c *Conn) UsingTLS() bool { - return c.usingTLS +// UsingTLS implements the ResponseWriter.UsingTLS +func (w *response) UsingTLS() bool { + return w.conn.usingTLS } -// 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") +// 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.Stderr("http: response.WriteHeader on hijacked connection") return } - if c.wroteHeader { - log.Stderr("http: multiple Conn.WriteHeader calls") + if w.wroteHeader { + log.Stderr("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 } - 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) @@ -208,57 +238,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.Stderr("http: response.Write on hijacked connection") return 0, ErrHijacked } - if !c.wroteHeader { - if c.Req.wantsHttp10KeepAlive() { - _, hasLength := c.header["Content-Length"] + if !w.wroteHeader { + if w.req.wantsHttp10KeepAlive() { + _, hasLength := w.header["Content-Length"] if hasLength { - _, connectionHeaderSet := c.header["Connection"] + _, connectionHeaderSet := w.header["Connection"] if !connectionHeaderSet { - c.header["Connection"] = "keep-alive" + w.header["Connection"] = "keep-alive" } } } - c.WriteHeader(StatusOK) + 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") } } @@ -272,22 +300,22 @@ 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; { + switch agent := w.req.UserAgent; { case strings.Index(agent, "MSIE") >= 0: msg = "Internet Explorer" case strings.Index(agent, "Chrome/") >= 0: @@ -298,53 +326,53 @@ 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, "") + 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() { +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 c.Req.wantsHttp10KeepAlive() { - _, sentLength := c.header["Content-Length"] - if sentLength && c.header["Connection"] == "keep-alive" { - c.closeAfterReply = false + if w.req.wantsHttp10KeepAlive() { + _, sentLength := w.header["Content-Length"] + if sentLength && w.header["Connection"] == "keep-alive" { + w.closeAfterReply = false } } - if !c.wroteHeader { - c.WriteHeader(StatusOK) + 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() } -// 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 @@ -356,9 +384,9 @@ 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 } @@ -367,32 +395,28 @@ func (c *Conn) serve() { // so we might as well run the handler in this goroutine. // [*] Not strictly true: HTTP pipelining. We could let them all process // in parallel even if their responses need to be serialized. - c.handler.ServeHTTP(c, req) + 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 } @@ -400,24 +424,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) +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. @@ -425,11 +449,11 @@ 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) { +func Redirect(w ResponseWriter, r *Request, 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 := "" + statusText[code] + ".\n" - if c.Req.Method == "POST" { + if r.Method == "POST" { note = "" } @@ -454,7 +478,7 @@ func Redirect(c *Conn, url string, code int) { // 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 + oldpath := r.URL.Path if oldpath == "" { // should not happen, but avoid a crash if it does oldpath = "/" } @@ -475,9 +499,9 @@ func Redirect(c *Conn, url string, code int) { } finish: - c.SetHeader("Location", url) - c.WriteHeader(code) - fmt.Fprintf(c, note, url) + w.SetHeader("Location", url) + w.WriteHeader(code) + fmt.Fprintf(w, note, url) } // Redirect to a fixed URL @@ -486,8 +510,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 @@ -563,11 +587,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 } @@ -575,7 +599,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 { @@ -586,7 +610,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. @@ -606,7 +630,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)) } @@ -616,7 +640,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) } @@ -658,8 +682,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() { @@ -690,9 +714,9 @@ func ListenAndServe(addr string, handler Handler) os.Error { // "log" // ) // -// func handler(conn *http.Conn, req *http.Request) { -// conn.SetHeader("Content-Type", "text/plain") -// conn.Write([]byte("This is an example server.\n")) +// 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() { diff --git a/src/pkg/http/status.go b/src/pkg/http/status.go index 82a66d7ad7..b6e2d65c6a 100644 --- a/src/pkg/http/status.go +++ b/src/pkg/http/status.go @@ -98,3 +98,9 @@ var statusText = map[int]string{ StatusGatewayTimeout: "Gateway Timeout", StatusHTTPVersionNotSupported: "HTTP Version Not Supported", } + +// StatusText returns a text for the HTTP status code. It returns the empty +// string if the code is unknown. +func StatusText(code int) string { + return statusText[code] +} diff --git a/src/pkg/rpc/debug.go b/src/pkg/rpc/debug.go index 638584f497..e1e53f0905 100644 --- a/src/pkg/rpc/debug.go +++ b/src/pkg/rpc/debug.go @@ -62,7 +62,7 @@ func (m methodArray) Less(i, j int) bool { return m[i].name < m[j].name } func (m methodArray) Swap(i, j int) { m[i], m[j] = m[j], m[i] } // Runs at /debug/rpc -func debugHTTP(c *http.Conn, req *http.Request) { +func debugHTTP(w http.ResponseWriter, req *http.Request) { // Build a sorted version of the data. var services = make(serviceArray, len(server.serviceMap)) i := 0 @@ -79,8 +79,8 @@ func debugHTTP(c *http.Conn, req *http.Request) { } server.Unlock() sort.Sort(services) - err := debug.Execute(services, c) + err := debug.Execute(services, w) if err != nil { - fmt.Fprintln(c, "rpc: error executing template:", err.String()) + fmt.Fprintln(w, "rpc: error executing template:", err.String()) } } diff --git a/src/pkg/rpc/server.go b/src/pkg/rpc/server.go index 91b88e23de..db46e35db7 100644 --- a/src/pkg/rpc/server.go +++ b/src/pkg/rpc/server.go @@ -445,16 +445,16 @@ var rpcPath string = "/_goRPC_" var debugPath string = "/debug/rpc" var connected = "200 Connected to Go RPC" -func serveHTTP(c *http.Conn, req *http.Request) { +func serveHTTP(w http.ResponseWriter, req *http.Request) { if req.Method != "CONNECT" { - c.SetHeader("Content-Type", "text/plain; charset=utf-8") - c.WriteHeader(http.StatusMethodNotAllowed) - io.WriteString(c, "405 must CONNECT to "+rpcPath+"\n") + w.SetHeader("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusMethodNotAllowed) + io.WriteString(w, "405 must CONNECT to "+rpcPath+"\n") return } - conn, _, err := c.Hijack() + conn, _, err := w.Hijack() if err != nil { - log.Stderr("rpc hijacking ", c.RemoteAddr, ": ", err.String()) + log.Stderr("rpc hijacking ", w.RemoteAddr(), ": ", err.String()) return } io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n") diff --git a/src/pkg/websocket/server.go b/src/pkg/websocket/server.go index b884884fa5..dd797f24e0 100644 --- a/src/pkg/websocket/server.go +++ b/src/pkg/websocket/server.go @@ -57,8 +57,8 @@ func getKeyNumber(s string) (r uint32) { } // ServeHTTP implements the http.Handler interface for a Web Socket -func (f Handler) ServeHTTP(c *http.Conn, req *http.Request) { - rwc, buf, err := c.Hijack() +func (f Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + rwc, buf, err := w.Hijack() if err != nil { panic("Hijack failed: " + err.String()) return @@ -98,7 +98,7 @@ func (f Handler) ServeHTTP(c *http.Conn, req *http.Request) { } var location string - if c.UsingTLS() { + if w.UsingTLS() { location = "wss://" + req.Host + req.URL.RawPath } else { location = "ws://" + req.Host + req.URL.RawPath @@ -161,30 +161,30 @@ Draft75Handler is an interface to a WebSocket based on the type Draft75Handler func(*Conn) // ServeHTTP implements the http.Handler interface for a Web Socket. -func (f Draft75Handler) ServeHTTP(c *http.Conn, req *http.Request) { +func (f Draft75Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if req.Method != "GET" || req.Proto != "HTTP/1.1" { - c.WriteHeader(http.StatusBadRequest) - io.WriteString(c, "Unexpected request") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "Unexpected request") return } if req.Header["Upgrade"] != "WebSocket" { - c.WriteHeader(http.StatusBadRequest) - io.WriteString(c, "missing Upgrade: WebSocket header") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing Upgrade: WebSocket header") return } if req.Header["Connection"] != "Upgrade" { - c.WriteHeader(http.StatusBadRequest) - io.WriteString(c, "missing Connection: Upgrade header") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing Connection: Upgrade header") return } origin, found := req.Header["Origin"] if !found { - c.WriteHeader(http.StatusBadRequest) - io.WriteString(c, "missing Origin header") + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing Origin header") return } - rwc, buf, err := c.Hijack() + rwc, buf, err := w.Hijack() if err != nil { panic("Hijack failed: " + err.String()) return @@ -192,7 +192,7 @@ func (f Draft75Handler) ServeHTTP(c *http.Conn, req *http.Request) { defer rwc.Close() var location string - if c.UsingTLS() { + if w.UsingTLS() { location = "wss://" + req.Host + req.URL.RawPath } else { location = "ws://" + req.Host + req.URL.RawPath