ref/lib/xrpc: Add an xserver library that proposes a simplified server API.
The purpose of this API is to reduce boilerplate and make the api easier
to understand. There are fewer restrictions about which methods can be
called when, and fewer steps to creating a server.
The only flexibility we lose is the ability to listen on multiple ListenSpecs.
Since ListenSpecs already allow users to listen on multiple addresses I expect
this situation will be rare. When it does come up, users could create
multiple servers with the same service object or dispatcher.
Change-Id: I4ecea023369e16c8cb1408d99ede6ea322cd9ecc
diff --git a/lib/xrpc/xserver.go b/lib/xrpc/xserver.go
new file mode 100644
index 0000000..cc1f775
--- /dev/null
+++ b/lib/xrpc/xserver.go
@@ -0,0 +1,121 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package xserver provides an alternate RPC server API with the goal of
+// being simpler to use and understand.
+package xrpc
+
+import (
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/rpc"
+ "v.io/v23/security"
+)
+
+type Server struct {
+ s rpc.Server
+}
+
+// NewServer creates a new Server instance to serve a service object.
+//
+// The server will listen for network connections as specified by the
+// ListenSpec attached to ctx. Depending on your RuntimeFactory, 'roaming'
+// support may be enabled. In this mode the server will listen for
+// changes in the network configuration using a Stream created on the
+// supplied Publisher and change the set of Endpoints it publishes to
+// the mount table accordingly.
+//
+// The server associates object with name by publishing the address of
+// this server in the namespace under the supplied name and using
+// authorizer to authorize access to it. RPCs invoked on the supplied
+// name will be delivered to methods implemented by the supplied
+// object. Reflection is used to match requests to the object's
+// method set. As a special-case, if the object implements the
+// Invoker interface, the Invoker is used to invoke methods directly,
+// without reflection. If name is an empty string, no attempt will
+// made to publish.
+func NewServer(ctx *context.T, name string, object interface{}, auth security.Authorizer, opts ...rpc.ServerOpt) (*Server, error) {
+ s, err := v23.NewServer(ctx, opts...)
+ if err != nil {
+ return nil, err
+ }
+ if _, err = s.Listen(v23.GetListenSpec(ctx)); err != nil {
+ return nil, err
+ }
+ if err = s.Serve(name, object, auth); err != nil {
+ return nil, err
+ }
+ return &Server{s: s}, nil
+}
+
+// NewDispatchingServer creates a new Server instance to serve a given dispatcher.
+//
+// The server will listen for network connections as specified by the
+// ListenSpec attached to ctx. Depending on your RuntimeFactory, 'roaming'
+// support may be enabled. In this mode the server will listen for
+// changes in the network configuration using a Stream created on the
+// supplied Publisher and change the set of Endpoints it publishes to
+// the mount table accordingly.
+//
+// The server associates dispatcher with the portion of the namespace
+// for which name is a prefix by publishing the address of this server
+// to the namespace under the supplied name. If name is an empty
+// string, no attempt will made to publish. RPCs invoked on the
+// supplied name will be delivered to the supplied Dispatcher's Lookup
+// method which will in turn return the object and security.Authorizer
+// used to serve the actual RPC call. If name is an empty string, no
+// attempt will made to publish that name to a mount table.
+func NewDispatchingServer(ctx *context.T, name string, disp rpc.Dispatcher, opts ...rpc.ServerOpt) (*Server, error) {
+ s, err := v23.NewServer(ctx, opts...)
+ if err != nil {
+ return nil, err
+ }
+ if _, err = s.Listen(v23.GetListenSpec(ctx)); err != nil {
+ return nil, err
+ }
+ if err = s.ServeDispatcher(name, disp); err != nil {
+ return nil, err
+ }
+ return &Server{s: s}, nil
+}
+
+// AddName adds the specified name to the mount table for this server.
+// AddName may be called multiple times.
+func (s *Server) AddName(name string) error {
+ return s.s.AddName(name)
+}
+
+// RemoveName removes the specified name from the mount table.
+// RemoveName may be called multiple times.
+func (s *Server) RemoveName(name string) {
+ s.s.RemoveName(name)
+}
+
+// Status returns the current status of the server, see ServerStatus
+// for details.
+func (s *Server) Status() rpc.ServerStatus {
+ return s.s.Status()
+}
+
+// WatchNetwork registers a channel over which NetworkChange's will
+// be sent. The Server will not block sending data over this channel
+// and hence change events may be lost if the caller doesn't ensure
+// there is sufficient buffering in the channel.
+func (s *Server) WatchNetwork(ch chan<- rpc.NetworkChange) {
+ s.s.WatchNetwork(ch)
+}
+
+// UnwatchNetwork unregisters a channel previously registered using
+// WatchNetwork.
+func (s *Server) UnwatchNetwork(ch chan<- rpc.NetworkChange) {
+ s.s.UnwatchNetwork(ch)
+}
+
+// Stop gracefully stops all services on this Server. New calls are
+// rejected, but any in-flight calls are allowed to complete. All
+// published mountpoints are unmounted. This call waits for this
+// process to complete, and returns once the server has been shut down.
+func (s *Server) Stop() error {
+ return s.s.Stop()
+}
diff --git a/lib/xrpc/xserver_test.go b/lib/xrpc/xserver_test.go
new file mode 100644
index 0000000..de0ee78
--- /dev/null
+++ b/lib/xrpc/xserver_test.go
@@ -0,0 +1,70 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xrpc
+
+import (
+ "testing"
+
+ "v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/rpc"
+ "v.io/v23/security"
+ _ "v.io/x/ref/runtime/factories/generic"
+ "v.io/x/ref/test"
+)
+
+func init() {
+ test.Init()
+}
+
+type service struct{}
+
+func (s *service) Yo(ctx *context.T, call rpc.ServerCall) (string, error) {
+ return "yo", nil
+}
+
+func TestXServer(t *testing.T) {
+ ctx, shutdown := test.V23Init()
+ defer shutdown()
+
+ server, err := NewServer(ctx, "", &service{}, nil)
+ if err != nil {
+ t.Fatalf("Error creating server: %v", err)
+ }
+ ep := server.Status().Endpoints[0]
+
+ var out string
+ if err := v23.GetClient(ctx).Call(ctx, ep.Name(), "Yo", nil, []interface{}{&out}); err != nil {
+ t.Fatalf("Call failed: %v", err)
+ }
+ if out != "yo" {
+ t.Fatalf("Wanted yo, got %s", out)
+ }
+}
+
+type dispatcher struct{}
+
+func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
+ return &service{}, nil, nil
+}
+
+func TestXDispatchingServer(t *testing.T) {
+ ctx, shutdown := test.V23Init()
+ defer shutdown()
+
+ server, err := NewDispatchingServer(ctx, "", &dispatcher{})
+ if err != nil {
+ t.Fatalf("Error creating server: %v", err)
+ }
+ ep := server.Status().Endpoints[0]
+
+ var out string
+ if err := v23.GetClient(ctx).Call(ctx, ep.Name(), "Yo", nil, []interface{}{&out}); err != nil {
+ t.Fatalf("Call failed: %v", err)
+ }
+ if out != "yo" {
+ t.Fatalf("Wanted yo, got %s", out)
+ }
+}