checkpoint
This commit is contained in:
parent
c73a88e801
commit
6ae48c9f29
@ -27,13 +27,13 @@ import (
|
|||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
NewPublication(topic string, msg interface{}) Publication
|
NewPublication(topic string, msg interface{}) Publication
|
||||||
NewRequest(service, method string, req interface{}) Request
|
NewRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request
|
||||||
NewProtoRequest(service, method string, req interface{}) Request
|
NewProtoRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request
|
||||||
NewJsonRequest(service, method string, req interface{}) Request
|
NewJsonRequest(service, method string, req interface{}, reqOpts ...RequestOption) Request
|
||||||
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
|
Call(ctx context.Context, req Request, rsp interface{}, opts ...CallOption) error
|
||||||
CallRemote(ctx context.Context, addr string, req Request, rsp interface{}, opts ...CallOption) error
|
CallRemote(ctx context.Context, addr string, req Request, rsp interface{}, opts ...CallOption) error
|
||||||
Stream(ctx context.Context, req Request, rspChan interface{}, opts ...CallOption) (Streamer, error)
|
Stream(ctx context.Context, req Request, opts ...CallOption) (Streamer, error)
|
||||||
StreamRemote(ctx context.Context, addr string, req Request, rspChan interface{}, opts ...CallOption) (Streamer, error)
|
StreamRemote(ctx context.Context, addr string, req Request, opts ...CallOption) (Streamer, error)
|
||||||
Publish(ctx context.Context, p Publication, opts ...PublishOption) error
|
Publish(ctx context.Context, p Publication, opts ...PublishOption) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,10 +48,15 @@ type Request interface {
|
|||||||
Method() string
|
Method() string
|
||||||
ContentType() string
|
ContentType() string
|
||||||
Request() interface{}
|
Request() interface{}
|
||||||
|
// indicates whether the request will be a streaming one rather than unary
|
||||||
|
Stream() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Streamer interface {
|
type Streamer interface {
|
||||||
|
Context() context.Context
|
||||||
Request() Request
|
Request() Request
|
||||||
|
Send(interface{}) error
|
||||||
|
Recv(interface{}) error
|
||||||
Error() error
|
Error() error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
@ -59,6 +64,7 @@ type Streamer interface {
|
|||||||
type Option func(*options)
|
type Option func(*options)
|
||||||
type CallOption func(*callOptions)
|
type CallOption func(*callOptions)
|
||||||
type PublishOption func(*publishOptions)
|
type PublishOption func(*publishOptions)
|
||||||
|
type RequestOption func(*requestOptions)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultClient Client = newRpcClient()
|
DefaultClient Client = newRpcClient()
|
||||||
@ -76,13 +82,13 @@ func CallRemote(ctx context.Context, address string, request Request, response i
|
|||||||
|
|
||||||
// Creates a streaming connection with a service and returns responses on the
|
// Creates a streaming connection with a service and returns responses on the
|
||||||
// channel passed in. It's upto the user to close the streamer.
|
// channel passed in. It's upto the user to close the streamer.
|
||||||
func Stream(ctx context.Context, request Request, responseChan interface{}, opts ...CallOption) (Streamer, error) {
|
func Stream(ctx context.Context, request Request, opts ...CallOption) (Streamer, error) {
|
||||||
return DefaultClient.Stream(ctx, request, responseChan, opts...)
|
return DefaultClient.Stream(ctx, request, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a streaming connection to the address specified.
|
// Creates a streaming connection to the address specified.
|
||||||
func StreamRemote(ctx context.Context, address string, request Request, responseChan interface{}, opts ...CallOption) (Streamer, error) {
|
func StreamRemote(ctx context.Context, address string, request Request, opts ...CallOption) (Streamer, error) {
|
||||||
return DefaultClient.StreamRemote(ctx, address, request, responseChan, opts...)
|
return DefaultClient.StreamRemote(ctx, address, request, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publishes a publication using the default client. Using the underlying broker
|
// Publishes a publication using the default client. Using the underlying broker
|
||||||
@ -103,16 +109,16 @@ func NewPublication(topic string, message interface{}) Publication {
|
|||||||
|
|
||||||
// Creates a new request using the default client. Content Type will
|
// Creates a new request using the default client. Content Type will
|
||||||
// be set to the default within options and use the appropriate codec
|
// be set to the default within options and use the appropriate codec
|
||||||
func NewRequest(service, method string, request interface{}) Request {
|
func NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||||
return DefaultClient.NewRequest(service, method, request)
|
return DefaultClient.NewRequest(service, method, request, reqOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new protobuf request using the default client
|
// Creates a new protobuf request using the default client
|
||||||
func NewProtoRequest(service, method string, request interface{}) Request {
|
func NewProtoRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||||
return DefaultClient.NewProtoRequest(service, method, request)
|
return DefaultClient.NewProtoRequest(service, method, request, reqOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new json request using the default client
|
// Creates a new json request using the default client
|
||||||
func NewJsonRequest(service, method string, request interface{}) Request {
|
func NewJsonRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||||
return DefaultClient.NewJsonRequest(service, method, request)
|
return DefaultClient.NewJsonRequest(service, method, request, reqOpts...)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,10 @@ type callOptions struct {
|
|||||||
|
|
||||||
type publishOptions struct{}
|
type publishOptions struct{}
|
||||||
|
|
||||||
|
type requestOptions struct {
|
||||||
|
stream bool
|
||||||
|
}
|
||||||
|
|
||||||
// Broker to be used for pub/sub
|
// Broker to be used for pub/sub
|
||||||
func Broker(b broker.Broker) Option {
|
func Broker(b broker.Broker) Option {
|
||||||
return func(o *options) {
|
return func(o *options) {
|
||||||
@ -80,3 +84,11 @@ func WithSelectOption(so selector.SelectOption) CallOption {
|
|||||||
o.selectOptions = append(o.selectOptions, so)
|
o.selectOptions = append(o.selectOptions, so)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Request Options
|
||||||
|
|
||||||
|
func StreamingRequest() RequestOption {
|
||||||
|
return func(o *requestOptions) {
|
||||||
|
o.stream = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -112,7 +112,7 @@ func (r *rpcClient) call(ctx context.Context, address string, request Request, r
|
|||||||
return client.Close()
|
return client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcClient) stream(ctx context.Context, address string, request Request, responseChan interface{}) (Streamer, error) {
|
func (r *rpcClient) stream(ctx context.Context, address string, req Request) (Streamer, error) {
|
||||||
msg := &transport.Message{
|
msg := &transport.Message{
|
||||||
Header: make(map[string]string),
|
Header: make(map[string]string),
|
||||||
}
|
}
|
||||||
@ -124,9 +124,9 @@ func (r *rpcClient) stream(ctx context.Context, address string, request Request,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.Header["Content-Type"] = request.ContentType()
|
msg.Header["Content-Type"] = req.ContentType()
|
||||||
|
|
||||||
cf, err := r.newCodec(request.ContentType())
|
cf, err := r.newCodec(req.ContentType())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||||
}
|
}
|
||||||
@ -136,13 +136,22 @@ func (r *rpcClient) stream(ctx context.Context, address string, request Request,
|
|||||||
return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
|
return nil, errors.InternalServerError("go.micro.client", fmt.Sprintf("Error sending request: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
client := newClientWithCodec(newRpcPlusCodec(msg, c, cf))
|
codec := newRpcPlusCodec(msg, c, cf)
|
||||||
call := client.StreamGo(request.Service(), request.Method(), request.Request(), responseChan)
|
|
||||||
|
err = codec.WriteRequest(&request{
|
||||||
|
Service: req.Service(),
|
||||||
|
ServiceMethod: req.Method(),
|
||||||
|
Seq: 0,
|
||||||
|
}, req.Request())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.InternalServerError("go.micro.client", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return &rpcStream{
|
return &rpcStream{
|
||||||
request: request,
|
context: ctx,
|
||||||
call: call,
|
request: req,
|
||||||
client: client,
|
codec: codec,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,11 +189,11 @@ func (r *rpcClient) Call(ctx context.Context, request Request, response interfac
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcClient) StreamRemote(ctx context.Context, address string, request Request, responseChan interface{}, opts ...CallOption) (Streamer, error) {
|
func (r *rpcClient) StreamRemote(ctx context.Context, address string, request Request, opts ...CallOption) (Streamer, error) {
|
||||||
return r.stream(ctx, address, request, responseChan)
|
return r.stream(ctx, address, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcClient) Stream(ctx context.Context, request Request, responseChan interface{}, opts ...CallOption) (Streamer, error) {
|
func (r *rpcClient) Stream(ctx context.Context, request Request, opts ...CallOption) (Streamer, error) {
|
||||||
var copts callOptions
|
var copts callOptions
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&copts)
|
opt(&copts)
|
||||||
@ -209,7 +218,7 @@ func (r *rpcClient) Stream(ctx context.Context, request Request, responseChan in
|
|||||||
address = fmt.Sprintf("%s:%d", address, node.Port)
|
address = fmt.Sprintf("%s:%d", address, node.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
stream, err := r.stream(ctx, address, request, responseChan)
|
stream, err := r.stream(ctx, address, request)
|
||||||
r.opts.selector.Mark(request.Service(), node, err)
|
r.opts.selector.Mark(request.Service(), node, err)
|
||||||
return stream, err
|
return stream, err
|
||||||
}
|
}
|
||||||
@ -247,14 +256,14 @@ func (r *rpcClient) NewPublication(topic string, message interface{}) Publicatio
|
|||||||
func (r *rpcClient) NewProtoPublication(topic string, message interface{}) Publication {
|
func (r *rpcClient) NewProtoPublication(topic string, message interface{}) Publication {
|
||||||
return newRpcPublication(topic, message, "application/octet-stream")
|
return newRpcPublication(topic, message, "application/octet-stream")
|
||||||
}
|
}
|
||||||
func (r *rpcClient) NewRequest(service, method string, request interface{}) Request {
|
func (r *rpcClient) NewRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||||
return newRpcRequest(service, method, request, r.opts.contentType)
|
return newRpcRequest(service, method, request, r.opts.contentType, reqOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcClient) NewProtoRequest(service, method string, request interface{}) Request {
|
func (r *rpcClient) NewProtoRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||||
return newRpcRequest(service, method, request, "application/octet-stream")
|
return newRpcRequest(service, method, request, "application/octet-stream", reqOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcClient) NewJsonRequest(service, method string, request interface{}) Request {
|
func (r *rpcClient) NewJsonRequest(service, method string, request interface{}, reqOpts ...RequestOption) Request {
|
||||||
return newRpcRequest(service, method, request, "application/json")
|
return newRpcRequest(service, method, request, "application/json", reqOpts...)
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,22 @@ type rpcRequest struct {
|
|||||||
method string
|
method string
|
||||||
contentType string
|
contentType string
|
||||||
request interface{}
|
request interface{}
|
||||||
|
opts requestOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRpcRequest(service, method string, request interface{}, contentType string) Request {
|
func newRpcRequest(service, method string, request interface{}, contentType string, reqOpts ...RequestOption) Request {
|
||||||
|
var opts requestOptions
|
||||||
|
|
||||||
|
for _, o := range reqOpts {
|
||||||
|
o(&opts)
|
||||||
|
}
|
||||||
|
|
||||||
return &rpcRequest{
|
return &rpcRequest{
|
||||||
service: service,
|
service: service,
|
||||||
method: method,
|
method: method,
|
||||||
request: request,
|
request: request,
|
||||||
contentType: contentType,
|
contentType: contentType,
|
||||||
|
opts: opts,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,3 +39,7 @@ func (r *rpcRequest) Method() string {
|
|||||||
func (r *rpcRequest) Request() interface{} {
|
func (r *rpcRequest) Request() interface{} {
|
||||||
return r.request
|
return r.request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *rpcRequest) Stream() bool {
|
||||||
|
return r.opts.stream
|
||||||
|
}
|
||||||
|
@ -1,19 +1,112 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
type rpcStream struct {
|
type rpcStream struct {
|
||||||
|
sync.RWMutex
|
||||||
|
seq uint64
|
||||||
|
closed bool
|
||||||
|
err error
|
||||||
request Request
|
request Request
|
||||||
call *call
|
codec clientCodec
|
||||||
client *client
|
context context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Context() context.Context {
|
||||||
|
return r.context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcStream) Request() Request {
|
func (r *rpcStream) Request() Request {
|
||||||
return r.request
|
return r.request
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Send(msg interface{}) error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
if r.closed {
|
||||||
|
r.err = errShutdown
|
||||||
|
return errShutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
seq := r.seq
|
||||||
|
r.seq++
|
||||||
|
|
||||||
|
req := request{
|
||||||
|
Service: r.request.Service(),
|
||||||
|
Seq: seq,
|
||||||
|
ServiceMethod: r.request.Method(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.codec.WriteRequest(&req, msg); err != nil {
|
||||||
|
r.err = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Recv(msg interface{}) error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
if r.closed {
|
||||||
|
r.err = errShutdown
|
||||||
|
return errShutdown
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp response
|
||||||
|
if err := r.codec.ReadResponseHeader(&resp); err != nil {
|
||||||
|
if err == io.EOF && !r.closed {
|
||||||
|
r.err = io.ErrUnexpectedEOF
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
r.err = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case len(resp.Error) > 0:
|
||||||
|
// We've got an error response. Give this to the request;
|
||||||
|
// any subsequent requests will get the ReadResponseBody
|
||||||
|
// error if there is one.
|
||||||
|
if resp.Error != lastStreamResponseError {
|
||||||
|
r.err = serverError(resp.Error)
|
||||||
|
} else {
|
||||||
|
r.err = io.EOF
|
||||||
|
}
|
||||||
|
if err := r.codec.ReadResponseBody(nil); err != nil {
|
||||||
|
r.err = errors.New("reading error payload: " + err.Error())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if err := r.codec.ReadResponseBody(msg); err != nil {
|
||||||
|
r.err = errors.New("reading body " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.err != nil && r.err != io.EOF && !r.closed {
|
||||||
|
log.Println("rpc: client protocol error:", r.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *rpcStream) Error() error {
|
func (r *rpcStream) Error() error {
|
||||||
return r.call.Error
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
return r.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rpcStream) Close() error {
|
func (r *rpcStream) Close() error {
|
||||||
return r.client.Close()
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
r.closed = true
|
||||||
|
return r.codec.Close()
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"reflect"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/youtube/vitess/go/trace"
|
"github.com/youtube/vitess/go/trace"
|
||||||
@ -38,7 +37,6 @@ type call struct {
|
|||||||
Reply interface{} // The reply from the function (*struct for single, chan * struct for streaming).
|
Reply interface{} // The reply from the function (*struct for single, chan * struct for streaming).
|
||||||
Error error // After completion, the error status.
|
Error error // After completion, the error status.
|
||||||
Done chan *call // Strobes when call is complete (nil for streaming RPCs)
|
Done chan *call // Strobes when call is complete (nil for streaming RPCs)
|
||||||
Stream bool // True for a streaming RPC call, false otherwise
|
|
||||||
Subseq uint64 // The next expected subseq in the packets
|
Subseq uint64 // The next expected subseq in the packets
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,28 +143,12 @@ func (client *client) input() {
|
|||||||
// We've got an error response. Give this to the request;
|
// We've got an error response. Give this to the request;
|
||||||
// any subsequent requests will get the ReadResponseBody
|
// any subsequent requests will get the ReadResponseBody
|
||||||
// error if there is one.
|
// error if there is one.
|
||||||
if !(call.Stream && resp.Error == lastStreamResponseError) {
|
call.Error = serverError(resp.Error)
|
||||||
call.Error = serverError(resp.Error)
|
|
||||||
}
|
|
||||||
err = client.codec.ReadResponseBody(nil)
|
err = client.codec.ReadResponseBody(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = errors.New("reading error payload: " + err.Error())
|
err = errors.New("reading error payload: " + err.Error())
|
||||||
}
|
}
|
||||||
client.done(seq)
|
client.done(seq)
|
||||||
case call.Stream:
|
|
||||||
// call.Reply is a chan *T2
|
|
||||||
// we need to create a T2 and get a *T2 back
|
|
||||||
value := reflect.New(reflect.TypeOf(call.Reply).Elem().Elem()).Interface()
|
|
||||||
err = client.codec.ReadResponseBody(value)
|
|
||||||
if err != nil {
|
|
||||||
call.Error = errors.New("reading body " + err.Error())
|
|
||||||
} else {
|
|
||||||
// writing on the channel could block forever. For
|
|
||||||
// instance, if a client calls 'close', this might block
|
|
||||||
// forever. the current suggestion is for the
|
|
||||||
// client to drain the receiving channel in that case
|
|
||||||
reflect.ValueOf(call.Reply).Send(reflect.ValueOf(value))
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
err = client.codec.ReadResponseBody(call.Reply)
|
err = client.codec.ReadResponseBody(call.Reply)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -203,12 +185,6 @@ func (client *client) done(seq uint64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (call *call) done() {
|
func (call *call) done() {
|
||||||
if call.Stream {
|
|
||||||
// need to close the channel. client won't be able to read any more.
|
|
||||||
reflect.ValueOf(call.Reply).Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case call.Done <- call:
|
case call.Done <- call:
|
||||||
// ok
|
// ok
|
||||||
@ -270,28 +246,6 @@ func (client *client) Go(ctx context.Context, service, serviceMethod string, arg
|
|||||||
return cal
|
return cal
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamGo invokes the streaming function asynchronously. It returns the call structure representing
|
|
||||||
// the invocation.
|
|
||||||
func (client *client) StreamGo(service string, serviceMethod string, args interface{}, replyStream interface{}) *call {
|
|
||||||
// first check the replyStream object is a stream of pointers to a data structure
|
|
||||||
typ := reflect.TypeOf(replyStream)
|
|
||||||
// FIXME: check the direction of the channel, maybe?
|
|
||||||
if typ.Kind() != reflect.Chan || typ.Elem().Kind() != reflect.Ptr {
|
|
||||||
log.Panic("rpc: replyStream is not a channel of pointers")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
call := new(call)
|
|
||||||
call.Service = service
|
|
||||||
call.ServiceMethod = serviceMethod
|
|
||||||
call.Args = args
|
|
||||||
call.Reply = replyStream
|
|
||||||
call.Stream = true
|
|
||||||
call.Subseq = 0
|
|
||||||
client.send(call)
|
|
||||||
return call
|
|
||||||
}
|
|
||||||
|
|
||||||
// call invokes the named function, waits for it to complete, and returns its error status.
|
// call invokes the named function, waits for it to complete, and returns its error status.
|
||||||
func (client *client) Call(ctx context.Context, service string, serviceMethod string, args interface{}, reply interface{}) error {
|
func (client *client) Call(ctx context.Context, service string, serviceMethod string, args interface{}, reply interface{}) error {
|
||||||
call := <-client.Go(ctx, service, serviceMethod, args, reply, make(chan *call, 1)).Done
|
call := <-client.Go(ctx, service, serviceMethod, args, reply, make(chan *call, 1)).Done
|
||||||
|
@ -60,15 +60,19 @@ func stream() {
|
|||||||
Count: int64(10),
|
Count: int64(10),
|
||||||
})
|
})
|
||||||
|
|
||||||
rspChan := make(chan *example.StreamingResponse, 10)
|
stream, err := client.Stream(context.Background(), req)
|
||||||
|
|
||||||
stream, err := client.Stream(context.Background(), req, rspChan)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("err:", err)
|
fmt.Println("err:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for rsp := range rspChan {
|
for stream.Error() == nil {
|
||||||
|
rsp := &example.StreamingResponse{}
|
||||||
|
err := stream.Recv(rsp)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
break
|
||||||
|
}
|
||||||
fmt.Println("Stream: rsp:", rsp.Count)
|
fmt.Println("Stream: rsp:", rsp.Count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,30 +127,29 @@ func (c *exampleClient) Call(ctx context.Context, in *Request, opts ...client.Ca
|
|||||||
|
|
||||||
func (c *exampleClient) Stream(ctx context.Context, in *StreamingRequest, opts ...client.CallOption) (Example_StreamClient, error) {
|
func (c *exampleClient) Stream(ctx context.Context, in *StreamingRequest, opts ...client.CallOption) (Example_StreamClient, error) {
|
||||||
req := c.c.NewRequest(c.serviceName, "Example.Stream", in)
|
req := c.c.NewRequest(c.serviceName, "Example.Stream", in)
|
||||||
outCh := make(chan *StreamingResponse)
|
stream, err := c.c.Stream(ctx, req, opts...)
|
||||||
stream, err := c.c.Stream(ctx, req, outCh, opts...)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &exampleStreamClient{stream, outCh}, nil
|
return &exampleStreamClient{stream}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Example_StreamClient interface {
|
type Example_StreamClient interface {
|
||||||
Next() (*StreamingResponse, error)
|
RecvMsg() (*StreamingResponse, error)
|
||||||
client.Streamer
|
client.Streamer
|
||||||
}
|
}
|
||||||
|
|
||||||
type exampleStreamClient struct {
|
type exampleStreamClient struct {
|
||||||
client.Streamer
|
client.Streamer
|
||||||
next chan *StreamingResponse
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *exampleStreamClient) Next() (*StreamingResponse, error) {
|
func (x *exampleStreamClient) RecvMsg() (*StreamingResponse, error) {
|
||||||
out, ok := <-x.next
|
m := new(StreamingResponse)
|
||||||
if !ok {
|
err := x.Recv(m)
|
||||||
return nil, fmt.Errorf(`chan closed`)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return out, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server API for Example service
|
// Server API for Example service
|
||||||
|
79
server/rpc_stream.go
Normal file
79
server/rpc_stream.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rpcStream struct {
|
||||||
|
sync.RWMutex
|
||||||
|
seq uint64
|
||||||
|
closed bool
|
||||||
|
err error
|
||||||
|
request Request
|
||||||
|
codec serverCodec
|
||||||
|
context context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Context() context.Context {
|
||||||
|
return r.context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Request() Request {
|
||||||
|
return r.request
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Send(msg interface{}) error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
seq := r.seq
|
||||||
|
r.seq++
|
||||||
|
|
||||||
|
resp := response{
|
||||||
|
ServiceMethod: r.request.Method(),
|
||||||
|
Seq: seq,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := codec.WriteResponse(&resp, msg, false)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("rpc: writing response:", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Recv(msg interface{}) error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
|
||||||
|
req := request{}
|
||||||
|
|
||||||
|
if err := codec.ReadRequestHeader(&req); err != nil {
|
||||||
|
// discard body
|
||||||
|
codec.ReadRequestBody(nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = codec.ReadRequestBody(msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Error() error {
|
||||||
|
r.RLock()
|
||||||
|
defer r.RUnlock()
|
||||||
|
return r.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *rpcStream) Close() error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
r.closed = true
|
||||||
|
return r.codec.Close()
|
||||||
|
}
|
@ -35,6 +35,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/golang/glog"
|
log "github.com/golang/glog"
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server interface {
|
type Server interface {
|
||||||
@ -61,10 +62,23 @@ type Request interface {
|
|||||||
Method() string
|
Method() string
|
||||||
ContentType() string
|
ContentType() string
|
||||||
Request() interface{}
|
Request() interface{}
|
||||||
// indicates whether the response should be streaming
|
// indicates whether the request will be streamed
|
||||||
Stream() bool
|
Stream() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Streamer represents a stream established with a client.
|
||||||
|
// A stream can be bidirectional which is indicated by the request.
|
||||||
|
// The last error will be left in Error().
|
||||||
|
// EOF indicated end of the stream.
|
||||||
|
type Streamer interface {
|
||||||
|
Context() context.Context
|
||||||
|
Request() Request
|
||||||
|
Send(interface{}) error
|
||||||
|
Recv(interface{}) error
|
||||||
|
Error() error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
type Option func(*options)
|
type Option func(*options)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -19,3 +19,5 @@ type HandlerWrapper func(HandlerFunc) HandlerFunc
|
|||||||
|
|
||||||
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
|
// SubscriberWrapper wraps the SubscriberFunc and returns the equivalent
|
||||||
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
|
type SubscriberWrapper func(SubscriberFunc) SubscriberFunc
|
||||||
|
|
||||||
|
type StreamWrapper func(Streamer) Streamer
|
||||||
|
Loading…
Reference in New Issue
Block a user