mirror of https://github.com/grpc/grpc-go.git
grpc-go initial commit
This commit is contained in:
commit
c463038273
|
@ -0,0 +1,28 @@
|
|||
Copyright 2014, Google Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,5 @@
|
|||
gRPC-Go: a Go implementation of gRPC, Google's RPC library
|
||||
|
||||
To install this package, you need to install Go 1.4 and setup your Go workspace on your computer. The simplest way to install the library is to run:
|
||||
|
||||
go get github.com/google/grpc-go/rpc
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
"github.com/grpc/grpc-go/rpc/transport"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// recv receives and parses an RPC response.
|
||||
// On error, it returns the error and indicates whether the call should be retried.
|
||||
//
|
||||
// TODO(zhaoq): Check whether the received message sequence is valid.
|
||||
func recv(stream *transport.Stream, reply proto.Message) error {
|
||||
p := &parser{s: stream}
|
||||
for {
|
||||
if err := recvProto(p, reply); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendRPC writes out various information of an RPC such as Context and Message.
|
||||
func sendRPC(ctx context.Context, callHdr *transport.CallHdr, t transport.ClientTransport, args proto.Message, opts *transport.Options) (_ *transport.Stream, err error) {
|
||||
stream, err := t.NewStream(ctx, callHdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if _, ok := err.(transport.ConnectionError); !ok {
|
||||
t.CloseStream(stream, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// TODO(zhaoq): Support compression.
|
||||
outBuf, err := encode(args, compressionNone)
|
||||
if err != nil {
|
||||
return nil, transport.StreamErrorf(codes.Internal, "%v", err)
|
||||
}
|
||||
err = t.Write(stream, outBuf, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Sent successfully.
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// callInfo contains all related configuration and information about an RPC.
|
||||
type callInfo struct {
|
||||
failFast bool
|
||||
headerMD metadata.MD
|
||||
trailerMD metadata.MD
|
||||
}
|
||||
|
||||
// Invoke is called by the generated code. It sends the RPC request on the
|
||||
// wire and returns after response is received.
|
||||
func Invoke(ctx context.Context, method string, args, reply proto.Message, cc *ClientConn, opts ...CallOption) error {
|
||||
var c callInfo
|
||||
for _, o := range opts {
|
||||
if err := o.before(&c); err != nil {
|
||||
return toRPCErr(err)
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
for _, o := range opts {
|
||||
o.after(&c)
|
||||
}
|
||||
}()
|
||||
callHdr := &transport.CallHdr{
|
||||
Host: cc.target,
|
||||
Method: method,
|
||||
}
|
||||
topts := &transport.Options{
|
||||
Last: true,
|
||||
Delay: false,
|
||||
}
|
||||
ts := 0
|
||||
var lastErr error // record the error that happened
|
||||
for {
|
||||
var (
|
||||
err error
|
||||
t transport.ClientTransport
|
||||
stream *transport.Stream
|
||||
)
|
||||
// TODO(zhaoq): Need a formal spec of retry strategy for non-failfast rpcs.
|
||||
if lastErr != nil && c.failFast {
|
||||
return lastErr
|
||||
}
|
||||
t, ts, err = cc.wait(ctx, ts)
|
||||
if err != nil {
|
||||
if lastErr != nil {
|
||||
// This was a retry; return the error from the last attempt.
|
||||
return lastErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
stream, err = sendRPC(ctx, callHdr, t, args, topts)
|
||||
if err != nil {
|
||||
if _, ok := err.(transport.ConnectionError); ok {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
if lastErr != nil {
|
||||
return toRPCErr(lastErr)
|
||||
}
|
||||
return toRPCErr(err)
|
||||
}
|
||||
// Try to acquire header metadata from the server if there is any.
|
||||
c.headerMD, err = stream.Header()
|
||||
if err != nil {
|
||||
return toRPCErr(err)
|
||||
}
|
||||
// Receive the response
|
||||
lastErr = recv(stream, reply)
|
||||
if _, ok := lastErr.(transport.ConnectionError); ok {
|
||||
continue
|
||||
}
|
||||
c.trailerMD = stream.Trailer()
|
||||
t.CloseStream(stream, lastErr)
|
||||
if lastErr != nil {
|
||||
return toRPCErr(lastErr)
|
||||
}
|
||||
return Errorf(stream.StatusCode(), stream.StatusDesc())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
Package rpc implements various components to perform RPC on top of transport package.
|
||||
*/
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grpc/grpc-go/rpc/credentials"
|
||||
"github.com/grpc/grpc-go/rpc/transport"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type dialOptions struct {
|
||||
protocol string
|
||||
authOptions []credentials.Credentials
|
||||
}
|
||||
|
||||
// DialOption configures how we set up the connection including auth
|
||||
// credentials.
|
||||
type DialOption func(*dialOptions)
|
||||
|
||||
// WithClientTLS returns a DialOption which configures a TLS credentials
|
||||
// for connection.
|
||||
func WithClientTLS(creds credentials.TransportAuthenticator) DialOption {
|
||||
return func(o *dialOptions) {
|
||||
o.authOptions = append(o.authOptions, creds)
|
||||
}
|
||||
}
|
||||
|
||||
// WithComputeEngine returns a DialOption which sets
|
||||
// credentials which use application default credentials as provided to
|
||||
// Google Compute Engine. Note that TLS credentials is typically also
|
||||
// needed. If it is the case, users need to pass WithTLS option too.
|
||||
func WithComputeEngine(creds credentials.Credentials) DialOption {
|
||||
return func(o *dialOptions) {
|
||||
o.authOptions = append(o.authOptions, creds)
|
||||
}
|
||||
}
|
||||
|
||||
// Dial creates a client connection the given target.
|
||||
// TODO(zhaoq): Have an option to make Dial return immediately without waiting
|
||||
// for connection to complete.
|
||||
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
|
||||
if target == "" {
|
||||
return nil, fmt.Errorf("rpc.Dial: target is empty")
|
||||
}
|
||||
cc := &ClientConn{
|
||||
target: target,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&cc.dopts)
|
||||
}
|
||||
err := cc.resetTransport(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc.shutdownChan = make(chan struct{})
|
||||
// Start to monitor the error status of transport.
|
||||
go cc.transportMonitor()
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// ClientConn represents a client connection to an RPC service.
|
||||
type ClientConn struct {
|
||||
target string
|
||||
dopts dialOptions
|
||||
shutdownChan chan struct{}
|
||||
|
||||
mu sync.Mutex
|
||||
// Is closed and becomes nil when a new transport is up.
|
||||
ready chan struct{}
|
||||
// Indicates the ClientConn is under destruction.
|
||||
closing bool
|
||||
// Every time a new transport is created, this is incremented by 1. Used
|
||||
// to avoid trying to recreate a transport while the new one is already
|
||||
// under construction.
|
||||
transportSeq int
|
||||
transport transport.ClientTransport
|
||||
}
|
||||
|
||||
func (cc *ClientConn) resetTransport(closeTransport bool) error {
|
||||
var retries int
|
||||
for {
|
||||
cc.mu.Lock()
|
||||
t := cc.transport
|
||||
ts := cc.transportSeq
|
||||
// Avoid wait() picking up a dying transport unnecessarily.
|
||||
cc.transportSeq = 0
|
||||
if cc.closing {
|
||||
cc.mu.Unlock()
|
||||
return fmt.Errorf("rpc.ClientConn.resetTransport: the channel is closing")
|
||||
}
|
||||
cc.mu.Unlock()
|
||||
if closeTransport {
|
||||
t.Close()
|
||||
}
|
||||
newTransport, err := transport.NewClientTransport(cc.dopts.protocol, cc.target, cc.dopts.authOptions)
|
||||
if err != nil {
|
||||
// TODO(zhaoq): Record the error with glog.V.
|
||||
closeTransport = false
|
||||
time.Sleep(backoff(retries))
|
||||
retries++
|
||||
continue
|
||||
}
|
||||
cc.mu.Lock()
|
||||
cc.transport = newTransport
|
||||
cc.transportSeq = ts + 1
|
||||
if cc.ready != nil {
|
||||
close(cc.ready)
|
||||
cc.ready = nil
|
||||
}
|
||||
cc.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Run in a goroutine to track the error in transport and create the
|
||||
// new transport if an error happens. It returns when the channel is closing.
|
||||
func (cc *ClientConn) transportMonitor() {
|
||||
for {
|
||||
select {
|
||||
// shutdownChan is needed to detect the channel teardown when
|
||||
// the ClientConn is idle (i.e., no RPC in flight).
|
||||
case <-cc.shutdownChan:
|
||||
return
|
||||
case <-cc.transport.Error():
|
||||
err := cc.resetTransport(true)
|
||||
if err != nil {
|
||||
// The channel is closing.
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When wait returns, either the new transport is up or ClientConn is
|
||||
// closing. Used to avoid working on a dying transport. It updates and
|
||||
// returns the transport and its version when there is no error.
|
||||
func (cc *ClientConn) wait(ctx context.Context, ts int) (transport.ClientTransport, int, error) {
|
||||
for {
|
||||
cc.mu.Lock()
|
||||
switch {
|
||||
case cc.closing:
|
||||
cc.mu.Unlock()
|
||||
return nil, 0, fmt.Errorf("ClientConn is closing")
|
||||
case ts < cc.transportSeq:
|
||||
// Worked on a dying transport. Try the new one immediately.
|
||||
defer cc.mu.Unlock()
|
||||
return cc.transport, cc.transportSeq, nil
|
||||
default:
|
||||
ready := cc.ready
|
||||
if ready == nil {
|
||||
ready = make(chan struct{})
|
||||
cc.ready = ready
|
||||
}
|
||||
cc.mu.Unlock()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, 0, transport.ContextErr(ctx.Err())
|
||||
// Wait until the new transport is ready.
|
||||
case <-ready:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close starts to tear down the ClientConn.
|
||||
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in
|
||||
// some edge cases (e.g., the caller opens and closes many ClientConn's in a
|
||||
// tight loop.
|
||||
func (cc *ClientConn) Close() {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if cc.closing {
|
||||
return
|
||||
}
|
||||
cc.closing = true
|
||||
cc.transport.Close()
|
||||
close(cc.shutdownChan)
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package codes defines the canonical error codes used by gRPC. It is
|
||||
// consistent across various languages.
|
||||
package codes
|
||||
|
||||
// A Code is an unsigned 32-bit error code as defined in the gRPC spec.
|
||||
type Code uint32
|
||||
|
||||
const (
|
||||
// OK is returned on success.
|
||||
OK Code = 0
|
||||
|
||||
// Canceled indicates the operation was cancelled (typically by the caller).
|
||||
Canceled Code = 1
|
||||
|
||||
// Unknown error. An example of where this error may be returned is
|
||||
// if a Status value received from another address space belongs to
|
||||
// an error-space that is not known in this address space. Also
|
||||
// errors raised by APIs that do not return enough error information
|
||||
// may be converted to this error.
|
||||
Unknown Code = 2
|
||||
|
||||
// InvalidArgument indicates client specified an invalid argument.
|
||||
// Note that this differs from FailedPrecondition. It indicates arguments
|
||||
// that are problematic regardless of the state of the system
|
||||
// (e.g., a malformed file name).
|
||||
InvalidArgument Code = 3
|
||||
|
||||
// DeadlineExceeded means operation expired before completion.
|
||||
// For operations that change the state of the system, this error may be
|
||||
// returned even if the operation has completed successfully. For
|
||||
// example, a successful response from a server could have been delayed
|
||||
// long enough for the deadline to expire.
|
||||
DeadlineExceeded Code = 4
|
||||
|
||||
// NotFound means some requested entity (e.g., file or directory) was
|
||||
// not found.
|
||||
NotFound Code = 5
|
||||
|
||||
// AlreadyExists means an attempt to create an entity failed because one
|
||||
// already exists.
|
||||
AlreadyExists Code = 6
|
||||
|
||||
// PermissionDenied indicates the caller does not have permission to
|
||||
// execute the specified operation. It must not be used for rejections
|
||||
// caused by exhausting some resource (use ResourceExhausted
|
||||
// instead for those errors). It must not be
|
||||
// used if the caller cannot be identified (use Unauthenticated
|
||||
// instead for those errors).
|
||||
PermissionDenied Code = 7
|
||||
|
||||
// Unauthenticated indicates the request does not have valid
|
||||
// authentication credentials for the operation.
|
||||
Unauthenticated Code = 16
|
||||
|
||||
// ResourceExhausted indicates some resource has been exhausted, perhaps
|
||||
// a per-user quota, or perhaps the entire file system is out of space.
|
||||
ResourceExhausted Code = 8
|
||||
|
||||
// FailedPrecondition indicates operation was rejected because the
|
||||
// system is not in a state required for the operation's execution.
|
||||
// For example, directory to be deleted may be non-empty, an rmdir
|
||||
// operation is applied to a non-directory, etc.
|
||||
//
|
||||
// A litmus test that may help a service implementor in deciding
|
||||
// between FailedPrecondition, Aborted, and Unavailable:
|
||||
// (a) Use Unavailable if the client can retry just the failing call.
|
||||
// (b) Use Aborted if the client should retry at a higher-level
|
||||
// (e.g., restarting a read-modify-write sequence).
|
||||
// (c) Use FailedPrecondition if the client should not retry until
|
||||
// the system state has been explicitly fixed. E.g., if an "rmdir"
|
||||
// fails because the directory is non-empty, FailedPrecondition
|
||||
// should be returned since the client should not retry unless
|
||||
// they have first fixed up the directory by deleting files from it.
|
||||
// (d) Use FailedPrecondition if the client performs conditional
|
||||
// REST Get/Update/Delete on a resource and the resource on the
|
||||
// server does not match the condition. E.g., conflicting
|
||||
// read-modify-write on the same resource.
|
||||
FailedPrecondition Code = 9
|
||||
|
||||
// Aborted indicates the operation was aborted, typically due to a
|
||||
// concurrency issue like sequencer check failures, transaction aborts,
|
||||
// etc.
|
||||
//
|
||||
// See litmus test above for deciding between FailedPrecondition,
|
||||
// Aborted, and Unavailable.
|
||||
Aborted Code = 10
|
||||
|
||||
// OutOfRange means operation was attempted past the valid range.
|
||||
// E.g., seeking or reading past end of file.
|
||||
//
|
||||
// Unlike InvalidArgument, this error indicates a problem that may
|
||||
// be fixed if the system state changes. For example, a 32-bit file
|
||||
// system will generate InvalidArgument if asked to read at an
|
||||
// offset that is not in the range [0,2^32-1], but it will generate
|
||||
// OutOfRange if asked to read from an offset past the current
|
||||
// file size.
|
||||
//
|
||||
// There is a fair bit of overlap between FailedPrecondition and
|
||||
// OutOfRange. We recommend using OutOfRange (the more specific
|
||||
// error) when it applies so that callers who are iterating through
|
||||
// a space can easily look for an OutOfRange error to detect when
|
||||
// they are done.
|
||||
OutOfRange Code = 11
|
||||
|
||||
// Unimplemented indicates operation is not implemented or not
|
||||
// supported/enabled in this service.
|
||||
Unimplemented Code = 12
|
||||
|
||||
// Internal errors. Means some invariants expected by underlying
|
||||
// system has been broken. If you see one of these errors,
|
||||
// something is very broken.
|
||||
Internal Code = 13
|
||||
|
||||
// Unavailable indicates the service is currently unavailable.
|
||||
// This is a most likely a transient condition and may be corrected
|
||||
// by retrying with a backoff.
|
||||
//
|
||||
// See litmus test above for deciding between FailedPrecondition,
|
||||
// Aborted, and Unavailable.
|
||||
Unavailable Code = 14
|
||||
|
||||
// DataLoss indicates unrecoverable data loss or corruption.
|
||||
DataLoss Code = 15
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
CC=g++
|
||||
CFLAGS=-c -Wall `pkg-config --cflags protobuf` -std=c++11
|
||||
LDFLAGS=-g
|
||||
LDLIBS=`pkg-config --libs protobuf` -lprotoc
|
||||
SOURCES=go_generator.cc go_plugin.cc
|
||||
OBJECTS=$(SOURCES:.cc=.o)
|
||||
EXECUTABLE=go_plugin
|
||||
|
||||
all: $(EXECUTABLE)
|
||||
|
||||
$(EXECUTABLE): $(OBJECTS)
|
||||
$(CC) $(LDFLAGS) $(OBJECTS) -o $@ $(LDLIBS)
|
||||
|
||||
.cc.o:
|
||||
$(CC) $(CFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
$(RM) $(OBJECTS) $(EXECUTABLE)
|
|
@ -0,0 +1,3 @@
|
|||
The plugin here is a short-term solution and does not support all the use cases.
|
||||
We are working on having a full-fledged solution as part of
|
||||
github.com/golang/protobuf. Once it is online, this code will be deleted.
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script serves as an example to demonstrate how to generate the gRPC-Go
|
||||
# interface and the related messages.
|
||||
#
|
||||
# We suggest the importing paths in proto file are relative to $GOPATH/src and
|
||||
# this script should be run at $GOPATH/src.
|
||||
#
|
||||
# If this is not what you need, feel free to make your own scripts. Again, this
|
||||
# script is for demonstration purpose.
|
||||
#
|
||||
locProtocGenGo=$1
|
||||
locGoPlugIn=$2
|
||||
proto=$3
|
||||
protoc --plugin=protoc-gen-go=$locProtocGenGo --go_out=. $proto
|
||||
protoc --plugin=protoc-gen-gogrpc=$locGoPlugIn --gogrpc_out=. $proto
|
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "./go_generator.h"
|
||||
|
||||
#include <cctype>
|
||||
|
||||
#include <google/protobuf/io/printer.h>
|
||||
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
|
||||
#include <google/protobuf/descriptor.pb.h>
|
||||
#include <google/protobuf/descriptor.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace grpc_go_generator {
|
||||
|
||||
bool NoStreaming(const google::protobuf::MethodDescriptor* method) {
|
||||
return !method->client_streaming() && !method->server_streaming();
|
||||
}
|
||||
|
||||
bool ClientOnlyStreaming(const google::protobuf::MethodDescriptor* method) {
|
||||
return method->client_streaming() && !method->server_streaming();
|
||||
}
|
||||
|
||||
bool ServerOnlyStreaming(const google::protobuf::MethodDescriptor* method) {
|
||||
return !method->client_streaming() && method->server_streaming();
|
||||
}
|
||||
|
||||
bool BidiStreaming(const google::protobuf::MethodDescriptor* method) {
|
||||
return method->client_streaming() && method->server_streaming();
|
||||
}
|
||||
|
||||
bool HasClientOnlyStreaming(const google::protobuf::FileDescriptor* file) {
|
||||
for (int i = 0; i < file->service_count(); i++) {
|
||||
for (int j = 0; j < file->service(i)->method_count(); j++) {
|
||||
if (ClientOnlyStreaming(file->service(i)->method(j))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string LowerCaseService(const string& service) {
|
||||
string ret = service;
|
||||
if (!ret.empty() && ret[0] >= 'A' && ret[0] <= 'Z') {
|
||||
ret[0] = ret[0] - 'A' + 'a';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string BadToUnderscore(std::string str) {
|
||||
for (unsigned i = 0; i < str.size(); ++i) {
|
||||
if (!std::isalnum(str[i])) {
|
||||
str[i] = '_';
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
string GenerateFullGoPackage(const google::protobuf::FileDescriptor* file) {
|
||||
// In opensouce environment, assume each directory has at most one package.
|
||||
size_t pos = file->name().find_last_of('/');
|
||||
if (pos != string::npos) {
|
||||
return file->name().substr(0, pos);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const string GetFullMessageQualifiedName(
|
||||
const google::protobuf::Descriptor* desc,
|
||||
const set<string>& imports,
|
||||
const map<string, string>& import_alias) {
|
||||
string pkg = GenerateFullGoPackage(desc->file());
|
||||
if (imports.find(pkg) == imports.end()) {
|
||||
// The message is in the same package as the services definition.
|
||||
return desc->name();
|
||||
}
|
||||
if (import_alias.find(pkg) != import_alias.end()) {
|
||||
// The message is in a package whose name is as same as the one consisting
|
||||
// of the service definition. Use the alias to differentiate.
|
||||
return import_alias.find(pkg)->second + "." + desc->name();
|
||||
}
|
||||
string prefix = !desc->file()->options().go_package().empty()
|
||||
? desc->file()->options().go_package() : desc->file()->package();
|
||||
return BadToUnderscore(prefix) + "." + desc->name();
|
||||
}
|
||||
|
||||
void PrintClientMethodDef(google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::MethodDescriptor* method,
|
||||
map<string, string>* vars,
|
||||
const set<string>& imports,
|
||||
const map<string, string>& import_alias) {
|
||||
(*vars)["Method"] = method->name();
|
||||
(*vars)["Request"] =
|
||||
GetFullMessageQualifiedName(method->input_type(), imports, import_alias);
|
||||
(*vars)["Response"] =
|
||||
GetFullMessageQualifiedName(method->output_type(), imports, import_alias);
|
||||
if (NoStreaming(method)) {
|
||||
printer->Print(*vars,
|
||||
"\t$Method$(ctx context.Context, in *$Request$, opts "
|
||||
"...rpc.CallOption) "
|
||||
"(*$Response$, error)\n");
|
||||
} else if (BidiStreaming(method)) {
|
||||
printer->Print(*vars,
|
||||
"\t$Method$(ctx context.Context, opts ...rpc.CallOption) "
|
||||
"($Service$_$Method$Client, error)\n");
|
||||
} else if (ServerOnlyStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"\t$Method$(ctx context.Context, m *$Request$, opts ...rpc.CallOption) "
|
||||
"($Service$_$Method$Client, error)\n");
|
||||
} else if (ClientOnlyStreaming(method)) {
|
||||
printer->Print(*vars,
|
||||
"\t$Method$(ctx context.Context, opts ...rpc.CallOption) "
|
||||
"($Service$_$Method$Client, error)\n");
|
||||
}
|
||||
}
|
||||
|
||||
void PrintClientMethodImpl(google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::MethodDescriptor* method,
|
||||
map<string, string>* vars,
|
||||
const set<string>& imports,
|
||||
const map<string, string>& import_alias) {
|
||||
(*vars)["Method"] = method->name();
|
||||
(*vars)["Request"] =
|
||||
GetFullMessageQualifiedName(method->input_type(), imports, import_alias);
|
||||
(*vars)["Response"] =
|
||||
GetFullMessageQualifiedName(method->output_type(), imports, import_alias);
|
||||
if (NoStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (c *$ServiceStruct$Client) $Method$(ctx context.Context, "
|
||||
"in *$Request$, opts ...rpc.CallOption) (*$Response$, error) {\n");
|
||||
printer->Print(*vars, "\tout := new($Response$)\n");
|
||||
printer->Print(*vars,
|
||||
"\terr := rpc.Invoke(ctx, \"/$Package$$Service$/$Method$\", "
|
||||
"in, out, c.cc, opts...)\n");
|
||||
printer->Print("\tif err != nil {\n");
|
||||
printer->Print("\t\treturn nil, err\n");
|
||||
printer->Print("\t}\n");
|
||||
printer->Print("\treturn out, nil\n");
|
||||
printer->Print("}\n\n");
|
||||
} else if (BidiStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (c *$ServiceStruct$Client) $Method$(ctx context.Context, opts "
|
||||
"...rpc.CallOption) ($Service$_$Method$Client, error) {\n"
|
||||
"\tstream, err := rpc.NewClientStream(ctx, c.cc, "
|
||||
"\"/$Package$$Service$/$Method$\", opts...)\n"
|
||||
"\tif err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\treturn &$ServiceStruct$$Method$Client{stream}, nil\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $Service$_$Method$Client interface {\n"
|
||||
"\tSend(*$Request$) error\n"
|
||||
"\tRecv() (*$Response$, error)\n"
|
||||
"\trpc.ClientStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $ServiceStruct$$Method$Client struct {\n"
|
||||
"\trpc.ClientStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Client) Send(m *$Request$) error {\n"
|
||||
"\treturn x.ClientStream.SendProto(m)\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Client) Recv() (*$Response$, error) "
|
||||
"{\n"
|
||||
"\tm := new($Response$)\n"
|
||||
"\tif err := x.ClientStream.RecvProto(m); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\treturn m, nil\n"
|
||||
"}\n\n");
|
||||
} else if (ServerOnlyStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (c *$ServiceStruct$Client) $Method$(ctx context.Context, m "
|
||||
"*$Request$, "
|
||||
"opts ...rpc.CallOption) ($Service$_$Method$Client, error) {\n"
|
||||
"\tstream, err := rpc.NewClientStream(ctx, c.cc, "
|
||||
"\"/$Package$$Service$/$Method$\", opts...)\n"
|
||||
"\tif err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\tx := &$ServiceStruct$$Method$Client{stream}\n"
|
||||
"\tif err := x.ClientStream.SendProto(m); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\tif err := x.ClientStream.CloseSend(); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\treturn x, nil\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $Service$_$Method$Client interface {\n"
|
||||
"\tRecv() (*$Response$, error)\n"
|
||||
"\trpc.ClientStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $ServiceStruct$$Method$Client struct {\n"
|
||||
"\trpc.ClientStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Client) Recv() (*$Response$, error) "
|
||||
"{\n"
|
||||
"\tm := new($Response$)\n"
|
||||
"\tif err := x.ClientStream.RecvProto(m); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\treturn m, nil\n"
|
||||
"}\n\n");
|
||||
} else if (ClientOnlyStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (c *$ServiceStruct$Client) $Method$(ctx context.Context, opts "
|
||||
"...rpc.CallOption) ($Service$_$Method$Client, error) {\n"
|
||||
"\tstream, err := rpc.NewClientStream(ctx, c.cc, "
|
||||
"\"/$Package$$Service$/$Method$\", opts...)\n"
|
||||
"\tif err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\treturn &$ServiceStruct$$Method$Client{stream}, nil\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $Service$_$Method$Client interface {\n"
|
||||
"\tSend(*$Request$) error\n"
|
||||
"\tCloseAndRecv() (*$Response$, error)\n"
|
||||
"\trpc.ClientStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $ServiceStruct$$Method$Client struct {\n"
|
||||
"\trpc.ClientStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Client) Send(m *$Request$) error {\n"
|
||||
"\treturn x.ClientStream.SendProto(m)\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Client) CloseAndRecv() (*$Response$, "
|
||||
"error) {\n"
|
||||
"\tif err := x.ClientStream.CloseSend(); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\tm := new($Response$)\n"
|
||||
"\tif err := x.ClientStream.RecvProto(m); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\t// Read EOF.\n"
|
||||
"\tif err := x.ClientStream.RecvProto(m); err == io.EOF {\n"
|
||||
"\t\treturn m, io.EOF\n"
|
||||
"\t}\n"
|
||||
"\t// gRPC protocol violation.\n"
|
||||
"\treturn m, fmt.Errorf(\"Violate gRPC client streaming protocol: no "
|
||||
"EOF after the response.\")\n"
|
||||
"}\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
void PrintClient(google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::ServiceDescriptor* service,
|
||||
map<string, string>* vars,
|
||||
const set<string>& imports,
|
||||
const map<string, string>& import_alias) {
|
||||
(*vars)["Service"] = service->name();
|
||||
(*vars)["ServiceStruct"] = LowerCaseService(service->name());
|
||||
printer->Print(*vars, "type $Service$Client interface {\n");
|
||||
for (int i = 0; i < service->method_count(); ++i) {
|
||||
PrintClientMethodDef(printer, service->method(i), vars, imports, import_alias);
|
||||
}
|
||||
printer->Print("}\n\n");
|
||||
|
||||
printer->Print(*vars,
|
||||
"type $ServiceStruct$Client struct {\n"
|
||||
"\tcc *rpc.ClientConn\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func New$Service$Client(cc *rpc.ClientConn) $Service$Client {\n"
|
||||
"\treturn &$ServiceStruct$Client{cc}\n"
|
||||
"}\n\n");
|
||||
for (int i = 0; i < service->method_count(); ++i) {
|
||||
PrintClientMethodImpl(printer, service->method(i), vars, imports, import_alias);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintServerMethodDef(google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::MethodDescriptor* method,
|
||||
map<string, string>* vars,
|
||||
const set<string>& imports,
|
||||
const map<string, string>& import_alias) {
|
||||
(*vars)["Method"] = method->name();
|
||||
(*vars)["Request"] =
|
||||
GetFullMessageQualifiedName(method->input_type(), imports, import_alias);
|
||||
(*vars)["Response"] =
|
||||
GetFullMessageQualifiedName(method->output_type(), imports, import_alias);
|
||||
if (NoStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"\t$Method$(context.Context, *$Request$) (*$Response$, error)\n");
|
||||
} else if (BidiStreaming(method)) {
|
||||
printer->Print(*vars, "\t$Method$($Service$_$Method$Server) error\n");
|
||||
} else if (ServerOnlyStreaming(method)) {
|
||||
printer->Print(*vars,
|
||||
"\t$Method$(*$Request$, $Service$_$Method$Server) error\n");
|
||||
} else if (ClientOnlyStreaming(method)) {
|
||||
printer->Print(*vars, "\t$Method$($Service$_$Method$Server) error\n");
|
||||
}
|
||||
}
|
||||
|
||||
void PrintServerHandler(google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::MethodDescriptor* method,
|
||||
map<string, string>* vars,
|
||||
const set<string>& imports,
|
||||
const map<string, string>& import_alias) {
|
||||
(*vars)["Method"] = method->name();
|
||||
(*vars)["Request"] =
|
||||
GetFullMessageQualifiedName(method->input_type(), imports, import_alias);
|
||||
(*vars)["Response"] =
|
||||
GetFullMessageQualifiedName(method->output_type(), imports, import_alias);
|
||||
if (NoStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func _$Service$_$Method$_Handler(srv interface{}, ctx context.Context,"
|
||||
" buf []byte) (proto.Message, error) {\n");
|
||||
printer->Print(*vars, "\tin := new($Request$)\n");
|
||||
printer->Print("\tif err := proto.Unmarshal(buf, in); err != nil {\n");
|
||||
printer->Print("\t\treturn nil, err\n");
|
||||
printer->Print("\t}\n");
|
||||
printer->Print(*vars,
|
||||
"\tout, err := srv.($Service$Server).$Method$(ctx, in)\n");
|
||||
printer->Print("\tif err != nil {\n");
|
||||
printer->Print("\t\treturn nil, err\n");
|
||||
printer->Print("\t}\n");
|
||||
printer->Print("\treturn out, nil\n");
|
||||
printer->Print("}\n\n");
|
||||
} else if (BidiStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func _$Service$_$Method$_Handler(srv interface{}, stream rpc.ServerStream) "
|
||||
"error {\n"
|
||||
"\treturn srv.($Service$Server).$Method$(&$ServiceStruct$$Method$Server"
|
||||
"{stream})\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $Service$_$Method$Server interface {\n"
|
||||
"\tSend(*$Response$) error\n"
|
||||
"\tRecv() (*$Request$, error)\n"
|
||||
"\trpc.ServerStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $ServiceStruct$$Method$Server struct {\n"
|
||||
"\trpc.ServerStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Server) Send(m *$Response$) error {\n"
|
||||
"\treturn x.ServerStream.SendProto(m)\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Server) Recv() (*$Request$, error) "
|
||||
"{\n"
|
||||
"\tm := new($Request$)\n"
|
||||
"\tif err := x.ServerStream.RecvProto(m); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\treturn m, nil\n"
|
||||
"}\n\n");
|
||||
} else if (ServerOnlyStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func _$Service$_$Method$_Handler(srv interface{}, stream rpc.ServerStream) "
|
||||
"error {\n"
|
||||
"\tm := new($Request$)\n"
|
||||
"\tif err := stream.RecvProto(m); err != nil {\n"
|
||||
"\t\treturn err\n"
|
||||
"\t}\n"
|
||||
"\treturn srv.($Service$Server).$Method$(m, "
|
||||
"&$ServiceStruct$$Method$Server{stream})\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $Service$_$Method$Server interface {\n"
|
||||
"\tSend(*$Response$) error\n"
|
||||
"\trpc.ServerStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $ServiceStruct$$Method$Server struct {\n"
|
||||
"\trpc.ServerStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Server) Send(m *$Response$) error {\n"
|
||||
"\treturn x.ServerStream.SendProto(m)\n"
|
||||
"}\n\n");
|
||||
} else if (ClientOnlyStreaming(method)) {
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func _$Service$_$Method$_Handler(srv interface{}, stream rpc.ServerStream) "
|
||||
"error {\n"
|
||||
"\treturn srv.($Service$Server).$Method$(&$ServiceStruct$$Method$Server"
|
||||
"{stream})\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $Service$_$Method$Server interface {\n"
|
||||
"\tSendAndClose(*$Response$) error\n"
|
||||
"\tRecv() (*$Request$, error)\n"
|
||||
"\trpc.ServerStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(*vars,
|
||||
"type $ServiceStruct$$Method$Server struct {\n"
|
||||
"\trpc.ServerStream\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Server) SendAndClose(m *$Response$) "
|
||||
"error {\n"
|
||||
"\tif err := x.ServerStream.SendProto(m); err != nil {\n"
|
||||
"\t\treturn err\n"
|
||||
"\t}\n"
|
||||
"\treturn nil\n"
|
||||
"}\n\n");
|
||||
printer->Print(
|
||||
*vars,
|
||||
"func (x *$ServiceStruct$$Method$Server) Recv() (*$Request$, error) {\n"
|
||||
"\tm := new($Request$)\n"
|
||||
"\tif err := x.ServerStream.RecvProto(m); err != nil {\n"
|
||||
"\t\treturn nil, err\n"
|
||||
"\t}\n"
|
||||
"\treturn m, nil\n"
|
||||
"}\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
void PrintServerMethodDesc(google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::MethodDescriptor* method,
|
||||
map<string, string>* vars) {
|
||||
(*vars)["Method"] = method->name();
|
||||
printer->Print("\t\t{\n");
|
||||
printer->Print(*vars, "\t\t\tMethodName:\t\"$Method$\",\n");
|
||||
printer->Print(*vars, "\t\t\tHandler:\t_$Service$_$Method$_Handler,\n");
|
||||
printer->Print("\t\t},\n");
|
||||
}
|
||||
|
||||
void PrintServerStreamingMethodDesc(
|
||||
google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::MethodDescriptor* method,
|
||||
map<string, string>* vars) {
|
||||
(*vars)["Method"] = method->name();
|
||||
printer->Print("\t\t{\n");
|
||||
printer->Print(*vars, "\t\t\tStreamName:\t\"$Method$\",\n");
|
||||
printer->Print(*vars, "\t\t\tHandler:\t_$Service$_$Method$_Handler,\n");
|
||||
printer->Print("\t\t},\n");
|
||||
}
|
||||
|
||||
void PrintServer(google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::ServiceDescriptor* service,
|
||||
map<string, string>* vars,
|
||||
const set<string>& imports,
|
||||
const map<string, string>& import_alias) {
|
||||
(*vars)["Service"] = service->name();
|
||||
printer->Print(*vars, "type $Service$Server interface {\n");
|
||||
for (int i = 0; i < service->method_count(); ++i) {
|
||||
PrintServerMethodDef(printer, service->method(i), vars, imports, import_alias);
|
||||
}
|
||||
printer->Print("}\n\n");
|
||||
|
||||
printer->Print(*vars,
|
||||
"func RegisterService(s *rpc.Server, srv $Service$Server) {\n"
|
||||
"\ts.RegisterService(&_$Service$_serviceDesc, srv)\n"
|
||||
"}\n\n");
|
||||
|
||||
for (int i = 0; i < service->method_count(); ++i) {
|
||||
PrintServerHandler(printer, service->method(i), vars, imports, import_alias);
|
||||
}
|
||||
|
||||
printer->Print(*vars,
|
||||
"var _$Service$_serviceDesc = rpc.ServiceDesc{\n"
|
||||
"\tServiceName: \"$Package$$Service$\",\n"
|
||||
"\tHandlerType: (*$Service$Server)(nil),\n"
|
||||
"\tMethods: []rpc.MethodDesc{\n");
|
||||
for (int i = 0; i < service->method_count(); ++i) {
|
||||
if (NoStreaming(service->method(i))) {
|
||||
PrintServerMethodDesc(printer, service->method(i), vars);
|
||||
}
|
||||
}
|
||||
printer->Print("\t},\n");
|
||||
|
||||
printer->Print("\tStreams: []rpc.StreamDesc{\n");
|
||||
for (int i = 0; i < service->method_count(); ++i) {
|
||||
if (!NoStreaming(service->method(i))) {
|
||||
PrintServerStreamingMethodDesc(printer, service->method(i), vars);
|
||||
}
|
||||
}
|
||||
printer->Print(
|
||||
"\t},\n"
|
||||
"}\n\n");
|
||||
}
|
||||
|
||||
bool IsSelfImport(const google::protobuf::FileDescriptor* self,
|
||||
const google::protobuf::FileDescriptor* import) {
|
||||
if (GenerateFullGoPackage(self) == GenerateFullGoPackage(import)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PrintMessageImports(
|
||||
google::protobuf::io::Printer* printer,
|
||||
const google::protobuf::FileDescriptor* file,
|
||||
map<string, string>* vars,
|
||||
const string& import_prefix,
|
||||
set<string>* imports,
|
||||
map<string, string>* import_alias) {
|
||||
set<const google::protobuf::FileDescriptor*> descs;
|
||||
for (int i = 0; i < file->service_count(); ++i) {
|
||||
const google::protobuf::ServiceDescriptor* service = file->service(i);
|
||||
for (int j = 0; j < service->method_count(); ++j) {
|
||||
const google::protobuf::MethodDescriptor* method = service->method(j);
|
||||
if (!IsSelfImport(file, method->input_type()->file())) {
|
||||
descs.insert(method->input_type()->file());
|
||||
}
|
||||
if (!IsSelfImport(file, method->output_type()->file())) {
|
||||
descs.insert(method->output_type()->file());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
set<string> pkgs;
|
||||
pkgs.insert((*vars)["PackageName"]);
|
||||
for (auto fd : descs) {
|
||||
string full_pkg = GenerateFullGoPackage(fd);
|
||||
if (full_pkg != "") {
|
||||
// Use ret_full to guarantee it only gets an alias once if a
|
||||
// package spans multiple files,
|
||||
auto ret_full = imports->insert(full_pkg);
|
||||
string fd_pkg = !fd->options().go_package().empty()
|
||||
? fd->options().go_package() : fd->package();
|
||||
// Use ret_pkg to guarantee the packages get the different alias
|
||||
// names if they are on different paths but use the same name.
|
||||
auto ret_pkg = pkgs.insert(fd_pkg);
|
||||
if (ret_full.second && !ret_pkg.second) {
|
||||
// the same package name in different directories. Require an alias.
|
||||
(*import_alias)[full_pkg] = "apb" + std::to_string(idx++);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto import : *imports) {
|
||||
string import_path = "import ";
|
||||
if (import_alias->find(import) != import_alias->end()) {
|
||||
import_path += (*import_alias)[import] + " ";
|
||||
}
|
||||
import_path += "\"" + import_prefix + import + "\"";
|
||||
printer->Print(import_path.c_str());
|
||||
printer->Print("\n");
|
||||
}
|
||||
printer->Print("\n");
|
||||
}
|
||||
|
||||
string GetServices(const google::protobuf::FileDescriptor* file,
|
||||
const vector<pair<string, string> >& options) {
|
||||
string output;
|
||||
google::protobuf::io::StringOutputStream output_stream(&output);
|
||||
google::protobuf::io::Printer printer(&output_stream, '$');
|
||||
map<string, string> vars;
|
||||
map<string, string> import_alias;
|
||||
set<string> imports;
|
||||
string package_name = !file->options().go_package().empty()
|
||||
? file->options().go_package()
|
||||
: file->package();
|
||||
vars["PackageName"] = BadToUnderscore(package_name);
|
||||
printer.Print(vars, "package $PackageName$\n\n");
|
||||
printer.Print("import (\n");
|
||||
if (HasClientOnlyStreaming(file)) {
|
||||
printer.Print(
|
||||
"\t\"fmt\"\n"
|
||||
"\t\"io\"\n");
|
||||
}
|
||||
printer.Print(
|
||||
"\t\"github.com/google/grpc-go/rpc\"\n"
|
||||
"\tcontext \"golang.org/x/net/context\"\n"
|
||||
"\tproto \"github.com/golang/protobuf/proto\"\n"
|
||||
")\n\n");
|
||||
|
||||
// TODO(zhaoq): Support other command line parameters supported by
|
||||
// the protoc-gen-go plugin.
|
||||
string import_prefix = "";
|
||||
for (auto& p : options) {
|
||||
if (p.first == "import_prefix") {
|
||||
import_prefix = p.second;
|
||||
}
|
||||
}
|
||||
PrintMessageImports(
|
||||
&printer, file, &vars, import_prefix, &imports, &import_alias);
|
||||
|
||||
// $Package$ is used to fully qualify method names.
|
||||
vars["Package"] = file->package();
|
||||
if (!file->package().empty()) {
|
||||
vars["Package"].append(".");
|
||||
}
|
||||
|
||||
for (int i = 0; i < file->service_count(); ++i) {
|
||||
PrintClient(&printer, file->service(0), &vars, imports, import_alias);
|
||||
printer.Print("\n");
|
||||
PrintServer(&printer, file->service(0), &vars, imports, import_alias);
|
||||
printer.Print("\n");
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
} // namespace grpc_go_generator
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NET_GRPC_COMPILER_GO_GENERATOR_H_
|
||||
#define NET_GRPC_COMPILER_GO_GENERATOR_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
class FileDescriptor;
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
|
||||
namespace grpc_go_generator {
|
||||
|
||||
string GetServices(const google::protobuf::FileDescriptor* file,
|
||||
const vector<std::pair<string, string> >& options);
|
||||
|
||||
} // namespace grpc_go_generator
|
||||
|
||||
#endif // NET_GRPC_COMPILER_GO_GENERATOR_H_
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Generates go gRPC service interface out of Protobuf IDL.
|
||||
//
|
||||
// This is a Proto2 compiler plugin. See net/proto2/compiler/proto/plugin.proto
|
||||
// and net/proto2/compiler/public/plugin.h for more information on plugins.
|
||||
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "./go_generator.h"
|
||||
#include <google/protobuf/compiler/code_generator.h>
|
||||
#include <google/protobuf/compiler/plugin.h>
|
||||
#include <google/protobuf/io/coded_stream.h>
|
||||
#include <google/protobuf/io/zero_copy_stream.h>
|
||||
#include <google/protobuf/descriptor.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class GoGrpcGenerator : public google::protobuf::compiler::CodeGenerator {
|
||||
public:
|
||||
GoGrpcGenerator() {}
|
||||
virtual ~GoGrpcGenerator() {}
|
||||
|
||||
virtual bool Generate(const google::protobuf::FileDescriptor* file,
|
||||
const string& parameter,
|
||||
google::protobuf::compiler::GeneratorContext* context,
|
||||
string* error) const {
|
||||
if (file->service_count() <= 0) {
|
||||
// Do not generate anything if there is no rpc service defined.
|
||||
return true;
|
||||
}
|
||||
// Get output file name.
|
||||
string file_name;
|
||||
if (file->name().size() > 6 &&
|
||||
file->name().find_last_of(".proto") == file->name().size() - 1) {
|
||||
file_name =
|
||||
file->name().substr(0, file->name().size() - 6) + "_grpc.pb.go";
|
||||
} else {
|
||||
*error = "Invalid proto file name. Proto file must end with .proto";
|
||||
return false;
|
||||
}
|
||||
|
||||
vector<pair<string, string> > options;
|
||||
google::protobuf::compiler::ParseGeneratorParameter(parameter, &options);
|
||||
unique_ptr<google::protobuf::io::ZeroCopyOutputStream> output(
|
||||
context->Open(file_name));
|
||||
google::protobuf::io::CodedOutputStream coded_out(output.get());
|
||||
string code = grpc_go_generator::GetServices(file, options);
|
||||
coded_out.WriteRaw(code.data(), code.size());
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
GoGrpcGenerator generator;
|
||||
return google::protobuf::compiler::PluginMain(argc, argv, &generator);
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,255 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package credentials implements various credentials supported by gRPC library.
|
||||
package credentials
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
metadataServer = "metadata"
|
||||
serviceAccountPath = "/computeMetadata/v1/instance/service-accounts/default/token"
|
||||
)
|
||||
|
||||
var (
|
||||
// alpnProtoStr are the specified application level protocols for gRPC.
|
||||
alpnProtoStr = []string{"h2-14", "h2-15", "h2-16"}
|
||||
)
|
||||
|
||||
// Credentials defines the common interface all supported credentials must
|
||||
// implement.
|
||||
type Credentials interface {
|
||||
// GetRequestMetadata gets the current request metadata, refreshing
|
||||
// tokens if required. This should be called by the transport layer on
|
||||
// each request, and the data should be populated in headers or other
|
||||
// context. The operation may do things like refresh tokens.
|
||||
GetRequestMetadata() (map[string]string, error)
|
||||
}
|
||||
|
||||
// TransportAuthenticator defines the common interface all supported transport
|
||||
// authentication protocols (e.g., TLS, SSL) must implement.
|
||||
type TransportAuthenticator interface {
|
||||
// Dial connects to the given network address and does the authentication
|
||||
// handshake specified by the corresponding authentication protocol.
|
||||
Dial(add string) (net.Conn, error)
|
||||
// NewListener creates a listener which accepts connections with requested
|
||||
// authentication handshake.
|
||||
NewListener(lis net.Listener) net.Listener
|
||||
Credentials
|
||||
}
|
||||
|
||||
// tlsCreds is the credentials required for authenticating a connection.
|
||||
type tlsCreds struct {
|
||||
// serverName is used to verify the hostname on the returned
|
||||
// certificates. It is also included in the client's handshake
|
||||
// to support virtual hosting. This is optional. If it is not
|
||||
// set gRPC internals will use the dialing address instead.
|
||||
serverName string
|
||||
// rootCAs defines the set of root certificate authorities
|
||||
// that clients use when verifying server certificates.
|
||||
// If rootCAs is nil, tls uses the host's root CA set.
|
||||
rootCAs *x509.CertPool
|
||||
// certificates contains one or more certificate chains
|
||||
// to present to the other side of the connection.
|
||||
// Server configurations must include at least one certificate.
|
||||
certificates []tls.Certificate
|
||||
}
|
||||
|
||||
// GetRequestMetadata returns nil, nil since TLS credentials does not have
|
||||
// metadata.
|
||||
func (c *tlsCreds) GetRequestMetadata() (map[string]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Dial connects to addr and performs TLS handshake.
|
||||
func (c *tlsCreds) Dial(addr string) (_ net.Conn, err error) {
|
||||
name := c.serverName
|
||||
if name == "" {
|
||||
name, _, err = net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse server address %v", err)
|
||||
}
|
||||
}
|
||||
return tls.Dial("tcp", addr, &tls.Config{
|
||||
RootCAs: c.rootCAs,
|
||||
NextProtos: alpnProtoStr,
|
||||
ServerName: name,
|
||||
})
|
||||
}
|
||||
|
||||
// NewListener creates a net.Listener with a TLS configuration constructed
|
||||
// from the information in tlsCreds.
|
||||
func (c *tlsCreds) NewListener(lis net.Listener) net.Listener {
|
||||
return tls.NewListener(lis, &tls.Config{
|
||||
Certificates: c.certificates,
|
||||
NextProtos: alpnProtoStr,
|
||||
})
|
||||
}
|
||||
|
||||
// NewClientTLSFromCert constructs a TLS from the input certificate for client.
|
||||
func NewClientTLSFromCert(cp *x509.CertPool, serverName string) TransportAuthenticator {
|
||||
return &tlsCreds{
|
||||
serverName: serverName,
|
||||
rootCAs: cp,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientTLSFromFile constructs a TLS from the input certificate file for client.
|
||||
func NewClientTLSFromFile(certFile, serverName string) (TransportAuthenticator, error) {
|
||||
b, err := ioutil.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(b) {
|
||||
return nil, fmt.Errorf("failed to append certificates")
|
||||
}
|
||||
return &tlsCreds{
|
||||
serverName: serverName,
|
||||
rootCAs: cp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewServerTLSFromCert constructs a TLS from the input certificate for server.
|
||||
func NewServerTLSFromCert(cert *tls.Certificate) TransportAuthenticator {
|
||||
return &tlsCreds{
|
||||
certificates: []tls.Certificate{*cert},
|
||||
}
|
||||
}
|
||||
|
||||
// NewServerTLSFromFile constructs a TLS from the input certificate file and key
|
||||
// file for server.
|
||||
func NewServerTLSFromFile(certFile, keyFile string) (TransportAuthenticator, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tlsCreds{
|
||||
certificates: []tls.Certificate{cert},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type tokenData struct {
|
||||
accessToken string
|
||||
expiresIn float64
|
||||
tokeType string
|
||||
}
|
||||
|
||||
type token struct {
|
||||
accessToken string
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
// expired returns true if there is no access token or the
|
||||
// access token is expired.
|
||||
func (t token) expired() bool {
|
||||
if t.accessToken == "" {
|
||||
return true
|
||||
}
|
||||
if t.expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.expiry.Before(time.Now())
|
||||
}
|
||||
|
||||
// computeEngine uses the Application Default Credentials as provided to Google Compute Engine instances.
|
||||
type computeEngine struct {
|
||||
mu sync.Mutex
|
||||
t token
|
||||
}
|
||||
|
||||
// GetRequestMetadata returns a refreshed access token.
|
||||
func (c *computeEngine) GetRequestMetadata() (map[string]string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.t.expired() {
|
||||
if err := c.refresh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return map[string]string{
|
||||
"authorization": "Bearer " + c.t.accessToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *computeEngine) refresh() error {
|
||||
// https://developers.google.com/compute/docs/metadata
|
||||
// v1 requires "Metadata-Flavor: Google" header.
|
||||
tokenURL := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: metadataServer,
|
||||
Path: serviceAccountPath,
|
||||
}
|
||||
req, err := http.NewRequest("GET", tokenURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Metadata-Flavor", "Google")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var td tokenData
|
||||
err = json.NewDecoder(resp.Body).Decode(&td)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// No need to check td.tokenType.
|
||||
c.t = token{
|
||||
accessToken: td.accessToken,
|
||||
expiry: time.Now().Add(time.Duration(td.expiresIn) * time.Second),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewComputeEngine constructs a credentials for GCE.
|
||||
func NewComputeEngine() (Credentials, error) {
|
||||
creds := &computeEngine{}
|
||||
// TODO(zhaoq): This is not optimal if refresh() is persistently failed.
|
||||
if err := creds.refresh(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return creds, nil
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc/grpc-go/rpc"
|
||||
"github.com/grpc/grpc-go/rpc/credentials"
|
||||
testpb "github.com/grpc/grpc-go/rpc/interop/testdata"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
|
||||
caFile = flag.String("tls_ca_file", "testdata/ca.pem", "The file containning the CA root cert file")
|
||||
serverHost = flag.String("server_host", "127.0.0.1", "The server host name")
|
||||
serverPort = flag.Int("server_port", 10000, "The server port number")
|
||||
tlsServerName = flag.String("tls_server_name", "x.test.youtube.com", "The server name use to verify the hostname returned by TLS handshake")
|
||||
testCase = flag.String("test_case", "large_unary", "The RPC method to be tested: large_unary|empty_unary|client_streaming|server_streaming|ping_pong|all")
|
||||
)
|
||||
|
||||
var (
|
||||
reqSizes = []int{27182, 8, 1828, 45904}
|
||||
respSizes = []int{31415, 9, 2653, 58979}
|
||||
)
|
||||
|
||||
func newPayload(t testpb.PayloadType, size int) *testpb.Payload {
|
||||
if size < 0 {
|
||||
log.Fatalf("Requested a response with invalid length %d", size)
|
||||
}
|
||||
body := make([]byte, size)
|
||||
switch t {
|
||||
case testpb.PayloadType_COMPRESSABLE:
|
||||
case testpb.PayloadType_UNCOMPRESSABLE:
|
||||
log.Fatalf("PayloadType UNCOMPRESSABLE is not supported")
|
||||
default:
|
||||
log.Fatalf("Unsupported payload type: %d", t)
|
||||
}
|
||||
return &testpb.Payload{
|
||||
Type: t.Enum(),
|
||||
Body: body,
|
||||
}
|
||||
}
|
||||
|
||||
func doEmptyUnaryCall(tc testpb.TestServiceClient) {
|
||||
reply, err := tc.EmptyCall(context.Background(), &testpb.Empty{})
|
||||
if err != nil {
|
||||
log.Fatal("/TestService/EmptyCall RPC failed: ", err)
|
||||
}
|
||||
if !proto.Equal(&testpb.Empty{}, reply) {
|
||||
log.Fatalf("/TestService/EmptyCall receives %v, want %v", reply, testpb.Empty{})
|
||||
}
|
||||
}
|
||||
|
||||
func doLargeUnaryCall(tc testpb.TestServiceClient) {
|
||||
argSize := 271828
|
||||
respSize := 314159
|
||||
pl := newPayload(testpb.PayloadType_COMPRESSABLE, argSize)
|
||||
req := &testpb.SimpleRequest{
|
||||
ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
|
||||
ResponseSize: proto.Int32(int32(respSize)),
|
||||
Payload: pl,
|
||||
}
|
||||
reply, err := tc.UnaryCall(context.Background(), req)
|
||||
if err != nil {
|
||||
log.Fatal("/TestService/UnaryCall RPC failed: ", err)
|
||||
}
|
||||
t := reply.GetPayload().GetType()
|
||||
s := len(reply.GetPayload().GetBody())
|
||||
if t != testpb.PayloadType_COMPRESSABLE || s != respSize {
|
||||
log.Fatalf("Got the reply with type %d len %d; want %d, %d", t, s, testpb.PayloadType_COMPRESSABLE, respSize)
|
||||
}
|
||||
}
|
||||
|
||||
func doClientStreaming(tc testpb.TestServiceClient) {
|
||||
stream, err := tc.StreamingInputCall(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("%v.StreamingInputCall(_) = _, %v", tc, err)
|
||||
}
|
||||
var sum int
|
||||
for _, s := range reqSizes {
|
||||
pl := newPayload(testpb.PayloadType_COMPRESSABLE, s)
|
||||
req := &testpb.StreamingInputCallRequest{
|
||||
Payload: pl,
|
||||
}
|
||||
if err := stream.Send(req); err != nil {
|
||||
log.Fatalf("%v.Send(%v) = %v", stream, req, err)
|
||||
}
|
||||
sum += s
|
||||
log.Printf("Sent a request of size %d, aggregated size %d", s, sum)
|
||||
|
||||
}
|
||||
reply, err := stream.CloseAndRecv()
|
||||
if err != io.EOF {
|
||||
log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, io.EOF)
|
||||
}
|
||||
if reply.GetAggregatedPayloadSize() != int32(sum) {
|
||||
log.Fatalf("%v.CloseAndRecv().GetAggregatePayloadSize() = %v; want %v", stream, reply.GetAggregatedPayloadSize(), sum)
|
||||
}
|
||||
}
|
||||
|
||||
func doServerStreaming(tc testpb.TestServiceClient) {
|
||||
respParam := make([]*testpb.ResponseParameters, len(respSizes))
|
||||
for i, s := range respSizes {
|
||||
respParam[i] = &testpb.ResponseParameters{
|
||||
Size: proto.Int32(int32(s)),
|
||||
}
|
||||
}
|
||||
req := &testpb.StreamingOutputCallRequest{
|
||||
ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
|
||||
ResponseParameters: respParam,
|
||||
}
|
||||
stream, err := tc.StreamingOutputCall(context.Background(), req)
|
||||
if err != nil {
|
||||
log.Fatalf("%v.StreamingOutputCall(_) = _, %v", tc, err)
|
||||
}
|
||||
var rpcStatus error
|
||||
var respCnt int
|
||||
var index int
|
||||
for {
|
||||
reply, err := stream.Recv()
|
||||
if err != nil {
|
||||
rpcStatus = err
|
||||
break
|
||||
}
|
||||
t := reply.GetPayload().GetType()
|
||||
if t != testpb.PayloadType_COMPRESSABLE {
|
||||
log.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
|
||||
}
|
||||
size := len(reply.GetPayload().GetBody())
|
||||
if size != int(respSizes[index]) {
|
||||
log.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
|
||||
}
|
||||
index++
|
||||
respCnt++
|
||||
}
|
||||
if rpcStatus != io.EOF {
|
||||
log.Fatalf("Failed to finish the server streaming rpc: %v", err)
|
||||
}
|
||||
if respCnt != len(respSizes) {
|
||||
log.Fatalf("Got %d reply, want %d", len(respSizes), respCnt)
|
||||
}
|
||||
}
|
||||
|
||||
func doPingPong(tc testpb.TestServiceClient) {
|
||||
stream, err := tc.FullDuplexCall(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("%v.FullDuplexCall(_) = _, %v", tc, err)
|
||||
}
|
||||
var index int
|
||||
for index < len(reqSizes) {
|
||||
respParam := []*testpb.ResponseParameters{
|
||||
&testpb.ResponseParameters{
|
||||
Size: proto.Int32(int32(respSizes[index])),
|
||||
},
|
||||
}
|
||||
pl := newPayload(testpb.PayloadType_COMPRESSABLE, reqSizes[index])
|
||||
req := &testpb.StreamingOutputCallRequest{
|
||||
ResponseType: testpb.PayloadType_COMPRESSABLE.Enum(),
|
||||
ResponseParameters: respParam,
|
||||
Payload: pl,
|
||||
}
|
||||
if err := stream.Send(req); err != nil {
|
||||
log.Fatalf("%v.Send(%v) = %v", stream, req, err)
|
||||
}
|
||||
reply, err := stream.Recv()
|
||||
if err != nil {
|
||||
log.Fatalf("%v.Recv() = %v", stream, err)
|
||||
}
|
||||
t := reply.GetPayload().GetType()
|
||||
if t != testpb.PayloadType_COMPRESSABLE {
|
||||
log.Fatalf("Got the reply of type %d, want %d", t, testpb.PayloadType_COMPRESSABLE)
|
||||
}
|
||||
size := len(reply.GetPayload().GetBody())
|
||||
if size != int(respSizes[index]) {
|
||||
log.Fatalf("Got reply body of length %d, want %d", size, respSizes[index])
|
||||
}
|
||||
index++
|
||||
}
|
||||
if err := stream.CloseSend(); err != nil {
|
||||
log.Fatalf("%v.CloseSend() got %v, want %v", stream, err, nil)
|
||||
}
|
||||
if _, err := stream.Recv(); err != io.EOF {
|
||||
log.Fatalf("%v failed to complele the ping pong test: %v", stream, err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
serverAddr := net.JoinHostPort(*serverHost, strconv.Itoa(*serverPort))
|
||||
var opts []rpc.DialOption
|
||||
if *useTLS {
|
||||
var sn string
|
||||
if *tlsServerName != "" {
|
||||
sn = *tlsServerName
|
||||
}
|
||||
var creds credentials.TransportAuthenticator
|
||||
if *caFile != "" {
|
||||
var err error
|
||||
creds, err = credentials.NewClientTLSFromFile(*caFile, sn)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create credentials %v", err)
|
||||
}
|
||||
} else {
|
||||
creds = credentials.NewClientTLSFromCert(nil, sn)
|
||||
}
|
||||
opts = append(opts, rpc.WithClientTLS(creds))
|
||||
}
|
||||
conn, err := rpc.Dial(serverAddr, opts...)
|
||||
if err != nil {
|
||||
log.Fatalf("fail to dial: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
tc := testpb.NewTestServiceClient(conn)
|
||||
switch *testCase {
|
||||
case "empty_unary":
|
||||
doEmptyUnaryCall(tc)
|
||||
case "large_unary":
|
||||
doLargeUnaryCall(tc)
|
||||
case "client_streaming":
|
||||
doClientStreaming(tc)
|
||||
case "server_streaming":
|
||||
doServerStreaming(tc)
|
||||
case "ping_pong":
|
||||
doPingPong(tc)
|
||||
case "all":
|
||||
doEmptyUnaryCall(tc)
|
||||
doLargeUnaryCall(tc)
|
||||
doClientStreaming(tc)
|
||||
doServerStreaming(tc)
|
||||
doPingPong(tc)
|
||||
default:
|
||||
log.Fatal("Unsupported test case: ", *testCase)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICIzCCAYwCCQCFTbF7XNSvvjANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzE3MjMxNzUxWhcNMjQw
|
||||
NzE0MjMxNzUxWjBWMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
|
||||
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0
|
||||
Y2EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMBA3wVeTGHZR1Rye/i+J8a2
|
||||
cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBlLoNY0lSv
|
||||
xDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JAHUGodjx+
|
||||
QAi6yCAurUZGvYXGgZSBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQoQVD8bwdtWJ
|
||||
AniGBwcCfqYyH+/KpA10AcebJVVTyYbYvI9Q8d6RSVu4PZy9OALHR/QrWBdYTAyz
|
||||
fNAmc2cmdkSRJzjhIaOstnQy1J+Fk0T9XyvQtq499yFbq9xogUVlEGH62xP6vH0Y
|
||||
5ukK//dCPAzA11YuX2rnex0JhuTQfcI=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQDhwxUnKCwlSaWAwzOB2LSHVegJHv7DDWminTg4wzLLsf+LQ8nZ
|
||||
bpjfn5vgIzxCuRh4Rp9QYM5FhfrJX9wcYawP/HTbJ7p7LVQO2QYAP+akMTHxgKuM
|
||||
BzVV++3wWToKfVZUjFX8nfTfGMGwWAHJDnlEGnU4tl9UujoCV4ENJtzFoQIDAQAB
|
||||
AoGAJ+6hpzNr24yTQZtFWQpDpEyFplddKJMOxDya3S9ppK3vTWrIITV2xNcucw7I
|
||||
ceTbdyrGsyjsU0/HdCcIf9ym2jfmGLUwmyhltKVw0QYcFB0XLkc0nI5YvEYoeVDg
|
||||
omZIXn1E3EW+sSIWSbkMu9bY2kstKXR2UZmMgWDtmBEPMaECQQD6yT4TAZM5hGBb
|
||||
ciBKgMUP6PwOhPhOMPIvijO50Aiu6iuCV88l1QIy38gWVhxjNrq6P346j4IBg+kB
|
||||
9alwpCODAkEA5nSnm9k6ykYeQWNS0fNWiRinCdl23A7usDGSuKKlm019iomJ/Rgd
|
||||
MKDOp0q/2OostbteOWM2MRFf4jMH3wyVCwJAfAdjJ8szoNKTRSagSbh9vWygnB2v
|
||||
IByc6l4TTuZQJRGzCveafz9lovuB3WohCABdQRd9ukCXL2CpsEpqzkafOQJAJUjc
|
||||
USedDlq3zGZwYM1Yw8d8RuirBUFZNqJelYai+nRYClDkRVFgb5yksoYycbq5TxGo
|
||||
VeqKOvgPpj4RWPHlLwJAGUMk3bqT91xBUCnLRs/vfoCpHpg6eywQTBDAV6xkyz4a
|
||||
RH3I7/+yj3ZxR2JoWHgUwZ7lZk1VnhffFye7SBXyag==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
|
||||
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||
dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
|
||||
MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
|
||||
BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
|
||||
c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
|
||||
JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
|
||||
RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
|
||||
3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
|
||||
BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
|
||||
b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
|
||||
KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
|
||||
wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
|
||||
aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grpc/grpc-go/rpc/credentials"
|
||||
testpb "github.com/grpc/grpc-go/rpc/interop/testdata"
|
||||
"github.com/grpc/grpc-go/rpc"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true, else plain TCP")
|
||||
certFile = flag.String("tls_cert_file", "testdata/server1.pem", "The TLS cert file")
|
||||
keyFile = flag.String("tls_key_file", "testdata/server1.key", "The TLS key file")
|
||||
port = flag.Int("port", 10000, "The server port")
|
||||
)
|
||||
|
||||
type testServer struct {
|
||||
}
|
||||
|
||||
func (s *testServer) EmptyCall(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) {
|
||||
return new(testpb.Empty), nil
|
||||
}
|
||||
|
||||
func newPayload(t testpb.PayloadType, size int32) (*testpb.Payload, error) {
|
||||
if size < 0 {
|
||||
return nil, fmt.Errorf("requested a response with invalid length %d", size)
|
||||
}
|
||||
body := make([]byte, size)
|
||||
switch t {
|
||||
case testpb.PayloadType_COMPRESSABLE:
|
||||
case testpb.PayloadType_UNCOMPRESSABLE:
|
||||
return nil, fmt.Errorf("payloadType UNCOMPRESSABLE is not supported")
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported payload type: %d", t)
|
||||
}
|
||||
return &testpb.Payload{
|
||||
Type: t.Enum(),
|
||||
Body: body,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *testServer) UnaryCall(ctx context.Context, in *testpb.SimpleRequest) (*testpb.SimpleResponse, error) {
|
||||
pl, err := newPayload(in.GetResponseType(), in.GetResponseSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testpb.SimpleResponse{
|
||||
Payload: pl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *testServer) StreamingOutputCall(args *testpb.StreamingOutputCallRequest, stream testpb.TestService_StreamingOutputCallServer) error {
|
||||
cs := args.GetResponseParameters()
|
||||
for _, c := range cs {
|
||||
if us := c.GetIntervalUs(); us > 0 {
|
||||
time.Sleep(time.Duration(us) * time.Microsecond)
|
||||
}
|
||||
pl, err := newPayload(args.GetResponseType(), c.GetSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := stream.Send(&testpb.StreamingOutputCallResponse{
|
||||
Payload: pl,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *testServer) StreamingInputCall(stream testpb.TestService_StreamingInputCallServer) error {
|
||||
var sum int
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return stream.SendAndClose(&testpb.StreamingInputCallResponse{
|
||||
AggregatedPayloadSize: proto.Int32(int32(sum)),
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := in.GetPayload().GetBody()
|
||||
sum += len(p)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *testServer) FullDuplexCall(stream testpb.TestService_FullDuplexCallServer) error {
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
// read done.
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cs := in.GetResponseParameters()
|
||||
for _, c := range cs {
|
||||
if us := c.GetIntervalUs(); us > 0 {
|
||||
time.Sleep(time.Duration(us) * time.Microsecond)
|
||||
}
|
||||
pl, err := newPayload(in.GetResponseType(), c.GetSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := stream.Send(&testpb.StreamingOutputCallResponse{
|
||||
Payload: pl,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *testServer) HalfDuplexCall(stream testpb.TestService_HalfDuplexCallServer) error {
|
||||
msgBuf := make([]*testpb.StreamingOutputCallRequest, 0)
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
// read done.
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgBuf = append(msgBuf, in)
|
||||
}
|
||||
for _, m := range msgBuf {
|
||||
cs := m.GetResponseParameters()
|
||||
for _, c := range cs {
|
||||
if us := c.GetIntervalUs(); us > 0 {
|
||||
time.Sleep(time.Duration(us) * time.Microsecond)
|
||||
}
|
||||
pl, err := newPayload(m.GetResponseType(), c.GetSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := stream.Send(&testpb.StreamingOutputCallResponse{
|
||||
Payload: pl,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
p := strconv.Itoa(*port)
|
||||
lis, err := net.Listen("tcp", ":"+p)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
server := rpc.NewServer()
|
||||
testpb.RegisterService(server, &testServer{})
|
||||
if *useTLS {
|
||||
creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate credentials %v", err)
|
||||
}
|
||||
server.Serve(creds.NewListener(lis))
|
||||
} else {
|
||||
server.Serve(lis)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICIzCCAYwCCQCFTbF7XNSvvjANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzE3MjMxNzUxWhcNMjQw
|
||||
NzE0MjMxNzUxWjBWMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
|
||||
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0
|
||||
Y2EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMBA3wVeTGHZR1Rye/i+J8a2
|
||||
cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBlLoNY0lSv
|
||||
xDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JAHUGodjx+
|
||||
QAi6yCAurUZGvYXGgZSBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQoQVD8bwdtWJ
|
||||
AniGBwcCfqYyH+/KpA10AcebJVVTyYbYvI9Q8d6RSVu4PZy9OALHR/QrWBdYTAyz
|
||||
fNAmc2cmdkSRJzjhIaOstnQy1J+Fk0T9XyvQtq499yFbq9xogUVlEGH62xP6vH0Y
|
||||
5ukK//dCPAzA11YuX2rnex0JhuTQfcI=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQDhwxUnKCwlSaWAwzOB2LSHVegJHv7DDWminTg4wzLLsf+LQ8nZ
|
||||
bpjfn5vgIzxCuRh4Rp9QYM5FhfrJX9wcYawP/HTbJ7p7LVQO2QYAP+akMTHxgKuM
|
||||
BzVV++3wWToKfVZUjFX8nfTfGMGwWAHJDnlEGnU4tl9UujoCV4ENJtzFoQIDAQAB
|
||||
AoGAJ+6hpzNr24yTQZtFWQpDpEyFplddKJMOxDya3S9ppK3vTWrIITV2xNcucw7I
|
||||
ceTbdyrGsyjsU0/HdCcIf9ym2jfmGLUwmyhltKVw0QYcFB0XLkc0nI5YvEYoeVDg
|
||||
omZIXn1E3EW+sSIWSbkMu9bY2kstKXR2UZmMgWDtmBEPMaECQQD6yT4TAZM5hGBb
|
||||
ciBKgMUP6PwOhPhOMPIvijO50Aiu6iuCV88l1QIy38gWVhxjNrq6P346j4IBg+kB
|
||||
9alwpCODAkEA5nSnm9k6ykYeQWNS0fNWiRinCdl23A7usDGSuKKlm019iomJ/Rgd
|
||||
MKDOp0q/2OostbteOWM2MRFf4jMH3wyVCwJAfAdjJ8szoNKTRSagSbh9vWygnB2v
|
||||
IByc6l4TTuZQJRGzCveafz9lovuB3WohCABdQRd9ukCXL2CpsEpqzkafOQJAJUjc
|
||||
USedDlq3zGZwYM1Yw8d8RuirBUFZNqJelYai+nRYClDkRVFgb5yksoYycbq5TxGo
|
||||
VeqKOvgPpj4RWPHlLwJAGUMk3bqT91xBUCnLRs/vfoCpHpg6eywQTBDAV6xkyz4a
|
||||
RH3I7/+yj3ZxR2JoWHgUwZ7lZk1VnhffFye7SBXyag==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
|
||||
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||
dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
|
||||
MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
|
||||
BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
|
||||
c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
|
||||
JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
|
||||
RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
|
||||
3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
|
||||
BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
|
||||
b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
|
||||
KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
|
||||
wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
|
||||
aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,96 @@
|
|||
// Message definitions to be used by integration test service definitions.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package grpc.testing;
|
||||
|
||||
message Empty {}
|
||||
|
||||
// The type of payload that should be returned.
|
||||
enum PayloadType {
|
||||
// Compressable text format.
|
||||
COMPRESSABLE = 0;
|
||||
|
||||
// Uncompressable binary format.
|
||||
UNCOMPRESSABLE = 1;
|
||||
|
||||
// Randomly chosen from all other formats defined in this enum.
|
||||
RANDOM = 2;
|
||||
}
|
||||
|
||||
// A block of data, to simply increase gRPC message size.
|
||||
message Payload {
|
||||
// The type of data in body.
|
||||
optional PayloadType type = 1;
|
||||
// Primary contents of payload.
|
||||
optional bytes body = 2;
|
||||
}
|
||||
|
||||
// Unary request.
|
||||
message SimpleRequest {
|
||||
// Desired payload type in the response from the server.
|
||||
// If response_type is RANDOM, server randomly chooses one from other formats.
|
||||
optional PayloadType response_type = 1;
|
||||
|
||||
// Desired payload size in the response from the server.
|
||||
// If response_type is COMPRESSABLE, this denotes the size before compression.
|
||||
optional int32 response_size = 2;
|
||||
|
||||
// Optional input payload sent along with the request.
|
||||
optional Payload payload = 3;
|
||||
}
|
||||
|
||||
// Unary response, as configured by the request.
|
||||
message SimpleResponse {
|
||||
// Payload to increase message size.
|
||||
optional Payload payload = 1;
|
||||
// The user the request came from, for verifying authentication was
|
||||
// successful when the client expected it.
|
||||
optional int64 effective_gaia_user_id = 2;
|
||||
}
|
||||
|
||||
// Client-streaming request.
|
||||
message StreamingInputCallRequest {
|
||||
// Optional input payload sent along with the request.
|
||||
optional Payload payload = 1;
|
||||
|
||||
// Not expecting any payload from the response.
|
||||
}
|
||||
|
||||
// Client-streaming response.
|
||||
message StreamingInputCallResponse {
|
||||
// Aggregated size of payloads received from the client.
|
||||
optional int32 aggregated_payload_size = 1;
|
||||
}
|
||||
|
||||
// Configuration for a particular response.
|
||||
message ResponseParameters {
|
||||
// Desired payload sizes in responses from the server.
|
||||
// If response_type is COMPRESSABLE, this denotes the size before compression.
|
||||
optional int32 size = 1;
|
||||
|
||||
// Desired interval between consecutive responses in the response stream in
|
||||
// microseconds.
|
||||
optional int32 interval_us = 2;
|
||||
}
|
||||
|
||||
// Server-streaming request.
|
||||
message StreamingOutputCallRequest {
|
||||
// Desired payload type in the response from the server.
|
||||
// If response_type is RANDOM, the payload from each response in the stream
|
||||
// might be of different types. This is to simulate a mixed type of payload
|
||||
// stream.
|
||||
optional PayloadType response_type = 1;
|
||||
|
||||
// Configuration for each expected response message.
|
||||
repeated ResponseParameters response_parameters = 2;
|
||||
|
||||
// Optional input payload sent along with the request.
|
||||
optional Payload payload = 3;
|
||||
}
|
||||
|
||||
// Server-streaming response, as configured by the request and parameters.
|
||||
message StreamingOutputCallResponse {
|
||||
// Payload to increase response size.
|
||||
optional Payload payload = 1;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// An integration test service that covers all the method signature permutations
|
||||
// of unary/streaming requests/responses.
|
||||
syntax = "proto2";
|
||||
|
||||
import "github.com/google/grpc_go/interop/testdata/empty.proto";
|
||||
import "github.com/google/grpc_go/interop/testdata/messages.proto";
|
||||
|
||||
package grpc.testing;
|
||||
|
||||
// A simple service to test the various types of RPCs and experiment with
|
||||
// performance with various types of payload.
|
||||
service TestService {
|
||||
// One empty request followed by one empty response.
|
||||
rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
|
||||
|
||||
// One request followed by one response.
|
||||
// The server returns the client payload as-is.
|
||||
rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
|
||||
|
||||
// One request followed by a sequence of responses (streamed download).
|
||||
// The server returns the payload with client desired type and sizes.
|
||||
rpc StreamingOutputCall(StreamingOutputCallRequest)
|
||||
returns (stream StreamingOutputCallResponse);
|
||||
|
||||
// A sequence of requests followed by one response (streamed upload).
|
||||
// The server returns the aggregated size of client payload as the result.
|
||||
rpc StreamingInputCall(stream StreamingInputCallRequest)
|
||||
returns (StreamingInputCallResponse);
|
||||
|
||||
// A sequence of requests with each request served by the server immediately.
|
||||
// As one request could lead to multiple responses, this interface
|
||||
// demonstrates the idea of full duplexing.
|
||||
rpc FullDuplexCall(stream StreamingOutputCallRequest)
|
||||
returns (stream StreamingOutputCallResponse);
|
||||
|
||||
// A sequence of requests followed by a sequence of responses.
|
||||
// The server buffers all the client requests and then serves them in order. A
|
||||
// stream of responses are returned to the client when the server starts with
|
||||
// first request.
|
||||
rpc HalfDuplexCall(stream StreamingOutputCallRequest)
|
||||
returns (stream StreamingOutputCallResponse);
|
||||
}
|
|
@ -0,0 +1,403 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: net/grpc/go/interop/test.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package grpc_testing is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
net/grpc/go/interop/test.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Payload
|
||||
SimpleRequest
|
||||
SimpleResponse
|
||||
SimpleContext
|
||||
StreamingInputCallRequest
|
||||
StreamingInputCallResponse
|
||||
ResponseParameters
|
||||
StreamingOutputCallRequest
|
||||
StreamingOutputCallResponse
|
||||
*/
|
||||
package grpc_testing
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = math.Inf
|
||||
|
||||
type Empty struct {
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Empty) Reset() { *m = Empty{} }
|
||||
func (m *Empty) String() string { return proto.CompactTextString(m) }
|
||||
func (*Empty) ProtoMessage() {}
|
||||
func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
// The type of payload that should be returned.
|
||||
type PayloadType int32
|
||||
|
||||
const (
|
||||
// Compressable text format.
|
||||
PayloadType_COMPRESSABLE PayloadType = 0
|
||||
// Uncompressable binary format.
|
||||
PayloadType_UNCOMPRESSABLE PayloadType = 1
|
||||
// Randomly chosen from all other formats defined in this enum.
|
||||
PayloadType_RANDOM PayloadType = 2
|
||||
)
|
||||
|
||||
var PayloadType_name = map[int32]string{
|
||||
0: "COMPRESSABLE",
|
||||
1: "UNCOMPRESSABLE",
|
||||
2: "RANDOM",
|
||||
}
|
||||
var PayloadType_value = map[string]int32{
|
||||
"COMPRESSABLE": 0,
|
||||
"UNCOMPRESSABLE": 1,
|
||||
"RANDOM": 2,
|
||||
}
|
||||
|
||||
func (x PayloadType) Enum() *PayloadType {
|
||||
p := new(PayloadType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
func (x PayloadType) String() string {
|
||||
return proto.EnumName(PayloadType_name, int32(x))
|
||||
}
|
||||
func (x *PayloadType) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(PayloadType_value, data, "PayloadType")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = PayloadType(value)
|
||||
return nil
|
||||
}
|
||||
func (PayloadType) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
// A block of data, to simply increase gRPC message size.
|
||||
type Payload struct {
|
||||
// The type of data in body.
|
||||
Type *PayloadType `protobuf:"varint,1,opt,name=type,enum=grpc.testing.PayloadType" json:"type,omitempty"`
|
||||
// Primary contents of payload.
|
||||
Body []byte `protobuf:"bytes,2,opt,name=body" json:"body,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Payload) Reset() { *m = Payload{} }
|
||||
func (m *Payload) String() string { return proto.CompactTextString(m) }
|
||||
func (*Payload) ProtoMessage() {}
|
||||
func (*Payload) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *Payload) GetType() PayloadType {
|
||||
if m != nil && m.Type != nil {
|
||||
return *m.Type
|
||||
}
|
||||
return PayloadType_COMPRESSABLE
|
||||
}
|
||||
|
||||
func (m *Payload) GetBody() []byte {
|
||||
if m != nil {
|
||||
return m.Body
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unary request.
|
||||
type SimpleRequest struct {
|
||||
// Desired payload type in the response from the server.
|
||||
// If response_type is RANDOM, server randomly chooses one from other formats.
|
||||
ResponseType *PayloadType `protobuf:"varint,1,opt,name=response_type,enum=grpc.testing.PayloadType" json:"response_type,omitempty"`
|
||||
// Desired payload size in the response from the server.
|
||||
// If response_type is COMPRESSABLE, this denotes the size before compression.
|
||||
ResponseSize *int32 `protobuf:"varint,2,opt,name=response_size" json:"response_size,omitempty"`
|
||||
// Optional input payload sent along with the request.
|
||||
Payload *Payload `protobuf:"bytes,3,opt,name=payload" json:"payload,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SimpleRequest) Reset() { *m = SimpleRequest{} }
|
||||
func (m *SimpleRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*SimpleRequest) ProtoMessage() {}
|
||||
func (*SimpleRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *SimpleRequest) GetResponseType() PayloadType {
|
||||
if m != nil && m.ResponseType != nil {
|
||||
return *m.ResponseType
|
||||
}
|
||||
return PayloadType_COMPRESSABLE
|
||||
}
|
||||
|
||||
func (m *SimpleRequest) GetResponseSize() int32 {
|
||||
if m != nil && m.ResponseSize != nil {
|
||||
return *m.ResponseSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *SimpleRequest) GetPayload() *Payload {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unary response, as configured by the request.
|
||||
type SimpleResponse struct {
|
||||
// Payload to increase message size.
|
||||
Payload *Payload `protobuf:"bytes,1,opt,name=payload" json:"payload,omitempty"`
|
||||
// The user the request came from, for verifying authentication was
|
||||
// successful when the client expected it.
|
||||
EffectiveGaiaUserId *int64 `protobuf:"varint,2,opt,name=effective_gaia_user_id" json:"effective_gaia_user_id,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SimpleResponse) Reset() { *m = SimpleResponse{} }
|
||||
func (m *SimpleResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*SimpleResponse) ProtoMessage() {}
|
||||
func (*SimpleResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
|
||||
|
||||
func (m *SimpleResponse) GetPayload() *Payload {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SimpleResponse) GetEffectiveGaiaUserId() int64 {
|
||||
if m != nil && m.EffectiveGaiaUserId != nil {
|
||||
return *m.EffectiveGaiaUserId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type SimpleContext struct {
|
||||
Value *string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *SimpleContext) Reset() { *m = SimpleContext{} }
|
||||
func (m *SimpleContext) String() string { return proto.CompactTextString(m) }
|
||||
func (*SimpleContext) ProtoMessage() {}
|
||||
func (*SimpleContext) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
|
||||
|
||||
func (m *SimpleContext) GetValue() string {
|
||||
if m != nil && m.Value != nil {
|
||||
return *m.Value
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
// Client-streaming request.
|
||||
type StreamingInputCallRequest struct {
|
||||
// Optional input payload sent along with the request.
|
||||
Payload *Payload `protobuf:"bytes,1,opt,name=payload" json:"payload,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *StreamingInputCallRequest) Reset() { *m = StreamingInputCallRequest{} }
|
||||
func (m *StreamingInputCallRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*StreamingInputCallRequest) ProtoMessage() {}
|
||||
func (*StreamingInputCallRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
|
||||
|
||||
func (m *StreamingInputCallRequest) GetPayload() *Payload {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Client-streaming response.
|
||||
type StreamingInputCallResponse struct {
|
||||
// Aggregated size of payloads received from the client.
|
||||
AggregatedPayloadSize *int32 `protobuf:"varint,1,opt,name=aggregated_payload_size" json:"aggregated_payload_size,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *StreamingInputCallResponse) Reset() { *m = StreamingInputCallResponse{} }
|
||||
func (m *StreamingInputCallResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*StreamingInputCallResponse) ProtoMessage() {}
|
||||
func (*StreamingInputCallResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
|
||||
|
||||
func (m *StreamingInputCallResponse) GetAggregatedPayloadSize() int32 {
|
||||
if m != nil && m.AggregatedPayloadSize != nil {
|
||||
return *m.AggregatedPayloadSize
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Configuration for a particular response.
|
||||
type ResponseParameters struct {
|
||||
// Desired payload sizes in responses from the server.
|
||||
// If response_type is COMPRESSABLE, this denotes the size before compression.
|
||||
Size *int32 `protobuf:"varint,1,opt,name=size" json:"size,omitempty"`
|
||||
// Desired interval between consecutive responses in the response stream in
|
||||
// microseconds.
|
||||
IntervalUs *int32 `protobuf:"varint,2,opt,name=interval_us" json:"interval_us,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ResponseParameters) Reset() { *m = ResponseParameters{} }
|
||||
func (m *ResponseParameters) String() string { return proto.CompactTextString(m) }
|
||||
func (*ResponseParameters) ProtoMessage() {}
|
||||
func (*ResponseParameters) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
|
||||
|
||||
func (m *ResponseParameters) GetSize() int32 {
|
||||
if m != nil && m.Size != nil {
|
||||
return *m.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *ResponseParameters) GetIntervalUs() int32 {
|
||||
if m != nil && m.IntervalUs != nil {
|
||||
return *m.IntervalUs
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Server-streaming request.
|
||||
type StreamingOutputCallRequest struct {
|
||||
// Desired payload type in the response from the server.
|
||||
// If response_type is RANDOM, the payload from each response in the stream
|
||||
// might be of different types. This is to simulate a mixed type of payload
|
||||
// stream.
|
||||
ResponseType *PayloadType `protobuf:"varint,1,opt,name=response_type,enum=grpc.testing.PayloadType" json:"response_type,omitempty"`
|
||||
// Configuration for each expected response message.
|
||||
ResponseParameters []*ResponseParameters `protobuf:"bytes,2,rep,name=response_parameters" json:"response_parameters,omitempty"`
|
||||
// Optional input payload sent along with the request.
|
||||
Payload *Payload `protobuf:"bytes,3,opt,name=payload" json:"payload,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *StreamingOutputCallRequest) Reset() { *m = StreamingOutputCallRequest{} }
|
||||
func (m *StreamingOutputCallRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*StreamingOutputCallRequest) ProtoMessage() {}
|
||||
func (*StreamingOutputCallRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
|
||||
|
||||
func (m *StreamingOutputCallRequest) GetResponseType() PayloadType {
|
||||
if m != nil && m.ResponseType != nil {
|
||||
return *m.ResponseType
|
||||
}
|
||||
return PayloadType_COMPRESSABLE
|
||||
}
|
||||
|
||||
func (m *StreamingOutputCallRequest) GetResponseParameters() []*ResponseParameters {
|
||||
if m != nil {
|
||||
return m.ResponseParameters
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *StreamingOutputCallRequest) GetPayload() *Payload {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Server-streaming response, as configured by the request and parameters.
|
||||
type StreamingOutputCallResponse struct {
|
||||
// Payload to increase response size.
|
||||
Payload *Payload `protobuf:"bytes,1,opt,name=payload" json:"payload,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *StreamingOutputCallResponse) Reset() { *m = StreamingOutputCallResponse{} }
|
||||
func (m *StreamingOutputCallResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*StreamingOutputCallResponse) ProtoMessage() {}
|
||||
func (*StreamingOutputCallResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
|
||||
|
||||
func (m *StreamingOutputCallResponse) GetPayload() *Payload {
|
||||
if m != nil {
|
||||
return m.Payload
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("grpc.testing.PayloadType", PayloadType_name, PayloadType_value)
|
||||
}
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 628 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x55, 0x4d, 0x6f, 0xd3, 0x40,
|
||||
0x10, 0xc5, 0x49, 0x3f, 0xd4, 0xc9, 0x87, 0xa2, 0xad, 0x0a, 0xa9, 0x5b, 0x95, 0xe2, 0x03, 0x4d,
|
||||
0x38, 0xd8, 0x28, 0x17, 0x4e, 0x15, 0xb4, 0x69, 0x2a, 0x90, 0xe8, 0x87, 0x9a, 0x56, 0xea, 0xcd,
|
||||
0xda, 0x24, 0xd3, 0x95, 0x25, 0xdb, 0x6b, 0xec, 0x75, 0xd5, 0x70, 0x42, 0xfc, 0x11, 0x38, 0xf6,
|
||||
0x0f, 0x70, 0xe4, 0x6f, 0x71, 0x66, 0x9d, 0xb5, 0x8b, 0x13, 0x5c, 0x48, 0x0f, 0x70, 0xf4, 0xee,
|
||||
0x9b, 0xf7, 0xde, 0xbc, 0x99, 0x4d, 0x60, 0xcb, 0x47, 0x61, 0xb1, 0x30, 0x18, 0x5a, 0x8c, 0x5b,
|
||||
0x8e, 0x2f, 0x30, 0xe4, 0x81, 0x25, 0x30, 0x12, 0x66, 0x10, 0x72, 0xc1, 0x49, 0x35, 0xb9, 0x33,
|
||||
0x93, 0x03, 0xc7, 0x67, 0x7a, 0x3b, 0x41, 0x4f, 0x2e, 0x3a, 0xd6, 0x20, 0x74, 0x46, 0x0c, 0xd5,
|
||||
0x97, 0xe5, 0x61, 0x14, 0x51, 0x86, 0x76, 0x84, 0x69, 0xa1, 0xbe, 0x99, 0x83, 0x2a, 0x0c, 0x7a,
|
||||
0x81, 0x18, 0xab, 0x5b, 0xe3, 0x0d, 0x2c, 0x9f, 0xd2, 0xb1, 0xcb, 0xe9, 0x88, 0xec, 0xc0, 0x82,
|
||||
0x18, 0x07, 0xd8, 0xd4, 0xb6, 0xb5, 0x56, 0xbd, 0xb3, 0x6e, 0xe6, 0x05, 0xcd, 0x14, 0x74, 0x2e,
|
||||
0x01, 0xa4, 0x0a, 0x0b, 0x03, 0x3e, 0x1a, 0x37, 0x4b, 0x12, 0x58, 0x35, 0x3e, 0x69, 0x50, 0xeb,
|
||||
0x3b, 0x5e, 0xe0, 0xe2, 0x19, 0x7e, 0x88, 0x25, 0x9c, 0xbc, 0x84, 0x5a, 0x88, 0x51, 0xc0, 0xfd,
|
||||
0x08, 0xed, 0xf9, 0x18, 0xd7, 0x72, 0x15, 0x91, 0xf3, 0x11, 0x27, 0xd4, 0x8b, 0xe4, 0x39, 0x2c,
|
||||
0x07, 0x0a, 0xd5, 0x2c, 0xcb, 0x83, 0x4a, 0x67, 0xad, 0x90, 0xc2, 0xb8, 0x84, 0x7a, 0xe6, 0x40,
|
||||
0x91, 0xe4, 0x2b, 0xb5, 0x3f, 0x54, 0x92, 0x2d, 0x78, 0x8c, 0x57, 0x57, 0x38, 0x14, 0xce, 0x35,
|
||||
0xda, 0x8c, 0x3a, 0xd4, 0x8e, 0x23, 0x0c, 0x6d, 0x67, 0x34, 0x71, 0x50, 0x36, 0x6e, 0xb2, 0xde,
|
||||
0xba, 0x5c, 0x8e, 0xe4, 0x46, 0x90, 0x1a, 0x2c, 0x5e, 0x53, 0x37, 0x56, 0x3d, 0xad, 0x74, 0x2e,
|
||||
0x61, 0x2d, 0x97, 0xb8, 0x2d, 0x11, 0xe8, 0x47, 0x0e, 0xf7, 0xc9, 0xba, 0xca, 0xb7, 0x63, 0xaa,
|
||||
0xe9, 0x98, 0x47, 0x0a, 0xd5, 0x47, 0xd1, 0xfc, 0xf2, 0xf9, 0xc7, 0xb3, 0x89, 0xa7, 0x8d, 0x69,
|
||||
0x4f, 0x53, 0x42, 0x46, 0x17, 0xd6, 0xfb, 0x22, 0x44, 0xea, 0xc9, 0xab, 0x77, 0x7e, 0x10, 0x8b,
|
||||
0x2e, 0x75, 0xdd, 0x2c, 0xe1, 0x39, 0xdb, 0x33, 0x76, 0x41, 0x2f, 0x22, 0x49, 0x43, 0x7a, 0x0a,
|
||||
0x4f, 0x28, 0x63, 0x21, 0x32, 0x2a, 0x70, 0x64, 0xa7, 0x84, 0x2a, 0xff, 0x84, 0x75, 0xd1, 0x78,
|
||||
0x05, 0x24, 0x03, 0x9f, 0xd2, 0x90, 0x7a, 0x28, 0xb7, 0x32, 0x4a, 0xc6, 0xff, 0x0b, 0x43, 0x56,
|
||||
0xa1, 0x32, 0xd9, 0x56, 0x99, 0x8a, 0xcc, 0x4e, 0x0d, 0xce, 0xf8, 0xa6, 0xe5, 0x84, 0x4f, 0x62,
|
||||
0x31, 0x63, 0xff, 0xe1, 0x0b, 0xb2, 0x0b, 0xab, 0x77, 0x15, 0xc1, 0x9d, 0x15, 0xa9, 0x56, 0x96,
|
||||
0xcd, 0x6f, 0x4f, 0xd7, 0x15, 0x58, 0x9e, 0x77, 0x91, 0x7a, 0xb0, 0x51, 0x68, 0xfb, 0x61, 0x5b,
|
||||
0xf5, 0xe2, 0x35, 0x54, 0xf2, 0xe6, 0x1b, 0x50, 0xed, 0x9e, 0x1c, 0x9d, 0x9e, 0xf5, 0xfa, 0xfd,
|
||||
0xbd, 0xfd, 0xf7, 0xbd, 0xc6, 0x23, 0x42, 0xa0, 0x7e, 0x71, 0x3c, 0x75, 0xa6, 0x11, 0x80, 0xa5,
|
||||
0xb3, 0xbd, 0xe3, 0x83, 0x93, 0xa3, 0x46, 0xa9, 0xf3, 0x7d, 0x01, 0x2a, 0xe7, 0x92, 0xb4, 0x2f,
|
||||
0x73, 0x75, 0x86, 0x48, 0xda, 0xb0, 0xd2, 0x4b, 0x1e, 0x6d, 0xe2, 0x86, 0xd4, 0xb2, 0xd5, 0x9a,
|
||||
0x1c, 0xe9, 0xd3, 0x9f, 0xe4, 0x10, 0x56, 0x2e, 0x7c, 0x1a, 0x2a, 0x68, 0xe1, 0x86, 0xa5, 0x53,
|
||||
0xd0, 0x37, 0x8b, 0x2f, 0xd3, 0x5e, 0x39, 0xac, 0x16, 0x44, 0x41, 0x5a, 0x33, 0x45, 0xf7, 0x0e,
|
||||
0x59, 0x6f, 0xcf, 0x81, 0x54, 0x5a, 0x46, 0xf9, 0x56, 0xd3, 0x88, 0x0b, 0xe4, 0xf7, 0x5d, 0x25,
|
||||
0x3b, 0xf7, 0xb0, 0xcc, 0x3e, 0x09, 0xbd, 0xf5, 0x77, 0x60, 0xa6, 0xf6, 0x55, 0xaa, 0x79, 0x50,
|
||||
0x3f, 0x8c, 0x5d, 0xf7, 0x20, 0x96, 0x3d, 0xdf, 0xfc, 0xbb, 0xce, 0x96, 0xa4, 0xd6, 0xad, 0x92,
|
||||
0x7b, 0x4b, 0xdd, 0xab, 0xff, 0x24, 0xb7, 0x6f, 0x41, 0x7b, 0xc8, 0x3d, 0x93, 0x71, 0xce, 0x5c,
|
||||
0x34, 0xe5, 0x1f, 0x80, 0x19, 0x89, 0x78, 0x30, 0x18, 0xdf, 0x91, 0x24, 0x4f, 0x96, 0x85, 0x54,
|
||||
0xc8, 0x9f, 0xaa, 0x46, 0xa9, 0x55, 0xfa, 0x19, 0x00, 0x00, 0xff, 0xff, 0x0b, 0xad, 0xee, 0x7c,
|
||||
0x7e, 0x06, 0x00, 0x00,
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package grpc_testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/grpc/grpc-go/rpc"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
context "golang.org/x/net/context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type TestServiceClient interface {
|
||||
EmptyCall(ctx context.Context, in *Empty, opts ...rpc.CallOption) (*Empty, error)
|
||||
UnaryCall(ctx context.Context, in *SimpleRequest, opts ...rpc.CallOption) (*SimpleResponse, error)
|
||||
StreamingOutputCall(ctx context.Context, m *StreamingOutputCallRequest, opts ...rpc.CallOption) (TestService_StreamingOutputCallClient, error)
|
||||
StreamingInputCall(ctx context.Context, opts ...rpc.CallOption) (TestService_StreamingInputCallClient, error)
|
||||
FullDuplexCall(ctx context.Context, opts ...rpc.CallOption) (TestService_FullDuplexCallClient, error)
|
||||
HalfDuplexCall(ctx context.Context, opts ...rpc.CallOption) (TestService_HalfDuplexCallClient, error)
|
||||
}
|
||||
|
||||
type testServiceClient struct {
|
||||
cc *rpc.ClientConn
|
||||
}
|
||||
|
||||
func NewTestServiceClient(cc *rpc.ClientConn) TestServiceClient {
|
||||
return &testServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *testServiceClient) EmptyCall(ctx context.Context, in *Empty, opts ...rpc.CallOption) (*Empty, error) {
|
||||
out := new(Empty)
|
||||
err := rpc.Invoke(ctx, "/grpc.testing.TestService/EmptyCall", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) UnaryCall(ctx context.Context, in *SimpleRequest, opts ...rpc.CallOption) (*SimpleResponse, error) {
|
||||
out := new(SimpleResponse)
|
||||
err := rpc.Invoke(ctx, "/grpc.testing.TestService/UnaryCall", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) StreamingOutputCall(ctx context.Context, m *StreamingOutputCallRequest, opts ...rpc.CallOption) (TestService_StreamingOutputCallClient, error) {
|
||||
stream, err := rpc.NewClientStream(ctx, c.cc, "/grpc.testing.TestService/StreamingOutputCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &testServiceStreamingOutputCallClient{stream}
|
||||
if err := x.ClientStream.SendProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type TestService_StreamingOutputCallClient interface {
|
||||
Recv() (*StreamingOutputCallResponse, error)
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceStreamingOutputCallClient struct {
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingOutputCallClient) Recv() (*StreamingOutputCallResponse, error) {
|
||||
m := new(StreamingOutputCallResponse)
|
||||
if err := x.ClientStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) StreamingInputCall(ctx context.Context, opts ...rpc.CallOption) (TestService_StreamingInputCallClient, error) {
|
||||
stream, err := rpc.NewClientStream(ctx, c.cc, "/grpc.testing.TestService/StreamingInputCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testServiceStreamingInputCallClient{stream}, nil
|
||||
}
|
||||
|
||||
type TestService_StreamingInputCallClient interface {
|
||||
Send(*StreamingInputCallRequest) error
|
||||
CloseAndRecv() (*StreamingInputCallResponse, error)
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceStreamingInputCallClient struct {
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallClient) Send(m *StreamingInputCallRequest) error {
|
||||
return x.ClientStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallClient) CloseAndRecv() (*StreamingInputCallResponse, error) {
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := new(StreamingInputCallResponse)
|
||||
if err := x.ClientStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read EOF.
|
||||
if err := x.ClientStream.RecvProto(m); err == io.EOF {
|
||||
return m, io.EOF
|
||||
}
|
||||
// gRPC protocol violation.
|
||||
return m, fmt.Errorf("Violate gRPC client streaming protocol: no EOF after the response.")
|
||||
}
|
||||
|
||||
func (c *testServiceClient) FullDuplexCall(ctx context.Context, opts ...rpc.CallOption) (TestService_FullDuplexCallClient, error) {
|
||||
stream, err := rpc.NewClientStream(ctx, c.cc, "/grpc.testing.TestService/FullDuplexCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testServiceFullDuplexCallClient{stream}, nil
|
||||
}
|
||||
|
||||
type TestService_FullDuplexCallClient interface {
|
||||
Send(*StreamingOutputCallRequest) error
|
||||
Recv() (*StreamingOutputCallResponse, error)
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceFullDuplexCallClient struct {
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallClient) Send(m *StreamingOutputCallRequest) error {
|
||||
return x.ClientStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallClient) Recv() (*StreamingOutputCallResponse, error) {
|
||||
m := new(StreamingOutputCallResponse)
|
||||
if err := x.ClientStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *testServiceClient) HalfDuplexCall(ctx context.Context, opts ...rpc.CallOption) (TestService_HalfDuplexCallClient, error) {
|
||||
stream, err := rpc.NewClientStream(ctx, c.cc, "/grpc.testing.TestService/HalfDuplexCall", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &testServiceHalfDuplexCallClient{stream}, nil
|
||||
}
|
||||
|
||||
type TestService_HalfDuplexCallClient interface {
|
||||
Send(*StreamingOutputCallRequest) error
|
||||
Recv() (*StreamingOutputCallResponse, error)
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
type testServiceHalfDuplexCallClient struct {
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallClient) Send(m *StreamingOutputCallRequest) error {
|
||||
return x.ClientStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallClient) Recv() (*StreamingOutputCallResponse, error) {
|
||||
m := new(StreamingOutputCallResponse)
|
||||
if err := x.ClientStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
type TestServiceServer interface {
|
||||
EmptyCall(context.Context, *Empty) (*Empty, error)
|
||||
UnaryCall(context.Context, *SimpleRequest) (*SimpleResponse, error)
|
||||
StreamingOutputCall(*StreamingOutputCallRequest, TestService_StreamingOutputCallServer) error
|
||||
StreamingInputCall(TestService_StreamingInputCallServer) error
|
||||
FullDuplexCall(TestService_FullDuplexCallServer) error
|
||||
HalfDuplexCall(TestService_HalfDuplexCallServer) error
|
||||
}
|
||||
|
||||
func RegisterService(s *rpc.Server, srv TestServiceServer) {
|
||||
s.RegisterService(&_TestService_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _TestService_EmptyCall_Handler(srv interface{}, ctx context.Context, buf []byte) (proto.Message, error) {
|
||||
in := new(Empty)
|
||||
if err := proto.Unmarshal(buf, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := srv.(TestServiceServer).EmptyCall(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func _TestService_UnaryCall_Handler(srv interface{}, ctx context.Context, buf []byte) (proto.Message, error) {
|
||||
in := new(SimpleRequest)
|
||||
if err := proto.Unmarshal(buf, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := srv.(TestServiceServer).UnaryCall(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func _TestService_StreamingOutputCall_Handler(srv interface{}, stream rpc.ServerStream) error {
|
||||
m := new(StreamingOutputCallRequest)
|
||||
if err := stream.RecvProto(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(TestServiceServer).StreamingOutputCall(m, &testServiceStreamingOutputCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_StreamingOutputCallServer interface {
|
||||
Send(*StreamingOutputCallResponse) error
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceStreamingOutputCallServer struct {
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingOutputCallServer) Send(m *StreamingOutputCallResponse) error {
|
||||
return x.ServerStream.SendProto(m)
|
||||
}
|
||||
|
||||
func _TestService_StreamingInputCall_Handler(srv interface{}, stream rpc.ServerStream) error {
|
||||
return srv.(TestServiceServer).StreamingInputCall(&testServiceStreamingInputCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_StreamingInputCallServer interface {
|
||||
SendAndClose(*StreamingInputCallResponse) error
|
||||
Recv() (*StreamingInputCallRequest, error)
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceStreamingInputCallServer struct {
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallServer) SendAndClose(m *StreamingInputCallResponse) error {
|
||||
if err := x.ServerStream.SendProto(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *testServiceStreamingInputCallServer) Recv() (*StreamingInputCallRequest, error) {
|
||||
m := new(StreamingInputCallRequest)
|
||||
if err := x.ServerStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _TestService_FullDuplexCall_Handler(srv interface{}, stream rpc.ServerStream) error {
|
||||
return srv.(TestServiceServer).FullDuplexCall(&testServiceFullDuplexCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_FullDuplexCallServer interface {
|
||||
Send(*StreamingOutputCallResponse) error
|
||||
Recv() (*StreamingOutputCallRequest, error)
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceFullDuplexCallServer struct {
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallServer) Send(m *StreamingOutputCallResponse) error {
|
||||
return x.ServerStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *testServiceFullDuplexCallServer) Recv() (*StreamingOutputCallRequest, error) {
|
||||
m := new(StreamingOutputCallRequest)
|
||||
if err := x.ServerStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _TestService_HalfDuplexCall_Handler(srv interface{}, stream rpc.ServerStream) error {
|
||||
return srv.(TestServiceServer).HalfDuplexCall(&testServiceHalfDuplexCallServer{stream})
|
||||
}
|
||||
|
||||
type TestService_HalfDuplexCallServer interface {
|
||||
Send(*StreamingOutputCallResponse) error
|
||||
Recv() (*StreamingOutputCallRequest, error)
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
type testServiceHalfDuplexCallServer struct {
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallServer) Send(m *StreamingOutputCallResponse) error {
|
||||
return x.ServerStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *testServiceHalfDuplexCallServer) Recv() (*StreamingOutputCallRequest, error) {
|
||||
m := new(StreamingOutputCallRequest)
|
||||
if err := x.ServerStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var _TestService_serviceDesc = rpc.ServiceDesc{
|
||||
ServiceName: "grpc.testing.TestService",
|
||||
HandlerType: (*TestServiceServer)(nil),
|
||||
Methods: []rpc.MethodDesc{
|
||||
{
|
||||
MethodName: "EmptyCall",
|
||||
Handler: _TestService_EmptyCall_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "UnaryCall",
|
||||
Handler: _TestService_UnaryCall_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []rpc.StreamDesc{
|
||||
{
|
||||
StreamName: "StreamingOutputCall",
|
||||
Handler: _TestService_StreamingOutputCall_Handler,
|
||||
},
|
||||
{
|
||||
StreamName: "StreamingInputCall",
|
||||
Handler: _TestService_StreamingInputCall_Handler,
|
||||
},
|
||||
{
|
||||
StreamName: "FullDuplexCall",
|
||||
Handler: _TestService_FullDuplexCall_Handler,
|
||||
},
|
||||
{
|
||||
StreamName: "HalfDuplexCall",
|
||||
Handler: _TestService_HalfDuplexCall_Handler,
|
||||
},
|
||||
},
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package metadata define the structure of the metadata supported by gRPC library.
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
binHdrSuffix = "-bin"
|
||||
)
|
||||
|
||||
// grpc-http2 requires ASCII header key and value (more detail can be found in
|
||||
// "Requests" subsection in go/grpc-http2).
|
||||
func isASCII(s string) bool {
|
||||
for _, c := range s {
|
||||
if c > 127 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// encodeKeyValue encodes key and value qualified for transmission via gRPC.
|
||||
// Transmitting binary headers violates HTTP/2 spec.
|
||||
// TODO(zhaoq): Maybe check if k is ASCII also.
|
||||
func encodeKeyValue(k, v string) (string, string) {
|
||||
if isASCII(v) {
|
||||
return k, v
|
||||
}
|
||||
key := k + binHdrSuffix
|
||||
val := base64.StdEncoding.EncodeToString([]byte(v))
|
||||
return key, string(val)
|
||||
}
|
||||
|
||||
// DecodeKeyValue returns the original key and value corresponding to the
|
||||
// encoded data in k, v.
|
||||
func DecodeKeyValue(k, v string) (string, string, error) {
|
||||
if !strings.HasSuffix(k, binHdrSuffix) {
|
||||
return k, v, nil
|
||||
}
|
||||
key := k[:len(k)-len(binHdrSuffix)]
|
||||
val, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return key, string(val), nil
|
||||
}
|
||||
|
||||
// MD is a mapping from metadata keys to values. Users should use the following
|
||||
// two convenience functions New and Pairs to generate MD.
|
||||
type MD map[string]string
|
||||
|
||||
// New creates a MD from given key-value map.
|
||||
func New(m map[string]string) MD {
|
||||
md := MD{}
|
||||
for k, v := range m {
|
||||
key, val := encodeKeyValue(k, v)
|
||||
md[key] = val
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// Pairs returns an MD formed by the mapping of key, value ...
|
||||
// Pairs panics if len(kv) is odd.
|
||||
func Pairs(kv ...string) MD {
|
||||
if len(kv)%2 == 1 {
|
||||
panic(fmt.Sprintf("Got the odd number of input pairs for metadata: %d", len(kv)))
|
||||
}
|
||||
md := MD{}
|
||||
var k string
|
||||
for i, s := range kv {
|
||||
if i%2 == 0 {
|
||||
k = s
|
||||
continue
|
||||
}
|
||||
key, val := encodeKeyValue(k, s)
|
||||
md[key] = val
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// Len returns the number of items in md.
|
||||
func (md MD) Len() int {
|
||||
return len(md)
|
||||
}
|
||||
|
||||
// Copy returns a copy of md.
|
||||
func (md MD) Copy() MD {
|
||||
out := MD{}
|
||||
for k, v := range md {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type mdKey struct{}
|
||||
|
||||
// NewContext creates a new context with md attached.
|
||||
func NewContext(ctx context.Context, md MD) context.Context {
|
||||
return context.WithValue(ctx, mdKey{}, md)
|
||||
}
|
||||
|
||||
// FromContext returns the MD in ctx if it exists.
|
||||
func FromContext(ctx context.Context) (md MD, ok bool) {
|
||||
md, ok = ctx.Value(mdKey{}).(MD)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const binaryValue = string(128)
|
||||
|
||||
func TestDecodeKeyValue(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
kin string
|
||||
vin string
|
||||
// output
|
||||
kout string
|
||||
vout string
|
||||
err error
|
||||
}{
|
||||
{"a", "abc", "a", "abc", nil},
|
||||
{"key-bin", "Zm9vAGJhcg==", "key", "foo\x00bar", nil},
|
||||
{"key-bin", "woA=", "key", binaryValue, nil},
|
||||
} {
|
||||
k, v, err := DecodeKeyValue(test.kin, test.vin)
|
||||
if k != test.kout || !reflect.DeepEqual(v, test.vout) || !reflect.DeepEqual(err, test.err) {
|
||||
t.Fatalf("DecodeKeyValue(%q, %q) = %q, %q, %v, want %q, %q, %v", test.kin, test.vin, k, v, err, test.kout, test.vout, test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPairsMD(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
kv []string
|
||||
// output
|
||||
md MD
|
||||
}{
|
||||
{[]string{}, MD{}},
|
||||
{[]string{"k1", "v1", "k2", binaryValue}, New(map[string]string{
|
||||
"k1": "v1",
|
||||
"k2-bin": "woA=",
|
||||
})},
|
||||
} {
|
||||
md := Pairs(test.kv...)
|
||||
if !reflect.DeepEqual(md, test.md) {
|
||||
t.Fatalf("Pairs(%v) = %v, want %v", test.kv, md, test.md)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,282 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
"github.com/grpc/grpc-go/rpc/transport"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// CallOption configures a Call before it starts or extracts information from
|
||||
// a Call after it completes.
|
||||
type CallOption interface {
|
||||
// before is called before the call is sent to any server. If before
|
||||
// returns a non-nil error, the RPC fails with that error.
|
||||
before(*callInfo) error
|
||||
|
||||
// after is called after the call has completed. after cannot return an
|
||||
// error, so any failures should be reported via output parameters.
|
||||
after(*callInfo)
|
||||
}
|
||||
|
||||
type beforeCall func(c *callInfo) error
|
||||
|
||||
func (o beforeCall) before(c *callInfo) error { return o(c) }
|
||||
func (o beforeCall) after(c *callInfo) {}
|
||||
|
||||
type afterCall func(c *callInfo)
|
||||
|
||||
func (o afterCall) before(c *callInfo) error { return nil }
|
||||
func (o afterCall) after(c *callInfo) { o(c) }
|
||||
|
||||
// Header returns a CallOptions that retrieves the header metadata
|
||||
// for a unary RPC.
|
||||
func Header(md *metadata.MD) CallOption {
|
||||
return afterCall(func(c *callInfo) {
|
||||
*md = c.headerMD
|
||||
})
|
||||
}
|
||||
|
||||
// Trailer returns a CallOptions that retrieves the trailer metadata
|
||||
// for a unary RPC.
|
||||
func Trailer(md *metadata.MD) CallOption {
|
||||
return afterCall(func(c *callInfo) {
|
||||
*md = c.trailerMD
|
||||
})
|
||||
}
|
||||
|
||||
// The format of the payload: compressed or not?
|
||||
type payloadFormat uint8
|
||||
|
||||
const (
|
||||
compressionNone payloadFormat = iota // no compression
|
||||
compressionFlate
|
||||
// More formats
|
||||
)
|
||||
|
||||
// parser reads complelete gRPC messages from the underlying reader.
|
||||
type parser struct {
|
||||
s io.Reader
|
||||
}
|
||||
|
||||
// msgFixedHeader defines the header of a gRPC message (go/grpc-wirefmt).
|
||||
type msgFixedHeader struct {
|
||||
T payloadFormat
|
||||
Length uint32
|
||||
}
|
||||
|
||||
// recvMsg is to read a complete gRPC message from the stream. It is blocking if
|
||||
// the message has not been complete yet. It returns the message and its type,
|
||||
// EOF is returned with nil msg and 0 pf if the entire stream is done. Other
|
||||
// non-nil error is returned if something is wrong on reading.
|
||||
func (p *parser) recvMsg() (pf payloadFormat, msg []byte, err error) {
|
||||
var hdr msgFixedHeader
|
||||
if err := binary.Read(p.s, binary.BigEndian, &hdr); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if hdr.Length == 0 {
|
||||
return hdr.T, nil, nil
|
||||
}
|
||||
msg = make([]byte, int(hdr.Length))
|
||||
if _, err := io.ReadFull(p.s, msg); err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return 0, nil, err
|
||||
}
|
||||
return hdr.T, msg, nil
|
||||
}
|
||||
|
||||
// encode serializes msg and prepends the message header. If msg is nil, it
|
||||
// generates the message header of 0 message length.
|
||||
func encode(msg proto.Message, pf payloadFormat) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
// Write message fixed header.
|
||||
if err := buf.WriteByte(uint8(pf)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var b []byte
|
||||
var length uint32
|
||||
if msg != nil {
|
||||
var err error
|
||||
// TODO(zhaoq): optimize to reduce memory alloc and copying.
|
||||
b, err = proto.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length = uint32(len(b))
|
||||
}
|
||||
if err := binary.Write(&buf, binary.BigEndian, length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := buf.Write(b); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func recvProto(p *parser, m proto.Message) error {
|
||||
pf, d, err := p.recvMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch pf {
|
||||
case compressionNone:
|
||||
if err := proto.Unmarshal(d, m); err != nil {
|
||||
return Errorf(codes.Internal, "%v", err)
|
||||
}
|
||||
default:
|
||||
return Errorf(codes.Internal, "compression is not supported yet.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rpcError defines the status from an RPC.
|
||||
type rpcError struct {
|
||||
code codes.Code
|
||||
desc string
|
||||
}
|
||||
|
||||
func (e rpcError) Error() string {
|
||||
return fmt.Sprintf("rpc error: code = %d desc = %q", e.code, e.desc)
|
||||
}
|
||||
|
||||
// Code returns the error code for err if it was produced by the rpc system.
|
||||
// Otherwise, it returns codes.Unknown.
|
||||
func Code(err error) codes.Code {
|
||||
if e, ok := err.(rpcError); ok {
|
||||
return e.code
|
||||
}
|
||||
return codes.Unknown
|
||||
}
|
||||
|
||||
// Errorf returns an error containing an error code and a description;
|
||||
// CodeOf extracts the Code.
|
||||
// Errorf returns nil if c is OK.
|
||||
func Errorf(c codes.Code, format string, a ...interface{}) error {
|
||||
if c == codes.OK {
|
||||
return nil
|
||||
}
|
||||
return rpcError{
|
||||
code: c,
|
||||
desc: fmt.Sprintf(format, a...),
|
||||
}
|
||||
}
|
||||
|
||||
// toRPCErr converts a transport error into a rpcError if possible.
|
||||
func toRPCErr(err error) error {
|
||||
switch e := err.(type) {
|
||||
case transport.StreamError:
|
||||
return rpcError{
|
||||
code: e.Code,
|
||||
desc: e.Desc,
|
||||
}
|
||||
case transport.ConnectionError:
|
||||
return rpcError{
|
||||
code: codes.Internal,
|
||||
desc: e.Desc,
|
||||
}
|
||||
}
|
||||
return Errorf(codes.Unknown, "failed to convert %v to rpcErr", err)
|
||||
}
|
||||
|
||||
// convertCode converts a standard Go error into its canonical code. Note that
|
||||
// this is only used to translate the error returned by the server applications.
|
||||
func convertCode(err error) codes.Code {
|
||||
switch err {
|
||||
case nil:
|
||||
return codes.OK
|
||||
case io.EOF:
|
||||
return codes.OutOfRange
|
||||
case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF:
|
||||
return codes.FailedPrecondition
|
||||
case os.ErrInvalid:
|
||||
return codes.InvalidArgument
|
||||
case context.Canceled:
|
||||
return codes.Canceled
|
||||
case context.DeadlineExceeded:
|
||||
return codes.DeadlineExceeded
|
||||
}
|
||||
switch {
|
||||
case os.IsExist(err):
|
||||
return codes.AlreadyExists
|
||||
case os.IsNotExist(err):
|
||||
return codes.NotFound
|
||||
case os.IsPermission(err):
|
||||
return codes.PermissionDenied
|
||||
}
|
||||
return codes.Unknown
|
||||
}
|
||||
|
||||
const (
|
||||
// how long to wait after the first failure before retrying
|
||||
baseDelay = 1.0 * time.Second
|
||||
// upper bound on backoff delay
|
||||
maxDelay = 120 * time.Second
|
||||
backoffFactor = 2.0 // backoff increases by this factor on each retry
|
||||
backoffRange = 0.4 // backoff is randomized downwards by this factor
|
||||
)
|
||||
|
||||
// backoff returns a value in [0, maxDelay] that increases exponentially with
|
||||
// retries, starting from baseDelay.
|
||||
func backoff(retries int) time.Duration {
|
||||
backoff, max := float64(baseDelay), float64(maxDelay)
|
||||
for backoff < max && retries > 0 {
|
||||
backoff = backoff * backoffFactor
|
||||
retries--
|
||||
}
|
||||
if backoff > max {
|
||||
backoff = max
|
||||
}
|
||||
|
||||
// Randomize backoff delays so that if a cluster of requests start at
|
||||
// the same time, they won't operate in lockstep. We just subtract up
|
||||
// to 40% so that we obey maxDelay.
|
||||
backoff -= backoff * backoffRange * rand.Float64()
|
||||
if backoff < 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Duration(backoff)
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/transport"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestSimpleParsing(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
p []byte
|
||||
// outputs
|
||||
err error
|
||||
b []byte
|
||||
pt payloadFormat
|
||||
}{
|
||||
{nil, io.EOF, nil, compressionNone},
|
||||
{[]byte{0, 0, 0, 0, 0}, nil, nil, compressionNone},
|
||||
{[]byte{0, 0, 0, 0, 1, 'a'}, nil, []byte{'a'}, compressionNone},
|
||||
{[]byte{1, 0}, io.ErrUnexpectedEOF, nil, compressionNone},
|
||||
{[]byte{0, 0, 0, 0, 10, 'a'}, io.ErrUnexpectedEOF, nil, compressionNone},
|
||||
} {
|
||||
buf := bytes.NewReader(test.p)
|
||||
parser := &parser{buf}
|
||||
pt, b, err := parser.recvMsg()
|
||||
if err != test.err || !bytes.Equal(b, test.b) || pt != test.pt {
|
||||
t.Fatalf("parser{%v}.recvMsg() = %v, %v, %v\nwant %v, %v, %v", test.p, pt, b, err, test.pt, test.b, test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultipleParsing(t *testing.T) {
|
||||
// Set a byte stream consists of 3 messages with their headers.
|
||||
p := []byte{0, 0, 0, 0, 1, 'a', 0, 0, 0, 0, 2, 'b', 'c', 0, 0, 0, 0, 1, 'd'}
|
||||
b := bytes.NewReader(p)
|
||||
parser := &parser{b}
|
||||
|
||||
wantRecvs := []struct {
|
||||
pt payloadFormat
|
||||
data []byte
|
||||
}{
|
||||
{compressionNone, []byte("a")},
|
||||
{compressionNone, []byte("bc")},
|
||||
{compressionNone, []byte("d")},
|
||||
}
|
||||
for i, want := range wantRecvs {
|
||||
pt, data, err := parser.recvMsg()
|
||||
if err != nil || pt != want.pt || !reflect.DeepEqual(data, want.data) {
|
||||
t.Fatalf("after %d calls, parser{%v}.recvMsg() = %v, %v, %v\nwant %v, %v, <nil>",
|
||||
i, p, pt, data, err, want.pt, want.data)
|
||||
}
|
||||
}
|
||||
|
||||
pt, data, err := parser.recvMsg()
|
||||
if err != io.EOF {
|
||||
t.Fatalf("after %d recvMsgs calls, parser{%v}.recvMsg() = %v, %v, %v\nwant _, _, %v",
|
||||
len(wantRecvs), p, pt, data, err, io.EOF)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
msg proto.Message
|
||||
pt payloadFormat
|
||||
// outputs
|
||||
b []byte
|
||||
err error
|
||||
}{
|
||||
{nil, compressionNone, []byte{0, 0, 0, 0, 0}, nil},
|
||||
} {
|
||||
b, err := encode(test.msg, test.pt)
|
||||
if err != test.err || !bytes.Equal(b, test.b) {
|
||||
t.Fatalf("encode(_, %d) = %v, %v\nwant %v, %v", test.pt, b, err, test.b, test.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToRPCErr(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
errIn error
|
||||
// outputs
|
||||
errOut error
|
||||
}{
|
||||
{transport.StreamErrorf(codes.Unknown, ""), Errorf(codes.Unknown, "")},
|
||||
{transport.ErrConnClosing, Errorf(codes.Internal, transport.ErrConnClosing.Desc)},
|
||||
} {
|
||||
err := toRPCErr(test.errIn)
|
||||
if err != test.errOut {
|
||||
t.Fatalf("toRPCErr{%v} = %v \nwant %v", test.errIn, err, test.errOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextErr(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
errIn error
|
||||
// outputs
|
||||
errOut transport.StreamError
|
||||
}{
|
||||
{context.DeadlineExceeded, transport.StreamErrorf(codes.DeadlineExceeded, "%v", context.DeadlineExceeded)},
|
||||
{context.Canceled, transport.StreamErrorf(codes.Canceled, "%v", context.Canceled)},
|
||||
} {
|
||||
err := transport.ContextErr(test.errIn)
|
||||
if err != test.errOut {
|
||||
t.Fatalf("ContextErr{%v} = %v \nwant %v", test.errIn, err, test.errOut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackoff(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
retries int
|
||||
maxResult time.Duration
|
||||
}{
|
||||
{0, time.Second},
|
||||
{1, time.Duration(1e9 * math.Pow(backoffFactor, 1))},
|
||||
{2, time.Duration(1e9 * math.Pow(backoffFactor, 2))},
|
||||
{3, time.Duration(1e9 * math.Pow(backoffFactor, 3))},
|
||||
{4, time.Duration(1e9 * math.Pow(backoffFactor, 4))},
|
||||
{int(math.Log2(float64(maxDelay)/float64(baseDelay))) + 1, maxDelay},
|
||||
} {
|
||||
delay := backoff(test.retries)
|
||||
if delay < 0 || delay > test.maxResult {
|
||||
t.Errorf("backoff(%d) = %v outside [0, %v]", test.retries, delay, test.maxResult)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,372 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
"github.com/grpc/grpc-go/rpc/transport"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type methodHandler func(srv interface{}, ctx context.Context, buf []byte) (proto.Message, error)
|
||||
|
||||
// MethodDesc represents an RPC service's method specification.
|
||||
type MethodDesc struct {
|
||||
MethodName string
|
||||
Handler methodHandler
|
||||
}
|
||||
|
||||
type streamHandler func(srv interface{}, stream ServerStream) error
|
||||
|
||||
// StreamDesc represents a streaming RPC service's method specification.
|
||||
type StreamDesc struct {
|
||||
StreamName string
|
||||
Handler streamHandler
|
||||
}
|
||||
|
||||
// ServiceDesc represents an RPC service's specification.
|
||||
type ServiceDesc struct {
|
||||
ServiceName string
|
||||
// The pointer to the service interface. Used to check whether the user
|
||||
// provided implementation satisfies the interface requirements.
|
||||
HandlerType interface{}
|
||||
Methods []MethodDesc
|
||||
Streams []StreamDesc
|
||||
}
|
||||
|
||||
// service consists of the information of the server serving this service and
|
||||
// the methods in this service.
|
||||
type service struct {
|
||||
server interface{} // the server for service methods
|
||||
md map[string]*MethodDesc
|
||||
sd map[string]*StreamDesc
|
||||
}
|
||||
|
||||
// Server is a gRPC server to serve RPC requests.
|
||||
type Server struct {
|
||||
opts options
|
||||
mu sync.Mutex
|
||||
lis map[net.Listener]bool
|
||||
conns map[transport.ServerTransport]bool
|
||||
m map[string]*service // service name -> service info
|
||||
}
|
||||
|
||||
type options struct {
|
||||
maxConcurrentStreams uint32
|
||||
}
|
||||
|
||||
// ServerOption sets options.
|
||||
type ServerOption func(*options)
|
||||
|
||||
// MaxConcurrentStreams returns an Option that will apply a limit on the number
|
||||
// of concurrent streams to each ServerTransport.
|
||||
func MaxConcurrentStreams(n uint32) ServerOption {
|
||||
return func(o *options) {
|
||||
o.maxConcurrentStreams = n
|
||||
}
|
||||
}
|
||||
|
||||
// NewServer creates a gRPC server which has no service registered and has not
|
||||
// started to accept requests yet.
|
||||
func NewServer(opt ...ServerOption) *Server {
|
||||
var opts options
|
||||
for _, o := range opt {
|
||||
o(&opts)
|
||||
}
|
||||
return &Server{
|
||||
lis: make(map[net.Listener]bool),
|
||||
opts: opts,
|
||||
conns: make(map[transport.ServerTransport]bool),
|
||||
m: make(map[string]*service),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterService register a service and its implementation to the gRPC
|
||||
// server. Called from the IDL generated code. This must be called before
|
||||
// invoking Serve.
|
||||
func (s *Server) RegisterService(sd *ServiceDesc, ss interface{}) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
// Does some sanity checks.
|
||||
if _, ok := s.m[sd.ServiceName]; ok {
|
||||
log.Fatalf("rpc: Duplicate service registration for %q", sd.ServiceName)
|
||||
}
|
||||
ht := reflect.TypeOf(sd.HandlerType).Elem()
|
||||
st := reflect.TypeOf(ss)
|
||||
if !st.Implements(ht) {
|
||||
log.Fatalf("rpc: The handler of type %v that does not satisfy %v", st, ht)
|
||||
}
|
||||
srv := &service{
|
||||
server: ss,
|
||||
md: make(map[string]*MethodDesc),
|
||||
sd: make(map[string]*StreamDesc),
|
||||
}
|
||||
for i := range sd.Methods {
|
||||
d := &sd.Methods[i]
|
||||
srv.md[d.MethodName] = d
|
||||
}
|
||||
for i := range sd.Streams {
|
||||
d := &sd.Streams[i]
|
||||
srv.sd[d.StreamName] = d
|
||||
}
|
||||
s.m[sd.ServiceName] = srv
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the listener lis, creating a new
|
||||
// ServerTransport and service goroutine for each. The service goroutines
|
||||
// read gRPC request and then call the registered handlers to reply to them.
|
||||
// Service returns when lis.Accept fails.
|
||||
func (s *Server) Serve(lis net.Listener) error {
|
||||
s.mu.Lock()
|
||||
if s.lis == nil {
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("the server has been stopped")
|
||||
}
|
||||
s.lis[lis] = true
|
||||
s.mu.Unlock()
|
||||
defer func() {
|
||||
lis.Close()
|
||||
s.mu.Lock()
|
||||
delete(s.lis, lis)
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
for {
|
||||
c, err := lis.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
if s.conns == nil {
|
||||
s.mu.Unlock()
|
||||
c.Close()
|
||||
return nil
|
||||
}
|
||||
st, err := transport.NewServerTransport("http2", c, s.opts.maxConcurrentStreams)
|
||||
if err != nil {
|
||||
s.mu.Unlock()
|
||||
c.Close()
|
||||
log.Println("failed to create ServerTransport: ", err)
|
||||
continue
|
||||
}
|
||||
s.conns[st] = true
|
||||
s.mu.Unlock()
|
||||
|
||||
go func() {
|
||||
st.HandleStreams(func(stream *transport.Stream) {
|
||||
s.handleStream(st, stream)
|
||||
})
|
||||
s.mu.Lock()
|
||||
delete(s.conns, st)
|
||||
s.mu.Unlock()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) sendProto(t transport.ServerTransport, stream *transport.Stream, msg proto.Message, pf payloadFormat, opts *transport.Options) error {
|
||||
p, err := encode(msg, pf)
|
||||
if err != nil {
|
||||
// This typically indicates a fatal issue (e.g., memory
|
||||
// corruption or hardware faults) the application program
|
||||
// cannot handle.
|
||||
//
|
||||
// TODO(zhaoq): There exist other options also such as only closing the
|
||||
// faulty stream locally and remotely (Other streams can keep going). Find
|
||||
// the optimal option.
|
||||
log.Fatalf("Server: failed to encode proto message %v", err)
|
||||
}
|
||||
return t.Write(stream, p, opts)
|
||||
}
|
||||
|
||||
func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, md *MethodDesc) {
|
||||
p := &parser{s: stream}
|
||||
for {
|
||||
pf, req, err := p.recvMsg()
|
||||
if err == io.EOF {
|
||||
// The entire stream is done (for unary rpc only).
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case transport.ConnectionError:
|
||||
// Nothing to do here.
|
||||
case transport.StreamError:
|
||||
t.WriteStatus(stream, err.Code, err.Desc)
|
||||
default:
|
||||
panic(fmt.Sprintf("BUG: Unexpected error (%T) from recvMsg: %v", err, err))
|
||||
}
|
||||
return
|
||||
}
|
||||
switch pf {
|
||||
case compressionNone:
|
||||
reply, appErr := md.Handler(srv.server, stream.Context(), req)
|
||||
if appErr != nil {
|
||||
t.WriteStatus(stream, convertCode(appErr), appErr.Error())
|
||||
return
|
||||
}
|
||||
opts := &transport.Options{
|
||||
Last: true,
|
||||
Delay: false,
|
||||
}
|
||||
statusCode := codes.OK
|
||||
statusDesc := ""
|
||||
if err := s.sendProto(t, stream, reply, compressionNone, opts); err != nil {
|
||||
if _, ok := err.(transport.ConnectionError); ok {
|
||||
return
|
||||
}
|
||||
if e, ok := err.(transport.StreamError); ok {
|
||||
statusCode = e.Code
|
||||
statusDesc = e.Desc
|
||||
} else {
|
||||
statusCode = codes.Unknown
|
||||
statusDesc = err.Error()
|
||||
}
|
||||
}
|
||||
t.WriteStatus(stream, statusCode, statusDesc)
|
||||
default:
|
||||
panic(fmt.Sprintf("payload format to be supported: %d", pf))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transport.Stream, srv *service, sd *StreamDesc) {
|
||||
ss := &serverStream{
|
||||
t: t,
|
||||
s: stream,
|
||||
p: &parser{s: stream},
|
||||
}
|
||||
if err := sd.Handler(srv.server, ss); err != nil {
|
||||
ss.statusCode = convertCode(err)
|
||||
ss.statusDesc = err.Error()
|
||||
}
|
||||
t.WriteStatus(ss.s, ss.statusCode, ss.statusDesc)
|
||||
}
|
||||
|
||||
func (s *Server) handleStream(t transport.ServerTransport, stream *transport.Stream) {
|
||||
sm := stream.Method()
|
||||
if sm != "" && sm[0] == '/' {
|
||||
sm = sm[1:]
|
||||
}
|
||||
pos := strings.LastIndex(sm, "/")
|
||||
if pos == -1 {
|
||||
t.WriteStatus(stream, codes.InvalidArgument, fmt.Sprintf("malformed method name: %q", stream.Method()))
|
||||
return
|
||||
}
|
||||
service := sm[:pos]
|
||||
method := sm[pos+1:]
|
||||
srv, ok := s.m[service]
|
||||
if !ok {
|
||||
t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown service %v", service))
|
||||
return
|
||||
}
|
||||
// Unary RPC or Streaming RPC?
|
||||
if md, ok := srv.md[method]; ok {
|
||||
s.processUnaryRPC(t, stream, srv, md)
|
||||
return
|
||||
}
|
||||
if sd, ok := srv.sd[method]; ok {
|
||||
s.processStreamingRPC(t, stream, srv, sd)
|
||||
return
|
||||
}
|
||||
t.WriteStatus(stream, codes.Unimplemented, fmt.Sprintf("unknown method %v", method))
|
||||
}
|
||||
|
||||
// Stop stops the gRPC server. Once it returns, the server stops accepting
|
||||
// connection requests and closes all the connected connections.
|
||||
func (s *Server) Stop() {
|
||||
s.mu.Lock()
|
||||
listeners := s.lis
|
||||
s.lis = nil
|
||||
cs := s.conns
|
||||
s.conns = nil
|
||||
s.mu.Unlock()
|
||||
for lis := range listeners {
|
||||
lis.Close()
|
||||
}
|
||||
for c := range cs {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// CloseConns closes all exiting transports but keeps s.lis accepting new
|
||||
// connections. This is for test only now.
|
||||
func (s *Server) CloseConns() {
|
||||
s.mu.Lock()
|
||||
for c := range s.conns {
|
||||
c.Close()
|
||||
delete(s.conns, c)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// SendHeader sends header metadata. It may be called at most once from a unary
|
||||
// RPC handler. The ctx is the RPC handler's Context or one derived from it.
|
||||
func SendHeader(ctx context.Context, md metadata.MD) error {
|
||||
if md.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
stream, ok := transport.StreamFromContext(ctx)
|
||||
if !ok {
|
||||
return fmt.Errorf("rpc: failed to fetch the stream from the context %v", ctx)
|
||||
}
|
||||
t := stream.ServerTransport()
|
||||
if t == nil {
|
||||
log.Fatalf("rpc.SendHeader: %v has no ServerTransport to send header metadata.", stream)
|
||||
}
|
||||
return t.WriteHeader(stream, md)
|
||||
}
|
||||
|
||||
// SetTrailer sets the trailer metadata that will be sent when an RPC returns.
|
||||
// It may be called at most once from a unary RPC handler. The ctx is the RPC
|
||||
// handler's Context or one derived from it.
|
||||
func SetTrailer(ctx context.Context, md metadata.MD) error {
|
||||
if md.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
stream, ok := transport.StreamFromContext(ctx)
|
||||
if !ok {
|
||||
return fmt.Errorf("rpc: failed to fetch the stream from the context %v", ctx)
|
||||
}
|
||||
return stream.SetTrailer(md)
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
"github.com/grpc/grpc-go/rpc/transport"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Stream defines the common interface a client or server stream has to satisfy.
|
||||
type Stream interface {
|
||||
// Context returns the context for this stream.
|
||||
Context() context.Context
|
||||
// SendProto blocks until it sends a proto message out or some
|
||||
// error happens.
|
||||
SendProto(proto.Message) error
|
||||
// RecvProto blocks until either a proto message is received or some
|
||||
// error happens.
|
||||
RecvProto(proto.Message) error
|
||||
}
|
||||
|
||||
// ClientStream defines the interface a client stream has to satify.
|
||||
type ClientStream interface {
|
||||
// Header returns the header metedata received from the server if there
|
||||
// is any. It blocks if the metadata is not ready to read.
|
||||
Header() (metadata.MD, error)
|
||||
// Trailer returns the trailer metadata from the server. It must be called
|
||||
// after stream.Recv() returns non-nil error (including io.EOF) for
|
||||
// bi-directional streaming and server streaming or stream.CloseAndRecv()
|
||||
// returns for client streaming in order to receive trailer metadata if
|
||||
// present.
|
||||
Trailer() metadata.MD
|
||||
// CloseSend closes the send direction of the stream.
|
||||
CloseSend() error
|
||||
Stream
|
||||
}
|
||||
|
||||
// NewClientStream creates a new Stream for the client side. This is called
|
||||
// by generated code.
|
||||
func NewClientStream(ctx context.Context, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error) {
|
||||
// TODO(zhaoq): CallOption is omitted. Add support when it is needed.
|
||||
callHdr := &transport.CallHdr{
|
||||
Host: cc.target,
|
||||
Method: method,
|
||||
}
|
||||
t, _, err := cc.wait(ctx, 0)
|
||||
if err != nil {
|
||||
return nil, toRPCErr(err)
|
||||
}
|
||||
s, err := t.NewStream(ctx, callHdr)
|
||||
if err != nil {
|
||||
return nil, toRPCErr(err)
|
||||
}
|
||||
return &clientStream{
|
||||
t: t,
|
||||
s: s,
|
||||
p: &parser{s: s},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// clientStream implements a client side Stream.
|
||||
type clientStream struct {
|
||||
t transport.ClientTransport
|
||||
s *transport.Stream
|
||||
p *parser
|
||||
}
|
||||
|
||||
// Context returns the clientStream's associated context.
|
||||
func (cs *clientStream) Context() context.Context {
|
||||
return cs.s.Context()
|
||||
}
|
||||
|
||||
// Header returns the header metedata received from the server if there
|
||||
// is any. Empty metadata.MD is returned if there is no header metadata.
|
||||
// It blocks if the metadata is not ready to read.
|
||||
func (cs *clientStream) Header() (md metadata.MD, err error) {
|
||||
return cs.s.Header()
|
||||
}
|
||||
|
||||
// Trailer returns the trailer metadata from the server. It must be called
|
||||
// after stream.Recv() returns non-nil error (including io.EOF) for
|
||||
// bi-directional streaming and server streaming or stream.CloseAndRecv()
|
||||
// returns for client streaming in order to receive trailer metadata if
|
||||
// present.
|
||||
func (cs *clientStream) Trailer() metadata.MD {
|
||||
return cs.s.Trailer()
|
||||
}
|
||||
|
||||
// SendProto blocks until m is sent out or an error happens. It closes the
|
||||
// stream when a non-nil error is met. This is called by generated code.
|
||||
func (cs *clientStream) SendProto(m proto.Message) (err error) {
|
||||
defer func() {
|
||||
if err == nil || err == io.EOF {
|
||||
return
|
||||
}
|
||||
if _, ok := err.(transport.ConnectionError); !ok {
|
||||
cs.t.CloseStream(cs.s, err)
|
||||
}
|
||||
err = toRPCErr(err)
|
||||
}()
|
||||
out, err := encode(m, compressionNone)
|
||||
if err != nil {
|
||||
return transport.StreamErrorf(codes.Internal, "%v", err)
|
||||
}
|
||||
return cs.t.Write(cs.s, out, &transport.Options{Last: false})
|
||||
}
|
||||
|
||||
// RecvProto blocks until it receives a proto message or an error happens.
|
||||
// When an non-nil error (including EOF which indicates the success of an
|
||||
// RPC) is met, it also closes the stream and returns the RPC status to
|
||||
// the caller. This is called by generated code.
|
||||
func (cs *clientStream) RecvProto(m proto.Message) (err error) {
|
||||
err = recvProto(cs.p, m)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if err == io.EOF {
|
||||
if cs.s.StatusCode() == codes.OK {
|
||||
// Returns io.EOF to indicate the end of the stream.
|
||||
return
|
||||
}
|
||||
return Errorf(cs.s.StatusCode(), cs.s.StatusDesc())
|
||||
}
|
||||
if _, ok := err.(transport.ConnectionError); !ok {
|
||||
cs.t.CloseStream(cs.s, err)
|
||||
}
|
||||
return toRPCErr(err)
|
||||
}
|
||||
|
||||
// CloseSend closes the send direction of the stream. It closes the stream
|
||||
// when non-nil error is met.
|
||||
func (cs *clientStream) CloseSend() (err error) {
|
||||
err = cs.t.Write(cs.s, nil, &transport.Options{Last: true})
|
||||
if err == nil || err == io.EOF {
|
||||
return
|
||||
}
|
||||
if _, ok := err.(transport.ConnectionError); !ok {
|
||||
cs.t.CloseStream(cs.s, err)
|
||||
}
|
||||
err = toRPCErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
// ServerStream defines the interface a server stream has to satisfy.
|
||||
type ServerStream interface {
|
||||
// SendHeader sends the header metadata. It should not be called
|
||||
// after SendProto.
|
||||
SendHeader(metadata.MD) error
|
||||
// SetTrailer sets the trailer metadata which will be sent with the
|
||||
// RPC status.
|
||||
SetTrailer(metadata.MD)
|
||||
Stream
|
||||
}
|
||||
|
||||
// serverStream implements a server side Stream.
|
||||
type serverStream struct {
|
||||
t transport.ServerTransport
|
||||
s *transport.Stream
|
||||
p *parser
|
||||
statusCode codes.Code
|
||||
statusDesc string
|
||||
}
|
||||
|
||||
// Context returns the associated context so that server applications can
|
||||
// manipulate it.
|
||||
func (ss *serverStream) Context() context.Context {
|
||||
return ss.s.Context()
|
||||
}
|
||||
|
||||
// SendHeader sends header metadata. It fails if called multiple times or if
|
||||
// called after SendProto.
|
||||
func (ss *serverStream) SendHeader(md metadata.MD) error {
|
||||
return ss.t.WriteHeader(ss.s, md)
|
||||
}
|
||||
|
||||
// SetTrailer sends trailer metadata. The metadata will be sent with the final
|
||||
// RPC status.
|
||||
func (ss *serverStream) SetTrailer(md metadata.MD) {
|
||||
if md.Len() == 0 {
|
||||
return
|
||||
}
|
||||
ss.s.SetTrailer(md)
|
||||
return
|
||||
}
|
||||
|
||||
// SendProto blocks until m is sent out or an error is met. This is called by
|
||||
// generated code.
|
||||
func (ss *serverStream) SendProto(m proto.Message) error {
|
||||
out, err := encode(m, compressionNone)
|
||||
if err != nil {
|
||||
err = transport.StreamErrorf(codes.Internal, "%v", err)
|
||||
return err
|
||||
}
|
||||
return ss.t.Write(ss.s, out, &transport.Options{Last: false})
|
||||
}
|
||||
|
||||
// RecvProto blocks until it receives a message or an error is met. This is
|
||||
// called by generated code.
|
||||
func (ss *serverStream) RecvProto(m proto.Message) error {
|
||||
return recvProto(ss.p, m)
|
||||
}
|
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package grpc_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/grpc/grpc-go/rpc"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/credentials"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
testpb "github.com/grpc/grpc-go/rpc/test/testdata"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
testMetadata = metadata.MD{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
)
|
||||
|
||||
type mathServer struct {
|
||||
}
|
||||
|
||||
func (s *mathServer) Div(ctx context.Context, in *testpb.DivArgs) (*testpb.DivReply, error) {
|
||||
md, ok := metadata.FromContext(ctx)
|
||||
if ok {
|
||||
if err := rpc.SendHeader(ctx, md); err != nil {
|
||||
log.Fatalf("rpc.SendHeader(%v, %v) = %v, want %v", ctx, md, err, nil)
|
||||
}
|
||||
rpc.SetTrailer(ctx, md)
|
||||
}
|
||||
n, d := in.GetDividend(), in.GetDivisor()
|
||||
if d == 0 {
|
||||
return nil, fmt.Errorf("math: divide by 0")
|
||||
}
|
||||
out := new(testpb.DivReply)
|
||||
out.Quotient = proto.Int64(n / d)
|
||||
out.Remainder = proto.Int64(n % d)
|
||||
// Simulate some service delay.
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
return out, nil // no error
|
||||
}
|
||||
|
||||
func (s *mathServer) DivMany(stream testpb.Math_DivManyServer) error {
|
||||
md, ok := metadata.FromContext(stream.Context())
|
||||
if ok {
|
||||
if err := stream.SendHeader(md); err != nil {
|
||||
log.Fatalf("%v.SendHeader(%v) = %v, want %v", stream, md, err, nil)
|
||||
}
|
||||
stream.SetTrailer(md)
|
||||
}
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
// read done.
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, d := in.GetDividend(), in.GetDivisor()
|
||||
if d == 0 {
|
||||
return fmt.Errorf("math: divide by 0")
|
||||
}
|
||||
err = stream.Send(&testpb.DivReply{
|
||||
Quotient: proto.Int64(n / d),
|
||||
Remainder: proto.Int64(n % d),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *mathServer) Fib(args *testpb.FibArgs, stream testpb.Math_FibServer) error {
|
||||
var (
|
||||
limit = args.GetLimit()
|
||||
count int64
|
||||
x, y int64 = 0, 1
|
||||
)
|
||||
for count = 0; limit == 0 || count < limit; count++ {
|
||||
// Send the next number in the Fibonacci sequence.
|
||||
stream.Send(&testpb.Num{
|
||||
Num: proto.Int64(x),
|
||||
})
|
||||
x, y = y, x+y
|
||||
}
|
||||
return nil // The RPC library will call stream.CloseSend for us.
|
||||
}
|
||||
|
||||
func (s *mathServer) Sum(stream testpb.Math_SumServer) error {
|
||||
var sum int64
|
||||
for {
|
||||
m, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return stream.SendAndClose(&testpb.Num{Num: &sum})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sum += m.GetNum()
|
||||
}
|
||||
}
|
||||
|
||||
const tlsDir = "testdata/"
|
||||
|
||||
func setUp(useTLS bool, maxStream uint32) (s *rpc.Server, mc testpb.MathClient) {
|
||||
lis, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
_, port, err := net.SplitHostPort(lis.Addr().String())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse listener address: %v", err)
|
||||
}
|
||||
s = rpc.NewServer(rpc.MaxConcurrentStreams(maxStream))
|
||||
ms := &mathServer{}
|
||||
testpb.RegisterService(s, ms)
|
||||
if useTLS {
|
||||
creds, err := credentials.NewServerTLSFromFile(tlsDir+"server1.pem", tlsDir+"server1.key")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate credentials %v", err)
|
||||
}
|
||||
go s.Serve(creds.NewListener(lis))
|
||||
} else {
|
||||
go s.Serve(lis)
|
||||
}
|
||||
addr := "localhost:" + port
|
||||
var conn *rpc.ClientConn
|
||||
if useTLS {
|
||||
creds, err := credentials.NewClientTLSFromFile(tlsDir+"ca.pem", "x.test.youtube.com")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create credentials %v", err)
|
||||
}
|
||||
conn, err = rpc.Dial(addr, rpc.WithClientTLS(creds))
|
||||
} else {
|
||||
conn, err = rpc.Dial(addr)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Dial(%q) = %v", addr, err)
|
||||
}
|
||||
mc = testpb.NewMathClient(conn)
|
||||
return
|
||||
}
|
||||
|
||||
func TestFailedRPC(t *testing.T) {
|
||||
s, mc := setUp(false, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
args := &testpb.DivArgs{
|
||||
Dividend: proto.Int64(8),
|
||||
Divisor: proto.Int64(0),
|
||||
}
|
||||
expectedErr := rpc.Errorf(codes.Unknown, "math: divide by 0")
|
||||
reply, rpcErr := mc.Div(context.Background(), args)
|
||||
if fmt.Sprint(rpcErr) != fmt.Sprint(expectedErr) {
|
||||
t.Fatalf(`mathClient.Div(_, _) = %v, %v; want <nil>, %v`, reply, rpcErr, expectedErr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetadataUnaryRPC(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
args := &testpb.DivArgs{
|
||||
Dividend: proto.Int64(8),
|
||||
Divisor: proto.Int64(2),
|
||||
}
|
||||
ctx := metadata.NewContext(context.Background(), testMetadata)
|
||||
var header, trailer metadata.MD
|
||||
_, err := mc.Div(ctx, args, rpc.Header(&header), rpc.Trailer(&trailer))
|
||||
if err != nil {
|
||||
t.Fatalf("mathClient.Div(%v, _, _, _) = _, %v; want _, <nil>", ctx, err)
|
||||
}
|
||||
if !reflect.DeepEqual(testMetadata, header) {
|
||||
t.Fatalf("Received header metadata %v, want %v", header, testMetadata)
|
||||
}
|
||||
if !reflect.DeepEqual(testMetadata, trailer) {
|
||||
t.Fatalf("Received trailer metadata %v, want %v", trailer, testMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
func performOneRPC(t *testing.T, mc testpb.MathClient, wg *sync.WaitGroup) {
|
||||
args := &testpb.DivArgs{
|
||||
Dividend: proto.Int64(8),
|
||||
Divisor: proto.Int64(3),
|
||||
}
|
||||
reply, err := mc.Div(context.Background(), args)
|
||||
want := &testpb.DivReply{
|
||||
Quotient: proto.Int64(2),
|
||||
Remainder: proto.Int64(2),
|
||||
}
|
||||
if err != nil || !proto.Equal(reply, want) {
|
||||
t.Errorf(`mathClient.Div(_, _) = %v, %v; want %v, <nil>`, reply, err, want)
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
// This test mimics a user who sends 1000 RPCs concurrently on a faulty transport.
|
||||
// TODO(zhaoq): Refactor to make this clearer and add more cases to test racy
|
||||
// and error-prone paths.
|
||||
func TestRetry(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
time.Sleep(1 * time.Second)
|
||||
// The server shuts down the network connection to make a
|
||||
// transport error which will be detected by the client side
|
||||
// code.
|
||||
s.CloseConns()
|
||||
wg.Done()
|
||||
}()
|
||||
// All these RPCs should succeed eventually.
|
||||
for i := 0; i < 1000; i++ {
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
wg.Add(1)
|
||||
go performOneRPC(t, mc, &wg)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TODO(zhaoq): Have a better test coverage of timeout and cancellation mechanism.
|
||||
func TestTimeout(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
args := &testpb.DivArgs{
|
||||
Dividend: proto.Int64(8),
|
||||
Divisor: proto.Int64(3),
|
||||
}
|
||||
// Performs 100 RPCs with various timeout values so that
|
||||
// the RPCs could timeout on different stages of their lifetime. This
|
||||
// is the best-effort to cover various cases when an rpc gets cancelled.
|
||||
for i := 1; i <= 100; i++ {
|
||||
ctx, _ := context.WithTimeout(context.Background(), time.Duration(i)*time.Microsecond)
|
||||
reply, err := mc.Div(ctx, args)
|
||||
if rpc.Code(err) != codes.DeadlineExceeded {
|
||||
t.Fatalf(`mathClient.Div(_, _) = %v, %v; want <nil>, error code: %d`, reply, err, codes.DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancel(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
args := &testpb.DivArgs{
|
||||
Dividend: proto.Int64(8),
|
||||
Divisor: proto.Int64(3),
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
time.AfterFunc(1*time.Millisecond, cancel)
|
||||
reply, err := mc.Div(ctx, args)
|
||||
if rpc.Code(err) != codes.Canceled {
|
||||
t.Fatalf(`mathClient.Div(_, _) = %v, %v; want <nil>, error code: %d`, reply, err, codes.Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
// The following tests the gRPC streaming RPC implementations.
|
||||
// TODO(zhaoq): Have better coverage on error cases.
|
||||
|
||||
func TestBidiStreaming(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
divs []string
|
||||
// output
|
||||
status error
|
||||
}{
|
||||
{[]string{"1/1", "3/2", "2/3", "1/2"}, io.EOF},
|
||||
{[]string{"2/5", "2/3", "3/0", "5/4"}, rpc.Errorf(codes.Unknown, "math: divide by 0")},
|
||||
} {
|
||||
stream, err := mc.DivMany(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create stream %v", err)
|
||||
}
|
||||
// Start a goroutine to parse and send the args.
|
||||
go func() {
|
||||
for _, args := range parseArgs(test.divs) {
|
||||
if err := stream.Send(args); err != nil {
|
||||
t.Errorf("Send failed: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Tell the server we're done sending args.
|
||||
stream.CloseSend()
|
||||
}()
|
||||
var rpcStatus error
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err != nil {
|
||||
rpcStatus = err
|
||||
break
|
||||
}
|
||||
}
|
||||
if rpcStatus != test.status {
|
||||
t.Fatalf(`mathClient.DivMany got %v ; want %v`, rpcStatus, test.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseArgs converts a list of "n/d" strings into DivArgs.
|
||||
// parseArgs crashes the process on error.
|
||||
func parseArgs(divs []string) (args []*testpb.DivArgs) {
|
||||
for _, div := range divs {
|
||||
parts := strings.Split(div, "/")
|
||||
n, err := strconv.ParseInt(parts[0], 10, 64)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
d, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
args = append(args, &testpb.DivArgs{
|
||||
Dividend: &n,
|
||||
Divisor: &d,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestMetadataStreamingRPC(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
ctx := metadata.NewContext(context.Background(), testMetadata)
|
||||
stream, err := mc.DivMany(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create stream %v", err)
|
||||
}
|
||||
go func() {
|
||||
headerMD, err := stream.Header()
|
||||
if err != nil || !reflect.DeepEqual(testMetadata, headerMD) {
|
||||
t.Errorf("#1 %v.Header() = %v, %v, want %v, <nil>", stream, headerMD, err, testMetadata)
|
||||
}
|
||||
// test the cached value.
|
||||
headerMD, err = stream.Header()
|
||||
if err != nil || !reflect.DeepEqual(testMetadata, headerMD) {
|
||||
t.Errorf("#2 %v.Header() = %v, %v, want %v, <nil>", stream, headerMD, err, testMetadata)
|
||||
}
|
||||
for _, args := range parseArgs([]string{"1/1", "3/2", "2/3"}) {
|
||||
if err := stream.Send(args); err != nil {
|
||||
t.Errorf("%v.Send(_) failed: %v", stream, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Tell the server we're done sending args.
|
||||
stream.CloseSend()
|
||||
}()
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
trailerMD := stream.Trailer()
|
||||
if !reflect.DeepEqual(testMetadata, trailerMD) {
|
||||
t.Fatalf("%v.Trailer() = %v, want %v", stream, trailerMD, testMetadata)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerStreaming(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
|
||||
args := &testpb.FibArgs{}
|
||||
// Requests the first 10 Fibonnaci numbers.
|
||||
args.Limit = proto.Int64(10)
|
||||
|
||||
// Start the stream and send the args.
|
||||
stream, err := mc.Fib(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create stream %v", err)
|
||||
}
|
||||
var rpcStatus error
|
||||
for {
|
||||
_, err := stream.Recv()
|
||||
if err != nil {
|
||||
rpcStatus = err
|
||||
break
|
||||
}
|
||||
}
|
||||
if rpcStatus != io.EOF {
|
||||
t.Fatalf(`mathClient.Fib got %v ; want <EOF>`, rpcStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientStreaming(t *testing.T) {
|
||||
s, mc := setUp(true, math.MaxUint32)
|
||||
defer s.Stop()
|
||||
|
||||
stream, err := mc.Sum(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create stream: %v", err)
|
||||
}
|
||||
for _, n := range []int64{1, -2, 0, 7} {
|
||||
if err := stream.Send(&testpb.Num{Num: &n}); err != nil {
|
||||
t.Fatalf("failed to send requests %v", err)
|
||||
}
|
||||
}
|
||||
if _, err := stream.CloseAndRecv(); err != io.EOF {
|
||||
t.Fatalf("stream.CloseAndRecv() got %v; want <EOF>", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExceedMaxStreamsLimit(t *testing.T) {
|
||||
// Only allows 1 live stream per server transport.
|
||||
s, mc := setUp(true, 1)
|
||||
defer s.Stop()
|
||||
var err error
|
||||
for {
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
_, err = mc.Sum(context.Background())
|
||||
// Loop until the settings of max concurrent streams is
|
||||
// received by the client.
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if rpc.Code(err) != codes.Unavailable {
|
||||
t.Fatalf("got %v, want error code %d", err, codes.Unavailable)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICIzCCAYwCCQCFTbF7XNSvvjANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzE3MjMxNzUxWhcNMjQw
|
||||
NzE0MjMxNzUxWjBWMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
|
||||
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0
|
||||
Y2EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMBA3wVeTGHZR1Rye/i+J8a2
|
||||
cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBlLoNY0lSv
|
||||
xDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JAHUGodjx+
|
||||
QAi6yCAurUZGvYXGgZSBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQoQVD8bwdtWJ
|
||||
AniGBwcCfqYyH+/KpA10AcebJVVTyYbYvI9Q8d6RSVu4PZy9OALHR/QrWBdYTAyz
|
||||
fNAmc2cmdkSRJzjhIaOstnQy1J+Fk0T9XyvQtq499yFbq9xogUVlEGH62xP6vH0Y
|
||||
5ukK//dCPAzA11YuX2rnex0JhuTQfcI=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQDhwxUnKCwlSaWAwzOB2LSHVegJHv7DDWminTg4wzLLsf+LQ8nZ
|
||||
bpjfn5vgIzxCuRh4Rp9QYM5FhfrJX9wcYawP/HTbJ7p7LVQO2QYAP+akMTHxgKuM
|
||||
BzVV++3wWToKfVZUjFX8nfTfGMGwWAHJDnlEGnU4tl9UujoCV4ENJtzFoQIDAQAB
|
||||
AoGAJ+6hpzNr24yTQZtFWQpDpEyFplddKJMOxDya3S9ppK3vTWrIITV2xNcucw7I
|
||||
ceTbdyrGsyjsU0/HdCcIf9ym2jfmGLUwmyhltKVw0QYcFB0XLkc0nI5YvEYoeVDg
|
||||
omZIXn1E3EW+sSIWSbkMu9bY2kstKXR2UZmMgWDtmBEPMaECQQD6yT4TAZM5hGBb
|
||||
ciBKgMUP6PwOhPhOMPIvijO50Aiu6iuCV88l1QIy38gWVhxjNrq6P346j4IBg+kB
|
||||
9alwpCODAkEA5nSnm9k6ykYeQWNS0fNWiRinCdl23A7usDGSuKKlm019iomJ/Rgd
|
||||
MKDOp0q/2OostbteOWM2MRFf4jMH3wyVCwJAfAdjJ8szoNKTRSagSbh9vWygnB2v
|
||||
IByc6l4TTuZQJRGzCveafz9lovuB3WohCABdQRd9ukCXL2CpsEpqzkafOQJAJUjc
|
||||
USedDlq3zGZwYM1Yw8d8RuirBUFZNqJelYai+nRYClDkRVFgb5yksoYycbq5TxGo
|
||||
VeqKOvgPpj4RWPHlLwJAGUMk3bqT91xBUCnLRs/vfoCpHpg6eywQTBDAV6xkyz4a
|
||||
RH3I7/+yj3ZxR2JoWHgUwZ7lZk1VnhffFye7SBXyag==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
|
||||
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||
dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
|
||||
MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
|
||||
BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
|
||||
c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
|
||||
JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
|
||||
RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
|
||||
3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
|
||||
BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
|
||||
b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
|
||||
KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
|
||||
wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
|
||||
aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
// Code generated by protoc-gen-go.
|
||||
// source: test.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package test is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
test.proto
|
||||
|
||||
It has these top-level messages:
|
||||
DivArgs
|
||||
DivReply
|
||||
FibArgs
|
||||
Num
|
||||
FibReply
|
||||
*/
|
||||
package test
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = math.Inf
|
||||
|
||||
type DivArgs struct {
|
||||
Dividend *int64 `protobuf:"varint,1,req,name=dividend" json:"dividend,omitempty"`
|
||||
Divisor *int64 `protobuf:"varint,2,req,name=divisor" json:"divisor,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DivArgs) Reset() { *m = DivArgs{} }
|
||||
func (m *DivArgs) String() string { return proto.CompactTextString(m) }
|
||||
func (*DivArgs) ProtoMessage() {}
|
||||
|
||||
func (m *DivArgs) GetDividend() int64 {
|
||||
if m != nil && m.Dividend != nil {
|
||||
return *m.Dividend
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DivArgs) GetDivisor() int64 {
|
||||
if m != nil && m.Divisor != nil {
|
||||
return *m.Divisor
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type DivReply struct {
|
||||
Quotient *int64 `protobuf:"varint,1,req,name=quotient" json:"quotient,omitempty"`
|
||||
Remainder *int64 `protobuf:"varint,2,req,name=remainder" json:"remainder,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *DivReply) Reset() { *m = DivReply{} }
|
||||
func (m *DivReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*DivReply) ProtoMessage() {}
|
||||
|
||||
func (m *DivReply) GetQuotient() int64 {
|
||||
if m != nil && m.Quotient != nil {
|
||||
return *m.Quotient
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *DivReply) GetRemainder() int64 {
|
||||
if m != nil && m.Remainder != nil {
|
||||
return *m.Remainder
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FibArgs struct {
|
||||
Limit *int64 `protobuf:"varint,1,opt,name=limit" json:"limit,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *FibArgs) Reset() { *m = FibArgs{} }
|
||||
func (m *FibArgs) String() string { return proto.CompactTextString(m) }
|
||||
func (*FibArgs) ProtoMessage() {}
|
||||
|
||||
func (m *FibArgs) GetLimit() int64 {
|
||||
if m != nil && m.Limit != nil {
|
||||
return *m.Limit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Num struct {
|
||||
Num *int64 `protobuf:"varint,1,req,name=num" json:"num,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Num) Reset() { *m = Num{} }
|
||||
func (m *Num) String() string { return proto.CompactTextString(m) }
|
||||
func (*Num) ProtoMessage() {}
|
||||
|
||||
func (m *Num) GetNum() int64 {
|
||||
if m != nil && m.Num != nil {
|
||||
return *m.Num
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type FibReply struct {
|
||||
Count *int64 `protobuf:"varint,1,req,name=count" json:"count,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *FibReply) Reset() { *m = FibReply{} }
|
||||
func (m *FibReply) String() string { return proto.CompactTextString(m) }
|
||||
func (*FibReply) ProtoMessage() {}
|
||||
|
||||
func (m *FibReply) GetCount() int64 {
|
||||
if m != nil && m.Count != nil {
|
||||
return *m.Count
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
syntax = "proto2";
|
||||
|
||||
package test;
|
||||
|
||||
option go_package = "test";
|
||||
|
||||
message DivArgs {
|
||||
required int64 dividend = 1;
|
||||
required int64 divisor = 2;
|
||||
}
|
||||
|
||||
message DivReply {
|
||||
required int64 quotient = 1;
|
||||
required int64 remainder = 2;
|
||||
}
|
||||
|
||||
message FibArgs {
|
||||
optional int64 limit = 1;
|
||||
}
|
||||
|
||||
message Num {
|
||||
required int64 num = 1;
|
||||
}
|
||||
|
||||
message FibReply {
|
||||
required int64 count = 1;
|
||||
}
|
||||
|
||||
service Math { // This name leads to "MathServer" and "MathClient".
|
||||
// Div divides args.dividend by args.divisor and returns the quotient and
|
||||
// remainder.
|
||||
rpc Div (DivArgs) returns (DivReply) {
|
||||
}
|
||||
|
||||
// DivMany accepts an arbitrary number of division args from the client stream
|
||||
// and sends back the results in the reply stream. The stream continues until
|
||||
// the client closes its end; the server does the same after sending all the
|
||||
// replies. The stream ends immediately if either end aborts.
|
||||
rpc DivMany (stream DivArgs) returns (stream DivReply) {
|
||||
}
|
||||
|
||||
// Fib generates numbers in the Fibonacci sequence. If args.limit > 0, Fib4
|
||||
// generates up to limit numbers; otherwise it continues until the call is
|
||||
// canceled. Unlike Fib above, Fib4 has no final FibReply.
|
||||
rpc Fib (FibArgs) returns (stream Num) {
|
||||
}
|
||||
|
||||
// Sum sums a stream of numbers, returning the final result once the stream
|
||||
// is closed.
|
||||
rpc Sum (stream Num) returns (Num) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"github.com/grpc/grpc-go/rpc"
|
||||
context "golang.org/x/net/context"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
type MathClient interface {
|
||||
Div(ctx context.Context, in *DivArgs, opts ...rpc.CallOption) (*DivReply, error)
|
||||
DivMany(ctx context.Context, opts ...rpc.CallOption) (Math_DivManyClient, error)
|
||||
Fib(ctx context.Context, m *FibArgs, opts ...rpc.CallOption) (Math_FibClient, error)
|
||||
Sum(ctx context.Context, opts ...rpc.CallOption) (Math_SumClient, error)
|
||||
}
|
||||
|
||||
type mathClient struct {
|
||||
cc *rpc.ClientConn
|
||||
}
|
||||
|
||||
func NewMathClient(cc *rpc.ClientConn) MathClient {
|
||||
return &mathClient{cc}
|
||||
}
|
||||
|
||||
func (c *mathClient) Div(ctx context.Context, in *DivArgs, opts ...rpc.CallOption) (*DivReply, error) {
|
||||
out := new(DivReply)
|
||||
err := rpc.Invoke(ctx, "/test.Math/Div", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *mathClient) DivMany(ctx context.Context, opts ...rpc.CallOption) (Math_DivManyClient, error) {
|
||||
stream, err := rpc.NewClientStream(ctx, c.cc, "/test.Math/DivMany", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mathDivManyClient{stream}, nil
|
||||
}
|
||||
|
||||
type Math_DivManyClient interface {
|
||||
Send(*DivArgs) error
|
||||
Recv() (*DivReply, error)
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
type mathDivManyClient struct {
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *mathDivManyClient) Send(m *DivArgs) error {
|
||||
return x.ClientStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *mathDivManyClient) Recv() (*DivReply, error) {
|
||||
m := new(DivReply)
|
||||
if err := x.ClientStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *mathClient) Fib(ctx context.Context, m *FibArgs, opts ...rpc.CallOption) (Math_FibClient, error) {
|
||||
stream, err := rpc.NewClientStream(ctx, c.cc, "/test.Math/Fib", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &mathFibClient{stream}
|
||||
if err := x.ClientStream.SendProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type Math_FibClient interface {
|
||||
Recv() (*Num, error)
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
type mathFibClient struct {
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *mathFibClient) Recv() (*Num, error) {
|
||||
m := new(Num)
|
||||
if err := x.ClientStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c *mathClient) Sum(ctx context.Context, opts ...rpc.CallOption) (Math_SumClient, error) {
|
||||
stream, err := rpc.NewClientStream(ctx, c.cc, "/test.Math/Sum", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mathSumClient{stream}, nil
|
||||
}
|
||||
|
||||
type Math_SumClient interface {
|
||||
Send(*Num) error
|
||||
CloseAndRecv() (*Num, error)
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
type mathSumClient struct {
|
||||
rpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *mathSumClient) Send(m *Num) error {
|
||||
return x.ClientStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *mathSumClient) CloseAndRecv() (*Num, error) {
|
||||
if err := x.ClientStream.CloseSend(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := new(Num)
|
||||
if err := x.ClientStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Read EOF.
|
||||
if err := x.ClientStream.RecvProto(m); err == io.EOF {
|
||||
return m, io.EOF
|
||||
}
|
||||
// gRPC protocol violation.
|
||||
return m, fmt.Errorf("Violate gRPC client streaming protocol: no EOF after the response.")
|
||||
}
|
||||
|
||||
|
||||
type MathServer interface {
|
||||
Div(context.Context, *DivArgs) (*DivReply, error)
|
||||
DivMany(Math_DivManyServer) error
|
||||
Fib(*FibArgs, Math_FibServer) error
|
||||
Sum(Math_SumServer) error
|
||||
}
|
||||
|
||||
func RegisterService(s *rpc.Server, srv MathServer) {
|
||||
s.RegisterService(&_Math_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Math_Div_Handler(srv interface{}, ctx context.Context, buf []byte) (proto.Message, error) {
|
||||
in := new(DivArgs)
|
||||
if err := proto.Unmarshal(buf, in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := srv.(MathServer).Div(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func _Math_DivMany_Handler(srv interface{}, stream rpc.ServerStream) error {
|
||||
return srv.(MathServer).DivMany(&mathDivManyServer{stream})
|
||||
}
|
||||
|
||||
type Math_DivManyServer interface {
|
||||
Send(*DivReply) error
|
||||
Recv() (*DivArgs, error)
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
type mathDivManyServer struct {
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *mathDivManyServer) Send(m *DivReply) error {
|
||||
return x.ServerStream.SendProto(m)
|
||||
}
|
||||
|
||||
func (x *mathDivManyServer) Recv() (*DivArgs, error) {
|
||||
m := new(DivArgs)
|
||||
if err := x.ServerStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func _Math_Fib_Handler(srv interface{}, stream rpc.ServerStream) error {
|
||||
m := new(FibArgs)
|
||||
if err := stream.RecvProto(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(MathServer).Fib(m, &mathFibServer{stream})
|
||||
}
|
||||
|
||||
type Math_FibServer interface {
|
||||
Send(*Num) error
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
type mathFibServer struct {
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *mathFibServer) Send(m *Num) error {
|
||||
return x.ServerStream.SendProto(m)
|
||||
}
|
||||
|
||||
func _Math_Sum_Handler(srv interface{}, stream rpc.ServerStream) error {
|
||||
return srv.(MathServer).Sum(&mathSumServer{stream})
|
||||
}
|
||||
|
||||
type Math_SumServer interface {
|
||||
SendAndClose(*Num) error
|
||||
Recv() (*Num, error)
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
type mathSumServer struct {
|
||||
rpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *mathSumServer) SendAndClose(m *Num) error {
|
||||
if err := x.ServerStream.SendProto(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *mathSumServer) Recv() (*Num, error) {
|
||||
m := new(Num)
|
||||
if err := x.ServerStream.RecvProto(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var _Math_serviceDesc = rpc.ServiceDesc{
|
||||
ServiceName: "test.Math",
|
||||
HandlerType: (*MathServer)(nil),
|
||||
Methods: []rpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Div",
|
||||
Handler: _Math_Div_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []rpc.StreamDesc{
|
||||
{
|
||||
StreamName: "DivMany",
|
||||
Handler: _Math_DivMany_Handler,
|
||||
},
|
||||
{
|
||||
StreamName: "Fib",
|
||||
Handler: _Math_Fib_Handler,
|
||||
},
|
||||
{
|
||||
StreamName: "Sum",
|
||||
Handler: _Math_Sum_Handler,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/bradfitz/http2"
|
||||
)
|
||||
|
||||
// TODO(zhaoq): Make the following configurable.
|
||||
const (
|
||||
// The initial window size for flow control.
|
||||
initialWindowSize = 65535
|
||||
// Window update is only sent when the inbound quota reaches
|
||||
// this threshold. Used to reduce the flow control traffic.
|
||||
windowUpdateThreshold = 16384
|
||||
)
|
||||
|
||||
// The following defines various control items which could flow through
|
||||
// the control buffer of transport. They represent different aspects of
|
||||
// control tasks, e.g., flow control, settings, streaming resetting, etc.
|
||||
type windowUpdate struct {
|
||||
streamID uint32
|
||||
increment uint32
|
||||
}
|
||||
|
||||
func (windowUpdate) isItem() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type settings struct {
|
||||
id http2.SettingID
|
||||
val uint32
|
||||
}
|
||||
|
||||
func (settings) isItem() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type resetStream struct {
|
||||
streamID uint32
|
||||
code http2.ErrCode
|
||||
}
|
||||
|
||||
func (resetStream) isItem() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// quotaPool is a pool which accumulates the quota and sends it to acquire()
|
||||
// when it is available.
|
||||
type quotaPool struct {
|
||||
c chan int
|
||||
|
||||
mu sync.Mutex
|
||||
quota int
|
||||
}
|
||||
|
||||
// newQuotaPool creates a quotaPool which has quota q available to consume.
|
||||
func newQuotaPool(q int) *quotaPool {
|
||||
qb := "aPool{c: make(chan int, 1)}
|
||||
qb.c <- q
|
||||
return qb
|
||||
}
|
||||
|
||||
// add adds n to the available quota and tries to send it on acquire.
|
||||
func (qb *quotaPool) add(n int) {
|
||||
qb.mu.Lock()
|
||||
defer qb.mu.Unlock()
|
||||
qb.quota += n
|
||||
if qb.quota <= 0 {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case qb.c <- qb.quota:
|
||||
qb.quota = 0
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// cancel cancels the pending quota sent on acquire, if any.
|
||||
func (qb *quotaPool) cancel() {
|
||||
qb.mu.Lock()
|
||||
defer qb.mu.Unlock()
|
||||
select {
|
||||
case n := <-qb.c:
|
||||
qb.quota += n
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// acquire returns the channel on which available quota amounts are sent.
|
||||
func (qb *quotaPool) acquire() <-chan int {
|
||||
return qb.c
|
||||
}
|
|
@ -0,0 +1,650 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bradfitz/http2"
|
||||
"github.com/bradfitz/http2/hpack"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/credentials"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// http2Client implements the ClientTransport interface with HTTP2.
|
||||
type http2Client struct {
|
||||
target string // server name/addr
|
||||
conn net.Conn // underlying communication channel
|
||||
nextID uint32 // the next stream ID to be used
|
||||
|
||||
// writableChan synchronizes write access to the transport.
|
||||
// A writer acquires the write lock by sending a value on writableChan
|
||||
// and releases it by receiving from writableChan.
|
||||
writableChan chan int
|
||||
// shutdownChan is closed when Close is called.
|
||||
// Blocking operations should select on shutdownChan to avoid
|
||||
// blocking forever after Close.
|
||||
// TODO(zhaoq): Maybe have a channel context?
|
||||
shutdownChan chan struct{}
|
||||
// errorChan is closed to notify the I/O error to the caller.
|
||||
errorChan chan struct{}
|
||||
|
||||
framer *http2.Framer
|
||||
hBuf *bytes.Buffer // the buffer for HPACK encoding
|
||||
hEnc *hpack.Encoder // HPACK encoder
|
||||
|
||||
// controlBuf delivers all the control related tasks (e.g., window
|
||||
// updates, reset streams, and various settings) to the controller.
|
||||
controlBuf *recvBuffer
|
||||
// sendQuotaPool provides flow control to outbound message.
|
||||
sendQuotaPool *quotaPool
|
||||
|
||||
// The scheme used: https if TLS is on, http otherwise.
|
||||
scheme string
|
||||
|
||||
authCreds []credentials.Credentials
|
||||
|
||||
mu sync.Mutex // guard the following variables
|
||||
state transportState // the state of underlying connection
|
||||
activeStreams map[uint32]*Stream
|
||||
// The max number of concurrent streams
|
||||
maxStreams uint32
|
||||
// Inbound quota for flow control
|
||||
recvQuota int
|
||||
}
|
||||
|
||||
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2
|
||||
// and starts to receive messages on it. Non-nil error returns if construction
|
||||
// fails.
|
||||
func newHTTP2Client(addr string, authOpts []credentials.Credentials) (_ ClientTransport, err error) {
|
||||
var (
|
||||
connErr error
|
||||
conn net.Conn
|
||||
)
|
||||
scheme := "http"
|
||||
// TODO(zhaoq): Use DialTimeout instead.
|
||||
for _, c := range authOpts {
|
||||
if ccreds, ok := c.(credentials.TransportAuthenticator); ok {
|
||||
scheme = "https"
|
||||
// TODO(zhaoq): Now the first TransportAuthenticator is used if there are
|
||||
// multiple ones provided. Revisit this if it is not appropriate. Probably
|
||||
// place the ClientTransport construction into a separate function to make
|
||||
// things clear.
|
||||
conn, connErr = ccreds.Dial(addr)
|
||||
break
|
||||
}
|
||||
}
|
||||
if scheme == "http" {
|
||||
conn, connErr = net.Dial("tcp", addr)
|
||||
}
|
||||
if connErr != nil {
|
||||
return nil, ConnectionErrorf("%v", connErr)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
}
|
||||
}()
|
||||
// Send connection preface to server.
|
||||
n, err := conn.Write(clientPreface)
|
||||
if err != nil {
|
||||
return nil, ConnectionErrorf("%v", err)
|
||||
}
|
||||
if n != len(clientPreface) {
|
||||
return nil, ConnectionErrorf("Wrting client preface, wrote %d bytes; want %d", n, len(clientPreface))
|
||||
}
|
||||
framer := http2.NewFramer(conn, conn)
|
||||
if err := framer.WriteSettings(); err != nil {
|
||||
return nil, ConnectionErrorf("%v", err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
t := &http2Client{
|
||||
target: addr,
|
||||
conn: conn,
|
||||
// The client initiated stream id is odd starting from 1.
|
||||
nextID: 1,
|
||||
writableChan: make(chan int, 1),
|
||||
shutdownChan: make(chan struct{}),
|
||||
errorChan: make(chan struct{}),
|
||||
framer: framer,
|
||||
hBuf: &buf,
|
||||
hEnc: hpack.NewEncoder(&buf),
|
||||
controlBuf: newRecvBuffer(),
|
||||
sendQuotaPool: newQuotaPool(initialWindowSize),
|
||||
scheme: scheme,
|
||||
state: reachable,
|
||||
activeStreams: make(map[uint32]*Stream),
|
||||
maxStreams: math.MaxUint32,
|
||||
authCreds: authOpts,
|
||||
}
|
||||
go t.controller()
|
||||
t.writableChan <- 0
|
||||
// Start the reader goroutine for incoming message. The threading model
|
||||
// on receiving is that each transport has a dedicated goroutine which
|
||||
// reads HTTP2 frame from network. Then it dispatches the frame to the
|
||||
// corresponding stream entity.
|
||||
go t.reader()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
|
||||
t.mu.Lock()
|
||||
// TODO(zhaoq): Handle uint32 overflow.
|
||||
s := &Stream{
|
||||
id: t.nextID,
|
||||
method: callHdr.Method,
|
||||
buf: newRecvBuffer(),
|
||||
sendQuotaPool: newQuotaPool(initialWindowSize),
|
||||
headerChan: make(chan struct{}),
|
||||
}
|
||||
s.windowHandler = func(n int) {
|
||||
t.addRecvQuota(s, n)
|
||||
}
|
||||
// Make a stream be able to cancel the pending operations by itself.
|
||||
s.ctx, s.cancel = context.WithCancel(ctx)
|
||||
s.dec = &recvBufferReader{
|
||||
ctx: s.ctx,
|
||||
recv: s.buf,
|
||||
}
|
||||
t.nextID += 2
|
||||
t.mu.Unlock()
|
||||
return s
|
||||
}
|
||||
|
||||
// NewStream creates a stream and register it into the transport as "active"
|
||||
// streams.
|
||||
func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Stream, err error) {
|
||||
if _, err := wait(ctx, t.shutdownChan, t.writableChan); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if _, ok := err.(ConnectionError); !ok {
|
||||
t.writableChan <- 0
|
||||
}
|
||||
}()
|
||||
// Record the timeout value on the context.
|
||||
var timeout time.Duration
|
||||
if dl, ok := ctx.Deadline(); ok {
|
||||
timeout = dl.Sub(time.Now())
|
||||
if timeout <= 0 {
|
||||
return nil, ContextErr(context.DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
// HPACK encodes various headers.
|
||||
t.hBuf.Reset()
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: ":method", Value: "POST"})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: ":scheme", Value: t.scheme})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: ":path", Value: callHdr.Method})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: ":authority", Value: callHdr.Host})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: "content-type", Value: "application/grpc"})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: "te", Value: "trailers"})
|
||||
for _, c := range t.authCreds {
|
||||
m, err := c.GetRequestMetadata()
|
||||
if err != nil {
|
||||
return nil, StreamErrorf(codes.InvalidArgument, "%v", err)
|
||||
}
|
||||
for k, v := range m {
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: v})
|
||||
}
|
||||
}
|
||||
if timeout > 0 {
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-timeout", Value: timeoutEncode(timeout)})
|
||||
}
|
||||
if md, ok := metadata.FromContext(ctx); ok {
|
||||
for k, v := range md {
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: v})
|
||||
}
|
||||
}
|
||||
first := true
|
||||
endHeaders := false
|
||||
// Sends the headers in a single batch even when they span multiple frames.
|
||||
for !endHeaders {
|
||||
size := t.hBuf.Len()
|
||||
if size > http2MaxFrameLen {
|
||||
size = http2MaxFrameLen
|
||||
} else {
|
||||
endHeaders = true
|
||||
}
|
||||
if first {
|
||||
// Sends a HeadersFrame to server to start a new stream.
|
||||
p := http2.HeadersFrameParam{
|
||||
StreamID: t.nextID,
|
||||
BlockFragment: t.hBuf.Next(size),
|
||||
EndStream: false,
|
||||
EndHeaders: endHeaders,
|
||||
}
|
||||
err = t.framer.WriteHeaders(p)
|
||||
first = false
|
||||
} else {
|
||||
// Sends Continuation frames for the leftover headers.
|
||||
err = t.framer.WriteContinuation(t.nextID, endHeaders, t.hBuf.Next(size))
|
||||
}
|
||||
if err != nil {
|
||||
t.notifyError()
|
||||
return nil, ConnectionErrorf("%v", err)
|
||||
}
|
||||
}
|
||||
s := t.newStream(ctx, callHdr)
|
||||
t.mu.Lock()
|
||||
if t.state != reachable {
|
||||
t.mu.Unlock()
|
||||
return nil, ErrConnClosing
|
||||
}
|
||||
if uint32(len(t.activeStreams)) >= t.maxStreams {
|
||||
t.mu.Unlock()
|
||||
return nil, StreamErrorf(codes.Unavailable, "failed to create new stream because the limit has been reached.")
|
||||
}
|
||||
t.activeStreams[s.id] = s
|
||||
t.mu.Unlock()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// CloseStream clears the footprint of a stream when the stream is not needed any more.
|
||||
// This must not be executed in reader's goroutine.
|
||||
func (t *http2Client) CloseStream(s *Stream, err error) {
|
||||
t.mu.Lock()
|
||||
delete(t.activeStreams, s.id)
|
||||
t.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
if s.state == streamDone {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
if !s.headerDone {
|
||||
close(s.headerChan)
|
||||
s.headerDone = true
|
||||
}
|
||||
s.state = streamDone
|
||||
s.mu.Unlock()
|
||||
// In case stream sending and receiving are invoked in separate
|
||||
// goroutines (e.g., bi-directional streaming), the caller needs
|
||||
// to call cancel on the stream to interrupt the blocking on
|
||||
// other goroutines.
|
||||
s.cancel()
|
||||
if _, ok := err.(StreamError); ok {
|
||||
t.controlBuf.put(&resetStream{s.id, http2.ErrCodeCancel})
|
||||
}
|
||||
}
|
||||
|
||||
// Close kicks off the shutdown process of the transport. This should be called
|
||||
// only once on a transport. Once it is called, the transport should not be
|
||||
// accessed any more.
|
||||
func (t *http2Client) Close() (err error) {
|
||||
t.mu.Lock()
|
||||
t.state = closing
|
||||
t.mu.Unlock()
|
||||
close(t.shutdownChan)
|
||||
err = t.conn.Close()
|
||||
t.mu.Lock()
|
||||
streams := t.activeStreams
|
||||
t.activeStreams = nil
|
||||
t.mu.Unlock()
|
||||
// Notify all active streams.
|
||||
for _, s := range streams {
|
||||
s.mu.Lock()
|
||||
if !s.headerDone {
|
||||
close(s.headerChan)
|
||||
s.headerDone = true
|
||||
}
|
||||
s.mu.Unlock()
|
||||
s.write(recvMsg{err: ErrConnClosing})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write formats the data into HTTP2 data frame(s) and sends it out. The caller
|
||||
// should proceed only if Write returns nil.
|
||||
// TODO(zhaoq): opts.Delay is ignored in this implementation. Support it later
|
||||
// if it improves the performance.
|
||||
func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error {
|
||||
r := bytes.NewBuffer(data)
|
||||
for {
|
||||
var p []byte
|
||||
if r.Len() > 0 {
|
||||
size := http2MaxFrameLen
|
||||
s.sendQuotaPool.add(0)
|
||||
// Wait until the stream has some quota to send the data.
|
||||
sq, err := wait(s.ctx, t.shutdownChan, s.sendQuotaPool.acquire())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.sendQuotaPool.add(0)
|
||||
// Wait until the transport has some quota to send the data.
|
||||
tq, err := wait(s.ctx, t.shutdownChan, t.sendQuotaPool.acquire())
|
||||
if err != nil {
|
||||
if _, ok := err.(StreamError); ok {
|
||||
t.sendQuotaPool.cancel()
|
||||
}
|
||||
return err
|
||||
}
|
||||
if sq < size {
|
||||
size = sq
|
||||
}
|
||||
if tq < size {
|
||||
size = tq
|
||||
}
|
||||
p = r.Next(size)
|
||||
ps := len(p)
|
||||
if ps < sq {
|
||||
// Overbooked stream quota. Return it back.
|
||||
s.sendQuotaPool.add(sq - ps)
|
||||
}
|
||||
if ps < tq {
|
||||
// Overbooked transport quota. Return it back.
|
||||
t.sendQuotaPool.add(tq - ps)
|
||||
}
|
||||
}
|
||||
var endStream bool
|
||||
if opts.Last && r.Len() == 0 {
|
||||
endStream = true
|
||||
}
|
||||
// Got some quota. Try to acquire writing privilege on the transport.
|
||||
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil {
|
||||
return err
|
||||
}
|
||||
// If WriteData fails, all the pending streams will be handled
|
||||
// by http2Client.Close(). No explicit CloseStream() needs to be
|
||||
// invoked.
|
||||
if err := t.framer.WriteData(s.id, endStream, p); err != nil {
|
||||
t.notifyError()
|
||||
return ConnectionErrorf("%v", err)
|
||||
}
|
||||
t.writableChan <- 0
|
||||
if r.Len() == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !opts.Last {
|
||||
return nil
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.state != streamDone {
|
||||
if s.state == streamReadDone {
|
||||
s.state = streamDone
|
||||
} else {
|
||||
s.state = streamWriteDone
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *http2Client) getStream(f http2.Frame) (*Stream, bool) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.activeStreams == nil {
|
||||
// The transport is closing.
|
||||
return nil, false
|
||||
}
|
||||
if s, ok := t.activeStreams[f.Header().StreamID]; ok {
|
||||
return s, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// addRecvQuota adjusts the inbound quota for the stream and the transport.
|
||||
// Window updates will deliver to the controller for sending when
|
||||
// the cumulative quota exceeds windowUpdateThreshold.
|
||||
func (t *http2Client) addRecvQuota(s *Stream, n int) {
|
||||
t.mu.Lock()
|
||||
t.recvQuota += n
|
||||
if t.recvQuota >= windowUpdateThreshold {
|
||||
t.controlBuf.put(&windowUpdate{0, uint32(t.recvQuota)})
|
||||
t.recvQuota = 0
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
s.recvQuota += n
|
||||
if s.recvQuota >= windowUpdateThreshold {
|
||||
t.controlBuf.put(&windowUpdate{s.id, uint32(s.recvQuota)})
|
||||
s.recvQuota = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Client) handleData(f *http2.DataFrame) {
|
||||
// Select the right stream to dispatch.
|
||||
s, ok := t.getStream(f)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// TODO(bradfitz, zhaoq): A copy is required here because there is no
|
||||
// guarantee f.Data() is consumed before the arrival of next frame.
|
||||
// Can this copy be eliminated?
|
||||
data := make([]byte, len(f.Data()))
|
||||
copy(data, f.Data())
|
||||
s.write(recvMsg{data: data})
|
||||
}
|
||||
|
||||
func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) {
|
||||
s, ok := t.getStream(f)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.state == streamDone {
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
s.state = streamDone
|
||||
s.statusCode, ok = http2RSTErrConvTab[http2.ErrCode(f.ErrCode)]
|
||||
if !ok {
|
||||
log.Println("No gRPC status found for http2 error ", f.ErrCode)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
s.write(recvMsg{err: io.EOF})
|
||||
}
|
||||
|
||||
func (t *http2Client) handleSettings(f *http2.SettingsFrame) {
|
||||
if v, ok := f.Value(http2.SettingMaxConcurrentStreams); ok {
|
||||
t.mu.Lock()
|
||||
t.maxStreams = v
|
||||
t.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Client) handlePing(f *http2.PingFrame) {
|
||||
log.Println("PingFrame handler to be implemented")
|
||||
}
|
||||
|
||||
func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) {
|
||||
log.Println("GoAwayFrame handler to be implemented")
|
||||
}
|
||||
|
||||
func (t *http2Client) handleWindowUpdate(f *http2.WindowUpdateFrame) {
|
||||
id := f.Header().StreamID
|
||||
incr := f.Increment
|
||||
if id == 0 {
|
||||
t.sendQuotaPool.add(int(incr))
|
||||
return
|
||||
}
|
||||
if s, ok := t.getStream(f); ok {
|
||||
s.sendQuotaPool.add(int(incr))
|
||||
}
|
||||
}
|
||||
|
||||
// operateHeader takes action on the decoded headers. It returns the current
|
||||
// stream if there are remaining headers on the wire (in the following
|
||||
// Continuation frame).
|
||||
func (t *http2Client) operateHeaders(hDec *hpackDecoder, s *Stream, frame headerFrame, endStream bool) (pendingStream *Stream) {
|
||||
defer func() {
|
||||
if pendingStream == nil {
|
||||
hDec.state = decodeState{}
|
||||
}
|
||||
}()
|
||||
endHeaders, err := hDec.decodeClientHTTP2Headers(s, frame)
|
||||
if err != nil {
|
||||
s.write(recvMsg{err: err})
|
||||
// Something wrong. Stops reading even when there is remaining.
|
||||
return nil
|
||||
}
|
||||
if !endHeaders {
|
||||
return s
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
if !s.headerDone {
|
||||
if !endStream && len(hDec.state.mdata) > 0 {
|
||||
s.header = hDec.state.mdata
|
||||
}
|
||||
close(s.headerChan)
|
||||
s.headerDone = true
|
||||
}
|
||||
if !endStream || s.state == streamDone {
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(hDec.state.mdata) > 0 {
|
||||
s.trailer = hDec.state.mdata
|
||||
}
|
||||
s.state = streamDone
|
||||
s.statusCode = hDec.state.statusCode
|
||||
s.statusDesc = hDec.state.statusDesc
|
||||
s.mu.Unlock()
|
||||
|
||||
s.write(recvMsg{err: io.EOF})
|
||||
return nil
|
||||
}
|
||||
|
||||
// reader runs as a separate goroutine in charge of reading data from network
|
||||
// connection.
|
||||
//
|
||||
// TODO(zhaoq): currently one reader per transport. Investigate whether this is
|
||||
// optimal.
|
||||
// TODO(zhaoq): Check the validity of the incoming frame sequence.
|
||||
func (t *http2Client) reader() {
|
||||
// Check the validity of server preface.
|
||||
frame, err := t.framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.notifyError()
|
||||
return
|
||||
}
|
||||
sf, ok := frame.(*http2.SettingsFrame)
|
||||
if !ok {
|
||||
t.notifyError()
|
||||
return
|
||||
}
|
||||
t.handleSettings(sf)
|
||||
|
||||
hDec := newHPACKDecoder()
|
||||
var curStream *Stream
|
||||
// loop to keep reading incoming messages on this transport.
|
||||
for {
|
||||
frame, err := t.framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.notifyError()
|
||||
return
|
||||
}
|
||||
switch frame := frame.(type) {
|
||||
case *http2.HeadersFrame:
|
||||
var ok bool
|
||||
if curStream, ok = t.getStream(frame); !ok {
|
||||
continue
|
||||
}
|
||||
endStream := frame.Header().Flags.Has(http2.FlagHeadersEndStream)
|
||||
curStream = t.operateHeaders(hDec, curStream, frame, endStream)
|
||||
case *http2.ContinuationFrame:
|
||||
if curStream == nil {
|
||||
continue
|
||||
}
|
||||
curStream = t.operateHeaders(hDec, curStream, frame, false)
|
||||
case *http2.DataFrame:
|
||||
t.handleData(frame)
|
||||
case *http2.RSTStreamFrame:
|
||||
t.handleRSTStream(frame)
|
||||
case *http2.SettingsFrame:
|
||||
t.handleSettings(frame)
|
||||
case *http2.PingFrame:
|
||||
t.handlePing(frame)
|
||||
case *http2.GoAwayFrame:
|
||||
t.handleGoAway(frame)
|
||||
case *http2.WindowUpdateFrame:
|
||||
t.handleWindowUpdate(frame)
|
||||
default:
|
||||
log.Printf("http2Client: unhandled frame type %v.", frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// controller running in a separate goroutine takes charge of sending control
|
||||
// frames (e.g., window update, reset stream, setting, etc.) to the server.
|
||||
func (t *http2Client) controller() {
|
||||
for {
|
||||
select {
|
||||
case i := <-t.controlBuf.get():
|
||||
t.controlBuf.load()
|
||||
select {
|
||||
case <-t.writableChan:
|
||||
switch i := i.(type) {
|
||||
case *windowUpdate:
|
||||
t.framer.WriteWindowUpdate(i.streamID, i.increment)
|
||||
case *settings:
|
||||
t.framer.WriteSettings(http2.Setting{i.id, i.val})
|
||||
case *resetStream:
|
||||
t.framer.WriteRSTStream(i.streamID, i.code)
|
||||
default:
|
||||
log.Printf("http2Client.controller got unexpected item type %v\n", i)
|
||||
}
|
||||
t.writableChan <- 0
|
||||
continue
|
||||
case <-t.shutdownChan:
|
||||
return
|
||||
}
|
||||
case <-t.shutdownChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Client) Error() <-chan struct{} {
|
||||
return t.errorChan
|
||||
}
|
||||
|
||||
func (t *http2Client) notifyError() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
// make sure t.errorChan is closed only once.
|
||||
if t.state == reachable {
|
||||
t.state = unreachable
|
||||
close(t.errorChan)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,601 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/bradfitz/http2"
|
||||
"github.com/bradfitz/http2/hpack"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// http2Server implements the ServerTransport interface with HTTP2.
|
||||
type http2Server struct {
|
||||
conn net.Conn
|
||||
maxStreamID uint32 // max stream ID ever seen
|
||||
// writableChan synchronizes write access to the transport.
|
||||
// A writer acquires the write lock by sending a value on writableChan
|
||||
// and releases it by receiving from writableChan.
|
||||
writableChan chan int
|
||||
// shutdownChan is closed when Close is called.
|
||||
// Blocking operations should select on shutdownChan to avoid
|
||||
// blocking forever after Close.
|
||||
// TODO(zhaoq): Maybe have a channel context?
|
||||
shutdownChan chan struct{}
|
||||
framer *http2.Framer
|
||||
hBuf *bytes.Buffer // the buffer for HPACK encoding
|
||||
hEnc *hpack.Encoder // HPACK encoder
|
||||
|
||||
// The max number of concurrent streams.
|
||||
maxStreams uint32
|
||||
// controlBuf delivers all the control related tasks (e.g., window
|
||||
// updates, reset streams, and various settings) to the controller.
|
||||
controlBuf *recvBuffer
|
||||
// sendQuotaPool provides flow control to outbound message.
|
||||
sendQuotaPool *quotaPool
|
||||
|
||||
mu sync.Mutex
|
||||
state transportState
|
||||
activeStreams map[uint32]*Stream
|
||||
// Inbound quota for flow control
|
||||
recvQuota int
|
||||
}
|
||||
|
||||
// newHTTP2Server constructs a ServerTransport based on HTTP2. ConnectionError is
|
||||
// returned if something goes wrong.
|
||||
func newHTTP2Server(conn net.Conn, maxStreams uint32) (_ ServerTransport, err error) {
|
||||
framer := http2.NewFramer(conn, conn)
|
||||
// Send initial settings as connection preface to client.
|
||||
// TODO(zhaoq): Have a better way to signal "no limit" because 0 is
|
||||
// permitted in the HTTP2 spec.
|
||||
if maxStreams == 0 {
|
||||
err = framer.WriteSettings()
|
||||
maxStreams = math.MaxUint32
|
||||
} else {
|
||||
err = framer.WriteSettings(http2.Setting{http2.SettingMaxConcurrentStreams, maxStreams})
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
t := &http2Server{
|
||||
conn: conn,
|
||||
framer: framer,
|
||||
hBuf: &buf,
|
||||
hEnc: hpack.NewEncoder(&buf),
|
||||
maxStreams: maxStreams,
|
||||
controlBuf: newRecvBuffer(),
|
||||
sendQuotaPool: newQuotaPool(initialWindowSize),
|
||||
state: reachable,
|
||||
writableChan: make(chan int, 1),
|
||||
shutdownChan: make(chan struct{}),
|
||||
activeStreams: make(map[uint32]*Stream),
|
||||
}
|
||||
go t.controller()
|
||||
t.writableChan <- 0
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// operateHeader takes action on the decoded headers. It returns the current
|
||||
// stream if there are remaining headers on the wire (in the following
|
||||
// Continuation frame).
|
||||
func (t *http2Server) operateHeaders(hDec *hpackDecoder, s *Stream, frame headerFrame, endStream bool, handle func(*Stream), wg *sync.WaitGroup) (pendingStream *Stream) {
|
||||
defer func() {
|
||||
if pendingStream == nil {
|
||||
hDec.state = decodeState{}
|
||||
}
|
||||
}()
|
||||
endHeaders, err := hDec.decodeServerHTTP2Headers(s, frame)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
if se, ok := err.(StreamError); ok {
|
||||
t.controlBuf.put(&resetStream{s.id, statusCodeConvTab[se.Code]})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if endStream {
|
||||
// s is just created by the caller. No lock needed.
|
||||
s.state = streamReadDone
|
||||
}
|
||||
if !endHeaders {
|
||||
return s
|
||||
}
|
||||
t.mu.Lock()
|
||||
if t.state != reachable {
|
||||
t.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
if uint32(len(t.activeStreams)) >= t.maxStreams {
|
||||
t.mu.Unlock()
|
||||
t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream})
|
||||
return nil
|
||||
}
|
||||
t.activeStreams[s.id] = s
|
||||
t.mu.Unlock()
|
||||
s.windowHandler = func(n int) {
|
||||
t.addRecvQuota(s, n)
|
||||
}
|
||||
if hDec.state.timeoutSet {
|
||||
s.ctx, s.cancel = context.WithTimeout(context.TODO(), hDec.state.timeout)
|
||||
} else {
|
||||
s.ctx, s.cancel = context.WithCancel(context.TODO())
|
||||
}
|
||||
// Cache the current stream to the context so that the server application
|
||||
// can find out. Required when the server wants to send some metadata
|
||||
// back to the client (unary call only).
|
||||
s.ctx = newContextWithStream(s.ctx, s)
|
||||
// Attach the received metadata to the context.
|
||||
if len(hDec.state.mdata) > 0 {
|
||||
s.ctx = metadata.NewContext(s.ctx, hDec.state.mdata)
|
||||
}
|
||||
|
||||
s.dec = &recvBufferReader{
|
||||
ctx: s.ctx,
|
||||
recv: s.buf,
|
||||
}
|
||||
s.method = hDec.state.method
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
handle(s)
|
||||
wg.Done()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleStreams receives incoming streams using the given handler. This is
|
||||
// typically run in a separate goroutine.
|
||||
func (t *http2Server) HandleStreams(handle func(*Stream)) {
|
||||
// Check the validity of client preface.
|
||||
preface := make([]byte, len(clientPreface))
|
||||
if _, err := io.ReadFull(t.conn, preface); err != nil {
|
||||
log.Printf("failed to receive the preface from client: %v", err)
|
||||
t.Close()
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(preface, clientPreface) {
|
||||
log.Printf("received bogus greeting from client: %q", preface)
|
||||
t.Close()
|
||||
return
|
||||
}
|
||||
|
||||
frame, err := t.framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Close()
|
||||
return
|
||||
}
|
||||
sf, ok := frame.(*http2.SettingsFrame)
|
||||
if !ok {
|
||||
log.Printf("invalid preface type %T from client", frame)
|
||||
t.Close()
|
||||
return
|
||||
}
|
||||
t.handleSettings(sf)
|
||||
|
||||
hDec := newHPACKDecoder()
|
||||
var curStream *Stream
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
for {
|
||||
frame, err := t.framer.ReadFrame()
|
||||
if err != nil {
|
||||
t.Close()
|
||||
return
|
||||
}
|
||||
switch frame := frame.(type) {
|
||||
case *http2.HeadersFrame:
|
||||
id := frame.Header().StreamID
|
||||
if id%2 != 1 || id <= t.maxStreamID {
|
||||
// illegal gRPC stream id.
|
||||
log.Println("http2Server: received an illegal stream id: ", id)
|
||||
t.Close()
|
||||
break
|
||||
}
|
||||
t.maxStreamID = id
|
||||
buf := newRecvBuffer()
|
||||
curStream = &Stream{
|
||||
id: frame.Header().StreamID,
|
||||
st: t,
|
||||
buf: buf,
|
||||
sendQuotaPool: newQuotaPool(initialWindowSize),
|
||||
}
|
||||
endStream := frame.Header().Flags.Has(http2.FlagHeadersEndStream)
|
||||
curStream = t.operateHeaders(hDec, curStream, frame, endStream, handle, &wg)
|
||||
case *http2.ContinuationFrame:
|
||||
if curStream == nil {
|
||||
continue
|
||||
}
|
||||
curStream = t.operateHeaders(hDec, curStream, frame, false, handle, &wg)
|
||||
case *http2.DataFrame:
|
||||
t.handleData(frame)
|
||||
case *http2.RSTStreamFrame:
|
||||
t.handleRSTStream(frame)
|
||||
case *http2.SettingsFrame:
|
||||
t.handleSettings(frame)
|
||||
case *http2.PingFrame:
|
||||
t.handlePing(frame)
|
||||
case *http2.WindowUpdateFrame:
|
||||
t.handleWindowUpdate(frame)
|
||||
default:
|
||||
log.Printf("http2Server: unhandled frame type %v.", frame)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Server) getStream(f http2.Frame) (*Stream, bool) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.activeStreams == nil {
|
||||
// The transport is closing.
|
||||
return nil, false
|
||||
}
|
||||
s, ok := t.activeStreams[f.Header().StreamID]
|
||||
if !ok {
|
||||
// The stream is already done.
|
||||
return nil, false
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
|
||||
// addRecvQuota adjusts the inbound quota for the stream and the transport.
|
||||
// Window updates will deliver to the controller for sending when
|
||||
// the cumulative quota exceeds windowUpdateThreshold.
|
||||
func (t *http2Server) addRecvQuota(s *Stream, n int) {
|
||||
t.mu.Lock()
|
||||
t.recvQuota += n
|
||||
if t.recvQuota >= windowUpdateThreshold {
|
||||
t.controlBuf.put(&windowUpdate{0, uint32(t.recvQuota)})
|
||||
t.recvQuota = 0
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
s.recvQuota += n
|
||||
if s.recvQuota >= windowUpdateThreshold {
|
||||
t.controlBuf.put(&windowUpdate{s.id, uint32(s.recvQuota)})
|
||||
s.recvQuota = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Server) handleData(f *http2.DataFrame) {
|
||||
// Select the right stream to dispatch.
|
||||
s, ok := t.getStream(f)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// TODO(bradfitz, zhaoq): A copy is required here because there is no
|
||||
// guarantee f.Data() is consumed before the arrival of next frame.
|
||||
// Can this copy be eliminated?
|
||||
data := make([]byte, len(f.Data()))
|
||||
copy(data, f.Data())
|
||||
s.write(recvMsg{data: data})
|
||||
if f.Header().Flags.Has(http2.FlagDataEndStream) {
|
||||
// Received the end of stream from the client.
|
||||
s.mu.Lock()
|
||||
if s.state != streamDone {
|
||||
if s.state == streamWriteDone {
|
||||
s.state = streamDone
|
||||
} else {
|
||||
s.state = streamReadDone
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
s.write(recvMsg{err: io.EOF})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Server) handleRSTStream(f *http2.RSTStreamFrame) {
|
||||
s, ok := t.getStream(f)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
// Sets the stream state to avoid sending RSTStreamFrame to client
|
||||
// unnecessarily.
|
||||
s.state = streamDone
|
||||
s.mu.Unlock()
|
||||
t.closeStream(s)
|
||||
}
|
||||
|
||||
func (t *http2Server) handleSettings(f *http2.SettingsFrame) {
|
||||
// TODO(zhaoq): Handle the useful settings from client.
|
||||
}
|
||||
|
||||
func (t *http2Server) handlePing(f *http2.PingFrame) {
|
||||
log.Println("PingFrame handler to be implemented")
|
||||
}
|
||||
|
||||
func (t *http2Server) handleWindowUpdate(f *http2.WindowUpdateFrame) {
|
||||
id := f.Header().StreamID
|
||||
incr := f.Increment
|
||||
if id == 0 {
|
||||
t.sendQuotaPool.add(int(incr))
|
||||
return
|
||||
}
|
||||
if s, ok := t.getStream(f); ok {
|
||||
s.sendQuotaPool.add(int(incr))
|
||||
}
|
||||
}
|
||||
|
||||
func (t *http2Server) writeHeaders(s *Stream, b *bytes.Buffer, endStream bool) error {
|
||||
first := true
|
||||
endHeaders := false
|
||||
var err error
|
||||
// Sends the headers in a single batch.
|
||||
for !endHeaders {
|
||||
size := t.hBuf.Len()
|
||||
if size > http2MaxFrameLen {
|
||||
size = http2MaxFrameLen
|
||||
} else {
|
||||
endHeaders = true
|
||||
}
|
||||
if first {
|
||||
p := http2.HeadersFrameParam{
|
||||
StreamID: s.id,
|
||||
BlockFragment: b.Next(size),
|
||||
EndStream: endStream,
|
||||
EndHeaders: endHeaders,
|
||||
}
|
||||
err = t.framer.WriteHeaders(p)
|
||||
first = false
|
||||
} else {
|
||||
err = t.framer.WriteContinuation(s.id, endHeaders, b.Next(size))
|
||||
}
|
||||
if err != nil {
|
||||
t.Close()
|
||||
return ConnectionErrorf("%v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeader sends the header metedata md back to the client.
|
||||
func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error {
|
||||
s.mu.Lock()
|
||||
if s.headerOk || s.state == streamDone {
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("transport: the stream is done or WriteHeader was already called")
|
||||
}
|
||||
s.headerOk = true
|
||||
s.mu.Unlock()
|
||||
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil {
|
||||
return err
|
||||
}
|
||||
t.hBuf.Reset()
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: "content-type", Value: "application/grpc"})
|
||||
for k, v := range md {
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: v})
|
||||
}
|
||||
if err := t.writeHeaders(s, t.hBuf, false); err != nil {
|
||||
return err
|
||||
}
|
||||
t.writableChan <- 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteStatus sends stream status to the client and terminates the stream.
|
||||
// There is no further I/O operations being able to perform on this stream.
|
||||
// TODO(zhaoq): Now it indicates the end of entire stream. Revisit if early
|
||||
// OK is adopted.
|
||||
func (t *http2Server) WriteStatus(s *Stream, statusCode codes.Code, statusDesc string) error {
|
||||
s.mu.RLock()
|
||||
if s.state == streamDone {
|
||||
s.mu.RUnlock()
|
||||
return nil
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil {
|
||||
// TODO(zhaoq): Print some errors using glog, e.g., glog.V(1).
|
||||
return err
|
||||
}
|
||||
t.hBuf.Reset()
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
|
||||
t.hEnc.WriteField(
|
||||
hpack.HeaderField{
|
||||
Name: "grpc-status",
|
||||
Value: strconv.Itoa(int(statusCode)),
|
||||
})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-message", Value: statusDesc})
|
||||
// Attach the trailer metadata.
|
||||
for k, v := range s.trailer {
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: k, Value: v})
|
||||
}
|
||||
if err := t.writeHeaders(s, t.hBuf, true); err != nil {
|
||||
return err
|
||||
}
|
||||
t.closeStream(s)
|
||||
t.writableChan <- 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write converts the data into HTTP2 data frame and sends it out. Non-nil error
|
||||
// is returns if it fails (e.g., framing error, transport error).
|
||||
func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error {
|
||||
// TODO(zhaoq): Support multi-writers for a single stream.
|
||||
var writeHeaderFrame bool
|
||||
s.mu.Lock()
|
||||
if !s.headerOk {
|
||||
writeHeaderFrame = true
|
||||
s.headerOk = true
|
||||
}
|
||||
s.mu.Unlock()
|
||||
if writeHeaderFrame {
|
||||
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil {
|
||||
return err
|
||||
}
|
||||
t.hBuf.Reset()
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"})
|
||||
t.hEnc.WriteField(hpack.HeaderField{Name: "content-type", Value: "application/grpc"})
|
||||
p := http2.HeadersFrameParam{
|
||||
StreamID: s.id,
|
||||
BlockFragment: t.hBuf.Bytes(),
|
||||
EndHeaders: true,
|
||||
}
|
||||
if err := t.framer.WriteHeaders(p); err != nil {
|
||||
t.Close()
|
||||
return ConnectionErrorf("%v", err)
|
||||
}
|
||||
t.writableChan <- 0
|
||||
}
|
||||
r := bytes.NewBuffer(data)
|
||||
for {
|
||||
if r.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
size := http2MaxFrameLen
|
||||
s.sendQuotaPool.add(0)
|
||||
// Wait until the stream has some quota to send the data.
|
||||
sq, err := wait(s.ctx, t.shutdownChan, s.sendQuotaPool.acquire())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.sendQuotaPool.add(0)
|
||||
// Wait until the transport has some quota to send the data.
|
||||
tq, err := wait(s.ctx, t.shutdownChan, t.sendQuotaPool.acquire())
|
||||
if err != nil {
|
||||
if _, ok := err.(StreamError); ok {
|
||||
t.sendQuotaPool.cancel()
|
||||
}
|
||||
return err
|
||||
}
|
||||
if sq < size {
|
||||
size = sq
|
||||
}
|
||||
if tq < size {
|
||||
size = tq
|
||||
}
|
||||
p := r.Next(size)
|
||||
ps := len(p)
|
||||
if ps < sq {
|
||||
// Overbooked stream quota. Return it back.
|
||||
s.sendQuotaPool.add(sq - ps)
|
||||
}
|
||||
if ps < tq {
|
||||
// Overbooked transport quota. Return it back.
|
||||
t.sendQuotaPool.add(tq - ps)
|
||||
}
|
||||
// Got some quota. Try to acquire writing privilege on the
|
||||
// transport.
|
||||
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.framer.WriteData(s.id, false, p); err != nil {
|
||||
t.Close()
|
||||
return ConnectionErrorf("%v", err)
|
||||
}
|
||||
t.writableChan <- 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// controller running in a separate goroutine takes charge of sending control
|
||||
// frames (e.g., window update, reset stream, setting, etc.) to the server.
|
||||
func (t *http2Server) controller() {
|
||||
for {
|
||||
select {
|
||||
case i := <-t.controlBuf.get():
|
||||
t.controlBuf.load()
|
||||
select {
|
||||
case <-t.writableChan:
|
||||
switch i := i.(type) {
|
||||
case *windowUpdate:
|
||||
t.framer.WriteWindowUpdate(i.streamID, i.increment)
|
||||
case *settings:
|
||||
t.framer.WriteSettings(http2.Setting{i.id, i.val})
|
||||
case *resetStream:
|
||||
t.framer.WriteRSTStream(i.streamID, i.code)
|
||||
default:
|
||||
log.Printf("http2Server.controller got unexpected item type %v\n", i)
|
||||
}
|
||||
t.writableChan <- 0
|
||||
continue
|
||||
case <-t.shutdownChan:
|
||||
return
|
||||
}
|
||||
case <-t.shutdownChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close starts shutting down the http2Server transport.
|
||||
// TODO(zhaoq): Now the destruction is not blocked on any pending streams. This
|
||||
// could cause some resource issue. Revisit this later.
|
||||
func (t *http2Server) Close() (err error) {
|
||||
t.mu.Lock()
|
||||
if t.state == closing {
|
||||
t.mu.Unlock()
|
||||
return
|
||||
}
|
||||
t.state = closing
|
||||
streams := t.activeStreams
|
||||
t.activeStreams = nil
|
||||
t.mu.Unlock()
|
||||
close(t.shutdownChan)
|
||||
err = t.conn.Close()
|
||||
// Notify all active streams.
|
||||
for _, s := range streams {
|
||||
s.write(recvMsg{err: ErrConnClosing})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// closeStream clears the footprint of a stream when the stream is not needed
|
||||
// any more.
|
||||
func (t *http2Server) closeStream(s *Stream) {
|
||||
t.mu.Lock()
|
||||
delete(t.activeStreams, s.id)
|
||||
t.mu.Unlock()
|
||||
s.mu.Lock()
|
||||
if s.state == streamDone {
|
||||
return
|
||||
}
|
||||
s.state = streamDone
|
||||
s.mu.Unlock()
|
||||
// In case stream sending and receiving are invoked in separate
|
||||
// goroutines (e.g., bi-directional streaming), the caller needs
|
||||
// to call cancel on the stream to interrupt the blocking on
|
||||
// other goroutines.
|
||||
s.cancel()
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bradfitz/http2"
|
||||
"github.com/bradfitz/http2/hpack"
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
// http2MaxFrameLen specifies the max length of a HTTP2 frame.
|
||||
http2MaxFrameLen = 16384 // 16KB frame
|
||||
// http://http2.github.io/http2-spec/#SettingValues
|
||||
http2InitHeaderTableSize = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
clientPreface = []byte(http2.ClientPreface)
|
||||
)
|
||||
|
||||
var http2RSTErrConvTab = map[http2.ErrCode]codes.Code{
|
||||
http2.ErrCodeNo: codes.Internal,
|
||||
http2.ErrCodeProtocol: codes.Internal,
|
||||
http2.ErrCodeInternal: codes.Internal,
|
||||
http2.ErrCodeFlowControl: codes.Internal,
|
||||
http2.ErrCodeSettingsTimeout: codes.Internal,
|
||||
http2.ErrCodeFrameSize: codes.Internal,
|
||||
http2.ErrCodeRefusedStream: codes.Unavailable,
|
||||
http2.ErrCodeCancel: codes.Canceled,
|
||||
http2.ErrCodeCompression: codes.Internal,
|
||||
http2.ErrCodeConnect: codes.Internal,
|
||||
http2.ErrCodeEnhanceYourCalm: codes.ResourceExhausted,
|
||||
http2.ErrCodeInadequateSecurity: codes.PermissionDenied,
|
||||
}
|
||||
|
||||
var statusCodeConvTab = map[codes.Code]http2.ErrCode{
|
||||
codes.Internal: http2.ErrCodeInternal, // pick an arbitrary one which is matched.
|
||||
codes.Canceled: http2.ErrCodeCancel,
|
||||
codes.Unavailable: http2.ErrCodeRefusedStream,
|
||||
codes.ResourceExhausted: http2.ErrCodeEnhanceYourCalm,
|
||||
codes.PermissionDenied: http2.ErrCodeInadequateSecurity,
|
||||
}
|
||||
|
||||
// Records the states during HPACK decoding. Must be reset once the
|
||||
// decoding of the entire headers are finished.
|
||||
type decodeState struct {
|
||||
// statusCode caches the stream status received from the trailer
|
||||
// the server sent. Client side only.
|
||||
statusCode codes.Code
|
||||
statusDesc string
|
||||
// Server side only fields.
|
||||
timeoutSet bool
|
||||
timeout time.Duration
|
||||
method string
|
||||
// key-value metadata map from the peer.
|
||||
mdata map[string]string
|
||||
}
|
||||
|
||||
// An hpackDecoder decodes HTTP2 headers which may span multiple frames.
|
||||
type hpackDecoder struct {
|
||||
h *hpack.Decoder
|
||||
state decodeState
|
||||
err error // The err when decoding
|
||||
}
|
||||
|
||||
// A headerFrame is either a http2.HeaderFrame or http2.ContinuationFrame.
|
||||
type headerFrame interface {
|
||||
Header() http2.FrameHeader
|
||||
HeaderBlockFragment() []byte
|
||||
HeadersEnded() bool
|
||||
}
|
||||
|
||||
// isReservedHeader checks whether hdr belongs to HTTP2 headers
|
||||
// reserved by gRPC protocol. Any other headers are classified as the
|
||||
// user-specified metadata.
|
||||
func isReservedHeader(hdr string) bool {
|
||||
if hdr[0] == ':' {
|
||||
return true
|
||||
}
|
||||
switch hdr {
|
||||
case "authority",
|
||||
"content-type",
|
||||
"grpc-message-type",
|
||||
"grpc-encoding",
|
||||
"grpc-message",
|
||||
"grpc-status",
|
||||
"grpc-timeout",
|
||||
"te",
|
||||
"user-agent":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func newHPACKDecoder() *hpackDecoder {
|
||||
d := &hpackDecoder{}
|
||||
d.h = hpack.NewDecoder(http2InitHeaderTableSize, func(f hpack.HeaderField) {
|
||||
switch f.Name {
|
||||
case "grpc-status":
|
||||
code, err := strconv.Atoi(f.Value)
|
||||
if err != nil {
|
||||
d.err = StreamErrorf(codes.Internal, "malformed grpc-status: %v", err)
|
||||
return
|
||||
}
|
||||
d.state.statusCode = codes.Code(code)
|
||||
case "grpc-message":
|
||||
d.state.statusDesc = f.Value
|
||||
case "grpc-timeout":
|
||||
d.state.timeoutSet = true
|
||||
var err error
|
||||
d.state.timeout, err = timeoutDecode(f.Value)
|
||||
if err != nil {
|
||||
d.err = StreamErrorf(codes.Internal, "malformed time-out: %v", err)
|
||||
return
|
||||
}
|
||||
case ":path":
|
||||
d.state.method = f.Value
|
||||
default:
|
||||
if !isReservedHeader(f.Name) {
|
||||
if d.state.mdata == nil {
|
||||
d.state.mdata = make(map[string]string)
|
||||
}
|
||||
k, v, err := metadata.DecodeKeyValue(f.Name, f.Value)
|
||||
if err != nil {
|
||||
log.Printf("Failed to decode (%q, %q): %v", f.Name, f.Value, err)
|
||||
return
|
||||
}
|
||||
d.state.mdata[k] = v
|
||||
}
|
||||
}
|
||||
})
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *hpackDecoder) decodeClientHTTP2Headers(s *Stream, frame headerFrame) (endHeaders bool, err error) {
|
||||
d.err = nil
|
||||
_, err = d.h.Write(frame.HeaderBlockFragment())
|
||||
if err != nil {
|
||||
err = StreamErrorf(codes.Internal, "HPACK header decode error: %v", err)
|
||||
}
|
||||
|
||||
if frame.HeadersEnded() {
|
||||
if closeErr := d.h.Close(); closeErr != nil && err == nil {
|
||||
err = StreamErrorf(codes.Internal, "HPACK decoder close error: %v", closeErr)
|
||||
}
|
||||
endHeaders = true
|
||||
}
|
||||
|
||||
if err == nil && d.err != nil {
|
||||
err = d.err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *hpackDecoder) decodeServerHTTP2Headers(s *Stream, frame headerFrame) (endHeaders bool, err error) {
|
||||
d.err = nil
|
||||
_, err = d.h.Write(frame.HeaderBlockFragment())
|
||||
if err != nil {
|
||||
err = StreamErrorf(codes.Internal, "HPACK header decode error: %v", err)
|
||||
}
|
||||
|
||||
if frame.HeadersEnded() {
|
||||
if closeErr := d.h.Close(); closeErr != nil && err == nil {
|
||||
err = StreamErrorf(codes.Internal, "HPACK decoder close error: %v", closeErr)
|
||||
}
|
||||
endHeaders = true
|
||||
}
|
||||
|
||||
if err == nil && d.err != nil {
|
||||
err = d.err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type timeoutUnit uint8
|
||||
|
||||
const (
|
||||
hour timeoutUnit = 'H'
|
||||
minute timeoutUnit = 'M'
|
||||
second timeoutUnit = 'S'
|
||||
millisecond timeoutUnit = 'm'
|
||||
microsecond timeoutUnit = 'u'
|
||||
nanosecond timeoutUnit = 'n'
|
||||
)
|
||||
|
||||
func timeoutUnitToDuration(u timeoutUnit) (d time.Duration, ok bool) {
|
||||
switch u {
|
||||
case hour:
|
||||
return time.Hour, true
|
||||
case minute:
|
||||
return time.Minute, true
|
||||
case second:
|
||||
return time.Second, true
|
||||
case millisecond:
|
||||
return time.Millisecond, true
|
||||
case microsecond:
|
||||
return time.Microsecond, true
|
||||
case nanosecond:
|
||||
return time.Nanosecond, true
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const maxTimeoutValue int64 = 100000000 - 1
|
||||
|
||||
// div does integer division and round-up the result. Note that this is
|
||||
// equivalent to (d+r-1)/r but has less chance to overflow.
|
||||
func div(d, r time.Duration) int64 {
|
||||
if m := d % r; m > 0 {
|
||||
return int64(d/r + 1)
|
||||
}
|
||||
return int64(d / r)
|
||||
}
|
||||
|
||||
// TODO(zhaoq): It is the simplistic and not bandwidth efficient. Improve it.
|
||||
func timeoutEncode(t time.Duration) string {
|
||||
if d := div(t, time.Nanosecond); d <= maxTimeoutValue {
|
||||
return strconv.FormatInt(d, 10) + "n"
|
||||
}
|
||||
if d := div(t, time.Microsecond); d <= maxTimeoutValue {
|
||||
return strconv.FormatInt(d, 10) + "u"
|
||||
}
|
||||
if d := div(t, time.Millisecond); d <= maxTimeoutValue {
|
||||
return strconv.FormatInt(d, 10) + "m"
|
||||
}
|
||||
if d := div(t, time.Second); d <= maxTimeoutValue {
|
||||
return strconv.FormatInt(d, 10) + "S"
|
||||
}
|
||||
if d := div(t, time.Minute); d <= maxTimeoutValue {
|
||||
return strconv.FormatInt(d, 10) + "M"
|
||||
}
|
||||
// Note that maxTimeoutValue * time.Hour > MaxInt64.
|
||||
return strconv.FormatInt(div(t, time.Hour), 10) + "H"
|
||||
}
|
||||
|
||||
func timeoutDecode(s string) (time.Duration, error) {
|
||||
size := len(s)
|
||||
if size < 2 {
|
||||
return 0, fmt.Errorf("timeout string is too short: %q", s)
|
||||
}
|
||||
unit := timeoutUnit(s[size-1])
|
||||
d, ok := timeoutUnitToDuration(unit)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("timeout unit is not recognized: %q", s)
|
||||
}
|
||||
t, err := strconv.ParseInt(s[:size-1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d * time.Duration(t), nil
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTimeoutEncode(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"12345678ns", "12345678n"},
|
||||
{"123456789ns", "123457u"},
|
||||
{"12345678us", "12345678u"},
|
||||
{"123456789us", "123457m"},
|
||||
{"12345678ms", "12345678m"},
|
||||
{"123456789ms", "123457S"},
|
||||
{"12345678s", "12345678S"},
|
||||
{"123456789s", "2057614M"},
|
||||
{"12345678m", "12345678M"},
|
||||
{"123456789m", "2057614H"},
|
||||
} {
|
||||
d, err := time.ParseDuration(test.in)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse duration string %s: %v", test.in, err)
|
||||
}
|
||||
out := timeoutEncode(d)
|
||||
if out != test.out {
|
||||
t.Fatalf("timeoutEncode(%s) = %s, want %s", test.in, out, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutDecode(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
// input
|
||||
s string
|
||||
// output
|
||||
d time.Duration
|
||||
err error
|
||||
}{
|
||||
{"1234S", time.Second * 1234, nil},
|
||||
{"1234x", 0, fmt.Errorf("timeout unit is not recognized: %q", "1234x")},
|
||||
{"1", 0, fmt.Errorf("timeout string is too short: %q", "1")},
|
||||
{"", 0, fmt.Errorf("timeout string is too short: %q", "")},
|
||||
} {
|
||||
d, err := timeoutDecode(test.s)
|
||||
if d != test.d || fmt.Sprint(err) != fmt.Sprint(test.err) {
|
||||
t.Fatalf("timeoutDecode(%q) = %d, %v, want %d, %v", test.s, int64(d), err, int64(test.d), test.err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICIzCCAYwCCQCFTbF7XNSvvjANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJB
|
||||
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
|
||||
cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzE3MjMxNzUxWhcNMjQw
|
||||
NzE0MjMxNzUxWjBWMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
|
||||
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0
|
||||
Y2EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMBA3wVeTGHZR1Rye/i+J8a2
|
||||
cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBlLoNY0lSv
|
||||
xDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JAHUGodjx+
|
||||
QAi6yCAurUZGvYXGgZSBAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAQoQVD8bwdtWJ
|
||||
AniGBwcCfqYyH+/KpA10AcebJVVTyYbYvI9Q8d6RSVu4PZy9OALHR/QrWBdYTAyz
|
||||
fNAmc2cmdkSRJzjhIaOstnQy1J+Fk0T9XyvQtq499yFbq9xogUVlEGH62xP6vH0Y
|
||||
5ukK//dCPAzA11YuX2rnex0JhuTQfcI=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,15 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQDhwxUnKCwlSaWAwzOB2LSHVegJHv7DDWminTg4wzLLsf+LQ8nZ
|
||||
bpjfn5vgIzxCuRh4Rp9QYM5FhfrJX9wcYawP/HTbJ7p7LVQO2QYAP+akMTHxgKuM
|
||||
BzVV++3wWToKfVZUjFX8nfTfGMGwWAHJDnlEGnU4tl9UujoCV4ENJtzFoQIDAQAB
|
||||
AoGAJ+6hpzNr24yTQZtFWQpDpEyFplddKJMOxDya3S9ppK3vTWrIITV2xNcucw7I
|
||||
ceTbdyrGsyjsU0/HdCcIf9ym2jfmGLUwmyhltKVw0QYcFB0XLkc0nI5YvEYoeVDg
|
||||
omZIXn1E3EW+sSIWSbkMu9bY2kstKXR2UZmMgWDtmBEPMaECQQD6yT4TAZM5hGBb
|
||||
ciBKgMUP6PwOhPhOMPIvijO50Aiu6iuCV88l1QIy38gWVhxjNrq6P346j4IBg+kB
|
||||
9alwpCODAkEA5nSnm9k6ykYeQWNS0fNWiRinCdl23A7usDGSuKKlm019iomJ/Rgd
|
||||
MKDOp0q/2OostbteOWM2MRFf4jMH3wyVCwJAfAdjJ8szoNKTRSagSbh9vWygnB2v
|
||||
IByc6l4TTuZQJRGzCveafz9lovuB3WohCABdQRd9ukCXL2CpsEpqzkafOQJAJUjc
|
||||
USedDlq3zGZwYM1Yw8d8RuirBUFZNqJelYai+nRYClDkRVFgb5yksoYycbq5TxGo
|
||||
VeqKOvgPpj4RWPHlLwJAGUMk3bqT91xBUCnLRs/vfoCpHpg6eywQTBDAV6xkyz4a
|
||||
RH3I7/+yj3ZxR2JoWHgUwZ7lZk1VnhffFye7SBXyag==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICmzCCAgSgAwIBAgIBAzANBgkqhkiG9w0BAQUFADBWMQswCQYDVQQGEwJBVTET
|
||||
MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||
dHkgTHRkMQ8wDQYDVQQDDAZ0ZXN0Y2EwHhcNMTQwNzIyMDYwMDU3WhcNMjQwNzE5
|
||||
MDYwMDU3WjBkMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
|
||||
BAcTB0NoaWNhZ28xFDASBgNVBAoTC0dvb2dsZSBJbmMuMRowGAYDVQQDFBEqLnRl
|
||||
c3QuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA4cMVJygs
|
||||
JUmlgMMzgdi0h1XoCR7+ww1pop04OMMyy7H/i0PJ2W6Y35+b4CM8QrkYeEafUGDO
|
||||
RYX6yV/cHGGsD/x02ye6ey1UDtkGAD/mpDEx8YCrjAc1Vfvt8Fk6Cn1WVIxV/J30
|
||||
3xjBsFgByQ55RBp1OLZfVLo6AleBDSbcxaECAwEAAaNrMGkwCQYDVR0TBAIwADAL
|
||||
BgNVHQ8EBAMCBeAwTwYDVR0RBEgwRoIQKi50ZXN0Lmdvb2dsZS5mcoIYd2F0ZXJ6
|
||||
b29pLnRlc3QuZ29vZ2xlLmJlghIqLnRlc3QueW91dHViZS5jb22HBMCoAQMwDQYJ
|
||||
KoZIhvcNAQEFBQADgYEAM2Ii0LgTGbJ1j4oqX9bxVcxm+/R5Yf8oi0aZqTJlnLYS
|
||||
wXcBykxTx181s7WyfJ49WwrYXo78zTDAnf1ma0fPq3e4mpspvyndLh1a+OarHa1e
|
||||
aT0DIIYk7qeEa1YcVljx2KyLd0r1BBAfrwyGaEPVeJQVYWaOJRU2we/KD4ojf9s=
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,438 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
Package transport defines and implements message oriented communication channel
|
||||
to complete various transactions (e.g., an RPC).
|
||||
*/
|
||||
package transport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/credentials"
|
||||
"github.com/grpc/grpc-go/rpc/metadata"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// recvMsg represents the received msg from the transport. All transport
|
||||
// protocol specific info has been removed.
|
||||
type recvMsg struct {
|
||||
data []byte
|
||||
// nil: received some data
|
||||
// io.EOF: stream is completed. data is nil.
|
||||
// other non-nil error: transport failure. data is nil.
|
||||
err error
|
||||
}
|
||||
|
||||
func (recvMsg) isItem() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// All items in an out of a recvBuffer should be the same type.
|
||||
type item interface {
|
||||
isItem() bool
|
||||
}
|
||||
|
||||
// recvBuffer is an unbounded channel of item.
|
||||
type recvBuffer struct {
|
||||
c chan item
|
||||
mu sync.Mutex
|
||||
backlog []item
|
||||
}
|
||||
|
||||
func newRecvBuffer() *recvBuffer {
|
||||
b := &recvBuffer{
|
||||
c: make(chan item, 1),
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *recvBuffer) put(r item) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.backlog = append(b.backlog, r)
|
||||
select {
|
||||
case b.c <- b.backlog[0]:
|
||||
b.backlog = b.backlog[1:]
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (b *recvBuffer) load() {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if len(b.backlog) > 0 {
|
||||
select {
|
||||
case b.c <- b.backlog[0]:
|
||||
b.backlog = b.backlog[1:]
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get returns the channel that receives an item in the buffer.
|
||||
//
|
||||
// Upon receipt of an item, the caller should call load to send another
|
||||
// item onto the channel if there is any.
|
||||
func (b *recvBuffer) get() <-chan item {
|
||||
return b.c
|
||||
}
|
||||
|
||||
// recvBufferReader implements io.Reader interface to read the data from
|
||||
// recvBuffer.
|
||||
type recvBufferReader struct {
|
||||
ctx context.Context
|
||||
recv *recvBuffer
|
||||
last *bytes.Reader // Stores the remaining data in the previous calls.
|
||||
err error
|
||||
}
|
||||
|
||||
// Read reads the next len(p) bytes from last. If last is drained, it tries to
|
||||
// read additional data from recv. It blocks if there no additional data available
|
||||
// in recv. If Read returns any non-nil error, it will continue to return that error.
|
||||
func (r *recvBufferReader) Read(p []byte) (n int, err error) {
|
||||
if r.err != nil {
|
||||
return 0, r.err
|
||||
}
|
||||
defer func() { r.err = err }()
|
||||
if r.last != nil && r.last.Len() > 0 {
|
||||
// Read remaining data left in last call.
|
||||
return r.last.Read(p)
|
||||
}
|
||||
select {
|
||||
case <-r.ctx.Done():
|
||||
return 0, ContextErr(r.ctx.Err())
|
||||
case i := <-r.recv.get():
|
||||
r.recv.load()
|
||||
m := i.(*recvMsg)
|
||||
if m.err != nil {
|
||||
return 0, m.err
|
||||
}
|
||||
r.last = bytes.NewReader(m.data)
|
||||
return r.last.Read(p)
|
||||
}
|
||||
}
|
||||
|
||||
type streamState uint8
|
||||
|
||||
const (
|
||||
streamActive streamState = iota
|
||||
streamWriteDone // EndStream sent
|
||||
streamReadDone // EndStream received
|
||||
streamDone // sendDone and recvDone or RSTStreamFrame is sent or received.
|
||||
)
|
||||
|
||||
// Stream represents an RPC in the transport layer.
|
||||
type Stream struct {
|
||||
id uint32
|
||||
// nil for client side Stream.
|
||||
st ServerTransport
|
||||
// ctx is the associated context of the stream.
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
// method records the associated RPC method of the stream.
|
||||
method string
|
||||
buf *recvBuffer
|
||||
dec io.Reader
|
||||
|
||||
// Inbound quota for flow control
|
||||
recvQuota int
|
||||
// The handler to control the window update procedure for both this
|
||||
// particular stream and the associated transport.
|
||||
windowHandler func(int)
|
||||
|
||||
sendQuotaPool *quotaPool
|
||||
// Close headerChan to indicate the end of reception of header metadata.
|
||||
headerChan chan struct{}
|
||||
// header caches the received header metadata.
|
||||
header metadata.MD
|
||||
// The key-value map of trailer metadata.
|
||||
trailer metadata.MD
|
||||
|
||||
mu sync.RWMutex
|
||||
// headerOK becomes true from the first header is about to send.
|
||||
headerOk bool
|
||||
state streamState
|
||||
// true iff headerChan is closed. Used to avoid closing headerChan
|
||||
// multiple times.
|
||||
headerDone bool
|
||||
// the status received from the server.
|
||||
statusCode codes.Code
|
||||
statusDesc string
|
||||
}
|
||||
|
||||
// Header acquires the key-value pairs of header metadata once it
|
||||
// is available. It blocks until i) the metadata is ready or ii) there is no
|
||||
// header metadata or iii) the stream is cancelled/expired.
|
||||
func (s *Stream) Header() (metadata.MD, error) {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return nil, ContextErr(s.ctx.Err())
|
||||
case <-s.headerChan:
|
||||
return s.header.Copy(), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Trailer returns the cached trailer metedata. Note that if it is not called
|
||||
// after the entire stream is done, it could return an empty MD. Client
|
||||
// side only.
|
||||
func (s *Stream) Trailer() metadata.MD {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.trailer.Copy()
|
||||
}
|
||||
|
||||
// ServerTransport returns the underlying ServerTransport for the stream.
|
||||
// The client side stream always returns nil.
|
||||
func (s *Stream) ServerTransport() ServerTransport {
|
||||
return s.st
|
||||
}
|
||||
|
||||
// Context returns the context of the stream.
|
||||
func (s *Stream) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
// Method returns the method for the stream.
|
||||
func (s *Stream) Method() string {
|
||||
return s.method
|
||||
}
|
||||
|
||||
// StatusCode returns statusCode received from the server.
|
||||
func (s *Stream) StatusCode() codes.Code {
|
||||
return s.statusCode
|
||||
}
|
||||
|
||||
// StatusDesc returns statusDesc received from the server.
|
||||
func (s *Stream) StatusDesc() string {
|
||||
return s.statusDesc
|
||||
}
|
||||
|
||||
// SetTrailer sets the trailer metadata which will be sent with the RPC status
|
||||
// by the server. This can only be called at most once. Server side only.
|
||||
func (s *Stream) SetTrailer(md metadata.MD) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.trailer != nil {
|
||||
return fmt.Errorf("transport: Trailer has been set")
|
||||
}
|
||||
s.trailer = md.Copy()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stream) write(m recvMsg) {
|
||||
s.buf.put(&m)
|
||||
}
|
||||
|
||||
// Read reads all the data available for this Stream from the transport and
|
||||
// passes them into the decoder, which converts them into a gRPC message stream.
|
||||
// The error is io.EOF when the stream is done or another non-nil error if
|
||||
// the stream broke.
|
||||
func (s *Stream) Read(p []byte) (n int, err error) {
|
||||
n, err = s.dec.Read(p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.windowHandler(n)
|
||||
return
|
||||
}
|
||||
|
||||
type key int
|
||||
|
||||
// The key to save transport.Stream in the context.
|
||||
const streamKey = key(0)
|
||||
|
||||
// newContextWithStream creates a new context from ctx and attaches stream
|
||||
// to it.
|
||||
func newContextWithStream(ctx context.Context, stream *Stream) context.Context {
|
||||
return context.WithValue(ctx, streamKey, stream)
|
||||
}
|
||||
|
||||
// StreamFromContext returns the stream saved in ctx.
|
||||
func StreamFromContext(ctx context.Context) (s *Stream, ok bool) {
|
||||
s, ok = ctx.Value(streamKey).(*Stream)
|
||||
return
|
||||
}
|
||||
|
||||
// state of transport
|
||||
type transportState int
|
||||
|
||||
const (
|
||||
reachable transportState = iota
|
||||
unreachable
|
||||
closing
|
||||
)
|
||||
|
||||
// NewServerTransport creates a ServerTransport with conn or non-nil error
|
||||
// if it fails.
|
||||
func NewServerTransport(protocol string, conn net.Conn, maxStreams uint32) (ServerTransport, error) {
|
||||
return newHTTP2Server(conn, maxStreams)
|
||||
}
|
||||
|
||||
// NewClientTransport establishes the transport with the required protocol
|
||||
// and returns it to the caller.
|
||||
func NewClientTransport(protocol, target string, authOpts []credentials.Credentials) (ClientTransport, error) {
|
||||
return newHTTP2Client(target, authOpts)
|
||||
}
|
||||
|
||||
// Options provides additional hints and information for message
|
||||
// transmission.
|
||||
type Options struct {
|
||||
// Indicate whether it is the last piece for this stream.
|
||||
Last bool
|
||||
// The hint to transport impl whether the data could be buffered for
|
||||
// batching write. Transport impl can feel free to ignore it.
|
||||
Delay bool
|
||||
}
|
||||
|
||||
// CallHdr carries the metadata from client to server.
|
||||
type CallHdr struct {
|
||||
Host string // peer host
|
||||
Method string // the operation to perform on the specified host
|
||||
}
|
||||
|
||||
// ClientTransport is the common interface for all gRPC client side transport
|
||||
// implementations.
|
||||
type ClientTransport interface {
|
||||
// Close tears down this transport. Once it returns, the transport
|
||||
// should not be accessed any more. The caller must make sure this
|
||||
// is called only once.
|
||||
Close() error
|
||||
|
||||
// Write sends the data for the given stream. A nil stream indicates
|
||||
// the write is to be performed on the transport as a whole.
|
||||
Write(s *Stream, data []byte, opts *Options) error
|
||||
|
||||
// NewStream creates a Stream for an RPC.
|
||||
NewStream(ctx context.Context, callHdr *CallHdr) (*Stream, error)
|
||||
|
||||
// CloseStream clears the footprint of a stream when the stream is
|
||||
// not needed any more. The err indicates the error incurred when
|
||||
// CloseStream is called. Must be called when a stream is finished
|
||||
// unless the associated transport is closing.
|
||||
CloseStream(stream *Stream, err error)
|
||||
|
||||
// Error returns a channel that is closed when some I/O error
|
||||
// happens. Typically the caller should have a goroutine to monitor
|
||||
// this in order to take action (e.g., close the current transport
|
||||
// and create a new one) in error case. It should not return nil
|
||||
// once the transport is initiated.
|
||||
Error() <-chan struct{}
|
||||
}
|
||||
|
||||
// ServerTransport is the common interface for all gRPC server side transport
|
||||
// implementations.
|
||||
type ServerTransport interface {
|
||||
// WriteStatus sends the status of a stream to the client.
|
||||
WriteStatus(s *Stream, statusCode codes.Code, statusDesc string) error
|
||||
// Write sends the data for the given stream.
|
||||
Write(s *Stream, data []byte, opts *Options) error
|
||||
// WriteHeader sends the header metedata for the given stream.
|
||||
WriteHeader(s *Stream, md metadata.MD) error
|
||||
// HandleStreams receives incoming streams using the given handler.
|
||||
HandleStreams(func(*Stream))
|
||||
// Close tears down the transport. Once it is called, the transport
|
||||
// should not be accessed any more. All the pending streams and their
|
||||
// handlers will be terminated asynchronously.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// StreamErrorf creates an StreamError with the specified error code and description.
|
||||
func StreamErrorf(c codes.Code, format string, a ...interface{}) StreamError {
|
||||
return StreamError{
|
||||
Code: c,
|
||||
Desc: fmt.Sprintf(format, a...),
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionErrorf creates an ConnectionError with the specified error description.
|
||||
func ConnectionErrorf(format string, a ...interface{}) ConnectionError {
|
||||
return ConnectionError{
|
||||
Desc: fmt.Sprintf(format, a...),
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionError is an error that results in the termination of the
|
||||
// entire connection and the retry of all the active streams.
|
||||
type ConnectionError struct {
|
||||
Desc string
|
||||
}
|
||||
|
||||
func (e ConnectionError) Error() string {
|
||||
return fmt.Sprintf("connection error: desc = %q", e.Desc)
|
||||
}
|
||||
|
||||
// Define some common ConnectionErrors.
|
||||
var ErrConnClosing = ConnectionError{Desc: "transport is closing"}
|
||||
|
||||
// StreamError is an error that only affects one stream within a connection.
|
||||
type StreamError struct {
|
||||
Code codes.Code
|
||||
Desc string
|
||||
}
|
||||
|
||||
func (e StreamError) Error() string {
|
||||
return fmt.Sprintf("stream error: code = %d desc = %q", e.Code, e.Desc)
|
||||
}
|
||||
|
||||
// ContextErr converts the error from context package into a StreamError.
|
||||
func ContextErr(err error) StreamError {
|
||||
switch err {
|
||||
case context.DeadlineExceeded:
|
||||
return StreamErrorf(codes.DeadlineExceeded, "%v", err)
|
||||
case context.Canceled:
|
||||
return StreamErrorf(codes.Canceled, "%v", err)
|
||||
}
|
||||
panic(fmt.Sprintf("Unexpected error from context packet: %v", err))
|
||||
}
|
||||
|
||||
// wait blocks until it can receive from ctx.Done, closing, or proceed.
|
||||
// If it receives from ctx.Done, it returns 0, the StreamError for ctx.Err.
|
||||
// If it receives from closing, it returns 0, ErrConnClosing.
|
||||
// If it receives from proceed, it returns the received integer, nil.
|
||||
func wait(ctx context.Context, closing <-chan struct{}, proceed <-chan int) (int, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return 0, ContextErr(ctx.Err())
|
||||
case <-closing:
|
||||
return 0, ErrConnClosing
|
||||
case i := <-proceed:
|
||||
return i, nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,407 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2014, Google Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
* * Neither the name of Google Inc. nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
package transport
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grpc/grpc-go/rpc/codes"
|
||||
"github.com/grpc/grpc-go/rpc/credentials"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
lis net.Listener
|
||||
port string
|
||||
// channel to signal server is ready to serve.
|
||||
readyChan chan bool
|
||||
mu sync.Mutex
|
||||
conns map[ServerTransport]bool
|
||||
}
|
||||
|
||||
var (
|
||||
tlsDir = "testdata/"
|
||||
expectedRequest = []byte("ping")
|
||||
expectedResponse = []byte("pong")
|
||||
expectedRequestLarge = make([]byte, initialWindowSize*2)
|
||||
expectedResponseLarge = make([]byte, initialWindowSize*2)
|
||||
)
|
||||
|
||||
type testStreamHandler struct {
|
||||
t ServerTransport
|
||||
}
|
||||
|
||||
func (h *testStreamHandler) handleStream(s *Stream) {
|
||||
req := expectedRequest
|
||||
resp := expectedResponse
|
||||
if s.Method() == "foo.Large" {
|
||||
req = expectedRequestLarge
|
||||
resp = expectedResponseLarge
|
||||
}
|
||||
p := make([]byte, len(req))
|
||||
_, err := io.ReadFull(s, p)
|
||||
if err != nil || !bytes.Equal(p, req) {
|
||||
if err == ErrConnClosing {
|
||||
return
|
||||
}
|
||||
log.Fatalf("handleStream got error: %v, want <nil>; result: %v, want %v", err, p, req)
|
||||
}
|
||||
// send a response back to the client.
|
||||
h.t.Write(s, resp, &Options{})
|
||||
// send the trailer to end the stream.
|
||||
h.t.WriteStatus(s, codes.OK, "")
|
||||
}
|
||||
|
||||
// handleStreamSuspension blocks until s.ctx is canceled.
|
||||
func (h *testStreamHandler) handleStreamSuspension(s *Stream) {
|
||||
<-s.ctx.Done()
|
||||
}
|
||||
|
||||
func (s *server) Start(useTLS bool, port int, maxStreams uint32, suspend bool) {
|
||||
var err error
|
||||
if port == 0 {
|
||||
s.lis, err = net.Listen("tcp", ":0")
|
||||
} else {
|
||||
s.lis, err = net.Listen("tcp", ":"+strconv.Itoa(port))
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
if useTLS {
|
||||
creds, err := credentials.NewServerTLSFromFile(tlsDir+"server1.pem", tlsDir+"server1.key")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate credentials %v", err)
|
||||
}
|
||||
s.lis = creds.NewListener(s.lis)
|
||||
}
|
||||
_, p, err := net.SplitHostPort(s.lis.Addr().String())
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse listener address: %v", err)
|
||||
}
|
||||
s.port = p
|
||||
if s.readyChan != nil {
|
||||
close(s.readyChan)
|
||||
}
|
||||
s.conns = make(map[ServerTransport]bool)
|
||||
for {
|
||||
conn, err := s.lis.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
t, err := NewServerTransport("http2", conn, maxStreams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.mu.Lock()
|
||||
if s.conns == nil {
|
||||
s.mu.Unlock()
|
||||
t.Close()
|
||||
return
|
||||
}
|
||||
s.conns[t] = true
|
||||
s.mu.Unlock()
|
||||
h := &testStreamHandler{t}
|
||||
if suspend {
|
||||
go t.HandleStreams(h.handleStreamSuspension)
|
||||
} else {
|
||||
go t.HandleStreams(h.handleStream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Wait(t *testing.T, timeout time.Duration) {
|
||||
select {
|
||||
case <-s.readyChan:
|
||||
case <-time.After(timeout):
|
||||
t.Fatalf("Timed out after %v waiting for server to be ready", timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Close() {
|
||||
s.lis.Close()
|
||||
s.mu.Lock()
|
||||
for c := range s.conns {
|
||||
c.Close()
|
||||
}
|
||||
s.conns = nil
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func setUp(t *testing.T, useTLS bool, port int, maxStreams uint32, suspend bool) (*server, ClientTransport) {
|
||||
server := &server{readyChan: make(chan bool)}
|
||||
go server.Start(useTLS, port, maxStreams, suspend)
|
||||
server.Wait(t, 2*time.Second)
|
||||
addr := "localhost:" + server.port
|
||||
var (
|
||||
ct ClientTransport
|
||||
connErr error
|
||||
)
|
||||
if useTLS {
|
||||
creds, err := credentials.NewClientTLSFromFile(tlsDir+"ca.pem", "x.test.youtube.com")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create credentials %v", err)
|
||||
}
|
||||
ct, connErr = NewClientTransport("http2", addr, []credentials.Credentials{creds})
|
||||
} else {
|
||||
ct, connErr = NewClientTransport("http2", addr, nil)
|
||||
}
|
||||
if connErr != nil {
|
||||
t.Fatalf("failed to create transport: %v", connErr)
|
||||
}
|
||||
return server, ct
|
||||
}
|
||||
|
||||
func TestClientSendAndReceive(t *testing.T) {
|
||||
server, ct := setUp(t, true, 0, math.MaxUint32, false)
|
||||
callHdr := &CallHdr{
|
||||
Host: "localhost",
|
||||
Method: "foo.Small",
|
||||
}
|
||||
s1, err1 := ct.NewStream(context.Background(), callHdr)
|
||||
if err1 != nil {
|
||||
t.Fatalf("failed to open stream: %v", err1)
|
||||
}
|
||||
if s1.id != 1 {
|
||||
t.Fatalf("wrong stream id: %d", s1.id)
|
||||
}
|
||||
s2, err2 := ct.NewStream(context.Background(), callHdr)
|
||||
if err2 != nil {
|
||||
t.Fatalf("failed to open stream: %v", err2)
|
||||
}
|
||||
if s2.id != 3 {
|
||||
t.Fatalf("wrong stream id: %d", s2.id)
|
||||
}
|
||||
opts := Options{
|
||||
Last: true,
|
||||
Delay: false,
|
||||
}
|
||||
if err := ct.Write(s1, expectedRequest, &opts); err != nil {
|
||||
t.Fatalf("failed to send data: %v", err)
|
||||
}
|
||||
p := make([]byte, len(expectedResponse))
|
||||
_, recvErr := io.ReadFull(s1, p)
|
||||
if recvErr != nil || !bytes.Equal(p, expectedResponse) {
|
||||
t.Fatalf("Error: %v, want <nil>; Result: %v, want %v", recvErr, p, expectedResponse)
|
||||
}
|
||||
_, recvErr = io.ReadFull(s1, p)
|
||||
if recvErr != io.EOF {
|
||||
t.Fatalf("Error: %v; want <EOF>", recvErr)
|
||||
}
|
||||
ct.Close()
|
||||
server.Close()
|
||||
}
|
||||
|
||||
func TestClientErrorNotify(t *testing.T) {
|
||||
server, ct := setUp(t, true, 0, math.MaxUint32, false)
|
||||
callHdr := &CallHdr{
|
||||
Host: "localhost",
|
||||
Method: "foo.Small",
|
||||
}
|
||||
s, err := ct.NewStream(context.Background(), callHdr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open stream: %v", err)
|
||||
}
|
||||
if s.id != 1 {
|
||||
t.Fatalf("wrong stream id: %d", s.id)
|
||||
}
|
||||
// Tear down the server.
|
||||
go server.Close()
|
||||
// ct.reader should detect the error and activate ct.Error().
|
||||
<-ct.Error()
|
||||
ct.Close()
|
||||
}
|
||||
|
||||
func performOneRPC(ct ClientTransport) {
|
||||
callHdr := &CallHdr{
|
||||
Host: "localhost",
|
||||
Method: "foo.Small",
|
||||
}
|
||||
s, err := ct.NewStream(context.Background(), callHdr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
opts := Options{
|
||||
Last: true,
|
||||
Delay: false,
|
||||
}
|
||||
if err := ct.Write(s, expectedRequest, &opts); err == nil {
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
// The following s.Recv()'s could error out because the
|
||||
// underlying transport is gone.
|
||||
//
|
||||
// Read response
|
||||
p := make([]byte, len(expectedResponse))
|
||||
io.ReadFull(s, p)
|
||||
// Read io.EOF
|
||||
io.ReadFull(s, p)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientMix(t *testing.T) {
|
||||
s, ct := setUp(t, true, 0, math.MaxUint32, false)
|
||||
go func(s *server) {
|
||||
time.Sleep(5 * time.Second)
|
||||
s.Close()
|
||||
}(s)
|
||||
go func(t ClientTransport) {
|
||||
<-ct.Error()
|
||||
ct.Close()
|
||||
}(ct)
|
||||
for i := 0; i < 1000; i++ {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
go performOneRPC(ct)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExceedMaxStreamsLimit(t *testing.T) {
|
||||
server, ct := setUp(t, true, 0, 1, false)
|
||||
defer func() {
|
||||
ct.Close()
|
||||
server.Close()
|
||||
}()
|
||||
callHdr := &CallHdr{
|
||||
Host: "localhost",
|
||||
Method: "foo.Small",
|
||||
}
|
||||
// Creates the 1st stream and keep it alive.
|
||||
_, err1 := ct.NewStream(context.Background(), callHdr)
|
||||
if err1 != nil {
|
||||
t.Fatalf("failed to open stream: %v", err1)
|
||||
}
|
||||
// Creates the 2nd stream. It has chance to succeed when the settings
|
||||
// frame from the server has not received at the client.
|
||||
s, err2 := ct.NewStream(context.Background(), callHdr)
|
||||
if err2 != nil {
|
||||
se, ok := err2.(StreamError)
|
||||
if !ok {
|
||||
t.Fatalf("Received unexpected error %v", err2)
|
||||
}
|
||||
if se.Code != codes.Unavailable {
|
||||
t.Fatalf("Got error code: %d, want: %d", se.Code, codes.Unavailable)
|
||||
}
|
||||
return
|
||||
}
|
||||
// If the 2nd stream is created successfully, sends the request.
|
||||
if err := ct.Write(s, expectedRequest, &Options{Last: true, Delay: false}); err != nil {
|
||||
t.Fatalf("failed to send data: %v", err)
|
||||
}
|
||||
// The 2nd stream was rejected by the server via a reset.
|
||||
p := make([]byte, len(expectedResponse))
|
||||
_, recvErr := io.ReadFull(s, p)
|
||||
if recvErr != io.EOF || s.StatusCode() != codes.Unavailable {
|
||||
t.Fatalf("Error: %v, StatusCode: %d; want <EOF>, %d", recvErr, s.StatusCode(), codes.Unavailable)
|
||||
}
|
||||
// Server's setting has been received. From now on, new stream will be rejected instantly.
|
||||
_, err3 := ct.NewStream(context.Background(), callHdr)
|
||||
if err3 == nil {
|
||||
t.Fatalf("Received unexpected <nil>, want an error with code %d", codes.Unavailable)
|
||||
}
|
||||
if se, ok := err3.(StreamError); !ok || se.Code != codes.Unavailable {
|
||||
t.Fatalf("Got: %v, want a StreamError with error code %d", err3, codes.Unavailable)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLargeMessage(t *testing.T) {
|
||||
server, ct := setUp(t, true, 0, math.MaxUint32, false)
|
||||
callHdr := &CallHdr{
|
||||
Host: "localhost",
|
||||
Method: "foo.Large",
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 2; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
s, err := ct.NewStream(context.Background(), callHdr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open stream: %v", err)
|
||||
}
|
||||
if err := ct.Write(s, expectedRequestLarge, &Options{Last: true, Delay: false}); err != nil {
|
||||
t.Fatalf("failed to send data: %v", err)
|
||||
}
|
||||
p := make([]byte, len(expectedResponseLarge))
|
||||
_, recvErr := io.ReadFull(s, p)
|
||||
if recvErr != nil || !bytes.Equal(p, expectedResponseLarge) {
|
||||
t.Fatalf("Error: %v, want <nil>; Result len: %d, want len %d", recvErr, len(p), len(expectedResponseLarge))
|
||||
}
|
||||
_, recvErr = io.ReadFull(s, p)
|
||||
if recvErr != io.EOF {
|
||||
t.Fatalf("Error: %v; want <EOF>", recvErr)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
ct.Close()
|
||||
server.Close()
|
||||
}
|
||||
|
||||
func TestLargeMessageSuspension(t *testing.T) {
|
||||
server, ct := setUp(t, true, 0, math.MaxUint32, true)
|
||||
callHdr := &CallHdr{
|
||||
Host: "localhost",
|
||||
Method: "foo.Large",
|
||||
}
|
||||
// Set a long enough timeout for writing a large message out.
|
||||
ctx, _ := context.WithTimeout(context.Background(), time.Second)
|
||||
s, err := ct.NewStream(ctx, callHdr)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open stream: %v", err)
|
||||
}
|
||||
// Write should not be done successfully due to flow control.
|
||||
err = ct.Write(s, expectedRequestLarge, &Options{Last: true, Delay: false})
|
||||
expectedErr := StreamErrorf(codes.DeadlineExceeded, "%v", context.DeadlineExceeded)
|
||||
if err == nil || err != expectedErr {
|
||||
t.Fatalf("Write got %v, want %v", err, expectedErr)
|
||||
}
|
||||
ct.Close()
|
||||
server.Close()
|
||||
}
|
||||
|
||||
func TestStreamContext(t *testing.T) {
|
||||
expectedStream := Stream{}
|
||||
ctx := newContextWithStream(context.Background(), &expectedStream)
|
||||
s, ok := StreamFromContext(ctx)
|
||||
if !ok || !reflect.DeepEqual(expectedStream, *s) {
|
||||
t.Fatalf("GetStreamFromContext(%v) = %v, %t, want: %v, true", ctx, *s, ok, expectedStream)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue