// license that can be found in the LICENSE file.
/*
- The rpc package provides access to the public methods of an object across a
+ The rpc package provides access to the exported methods of an object across a
network or other I/O connection. A server registers an object, making it visible
- as a service with the name of the type of the object. After registration, public
+ as a service with the name of the type of the object. After registration, exported
methods of the object will be accessible remotely. A server may register multiple
objects (services) of different types but it is an error to register multiple
objects of the same type.
Only methods that satisfy these criteria will be made available for remote access;
other methods will be ignored:
- - the method receiver and name are publicly visible, that is, begin with an upper case letter.
- - the method has two arguments, both pointers to publicly visible types.
+ - the method receiver and name are exported, that is, begin with an upper case letter.
+ - the method has two arguments, both pointers to exported types.
- the method has return type os.Error.
The method's first argument represents the arguments provided by the caller; the
"utf8"
)
+const (
+ // Defaults used by HandleHTTP
+ DefaultRPCPath = "/_goRPC_"
+ DefaultDebugPath = "/debug/rpc"
+)
+
// Precompute the reflect type for os.Error. Can't use os.Error directly
// because Typeof takes an empty interface value. This is annoying.
var unusedError *os.Error
RemoteAddr string
}
-type serverType struct {
+// Server represents an RPC Server.
+type Server struct {
sync.Mutex // protects the serviceMap
serviceMap map[string]*service
}
-// This variable is a global whose "public" methods are really private methods
-// called from the global functions of this package: rpc.Register, rpc.ServeConn, etc.
-// For example, rpc.Register() calls server.add().
-var server = &serverType{serviceMap: make(map[string]*service)}
+// NewServer returns a new Server.
+func NewServer() *Server {
+ return &Server{serviceMap: make(map[string]*service)}
+}
+
+// DefaultServer is the default instance of *Server.
+var DefaultServer = NewServer()
-// Is this a publicly visible - upper case - name?
-func isPublic(name string) bool {
+// Is this an exported - upper case - name?
+func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
-func (server *serverType) register(rcvr interface{}) os.Error {
+// Register publishes in the server the set of methods of the
+// receiver value that satisfy the following conditions:
+// - exported method
+// - two arguments, both pointers to exported structs
+// - one return value, of type os.Error
+// It returns an error if the receiver is not an exported type or has no
+// suitable methods.
+func (server *Server) Register(rcvr interface{}) os.Error {
server.Lock()
defer server.Unlock()
if server.serviceMap == nil {
if sname == "" {
log.Exit("rpc: no service name for type", s.typ.String())
}
- if s.typ.PkgPath() != "" && !isPublic(sname) {
- s := "rpc Register: type " + sname + " is not public"
+ if s.typ.PkgPath() != "" && !isExported(sname) {
+ s := "rpc Register: type " + sname + " is not exported"
log.Print(s)
return os.ErrorString(s)
}
method := s.typ.Method(m)
mtype := method.Type
mname := method.Name
- if mtype.PkgPath() != "" || !isPublic(mname) {
+ if mtype.PkgPath() != "" || !isExported(mname) {
continue
}
// Method needs three ins: receiver, *args, *reply.
log.Println(mname, "reply type not a pointer:", mtype.In(2))
continue
}
- if argType.Elem().PkgPath() != "" && !isPublic(argType.Elem().Name()) {
- log.Println(mname, "argument type not public:", argType)
+ if argType.Elem().PkgPath() != "" && !isExported(argType.Elem().Name()) {
+ log.Println(mname, "argument type not exported:", argType)
continue
}
- if replyType.Elem().PkgPath() != "" && !isPublic(replyType.Elem().Name()) {
- log.Println(mname, "reply type not public:", replyType)
+ if replyType.Elem().PkgPath() != "" && !isExported(replyType.Elem().Name()) {
+ log.Println(mname, "reply type not exported:", replyType)
continue
}
if mtype.NumIn() == 4 {
}
if len(s.method) == 0 {
- s := "rpc Register: type " + sname + " has no public methods of suitable type"
+ s := "rpc Register: type " + sname + " has no exported methods of suitable type"
log.Print(s)
return os.ErrorString(s)
}
return c.rwc.Close()
}
-func (server *serverType) input(codec ServerCodec) {
+
+// ServeConn runs the server on a single connection.
+// ServeConn blocks, serving the connection until the client hangs up.
+// The caller typically invokes ServeConn in a go statement.
+// ServeConn uses the gob wire format (see package gob) on the
+// connection. To use an alternate codec, use ServeCodec.
+func (server *Server) ServeConn(conn io.ReadWriteCloser) {
+ server.ServeCodec(&gobServerCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(conn)})
+}
+
+// ServeCodec is like ServeConn but uses the specified codec to
+// decode requests and encode responses.
+func (server *Server) ServeCodec(codec ServerCodec) {
sending := new(sync.Mutex)
for {
// Grab the request header.
codec.Close()
}
-func (server *serverType) accept(lis net.Listener) {
+// Accept accepts connections on the listener and serves requests
+// for each incoming connection. Accept blocks; the caller typically
+// invokes it in a go statement.
+func (server *Server) Accept(lis net.Listener) {
for {
conn, err := lis.Accept()
if err != nil {
log.Exit("rpc.Serve: accept:", err.String()) // TODO(r): exit?
}
- go ServeConn(conn)
+ go server.ServeConn(conn)
}
}
-// Register publishes in the server the set of methods of the
-// receiver value that satisfy the following conditions:
-// - public method
-// - two arguments, both pointers to public structs
-// - one return value of type os.Error
-// It returns an error if the receiver is not public or has no
+// Register publishes in the DefaultServer the set of methods
+// of the receiver value that satisfy the following conditions:
+// - exported method
+// - two arguments, both pointers to exported structs
+// - one return value, of type os.Error
+// It returns an error if the receiver is not an exported type or has no
// suitable methods.
-func Register(rcvr interface{}) os.Error { return server.register(rcvr) }
+func Register(rcvr interface{}) os.Error { return DefaultServer.Register(rcvr) }
// A ServerCodec implements reading of RPC requests and writing of
// RPC responses for the server side of an RPC session.
Close() os.Error
}
-// ServeConn runs the server on a single connection.
+// ServeConn runs the DefaultServer on a single connection.
// ServeConn blocks, serving the connection until the client hangs up.
// The caller typically invokes ServeConn in a go statement.
// ServeConn uses the gob wire format (see package gob) on the
// connection. To use an alternate codec, use ServeCodec.
func ServeConn(conn io.ReadWriteCloser) {
- ServeCodec(&gobServerCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(conn)})
+ DefaultServer.ServeConn(conn)
}
// ServeCodec is like ServeConn but uses the specified codec to
// decode requests and encode responses.
func ServeCodec(codec ServerCodec) {
- server.input(codec)
+ DefaultServer.ServeCodec(codec)
}
// Accept accepts connections on the listener and serves requests
-// for each incoming connection. Accept blocks; the caller typically
-// invokes it in a go statement.
-func Accept(lis net.Listener) { server.accept(lis) }
+// to DefaultServer for each incoming connection.
+// Accept blocks; the caller typically invokes it in a go statement.
+func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
// Can connect to RPC service using HTTP CONNECT to rpcPath.
-var rpcPath string = "/_goRPC_"
-var debugPath string = "/debug/rpc"
var connected = "200 Connected to Go RPC"
-func serveHTTP(w http.ResponseWriter, req *http.Request) {
+// ServeHTTP implements an http.Handler that answers RPC requests.
+func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if req.Method != "CONNECT" {
w.SetHeader("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusMethodNotAllowed)
- io.WriteString(w, "405 must CONNECT to "+rpcPath+"\n")
+ io.WriteString(w, "405 must CONNECT\n")
return
}
conn, _, err := w.Hijack()
return
}
io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n")
- ServeConn(conn)
+ server.ServeConn(conn)
+}
+
+// HandleHTTP registers an HTTP handler for RPC messages on rpcPath,
+// and a debugging handler on debugPath.
+// It is still necessary to invoke http.Serve(), typically in a go statement.
+func (server *Server) HandleHTTP(rpcPath, debugPath string) {
+ http.Handle(rpcPath, server)
+ http.Handle(debugPath, debugHTTP{server})
}
-// HandleHTTP registers an HTTP handler for RPC messages.
+// HandleHTTP registers an HTTP handler for RPC messages to DefaultServer
+// on DefaultRPCPath and a debugging handler on DefaultDebugPath.
// It is still necessary to invoke http.Serve(), typically in a go statement.
func HandleHTTP() {
- http.Handle(rpcPath, http.HandlerFunc(serveHTTP))
- http.Handle(debugPath, http.HandlerFunc(debugHTTP))
+ DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)
}
"testing"
)
-var serverAddr string
-var httpServerAddr string
-var once sync.Once
-
-const second = 1e9
+var (
+ serverAddr, newServerAddr string
+ httpServerAddr string
+ once, newOnce, httpOnce sync.Once
+)
+const (
+ second = 1e9
+ newHttpPath = "/foo"
+)
type Args struct {
A, B int
panic("ERROR")
}
-func startServer() {
- Register(new(Arith))
-
+func listenTCP() (net.Listener, string) {
l, e := net.Listen("tcp", "127.0.0.1:0") // any available address
if e != nil {
log.Exitf("net.Listen tcp :0: %v", e)
}
- serverAddr = l.Addr().String()
+ return l, l.Addr().String()
+}
+
+func startServer() {
+ Register(new(Arith))
+
+ var l net.Listener
+ l, serverAddr = listenTCP()
log.Println("Test RPC server listening on", serverAddr)
go Accept(l)
HandleHTTP()
- l, e = net.Listen("tcp", "127.0.0.1:0") // any available address
- if e != nil {
- log.Printf("net.Listen tcp :0: %v", e)
- os.Exit(1)
- }
+ httpOnce.Do(startHttpServer)
+}
+
+func startNewServer() {
+ s := NewServer()
+ s.Register(new(Arith))
+
+ var l net.Listener
+ l, newServerAddr = listenTCP()
+ log.Println("NewServer test RPC server listening on", newServerAddr)
+ go Accept(l)
+
+ s.HandleHTTP(newHttpPath, "/bar")
+ httpOnce.Do(startHttpServer)
+}
+
+func startHttpServer() {
+ var l net.Listener
+ l, httpServerAddr = listenTCP()
httpServerAddr = l.Addr().String()
log.Println("Test HTTP RPC server listening on", httpServerAddr)
go http.Serve(l, nil)
func TestRPC(t *testing.T) {
once.Do(startServer)
+ testRPC(t, serverAddr)
+ newOnce.Do(startNewServer)
+ testRPC(t, newServerAddr)
+}
- client, err := Dial("tcp", serverAddr)
+func testRPC(t *testing.T, addr string) {
+ client, err := Dial("tcp", addr)
if err != nil {
t.Fatal("dialing", err)
}
func TestHTTPRPC(t *testing.T) {
once.Do(startServer)
+ testHTTPRPC(t, "")
+ newOnce.Do(startNewServer)
+ testHTTPRPC(t, newHttpPath)
+}
- client, err := DialHTTP("tcp", httpServerAddr)
+func testHTTPRPC(t *testing.T, path string) {
+ var client *Client
+ var err os.Error
+ if path == "" {
+ client, err = DialHTTP("tcp", httpServerAddr)
+ } else {
+ client, err = DialHTTPPath("tcp", httpServerAddr, path)
+ }
if err != nil {
t.Fatal("dialing", err)
}