grpc-go initial commit

This commit is contained in:
iamqizhao 2015-02-05 17:14:05 -08:00
commit c463038273
50 changed files with 8099 additions and 0 deletions

28
LICENSE Normal file
View File

@ -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.

5
README.md Normal file
View File

@ -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

165
rpc/call.go Normal file
View File

@ -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())
}
}

217
rpc/clientconn.go Normal file
View File

@ -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)
}

157
rpc/codes/codes.go Normal file
View File

@ -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
)

18
rpc/compiler/Makefile Normal file
View File

@ -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)

3
rpc/compiler/README Normal file
View File

@ -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.

16
rpc/compiler/gen.sh Executable file
View File

@ -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

View File

@ -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

View File

@ -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_

BIN
rpc/compiler/go_generator.o Normal file

Binary file not shown.

BIN
rpc/compiler/go_plugin Executable file

Binary file not shown.

90
rpc/compiler/go_plugin.cc Normal file
View File

@ -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);
}

BIN
rpc/compiler/go_plugin.o Normal file

Binary file not shown.

View File

@ -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
}

View File

@ -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)
}
}

14
rpc/interop/client/testdata/ca.pem vendored Normal file
View File

@ -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-----

15
rpc/interop/client/testdata/server1.key vendored Normal file
View File

@ -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-----

16
rpc/interop/client/testdata/server1.pem vendored Normal file
View File

@ -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-----

View File

@ -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)
}
}

14
rpc/interop/server/testdata/ca.pem vendored Normal file
View File

@ -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-----

15
rpc/interop/server/testdata/server1.key vendored Normal file
View File

@ -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-----

16
rpc/interop/server/testdata/server1.pem vendored Normal file
View File

@ -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-----

96
rpc/interop/testdata/messages.proto vendored Normal file
View File

@ -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;
}

42
rpc/interop/testdata/test.proto vendored Normal file
View File

@ -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);
}

403
rpc/interop/testdata/test_proto.pb.go vendored Normal file
View File

@ -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,
}

View File

@ -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,
},
},
}

144
rpc/metadata/metadata.go Normal file
View File

@ -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
}

View File

@ -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)
}
}
}

282
rpc/rpc_util.go Normal file
View File

@ -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)
}

172
rpc/rpc_util_test.go Normal file
View File

@ -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)
}
}
}

372
rpc/server.go Normal file
View File

@ -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)
}

237
rpc/stream.go Normal file
View File

@ -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)
}

466
rpc/test/end2end_test.go Normal file
View File

@ -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)
}
}

14
rpc/test/testdata/ca.pem vendored Normal file
View File

@ -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-----

15
rpc/test/testdata/server1.key vendored Normal file
View File

@ -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-----

16
rpc/test/testdata/server1.pem vendored Normal file
View File

@ -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-----

157
rpc/test/testdata/test.pb.go vendored Normal file
View File

@ -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() {
}

52
rpc/test/testdata/test.proto vendored Normal file
View File

@ -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) {
}
}

293
rpc/test/testdata/test_grpc.pb.go vendored Normal file
View File

@ -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,
},
},
}

126
rpc/transport/control.go Normal file
View File

@ -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 := &quotaPool{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
}

View File

@ -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)
}
}

View File

@ -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()
}

291
rpc/transport/http_util.go Normal file
View File

@ -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
}

View File

@ -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)
}
}
}

14
rpc/transport/testdata/ca.pem vendored Normal file
View File

@ -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-----

15
rpc/transport/testdata/server1.key vendored Normal file
View File

@ -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-----

16
rpc/transport/testdata/server1.pem vendored Normal file
View File

@ -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-----

438
rpc/transport/transport.go Normal file
View File

@ -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
}
}

View File

@ -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)
}
}