blob: 46221369f0e62e855c4c1f0ed5b0d5eb96895d2d [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Todd Wang8c4e5cc2015-04-09 11:30:52 -07005// Package wsprlib implements utilities for the wspr web socket proxy, which
6// converts between the Vanadium RPC protocol and a custom web socket based
7// protocol.
Todd Wang5b77a342015-04-06 18:31:37 -07008package wsprlib
Jiri Simsa78b646f2014-10-08 10:23:05 -07009
10import (
11 "bytes"
12 "crypto/tls"
Jiri Simsa78b646f2014-10-08 10:23:05 -070013 "fmt"
14 "io"
Adam Sadovskydf33b672014-10-27 15:50:22 -070015 "net"
Jiri Simsa78b646f2014-10-08 10:23:05 -070016 "net/http"
Jiri Simsa78b646f2014-10-08 10:23:05 -070017 "sync"
18 "time"
19
Jiri Simsa1f1302c2015-02-23 16:18:34 -080020 "v.io/v23"
21 "v.io/v23/context"
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070022 "v.io/v23/rpc"
Jiri Simsa78b646f2014-10-08 10:23:05 -070023
Todd Wang5b77a342015-04-06 18:31:37 -070024 "v.io/x/ref/services/wspr/internal/account"
25 "v.io/x/ref/services/wspr/internal/principal"
Jiri Simsa78b646f2014-10-08 10:23:05 -070026)
27
28const (
29 pingInterval = 50 * time.Second // how often the server pings the client.
30 pongTimeout = pingInterval + 10*time.Second // maximum wait for pong.
31)
32
Jiri Simsa78b646f2014-10-08 10:23:05 -070033type WSPR struct {
Adam Sadovskydf33b672014-10-27 15:50:22 -070034 mu sync.Mutex
35 tlsCert *tls.Certificate
Matt Rosencrantzc90eb7b2015-01-09 08:32:01 -080036 ctx *context.T
Adam Sadovskydf33b672014-10-27 15:50:22 -070037 // HTTP port for WSPR to serve on. Note, WSPR always serves on localhost.
38 httpPort int
39 ln *net.TCPListener // HTTP listener
Matt Rosencrantz94502cf2015-03-18 09:43:44 -070040 listenSpec *rpc.ListenSpec
Benjamin Prosnitz3c738502014-11-04 14:51:38 -080041 namespaceRoots []string
Ankur78bc4182014-10-13 17:34:27 -070042 principalManager *principal.PrincipalManager
Benjamin Prosnitz10fab582014-11-11 13:28:15 -080043 accountManager *account.AccountManager
Ankur78bc4182014-10-13 17:34:27 -070044 pipes map[*http.Request]*pipe
Jiri Simsa78b646f2014-10-08 10:23:05 -070045}
46
Jiri Simsa78b646f2014-10-08 10:23:05 -070047func readFromRequest(r *http.Request) (*bytes.Buffer, error) {
48 var buf bytes.Buffer
49 if readBytes, err := io.Copy(&buf, r.Body); err != nil {
50 return nil, fmt.Errorf("error copying message out of request: %v", err)
51 } else if wantBytes := r.ContentLength; readBytes != wantBytes {
52 return nil, fmt.Errorf("read %d bytes, wanted %d", readBytes, wantBytes)
53 }
54 return &buf, nil
55}
56
Adam Sadovskydf33b672014-10-27 15:50:22 -070057// Starts listening for requests and returns the network endpoint address.
Suharsh Sivakumar94d00662015-01-21 14:31:30 -080058func (wspr *WSPR) Listen() net.Addr {
59 addr := fmt.Sprintf("127.0.0.1:%d", wspr.httpPort)
Adam Sadovskydf33b672014-10-27 15:50:22 -070060 ln, err := net.Listen("tcp", addr)
61 if err != nil {
Cosmos Nicolaoud9229922015-06-24 14:12:24 -070062 wspr.ctx.Fatalf("Listen failed: %s", err)
Adam Sadovskydf33b672014-10-27 15:50:22 -070063 }
Suharsh Sivakumar94d00662015-01-21 14:31:30 -080064 wspr.ln = ln.(*net.TCPListener)
Cosmos Nicolaoud9229922015-06-24 14:12:24 -070065 wspr.ctx.VI(1).Infof("Listening at %s", ln.Addr().String())
Adam Sadovskydf33b672014-10-27 15:50:22 -070066 return ln.Addr()
67}
68
69// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted connections.
70// It's used by ListenAndServe and ListenAndServeTLS so dead TCP connections
71// (e.g. closing laptop mid-download) eventually go away.
72// Copied from http/server.go, since it's not exported.
73type tcpKeepAliveListener struct {
74 *net.TCPListener
75}
76
77func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
78 tc, err := ln.AcceptTCP()
79 if err != nil {
80 return
81 }
82 tc.SetKeepAlive(true)
83 tc.SetKeepAlivePeriod(3 * time.Minute)
84 return tc, nil
85}
86
87// Starts serving http requests. This method is blocking.
Suharsh Sivakumar94d00662015-01-21 14:31:30 -080088func (wspr *WSPR) Serve() {
Adam Sadovskydf33b672014-10-27 15:50:22 -070089 // Configure HTTP routes.
Suharsh Sivakumar94d00662015-01-21 14:31:30 -080090 http.HandleFunc("/ws", wspr.handleWS)
Jiri Simsa78b646f2014-10-08 10:23:05 -070091 // Everything else is a 404.
Adam Sadovskydf33b672014-10-27 15:50:22 -070092 // Note: the pattern "/" matches all paths not matched by other registered
93 // patterns, not just the URL with Path == "/".
Jiri Simsa78b646f2014-10-08 10:23:05 -070094 // (http://golang.org/pkg/net/http/#ServeMux)
95 http.Handle("/", http.NotFoundHandler())
Adam Sadovskydf33b672014-10-27 15:50:22 -070096
Suharsh Sivakumar94d00662015-01-21 14:31:30 -080097 if err := http.Serve(tcpKeepAliveListener{wspr.ln}, nil); err != nil {
Cosmos Nicolaoud9229922015-06-24 14:12:24 -070098 wspr.ctx.Fatalf("Serve failed: %s", err)
Jiri Simsa78b646f2014-10-08 10:23:05 -070099 }
100}
101
Suharsh Sivakumar94d00662015-01-21 14:31:30 -0800102func (wspr *WSPR) Shutdown() {
Ankur7a477012014-12-09 10:29:29 -0800103 // TODO(ataly, bprosnitz): Get rid of this method if possible.
Jiri Simsa78b646f2014-10-08 10:23:05 -0700104}
105
Suharsh Sivakumar94d00662015-01-21 14:31:30 -0800106func (wspr *WSPR) CleanUpPipe(req *http.Request) {
107 wspr.mu.Lock()
108 defer wspr.mu.Unlock()
109 delete(wspr.pipes, req)
Jiri Simsa78b646f2014-10-08 10:23:05 -0700110}
111
112// Creates a new WebSocket Proxy object.
Matt Rosencrantz94502cf2015-03-18 09:43:44 -0700113func NewWSPR(ctx *context.T, httpPort int, listenSpec *rpc.ListenSpec, identdEP string, namespaceRoots []string) *WSPR {
Jiri Simsa78b646f2014-10-08 10:23:05 -0700114 if listenSpec.Proxy == "" {
Cosmos Nicolaoud9229922015-06-24 14:12:24 -0700115 ctx.Fatalf("a vanadium proxy must be set")
Jiri Simsa78b646f2014-10-08 10:23:05 -0700116 }
Jiri Simsa78b646f2014-10-08 10:23:05 -0700117
Ankur78bc4182014-10-13 17:34:27 -0700118 wspr := &WSPR{
Suharsh Sivakumar94d00662015-01-21 14:31:30 -0800119 ctx: ctx,
Benjamin Prosnitz3c738502014-11-04 14:51:38 -0800120 httpPort: httpPort,
121 listenSpec: listenSpec,
Benjamin Prosnitz3c738502014-11-04 14:51:38 -0800122 namespaceRoots: namespaceRoots,
Benjamin Prosnitz3c738502014-11-04 14:51:38 -0800123 pipes: map[*http.Request]*pipe{},
Ankur78bc4182014-10-13 17:34:27 -0700124 }
125
Jiri Simsa1f1302c2015-02-23 16:18:34 -0800126 p := v23.GetPrincipal(ctx)
Ankur7a477012014-12-09 10:29:29 -0800127 var err error
Nicolas Lacasse83aa8552015-07-06 11:51:11 -0700128 // TODO(nlacasse): Use a serializer that can actually persist, as we do in browspr.
129 if wspr.principalManager, err = principal.NewPrincipalManager(p, principal.NewInMemorySerializer()); err != nil {
Cosmos Nicolaoud9229922015-06-24 14:12:24 -0700130 ctx.Fatalf("principal.NewPrincipalManager failed: %s", err)
Ankur78bc4182014-10-13 17:34:27 -0700131 }
132
Matt Rosencrantzc90eb7b2015-01-09 08:32:01 -0800133 wspr.accountManager = account.NewAccountManager(identdEP, wspr.principalManager)
Benjamin Prosnitz10fab582014-11-11 13:28:15 -0800134
Ankur78bc4182014-10-13 17:34:27 -0700135 return wspr
136}
137
Suharsh Sivakumar94d00662015-01-21 14:31:30 -0800138func (wspr *WSPR) logAndSendBadReqErr(w http.ResponseWriter, msg string) {
Cosmos Nicolaoud9229922015-06-24 14:12:24 -0700139 wspr.ctx.Error(msg)
Ankur78bc4182014-10-13 17:34:27 -0700140 http.Error(w, msg, http.StatusBadRequest)
141 return
Jiri Simsa78b646f2014-10-08 10:23:05 -0700142}
143
144// HTTP Handlers
145
Suharsh Sivakumar94d00662015-01-21 14:31:30 -0800146func (wspr *WSPR) handleWS(w http.ResponseWriter, r *http.Request) {
Jiri Simsa78b646f2014-10-08 10:23:05 -0700147 if r.Method != "GET" {
148 http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
149 return
150 }
Cosmos Nicolaoud9229922015-06-24 14:12:24 -0700151 wspr.ctx.VI(0).Info("Creating a new websocket")
Suharsh Sivakumar94d00662015-01-21 14:31:30 -0800152 p := newPipe(w, r, wspr, nil)
Jiri Simsa78b646f2014-10-08 10:23:05 -0700153
154 if p == nil {
155 return
156 }
Suharsh Sivakumar94d00662015-01-21 14:31:30 -0800157 wspr.mu.Lock()
158 defer wspr.mu.Unlock()
159 wspr.pipes[r] = p
Jiri Simsa78b646f2014-10-08 10:23:05 -0700160}