package rpc2
import (
"errors"
"io"
"log"
"net"
"reflect"
"unicode"
"unicode/utf8"
"github.com/cenkalti/hub"
)
var typeOfError = reflect .TypeOf ((*error )(nil )).Elem ()
var typeOfClient = reflect .TypeOf ((*Client )(nil ))
const (
clientConnected hub .Kind = iota
clientDisconnected
)
type Server struct {
handlers map [string ]*handler
eventHub *hub .Hub
}
type handler struct {
fn reflect .Value
argType reflect .Type
replyType reflect .Type
}
type connectionEvent struct {
Client *Client
}
type disconnectionEvent struct {
Client *Client
}
func (connectionEvent ) Kind () hub .Kind { return clientConnected }
func (disconnectionEvent ) Kind () hub .Kind { return clientDisconnected }
func NewServer () *Server {
return &Server {
handlers : make (map [string ]*handler ),
eventHub : &hub .Hub {},
}
}
func (s *Server ) Handle (method string , handlerFunc interface {}) {
addHandler (s .handlers , method , handlerFunc )
}
func addHandler(handlers map [string ]*handler , mname string , handlerFunc interface {}) {
if _ , ok := handlers [mname ]; ok {
panic ("rpc2: multiple registrations for " + mname )
}
method := reflect .ValueOf (handlerFunc )
mtype := method .Type ()
if mtype .NumIn () != 3 {
log .Panicln ("method" , mname , "has wrong number of ins:" , mtype .NumIn ())
}
clientType := mtype .In (0 )
if clientType .Kind () != reflect .Ptr {
log .Panicln ("method" , mname , "client type not a pointer:" , clientType )
}
if clientType != typeOfClient {
log .Panicln ("method" , mname , "first argument" , clientType .String (), "not *rpc2.Client" )
}
argType := mtype .In (1 )
if !isExportedOrBuiltinType (argType ) {
log .Panicln (mname , "argument type not exported:" , argType )
}
replyType := mtype .In (2 )
if replyType .Kind () != reflect .Ptr {
log .Panicln ("method" , mname , "reply type not a pointer:" , replyType )
}
if !isExportedOrBuiltinType (replyType ) {
log .Panicln ("method" , mname , "reply type not exported:" , replyType )
}
if mtype .NumOut () != 1 {
log .Panicln ("method" , mname , "has wrong number of outs:" , mtype .NumOut ())
}
if returnType := mtype .Out (0 ); returnType != typeOfError {
log .Panicln ("method" , mname , "returns" , returnType .String (), "not error" )
}
handlers [mname ] = &handler {
fn : method ,
argType : argType ,
replyType : replyType ,
}
}
func isExportedOrBuiltinType(t reflect .Type ) bool {
for t .Kind () == reflect .Ptr {
t = t .Elem ()
}
return isExported (t .Name ()) || t .PkgPath () == ""
}
func isExported(name string ) bool {
rune , _ := utf8 .DecodeRuneInString (name )
return unicode .IsUpper (rune )
}
func (s *Server ) OnConnect (f func (*Client )) {
s .eventHub .Subscribe (clientConnected , func (e hub .Event ) {
go f (e .(connectionEvent ).Client )
})
}
func (s *Server ) OnDisconnect (f func (*Client )) {
s .eventHub .Subscribe (clientDisconnected , func (e hub .Event ) {
go f (e .(disconnectionEvent ).Client )
})
}
func (s *Server ) Accept (lis net .Listener ) {
for {
conn , err := lis .Accept ()
if err != nil {
if !errors .Is (err , net .ErrClosed ) {
log .Print ("rpc.Serve: accept:" , err .Error())
}
return
}
go s .ServeConn (conn )
}
}
func (s *Server ) ServeConn (conn io .ReadWriteCloser ) {
s .ServeCodec (NewGobCodec (conn ))
}
func (s *Server ) ServeCodec (codec Codec ) {
s .ServeCodecWithState (codec , NewState ())
}
func (s *Server ) ServeCodecWithState (codec Codec , state *State ) {
defer codec .Close ()
c := NewClientWithCodec (codec )
c .server = true
c .handlers = s .handlers
c .State = state
s .eventHub .Publish (connectionEvent {c })
c .Run ()
s .eventHub .Publish (disconnectionEvent {c })
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .