tractor.dev/toolkit-go@v0.0.0-20241010005851-214d91207d07/duplex/rpc/server.go (about) 1 package rpc 2 3 import ( 4 "context" 5 "io" 6 "log" 7 "net" 8 9 "tractor.dev/toolkit-go/duplex/codec" 10 "tractor.dev/toolkit-go/duplex/mux" 11 ) 12 13 // Server wraps a Handler and codec to respond to RPC calls. 14 type Server struct { 15 Handler Handler 16 Codec codec.Codec 17 } 18 19 // ServeMux will Accept sessions until the Listener is closed, and will Respond to accepted sessions in their own goroutine. 20 func (s *Server) ServeMux(l mux.Listener) error { 21 for { 22 sess, err := l.Accept() 23 if err != nil { 24 return err 25 } 26 go s.Respond(sess, nil) 27 } 28 } 29 30 // Serve will Accept sessions until the Listener is closed, and will Respond to accepted sessions in their own goroutine. 31 func (s *Server) Serve(l net.Listener) error { 32 return s.ServeMux(mux.ListenerFrom(l)) 33 } 34 35 // Respond will Accept channels until the Session is closed and respond with the server handler in its own goroutine. 36 // If Handler was not set, an empty RespondMux is used. If the handler does not initiate a response, a nil value is 37 // returned. If the handler does not call Continue, the channel will be closed. Respond will panic if Codec is nil. 38 // 39 // If the context is not nil, it will be added to Calls. Otherwise the Call Context will be set to a context.Background(). 40 func (s *Server) Respond(sess mux.Session, ctx context.Context) { 41 defer sess.Close() 42 43 if s.Codec == nil { 44 panic("rpc.Respond: nil codec") 45 } 46 47 hn := s.Handler 48 if hn == nil { 49 hn = NewRespondMux() 50 } 51 52 for { 53 ch, err := sess.Accept() 54 if err != nil { 55 if err == io.EOF { 56 return 57 } 58 panic(err) 59 } 60 go s.respond(hn, sess, ch, ctx) 61 } 62 } 63 64 func (s *Server) respond(hn Handler, sess mux.Session, ch mux.Channel, ctx context.Context) { 65 framer := &FrameCodec{Codec: s.Codec} 66 dec := framer.Decoder(ch) 67 68 var call Call 69 err := dec.Decode(&call) 70 if err != nil { 71 log.Println("rpc.Respond:", err) 72 return 73 } 74 75 call.S = cleanSelector(call.Selector()) 76 call.Decoder = dec 77 call.Caller = &Client{ 78 Session: sess, 79 codec: s.Codec, 80 } 81 if ctx == nil { 82 call.Context = context.Background() 83 } else { 84 call.Context = ctx 85 } 86 call.Channel = ch 87 88 header := &ResponseHeader{} 89 resp := &responder{ 90 ch: ch, 91 c: framer, 92 header: header, 93 } 94 95 hn.RespondRPC(resp, &call) 96 if !resp.responded { 97 resp.Return() 98 } 99 if !resp.header.C { 100 ch.Close() 101 } 102 }