func (t caseTest) TestHelo() error { deviceId, err := id.Generate() if err != nil { return fmt.Errorf("On test %v, error generating device ID: %#v", t.CaseTestType, err) } addExistsHook(deviceId, true) defer removeExistsHook(deviceId) channelId, err := id.Generate() if err != nil { return fmt.Errorf("On test %v, error generating channel ID: %#v", t.CaseTestType, err) } origin, err := testServer.Origin() if err != nil { return fmt.Errorf("On test %v, error initializing test server: %#v", t.CaseTestType, err) } conn, err := client.DialOrigin(origin) if err != nil { return fmt.Errorf("On test %v, error dialing origin: %#v", t.CaseTestType, err) } defer conn.Close() defer conn.Purge() request := CaseHelo{t.CaseTestType, client.NewHelo(deviceId, []string{channelId}).(client.ClientHelo)} reply, err := conn.WriteRequest(request) if t.statusCode >= 200 && t.statusCode < 300 { if err != nil { return fmt.Errorf("On test %v, error writing handshake request: %#v", t.CaseTestType, err) } helo, ok := reply.(client.ServerHelo) if !ok { return fmt.Errorf("On test %v, type assertion failed for handshake reply: %#v", t.CaseTestType, reply) } if helo.StatusCode != t.statusCode { return fmt.Errorf("On test %v, unexpected reply status: got %#v; want %#v", t.CaseTestType, helo.StatusCode, t.statusCode) } if t.shouldReset { if helo.DeviceId == deviceId { return fmt.Errorf("On test %v, want new device ID; got %#v", t.CaseTestType, deviceId) } return nil } if helo.DeviceId != deviceId { return fmt.Errorf("On test %v, mismatched device ID: got %#v; want %#v", t.CaseTestType, helo.DeviceId, deviceId) } return nil } if err != io.EOF { return fmt.Errorf("On test %v, error writing handshake: got %#v; want io.EOF", t.CaseTestType, err) } err = conn.Close() clientErr, ok := err.(client.Error) if !ok { return fmt.Errorf("On test %v, type assertion failed for close error: %#v", t.CaseTestType, err) } if clientErr.Status() != t.statusCode { return fmt.Errorf("On test %v, unexpected close error status: got %#v; want %#v", t.CaseTestType, clientErr.Status(), t.statusCode) } return nil }
func reconnect(origin, deviceId, channelId, endpoint string) (err error) { socket, err := ws.Dial(origin, "", origin) if err != nil { return fmt.Errorf("Error dialing origin: %s", err) } connId, err := id.Generate() if err != nil { return fmt.Errorf("Error generating connection ID: %#v", err) } conn := client.NewConn(socket, connId, true) defer conn.Close() defer conn.Purge() actualId, err := conn.WriteHelo(deviceId, channelId) if err != nil { return fmt.Errorf("Error writing handshake request: %s", err) } if actualId != deviceId { return fmt.Errorf("Mismatched device IDs: got %q; want %q", actualId, deviceId) } if err = roundTrip(conn, deviceId, channelId, endpoint, 2); err != nil { return fmt.Errorf("Error sending notification after reconnect: %s", err) } return nil }
func TestPrematureUnregister(t *testing.T) { channelId, err := id.Generate() if err != nil { t.Fatalf("Error generating channel ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } conn, err := client.DialOrigin(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() conn.RegisterDecoder("unregister", client.DecoderFunc(decodeUnregisterReply)) request := client.NewUnregister(channelId, true) _, err = conn.WriteRequest(request) if err != io.EOF { t.Fatalf("Error writing deregistration request: got %#v; want io.EOF", err) } err = conn.Close() clientErr, ok := err.(client.Error) if !ok { t.Fatalf("Type assertion failed for close error: %#v", err) } if clientErr.Status() != 401 { t.Errorf("Unexpected close error status: got %#v; want 401", clientErr.Status()) } }
func TestMultipleRegister(t *testing.T) { channelId, err := id.Generate() if err != nil { t.Fatalf("Error generating channel ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } conn, _, err := client.Dial(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() request := MultipleRegister{client.NewRegister(channelId).(client.ClientRegister)} _, err = conn.WriteRequest(request) if err != io.EOF { t.Fatalf("Error writing registration request: got %#v; want io.EOF", err) } err = conn.Close() clientErr, ok := err.(client.Error) if !ok { t.Fatalf("Type assertion failed for close error: %#v", err) } if clientErr.Status() != 401 { t.Errorf("Unexpected close error status: got %#v; want 401", clientErr.Status()) } }
// Status determines whether etcd can respond to requests. Implements // Locator.Status(). func (l *EtcdLocator) Status() (ok bool, err error) { fakeID, err := id.Generate() if err != nil { return false, err } key, expected := "status_"+fakeID, "test" if _, err = l.client.Set(key, expected, uint64(6*time.Second)); err != nil { if l.logger.ShouldLog(ERROR) { l.logger.Error("etcd", "Error storing health check key", LogFields{"error": err.Error(), "key": key}) } return false, err } resp, err := l.client.Get(key, false, false) if err != nil { if l.logger.ShouldLog(ERROR) { l.logger.Error("etcd", "Error fetching health check key", LogFields{"error": err.Error(), "key": key}) } return false, err } if resp.Node.Value != expected { if l.logger.ShouldLog(ERROR) { l.logger.Error("etcd", "Unexpected health check result", LogFields{ "key": key, "expected": expected, "actual": resp.Node.Value}) } return false, ErrEtcdStatus } l.client.Delete(key, false) return true, nil }
func TestMultiRegister(t *testing.T) { channelId, err := id.Generate() if err != nil { t.Fatalf("Error generating channel ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } conn, _, err := client.Dial(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() endpoint, err := conn.Register(channelId) if err != nil { t.Fatalf("Error writing registration request: %#v", err) } if !isValidEndpoint(endpoint) { t.Errorf("Invalid push endpoint: %#v", endpoint) } _, err = conn.Register("") if err != io.EOF { t.Fatalf("Error writing malformed registration request: got %#v; want io.EOF", err) } err = conn.Close() if clientErr, ok := err.(client.Error); ok && clientErr.Status() != 401 { t.Errorf("Unexpected close error status: got %#v; want 401", clientErr.Status()) } else if !ok { t.Fatalf("Type assertion failed for close error: %#v", err) } }
func Dial(origin string) (conn *Conn, deviceId string, err error) { if deviceId, err = id.Generate(); err != nil { return nil, "", err } if conn, err = DialId(origin, &deviceId); err != nil { return nil, "", err } return conn, deviceId, nil }
func (t idTest) TestHelo() error { deviceId, err := id.Generate() if err != nil { return fmt.Errorf("On handshake test %v, error generating device ID: %#v", t.name, err) } addExistsHook(deviceId, true) defer removeExistsHook(deviceId) origin, err := testServer.Origin() if err != nil { return fmt.Errorf("On handshake test %v, error initializing test server: %#v", t.name, err) } conn, err := client.DialOrigin(origin) if err != nil { return fmt.Errorf("On handshake test %v, error dialing origin: %#v", t.name, err) } defer conn.Close() defer conn.Purge() request := CustomHelo{ MessageType: "hello", DeviceId: deviceId, ChannelIds: []interface{}{t.channelId}, replies: make(chan client.Reply), errors: make(chan error), } reply, err := conn.WriteRequest(request) if t.statusCode >= 200 && t.statusCode < 300 { if err != nil { return fmt.Errorf("On handshake test %v, error writing request: %#v", t.name, err) } helo, ok := reply.(client.ServerHelo) if !ok { return fmt.Errorf("On handshake test %v, type assertion failed for reply: %#v", t.name, reply) } if helo.StatusCode != 200 { return fmt.Errorf("On handshake test %v, unexpected status code: got %#v; want 200", t.name, helo.StatusCode) } // The Simple Push server requires the channelIDs field to be present in // the handshake, but does not validate its contents, since any queued // messages will be immediately flushed to the client. if helo.DeviceId != deviceId { return fmt.Errorf("On handshake test %v, mismatched device ID: got %#v; want %#v", t.name, helo.DeviceId, deviceId) } return nil } if err != io.EOF { return fmt.Errorf("On handshake test %v, error writing request: got %#v; want io.EOF", t.name, err) } err = conn.Close() clientErr, ok := err.(client.Error) if !ok { return fmt.Errorf("On handshake test %v, type assertion failed for close error: %#v", t.name, err) } if clientErr.Status() != t.statusCode { return fmt.Errorf("On handshake test %v, unexpected close error status: got %#v; want %#v", t.name, clientErr.Status(), t.statusCode) } return nil }
// Subscribe subscribes a client to a new channel. func (c *Conn) Subscribe() (channelId, endpoint string, err error) { if channelId, err = id.Generate(); err != nil { return "", "", err } if endpoint, err = c.Register(channelId); err != nil { return "", "", err } return }
func DialOrigin(origin string) (*Conn, error) { socket, err := ws.Dial(origin, "", origin) if err != nil { return nil, err } id, err := id.Generate() if err != nil { return nil, err } return NewConn(socket, id, false), nil }
func TestMessageTypes(t *testing.T) { longId, err := generateIdSize(64000) if err != nil { t.Fatalf("Error generating longId: %#v", err) } existingId, err := id.Generate() if err != nil { t.Fatalf("Error generating existingId: %#v", err) } addExistsHook(existingId, true) defer removeExistsHook(existingId) missingId, err := id.Generate() if err != nil { t.Fatalf("Error generating missingId: %#v", err) } addExistsHook(missingId, false) defer removeExistsHook(missingId) specialTypes := []typeTest{ {name: "long device ID", messageType: "hello", deviceId: longId, statusCode: 200, shouldReset: true}, {name: "long message type", messageType: longId, deviceId: validId, statusCode: 401}, {name: "existing device ID with channels", messageType: "hello", deviceId: existingId, statusCode: 200, shouldReset: false}, // Sending channel IDs with an unknown device ID should return a new device ID. {name: "unknown device ID with channels", messageType: "hello", deviceId: missingId, statusCode: 200, shouldReset: true}, } for _, test := range specialTypes { if err := test.Run(); err != nil { t.Error(err) } } for _, test := range typeTests { if err := test.Run(); err != nil { t.Error(err) } } }
func TestMessageTypes(t *testing.T) { longId, err := generateIdSize(64000) if err != nil { t.Fatalf("Error generating longId: %#v", err) } existingId, err := id.Generate() if err != nil { t.Fatalf("Error generating existingId: %#v", err) } addExistsHook(existingId, true) defer removeExistsHook(existingId) missingId, err := id.Generate() if err != nil { t.Fatalf("Error generating missingId: %#v", err) } addExistsHook(missingId, false) defer removeExistsHook(missingId) specialTypes := []typeTest{ {"long device ID", "hello", longId, 503, true}, {"long message type", longId, validId, 401, true}, {"existing device ID with channels", "hello", existingId, 200, false}, // Sending channel IDs with an unknown device ID should return a new device ID. {"unknown device ID with channels", "hello", missingId, 200, true}, } for _, test := range specialTypes { if err := test.Run(); err != nil { t.Error(err) } } for _, test := range typeTests { if err := test.Run(); err != nil { t.Error(err) } } }
func TestDuplicateRegisterHandshake(t *testing.T) { deviceId, err := id.Generate() if err != nil { t.Fatalf("Error generating device ID: %#v", err) } addExistsHook(deviceId, true) defer removeExistsHook(deviceId) channelId, err := id.Generate() if err != nil { t.Fatalf("Error generating channel ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } conn, err := client.DialOrigin(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() actualId, err := conn.WriteHelo(deviceId, channelId) if err != nil { t.Fatalf("Error writing handshake request: %#v", err) } if actualId != deviceId { t.Errorf("Mismatched device ID: got %#v; want %#v", actualId, deviceId) } if !AllowDupes { return } endpoint, err := conn.Register(channelId) if err != nil { t.Fatalf("Error writing duplicate registration request: %#v", err) } if !isValidEndpoint(endpoint) { t.Errorf("Invalid push endpoint for channel %#v: %#v", channelId, endpoint) } }
func TestPrematureACK(t *testing.T) { channelId, err := id.Generate() if err != nil { t.Fatalf("Error generating channel ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } conn, err := client.DialOrigin(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() conn.RegisterDecoder("ack", client.DecoderFunc(decodeServerInvalidACK)) updates := []client.Update{ client.Update{ ChannelId: channelId, Version: time.Now().UTC().Unix(), }, } request := ClientInvalidACK{client.NewACK(updates, true)} reply, err := conn.WriteRequest(request) if err != nil { t.Fatalf("Error writing acknowledgement: %#v", err) } if reply.Status() != 401 { t.Errorf("Incorrect status code: got %#v, wanted 401", reply.Status()) } if r, ok := reply.(ServerInvalidACK); ok { if len(r.Updates) != len(updates) { t.Errorf("Incorrect update count: got %#v; want %#v", len(r.Updates), len(updates)) } else { for index, update := range r.Updates { if update != updates[index] { t.Errorf("On update %#v, got %#v; want %#v", index, update, updates[index]) } } } } else { t.Errorf("Type assertion failed for reply: %#v", reply) } // The connection should be closed by the push server after sending // the error response. if err = conn.Close(); err != io.EOF { t.Fatalf("Unexpected close error: got %#v; want io.EOF", err) } }
// ServeHTTP implements http.Handler.ServeHTTP. func (h *LogHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { receivedAt := time.Now() // The `X-Request-Id` header is used by Heroku, restify, etc. to correlate // logs for the same request. requestID := req.Header.Get(HeaderID) if !id.Valid(requestID) { requestID, _ = id.Generate() req.Header.Set(HeaderID, requestID) } writer := &logResponseWriter{ResponseWriter: res, StatusCode: http.StatusOK} defer h.logResponse(writer, req, requestID, receivedAt) h.Handler.ServeHTTP(writer, req) }
func TestDupeDisconnect(t *testing.T) { channelId, err := id.Generate() if err != nil { t.Fatalf("Error generating channel ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %s", err) } conn, deviceId, err := client.Dial(origin, channelId) if err != nil { t.Fatalf("Error dialing origin: %s", err) } defer conn.Close() stopChan := make(chan bool) defer close(stopChan) errChan := make(chan error) go func() { var err error select { case <-conn.CloseNotify(): case <-time.After(5 * time.Second): err = fmt.Errorf("Initial connection for %q not closed", deviceId) } select { case <-stopChan: case errChan <- err: } }() go func(dupeId string) { dupeConn, err := client.DialId(origin, &dupeId, channelId) if err != nil { err = fmt.Errorf("Error reconnecting to origin: %s", err) } defer dupeConn.Close() select { case <-stopChan: case errChan <- err: } }(deviceId) for i := 0; i < 2; i++ { if err := <-errChan; err != nil { t.Fatal(err) } } }
// IsEtcdHealthy indicates whether etcd can respond to requests. func IsEtcdHealthy(client *etcd.Client) (ok bool, err error) { fakeID, err := id.Generate() if err != nil { return false, fmt.Errorf("Error generating health check key: %s", err) } key, expected := "status_"+fakeID, "test" if _, err = client.Set(key, expected, uint64(3*time.Second)); err != nil { return false, fmt.Errorf("Error storing health check key %#v: %s", key, err) } resp, err := client.Get(key, false, false) if err != nil { return false, fmt.Errorf("Error fetching health check key %#v: %s", key, err) } if resp.Node.Value != expected { return false, fmt.Errorf( "Unexpected value for health check key %#v: got %s; want %s", key, resp.Node.Value, expected) } return true, nil }
func TestTooManyChannels(t *testing.T) { deviceId, err := id.Generate() if err != nil { t.Fatalf("Error generating device ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } conn, err := client.DialOrigin(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() actualId, err := conn.WriteHelo(deviceId, channelIds...) if err != nil { t.Fatalf("Error writing large handshake request: %#v", err) } if actualId == deviceId { t.Errorf("Want new device ID; got %#v", actualId) } }
func connect(origin string) (deviceId, channelId, endpoint string, err error) { if channelId, err = id.Generate(); err != nil { err = fmt.Errorf("Error generating channel ID: %s", err) return } conn, deviceId, err := client.Dial(origin) if err != nil { err = fmt.Errorf("Error dialing origin: %s", err) return } defer conn.Close() defer conn.Purge() if endpoint, err = conn.Register(channelId); err != nil { err = fmt.Errorf("Error subscribing to channel %q: %s", channelId, err) return } if err = roundTrip(conn, deviceId, channelId, endpoint, 1); err != nil { err = fmt.Errorf("Error sending initial notification: %s", err) return } return }
func TestHandshakeWithId(t *testing.T) { origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } deviceId, err := id.Generate() if err != nil { t.Fatalf("Error generating device ID: %#v", err) } conn, err := client.DialOrigin(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() actualId, err := conn.WriteHelo(deviceId) if err != nil { t.Fatalf("Error writing handshake request: %#v", err) } if actualId != deviceId { t.Errorf("Mismatched device IDs: got %#v; want %#v", actualId, deviceId) } }
// Status queries whether memcached is available for reading and writing. // Implements Store.Status(). func (s *GomemcStore) Status() (success bool, err error) { fakeID, err := id.Generate() if err != nil { return false, err } key, expected := "status_"+fakeID, []byte("test") err = s.client.Set( &mc.Item{ Key: key, Value: expected, Expiration: 6, }) if err != nil { if s.logger.ShouldLog(ERROR) { s.logger.Error("gomemc", "Error storing health check key", LogFields{"error": err.Error(), "key": key}) } return false, err } raw, err := s.client.Get(key) if err != nil { if s.logger.ShouldLog(ERROR) { s.logger.Error("gomemc", "Error fetching health check key", LogFields{"error": err.Error(), "key": key}) } return false, err } if !bytes.Equal(raw.Value, expected) { if s.logger.ShouldLog(ERROR) { s.logger.Error("gomemc", "Unexpected health check result", LogFields{"expected": string(expected), "actual": string(raw.Value)}) } return false, ErrMemcacheStatus } s.client.Delete(key) return true, nil }
func TestDuplicateHandshake(t *testing.T) { deviceId, err := id.Generate() if err != nil { t.Fatalf("Error generating device ID: %#v", err) } origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } conn, err := client.DialOrigin(origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } defer conn.Close() defer conn.Purge() firstId, err := conn.WriteHelo(deviceId) if err != nil { t.Fatalf("Error writing initial handshake request: %#v", err) } if firstId != deviceId { t.Errorf("Mismatched device ID for initial handshake: got %#v; want %#v", firstId, deviceId) } secondId, err := conn.WriteHelo(firstId) if err != nil { t.Fatalf("Error writing duplicate handshake request: %#v", err) } if secondId != firstId { t.Errorf("Mismatched device ID for duplicate handshake: got %#v; want %#v", secondId, firstId) } thirdId, err := conn.WriteHelo("") if err != nil { t.Fatalf("Error writing implicit handshake request: %#v", err) } if thirdId != secondId { t.Errorf("Mismatched device ID for implicit handshake: got %#v; want %#v", thirdId, secondId) } }
// Status queries whether memcached is available for reading and writing. // Implements Store.Status(). func (s *EmceeStore) Status() (success bool, err error) { fakeID, err := id.Generate() if err != nil { return false, err } key, expected := "status_"+fakeID, "test" client, err := s.getClient() defer s.releaseWithout(client, &err) if err != nil { return false, err } if err = client.Set(key, expected, 6*time.Second); err != nil { if s.logger.ShouldLog(ERROR) { s.logger.Error("emcee", "Error storing health check key", LogFields{"error": err.Error(), "key": key}) } return false, err } var actual string if err = client.Get(key, &actual); err != nil { if s.logger.ShouldLog(ERROR) { s.logger.Error("emcee", "Error fetching health check key", LogFields{"error": err.Error(), "key": key}) } return false, err } if expected != actual { if s.logger.ShouldLog(ERROR) { s.logger.Error("emcee", "Unexpected health check result", LogFields{ "key": key, "expected": expected, "actual": actual}) } return false, ErrMemcacheStatus } client.Delete(key, 0) return true, nil }
func TestUnregisterRace(t *testing.T) { origin, err := testServer.Origin() if err != nil { t.Fatalf("Error initializing test server: %#v", err) } socket, err := ws.Dial(origin, "", origin) if err != nil { t.Fatalf("Error dialing origin: %#v", err) } connId, err := id.Generate() if err != nil { t.Fatalf("Error generating connection ID: %#v", err) } // Spool all notifications, including those received on dregistered channels. conn := client.NewConn(socket, connId, true) defer conn.Close() if _, err = conn.WriteHelo(""); err != nil { t.Fatalf("Error writing handshake request: %#v", err) } defer conn.Purge() channelId, endpoint, err := conn.Subscribe() if err != nil { t.Fatalf("Error subscribing to channel: %#v", err) } if !isValidEndpoint(endpoint) { t.Fatalf("Invalid push endpoint for channel %#v: %#v", channelId, endpoint) } version := time.Now().UTC().Unix() var notifyWait sync.WaitGroup signal, errors := make(chan bool), make(chan error) notifyWait.Add(2) go func() { defer notifyWait.Done() timeout := time.After(1 * time.Minute) var ( isRemoved bool pendingTimer <-chan time.Time ) for ok := true; ok; { var packet client.Packet select { case ok = <-signal: case <-timeout: ok = false errors <- client.ErrTimedOut case <-pendingTimer: ok = false // Read the update, but don't call AcceptUpdate(). case packet, ok = <-conn.Packets: if !ok { err = client.ErrChanClosed break } var ( updates client.ServerUpdates hasUpdates bool ) if updates, hasUpdates = packet.(client.ServerUpdates); !hasUpdates { break } var ( update client.Update hasUpdate bool ) for _, update = range updates { if hasUpdate = update.ChannelId == channelId; hasUpdate { break } } if !hasUpdate { break } var err error if update.Version != version { err = fmt.Errorf("Expected update %#v, not %#v", version, update.Version) } else if isRemoved { err = fmt.Errorf("Update %#v resent on deregistered channel %#v", update.Version, update.ChannelId) } else { err = conn.Unregister(channelId) } if err != nil { ok = false errors <- err break } isRemoved = true timeout = nil // Queued updates should be sent immediately. pendingTimer = time.After(1 * time.Second) } } }() go func() { defer notifyWait.Done() select { case <-signal: case errors <- client.Notify(endpoint, version): } }() go func() { notifyWait.Wait() close(errors) }() for err = range errors { if err != nil { close(signal) t.Fatal(err) } } }
// A client connects! func (self *Serv) Hello(worker *Worker, cmd PushCommand, sock *PushWS) (result int, arguments JsMap) { var uaid string args := cmd.Arguments if self.logger.ShouldLog(INFO) { chidss := "" if chids, ok := args["channelIDs"]; ok { chidss = "[" + strings.Join(chids.([]string), ", ") + "]" } self.logger.Info("server", "handling 'hello'", LogFields{"uaid": args["uaid"].(string), "channelIDs": chidss}) } // TODO: If the client needs to connect to a different server, // Look up the appropriate server (based on UAID) // return a response that looks like: // { uaid: UAIDValue, status: 302, redirect: NewWS_URL } // New connects overwrite previous connections. // Raw client if args["uaid"] == "" { uaid, _ = id.Generate() if self.logger.ShouldLog(DEBUG) { self.logger.Debug("server", "Generating new UAID", LogFields{"uaid": uaid}) } } else { uaid = args["uaid"].(string) if self.logger.ShouldLog(DEBUG) { self.logger.Debug("server", "Using existing UAID", LogFields{"uaid": uaid}) } delete(args, "uaid") } if connect, _ := args["connect"].([]byte); len(connect) > 0 && self.prop != nil { if err := self.prop.Register(uaid, connect); err != nil { if self.logger.ShouldLog(WARNING) { self.logger.Warn("server", "Could not set proprietary info", LogFields{"error": err.Error(), "connect": string(connect)}) } } } // Create a new, live client entry for this record. // See Bye for discussion of potential longer term storage of this info sock.Uaid = uaid client := &Client{ Worker: worker, PushWS: *sock, UAID: uaid, } self.app.AddClient(uaid, client) self.logger.Info("dash", "Client registered", nil) // We don't register the list of known ChannelIDs since we echo // back any ChannelIDs sent on behalf of this UAID. args["uaid"] = uaid arguments = args result = 200 return result, arguments }