wspr: change receivers to pointers (critical bugfix); split http listen & serve
Change-Id: I5f5ab1244f331bd4332f0cb9d579fcb9214ad1c0
diff --git a/services/wsprd/wspr/wspr.go b/services/wsprd/wspr/wspr.go
index 83dd9f1..d88d27b 100644
--- a/services/wsprd/wspr/wspr.go
+++ b/services/wsprd/wspr/wspr.go
@@ -20,7 +20,7 @@
"encoding/json"
"fmt"
"io"
- "log"
+ "net"
"net/http"
_ "net/http/pprof"
"sync"
@@ -70,10 +70,12 @@
}
type WSPR struct {
- mu sync.Mutex
- tlsCert *tls.Certificate
- rt veyron2.Runtime
- httpPort int // HTTP port for WSPR to serve on. Port rather than address to discourage serving in a way that isn't local.
+ mu sync.Mutex
+ tlsCert *tls.Certificate
+ rt veyron2.Runtime
+ // HTTP port for WSPR to serve on. Note, WSPR always serves on localhost.
+ httpPort int
+ ln *net.TCPListener // HTTP listener
logger vlog.Logger
listenSpec ipc.ListenSpec
identdEP string
@@ -99,33 +101,62 @@
return &buf, nil
}
-// Starts the proxy and listens for requests. This method is blocking.
-func (ctx WSPR) Run() {
- // Initialize the Blesser service
+// Starts listening for requests and returns the network endpoint address.
+func (ctx *WSPR) Listen() net.Addr {
+ addr := fmt.Sprintf("127.0.0.1:%d", ctx.httpPort)
+ ln, err := net.Listen("tcp", addr)
+ if err != nil {
+ vlog.Fatalf("Listen failed: %s", err)
+ }
+ ctx.ln = ln.(*net.TCPListener)
+ ctx.logger.VI(1).Infof("Listening at %s", ln.Addr().String())
+ return ln.Addr()
+}
+
+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted connections.
+// It's used by ListenAndServe and ListenAndServeTLS so dead TCP connections
+// (e.g. closing laptop mid-download) eventually go away.
+// Copied from http/server.go, since it's not exported.
+type tcpKeepAliveListener struct {
+ *net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+ tc, err := ln.AcceptTCP()
+ if err != nil {
+ return
+ }
+ tc.SetKeepAlive(true)
+ tc.SetKeepAlivePeriod(3 * time.Minute)
+ return tc, nil
+}
+
+// Starts serving http requests. This method is blocking.
+func (ctx *WSPR) Serve() {
+ // Initialize the Blesser service.
ctx.blesser = &bs{client: ctx.rt.Client(), name: ctx.identdEP}
- // HTTP routes
+ // Configure HTTP routes.
http.HandleFunc("/debug", ctx.handleDebug)
http.HandleFunc("/create-account", ctx.handleCreateAccount)
http.HandleFunc("/assoc-account", ctx.handleAssocAccount)
http.HandleFunc("/ws", ctx.handleWS)
// Everything else is a 404.
- // Note: the pattern "/" matches all paths not matched by other
- // registered patterns, not just the URL with Path == "/".'
+ // Note: the pattern "/" matches all paths not matched by other registered
+ // patterns, not just the URL with Path == "/".
// (http://golang.org/pkg/net/http/#ServeMux)
http.Handle("/", http.NotFoundHandler())
- ctx.logger.VI(1).Infof("Listening at port %d.", ctx.httpPort)
- httpErr := http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", ctx.httpPort), nil)
- if httpErr != nil {
- log.Fatalf("Failed to HTTP serve: %s", httpErr)
+
+ if err := http.Serve(tcpKeepAliveListener{ctx.ln}, nil); err != nil {
+ vlog.Fatalf("Serve failed: %s", err)
}
}
-func (ctx WSPR) Shutdown() {
+func (ctx *WSPR) Shutdown() {
ctx.rt.Cleanup()
}
-func (ctx WSPR) CleanUpPipe(req *http.Request) {
+func (ctx *WSPR) CleanUpPipe(req *http.Request) {
ctx.mu.Lock()
defer ctx.mu.Unlock()
delete(ctx.pipes, req)
@@ -134,15 +165,15 @@
// Creates a new WebSocket Proxy object.
func NewWSPR(httpPort int, listenSpec ipc.ListenSpec, identdEP string, opts ...veyron2.ROpt) *WSPR {
if listenSpec.Proxy == "" {
- log.Fatalf("a veyron proxy must be set")
+ vlog.Fatalf("a veyron proxy must be set")
}
if identdEP == "" {
- log.Fatalf("an identd server must be set")
+ vlog.Fatalf("an identd server must be set")
}
newrt, err := rt.New(opts...)
if err != nil {
- log.Fatalf("rt.New failed: %s", err)
+ vlog.Fatalf("rt.New failed: %s", err)
}
wspr := &WSPR{
@@ -164,19 +195,19 @@
if wspr.useOldModel {
if wspr.idManager, err = identity.NewIDManager(newrt, &identity.InMemorySerializer{}); err != nil {
- log.Fatalf("identity.NewIDManager failed: %s", err)
+ vlog.Fatalf("identity.NewIDManager failed: %s", err)
}
}
// TODO(nlacasse, bjornick) use a serializer that can actually persist.
if wspr.principalManager, err = principal.NewPrincipalManager(newrt.Principal(), &principal.InMemorySerializer{}); err != nil {
- log.Fatalf("principal.NewPrincipalManager failed: %s", err)
+ vlog.Fatalf("principal.NewPrincipalManager failed: %s", err)
}
return wspr
}
-func (ctx WSPR) logAndSendBadReqErr(w http.ResponseWriter, msg string) {
+func (ctx *WSPR) logAndSendBadReqErr(w http.ResponseWriter, msg string) {
ctx.logger.Error(msg)
http.Error(w, msg, http.StatusBadRequest)
return
@@ -184,7 +215,7 @@
// HTTP Handlers
-func (ctx WSPR) handleDebug(w http.ResponseWriter, r *http.Request) {
+func (ctx *WSPR) handleDebug(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
w.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintf(w, "")
@@ -202,13 +233,13 @@
`))
}
-func (ctx WSPR) handleWS(w http.ResponseWriter, r *http.Request) {
+func (ctx *WSPR) handleWS(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
return
}
ctx.logger.VI(0).Info("Creating a new websocket")
- p := newPipe(w, r, &ctx, nil)
+ p := newPipe(w, r, ctx, nil)
if p == nil {
return
@@ -232,7 +263,7 @@
// which is exchanged for blessings from the veyron blessing server.
// An account based on the blessings is then added to WSPR's principal
// manager, and the set of blessing strings are returned to the client.
-func (ctx WSPR) handleCreateAccount(w http.ResponseWriter, r *http.Request) {
+func (ctx *WSPR) handleCreateAccount(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
return
@@ -298,7 +329,7 @@
}
// Handler for associating an existing principal with an origin.
-func (ctx WSPR) handleAssocAccount(w http.ResponseWriter, r *http.Request) {
+func (ctx *WSPR) handleAssocAccount(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
return
@@ -328,7 +359,7 @@
}
// TODO(ataly, ashankar): Remove this method once the old security model is killed.
-func (ctx WSPR) handleCreateAccountOldModel(blessingsAny vdlutil.Any, w http.ResponseWriter) {
+func (ctx *WSPR) handleCreateAccountOldModel(blessingsAny vdlutil.Any, w http.ResponseWriter) {
blessing, ok := blessingsAny.(security.PublicID)
if !ok {
ctx.logAndSendBadReqErr(w, "Error creating PublicID from wire data")
@@ -366,7 +397,7 @@
}
// TODO(ataly, ashankar): Remove this method once the old security model is killed.
-func (ctx WSPR) handleAssocAccountOldModel(data assocAccountInput, w http.ResponseWriter) {
+func (ctx *WSPR) handleAssocAccountOldModel(data assocAccountInput, w http.ResponseWriter) {
if err := ctx.idManager.AddOrigin(data.Origin, data.Name, nil); err != nil {
http.Error(w, fmt.Sprintf("Error associating account: %v", err), http.StatusBadRequest)
return