mirror of
https://github.com/fullstorydev/grpcurl.git
synced 2026-05-22 19:51:44 +03:00
fix latest CI breakages by forking code from grpc-go's interop/testing
Also moves testing package to internal/testing
This commit is contained in:
9
internal/testing/cmd/bankdemo/README.md
Normal file
9
internal/testing/cmd/bankdemo/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# bankdemo
|
||||
|
||||
The `bankdemo` program is an example gRPC server that was used to demo `grpcurl` at Gophercon 2018.
|
||||
|
||||
It demonstrates interesting concepts for building a gRPC server, including chat functionality (that relies on full-duplex bidirectional streams). This code was written specifically to provide an interesting concrete demonstration and, as such, should not be considered in any way production-worthy.
|
||||
|
||||
The demo app tracks user accounts, transactions, and balances completely in memory. Every few seconds, as well as on graceful shutdown (like when the server receives a SIGTERM or SIGINT signal), this state is saved to a file named `accounts.json`, so that the data can be restored if the process restarts.
|
||||
|
||||
In addition to bank account data, the server also tracks "chat sessions", for demonstrating bidirectional streams in the form of an application where customers can chat with support agents.
|
||||
49
internal/testing/cmd/bankdemo/auth.go
Normal file
49
internal/testing/cmd/bankdemo/auth.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func getCustomer(ctx context.Context) string {
|
||||
// we'll just treat the "auth token" as if it is a
|
||||
// customer ID, but reject tokens that begin with "agent"
|
||||
// (those are auth tokens for support agents, not customers)
|
||||
cust := getAuthCode(ctx)
|
||||
if strings.HasPrefix(cust, "agent") {
|
||||
return ""
|
||||
}
|
||||
return cust
|
||||
}
|
||||
|
||||
func getAgent(ctx context.Context) string {
|
||||
// we'll just treat the "auth token" as if it is an agent's
|
||||
// user ID, but reject tokens that don't begin with "agent"
|
||||
// (those are auth tokens for customers, not support agents)
|
||||
agent := getAuthCode(ctx)
|
||||
if !strings.HasPrefix(agent, "agent") {
|
||||
return ""
|
||||
}
|
||||
return agent
|
||||
}
|
||||
|
||||
func getAuthCode(ctx context.Context) string {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
vals := md.Get("authorization")
|
||||
if len(vals) != 1 {
|
||||
return ""
|
||||
}
|
||||
pieces := strings.SplitN(strings.ToLower(vals[0]), " ", 2)
|
||||
if len(pieces) != 2 {
|
||||
return ""
|
||||
}
|
||||
if pieces[0] != "token" {
|
||||
return ""
|
||||
}
|
||||
return pieces[1]
|
||||
}
|
||||
237
internal/testing/cmd/bankdemo/bank.go
Normal file
237
internal/testing/cmd/bankdemo/bank.go
Normal file
@@ -0,0 +1,237 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// bankServer implements the Bank gRPC service.
|
||||
type bankServer struct {
|
||||
allAccounts *accounts
|
||||
}
|
||||
|
||||
func (s *bankServer) OpenAccount(ctx context.Context, req *OpenAccountRequest) (*Account, error) {
|
||||
cust := getCustomer(ctx)
|
||||
if cust == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
switch req.Type {
|
||||
case Account_CHECKING, Account_SAVING, Account_MONEY_MARKET:
|
||||
if req.InitialDepositCents < 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "initial deposit amount cannot be negative: %s", dollars(req.InitialDepositCents))
|
||||
}
|
||||
case Account_LINE_OF_CREDIT, Account_LOAN, Account_EQUITIES:
|
||||
if req.InitialDepositCents != 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "initial deposit amount must be zero for account type %v: %s", req.Type, dollars(req.InitialDepositCents))
|
||||
}
|
||||
default:
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid account type: %v", req.Type)
|
||||
}
|
||||
|
||||
return s.allAccounts.openAccount(cust, req.Type, req.InitialDepositCents), nil
|
||||
}
|
||||
|
||||
func (s *bankServer) CloseAccount(ctx context.Context, req *CloseAccountRequest) (*empty.Empty, error) {
|
||||
cust := getCustomer(ctx)
|
||||
if cust == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
if err := s.allAccounts.closeAccount(cust, req.AccountNumber); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &empty.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *bankServer) GetAccounts(ctx context.Context, _ *empty.Empty) (*GetAccountsResponse, error) {
|
||||
cust := getCustomer(ctx)
|
||||
if cust == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
accounts := s.allAccounts.getAllAccounts(cust)
|
||||
return &GetAccountsResponse{Accounts: accounts}, nil
|
||||
}
|
||||
|
||||
func (s *bankServer) GetTransactions(req *GetTransactionsRequest, stream Bank_GetTransactionsServer) error {
|
||||
cust := getCustomer(stream.Context())
|
||||
if cust == "" {
|
||||
return status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
acct, err := s.allAccounts.getAccount(cust, req.AccountNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var start, end time.Time
|
||||
if req.Start != nil {
|
||||
start, err = ptypes.Timestamp(req.Start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if req.End != nil {
|
||||
end, err = ptypes.Timestamp(req.End)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
end = time.Date(9999, 12, 31, 23, 59, 59, 999999999, time.Local)
|
||||
}
|
||||
|
||||
txns := acct.getTransactions()
|
||||
for _, txn := range txns {
|
||||
t, err := ptypes.Timestamp(txn.Date)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (t.After(start) || t.Equal(start)) &&
|
||||
(t.Before(end) || t.Equal(end)) {
|
||||
|
||||
if err := stream.Send(txn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *bankServer) Deposit(ctx context.Context, req *DepositRequest) (*BalanceResponse, error) {
|
||||
cust := getCustomer(ctx)
|
||||
if cust == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
switch req.Source {
|
||||
case DepositRequest_ACH, DepositRequest_CASH, DepositRequest_CHECK, DepositRequest_WIRE:
|
||||
// ok
|
||||
default:
|
||||
return nil, status.Errorf(codes.InvalidArgument, "unknown deposit source: %v", req.Source)
|
||||
}
|
||||
|
||||
if req.AmountCents <= 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "deposit amount cannot be non-positive: %s", dollars(req.AmountCents))
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf("%v deposit", req.Source)
|
||||
if req.Desc != "" {
|
||||
desc = fmt.Sprintf("%s: %s", desc, req.Desc)
|
||||
}
|
||||
acct, err := s.allAccounts.getAccount(cust, req.AccountNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newBalance, err := acct.newTransaction(req.AmountCents, desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BalanceResponse{
|
||||
AccountNumber: req.AccountNumber,
|
||||
BalanceCents: newBalance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *bankServer) Withdraw(ctx context.Context, req *WithdrawRequest) (*BalanceResponse, error) {
|
||||
cust := getCustomer(ctx)
|
||||
if cust == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
if req.AmountCents >= 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "withdrawal amount cannot be non-negative: %s", dollars(req.AmountCents))
|
||||
}
|
||||
|
||||
acct, err := s.allAccounts.getAccount(cust, req.AccountNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newBalance, err := acct.newTransaction(req.AmountCents, req.Desc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BalanceResponse{
|
||||
AccountNumber: req.AccountNumber,
|
||||
BalanceCents: newBalance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *bankServer) Transfer(ctx context.Context, req *TransferRequest) (*TransferResponse, error) {
|
||||
cust := getCustomer(ctx)
|
||||
if cust == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
if req.AmountCents <= 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "transfer amount cannot be non-positive: %s", dollars(req.AmountCents))
|
||||
}
|
||||
|
||||
var srcAcct *account
|
||||
var srcDesc string
|
||||
switch src := req.Source.(type) {
|
||||
case *TransferRequest_ExternalSource:
|
||||
srcDesc = fmt.Sprintf("ACH %09d:%06d", src.ExternalSource.AchRoutingNumber, src.ExternalSource.AchAccountNumber)
|
||||
if src.ExternalSource.AchAccountNumber == 0 || src.ExternalSource.AchRoutingNumber == 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "external source routing and account numbers cannot be zero: %s", srcDesc)
|
||||
}
|
||||
case *TransferRequest_SourceAccountNumber:
|
||||
srcDesc = fmt.Sprintf("account %06d", src.SourceAccountNumber)
|
||||
var err error
|
||||
if srcAcct, err = s.allAccounts.getAccount(cust, src.SourceAccountNumber); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var destAcct *account
|
||||
var destDesc string
|
||||
switch dest := req.Dest.(type) {
|
||||
case *TransferRequest_ExternalDest:
|
||||
destDesc = fmt.Sprintf("ACH %09d:%06d", dest.ExternalDest.AchRoutingNumber, dest.ExternalDest.AchAccountNumber)
|
||||
if dest.ExternalDest.AchAccountNumber == 0 || dest.ExternalDest.AchRoutingNumber == 0 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "external source routing and account numbers cannot be zero: %s", destDesc)
|
||||
}
|
||||
case *TransferRequest_DestAccountNumber:
|
||||
destDesc = fmt.Sprintf("account %06d", dest.DestAccountNumber)
|
||||
var err error
|
||||
if destAcct, err = s.allAccounts.getAccount(cust, dest.DestAccountNumber); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var srcBalance int32
|
||||
if srcAcct != nil {
|
||||
desc := fmt.Sprintf("transfer to %s", destDesc)
|
||||
if req.Desc != "" {
|
||||
desc = fmt.Sprintf("%s: %s", desc, req.Desc)
|
||||
}
|
||||
var err error
|
||||
if srcBalance, err = srcAcct.newTransaction(-req.AmountCents, desc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var destBalance int32
|
||||
if destAcct != nil {
|
||||
desc := fmt.Sprintf("transfer from %s", srcDesc)
|
||||
if req.Desc != "" {
|
||||
desc = fmt.Sprintf("%s: %s", desc, req.Desc)
|
||||
}
|
||||
var err error
|
||||
if destBalance, err = destAcct.newTransaction(req.AmountCents, desc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &TransferResponse{
|
||||
SrcAccountNumber: req.GetSourceAccountNumber(),
|
||||
SrcBalanceCents: srcBalance,
|
||||
DestAccountNumber: req.GetDestAccountNumber(),
|
||||
DestBalanceCents: destBalance,
|
||||
}, nil
|
||||
}
|
||||
1307
internal/testing/cmd/bankdemo/bank.pb.go
Normal file
1307
internal/testing/cmd/bankdemo/bank.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
125
internal/testing/cmd/bankdemo/bank.proto
Normal file
125
internal/testing/cmd/bankdemo/bank.proto
Normal file
@@ -0,0 +1,125 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "main";
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// Bank provides operations for interacting with bank accounts. All
|
||||
// operations operate for the authenticated user (identified via an
|
||||
// "authorization" request header, where the type is "token" and the
|
||||
// credential is the customer's ID).
|
||||
service Bank {
|
||||
// OpenAccount creates an account with the type and given initial deposit
|
||||
// as its balance.
|
||||
rpc OpenAccount(OpenAccountRequest) returns (Account);
|
||||
// CloseAccount closes the indicated account. An account can only be
|
||||
// closed if its balance is zero.
|
||||
rpc CloseAccount(CloseAccountRequest) returns (google.protobuf.Empty);
|
||||
// GetAccounts lists all accounts for the current customer.
|
||||
rpc GetAccounts(google.protobuf.Empty) returns (GetAccountsResponse);
|
||||
// GetTransactions streams all transactions that match the given criteria.
|
||||
// If the given start date is not specified, transactions since beginning
|
||||
// of time are included. Similarly, if the given end date is not specified,
|
||||
// transactions all the way to the presnet are included.
|
||||
rpc GetTransactions(GetTransactionsRequest) returns (stream Transaction);
|
||||
// Deposit increases the balance of an account by depositing funds into it.
|
||||
rpc Deposit(DepositRequest) returns (BalanceResponse);
|
||||
// Withdraw decreases the balance of an account by withdrawing funds from it.
|
||||
rpc Withdraw(WithdrawRequest) returns (BalanceResponse);
|
||||
// Transfer moves money from one account to another. The source and destination
|
||||
// accounts can be with this bank (e.g. "local" account numbers) or can be
|
||||
// external accounts, identified by their ACH routing and account numbers.
|
||||
rpc Transfer(TransferRequest) returns (TransferResponse);
|
||||
}
|
||||
|
||||
message OpenAccountRequest {
|
||||
int32 initial_deposit_cents = 1;
|
||||
Account.Type type = 2;
|
||||
}
|
||||
|
||||
message CloseAccountRequest {
|
||||
uint64 account_number = 1;
|
||||
}
|
||||
|
||||
message GetAccountsResponse {
|
||||
repeated Account accounts = 1;
|
||||
}
|
||||
|
||||
message Account {
|
||||
uint64 account_number = 1;
|
||||
enum Type {
|
||||
UNKNOWN = 0;
|
||||
CHECKING = 1;
|
||||
SAVING = 2;
|
||||
MONEY_MARKET = 3;
|
||||
LINE_OF_CREDIT = 4;
|
||||
LOAN = 5;
|
||||
EQUITIES = 6;
|
||||
}
|
||||
Type type = 2;
|
||||
int32 balance_cents = 3;
|
||||
}
|
||||
|
||||
message GetTransactionsRequest {
|
||||
uint64 account_number = 1;
|
||||
google.protobuf.Timestamp start = 2;
|
||||
google.protobuf.Timestamp end = 3;
|
||||
}
|
||||
|
||||
message Transaction {
|
||||
uint64 account_number = 1;
|
||||
uint64 seq_number = 2;
|
||||
google.protobuf.Timestamp date = 3;
|
||||
int32 amount_cents = 4;
|
||||
string desc = 5;
|
||||
}
|
||||
|
||||
message DepositRequest {
|
||||
uint64 account_number = 1;
|
||||
int32 amount_cents = 2;
|
||||
enum Source {
|
||||
UNKNOWN = 0;
|
||||
CASH = 1;
|
||||
CHECK = 2;
|
||||
ACH = 3;
|
||||
WIRE = 4;
|
||||
}
|
||||
Source source = 3;
|
||||
string desc = 4;
|
||||
}
|
||||
|
||||
message BalanceResponse {
|
||||
uint64 account_number = 1;
|
||||
int32 balance_cents = 2;
|
||||
}
|
||||
|
||||
message WithdrawRequest {
|
||||
uint64 account_number = 1;
|
||||
int32 amount_cents = 2;
|
||||
string desc = 3;
|
||||
}
|
||||
|
||||
message TransferRequest {
|
||||
message ExternalAccount {
|
||||
uint64 ach_routing_number = 1;
|
||||
uint64 ach_account_number = 2;
|
||||
}
|
||||
oneof source {
|
||||
uint64 source_account_number = 1;
|
||||
ExternalAccount external_source = 2;
|
||||
}
|
||||
oneof dest {
|
||||
uint64 dest_account_number = 3;
|
||||
ExternalAccount external_dest = 4;
|
||||
}
|
||||
int32 amount_cents = 5;
|
||||
string desc = 6;
|
||||
}
|
||||
|
||||
message TransferResponse {
|
||||
uint64 src_account_number = 1;
|
||||
int32 src_balance_cents = 2;
|
||||
uint64 dest_account_number = 3;
|
||||
int32 dest_balance_cents = 4;
|
||||
}
|
||||
465
internal/testing/cmd/bankdemo/chat.go
Normal file
465
internal/testing/cmd/bankdemo/chat.go
Normal file
@@ -0,0 +1,465 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// chatServer implements the Support gRPC service, for providing
|
||||
// a capability to connect customers and support agents in real-time
|
||||
// chat.
|
||||
type chatServer struct {
|
||||
chatsBySession map[string]*session
|
||||
chatsAwaitingAgent []string
|
||||
lastSession int32
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type session struct {
|
||||
Session
|
||||
active bool
|
||||
cust *listener
|
||||
agents map[string]*listener
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
ch chan<- *ChatEntry
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (l *listener) send(e *ChatEntry) {
|
||||
select {
|
||||
case l.ch <- e:
|
||||
case <-l.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) copySession() *Session {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return &Session{
|
||||
SessionId: s.SessionId,
|
||||
CustomerName: s.Session.CustomerName,
|
||||
History: s.Session.History,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *chatServer) ChatCustomer(stream Support_ChatCustomerServer) error {
|
||||
ctx, cancel := context.WithCancel(stream.Context())
|
||||
defer cancel()
|
||||
|
||||
cust := getCustomer(ctx)
|
||||
if cust == "" {
|
||||
return status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
var sess *session
|
||||
var ch chan *ChatEntry
|
||||
var chCancel context.CancelFunc
|
||||
cleanup := func() {
|
||||
if sess != nil {
|
||||
sess.mu.Lock()
|
||||
sess.cust = nil
|
||||
sess.mu.Unlock()
|
||||
chCancel()
|
||||
close(ch)
|
||||
go func() {
|
||||
// drain channel to prevent deadlock
|
||||
for range ch {
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
defer cleanup()
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch req := req.Req.(type) {
|
||||
case *ChatCustomerRequest_Init:
|
||||
if sess != nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "already called init, currently in chat session %q", sess.SessionId)
|
||||
}
|
||||
sessionID := req.Init.ResumeSessionId
|
||||
if sessionID == "" {
|
||||
sess, ch, chCancel = s.newSession(ctx, cust)
|
||||
} else if sess, ch, chCancel = s.resumeSession(ctx, cust, sessionID); sess == nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "cannot resume session %q; it is not an open session", sessionID)
|
||||
}
|
||||
err := stream.Send(&ChatCustomerResponse{
|
||||
Resp: &ChatCustomerResponse_Session{
|
||||
Session: sess.copySession(),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// monitor the returned channel, sending incoming agent messages down the pipe
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case entry, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if e, ok := entry.Entry.(*ChatEntry_AgentMsg); ok {
|
||||
stream.Send(&ChatCustomerResponse{
|
||||
Resp: &ChatCustomerResponse_Msg{
|
||||
Msg: e.AgentMsg,
|
||||
},
|
||||
})
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
case *ChatCustomerRequest_Msg:
|
||||
if sess == nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "never called init, no chat session for message")
|
||||
}
|
||||
|
||||
entry := &ChatEntry{
|
||||
Date: ptypes.TimestampNow(),
|
||||
Entry: &ChatEntry_CustomerMsg{
|
||||
CustomerMsg: req.Msg,
|
||||
},
|
||||
}
|
||||
func() {
|
||||
sess.mu.Lock()
|
||||
sess.Session.History = append(sess.Session.History, entry)
|
||||
sess.mu.Unlock()
|
||||
|
||||
sess.mu.RLock()
|
||||
defer sess.mu.RUnlock()
|
||||
for _, l := range sess.agents {
|
||||
l.send(entry)
|
||||
}
|
||||
}()
|
||||
|
||||
case *ChatCustomerRequest_HangUp:
|
||||
if sess == nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "never called init, no chat session to hang up")
|
||||
}
|
||||
s.closeSession(sess)
|
||||
cleanup()
|
||||
sess = nil
|
||||
|
||||
default:
|
||||
return status.Error(codes.InvalidArgument, "unknown request type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *chatServer) ChatAgent(stream Support_ChatAgentServer) error {
|
||||
ctx, cancel := context.WithCancel(stream.Context())
|
||||
defer cancel()
|
||||
|
||||
agent := getAgent(ctx)
|
||||
if agent == "" {
|
||||
return status.Error(codes.Unauthenticated, codes.Unauthenticated.String())
|
||||
}
|
||||
|
||||
var sess *session
|
||||
var ch chan *ChatEntry
|
||||
var chCancel context.CancelFunc
|
||||
cleanup := func() {
|
||||
if sess != nil {
|
||||
sess.mu.Lock()
|
||||
delete(sess.agents, agent)
|
||||
if len(sess.agents) == 0 {
|
||||
s.mu.Lock()
|
||||
s.chatsAwaitingAgent = append(s.chatsAwaitingAgent, sess.SessionId)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
sess.mu.Unlock()
|
||||
chCancel()
|
||||
close(ch)
|
||||
go func() {
|
||||
// drain channel to prevent deadlock
|
||||
for range ch {
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
checkSession := func() {
|
||||
// see if session was concurrently closed
|
||||
if sess != nil {
|
||||
sess.mu.RLock()
|
||||
active := sess.active
|
||||
sess.mu.RUnlock()
|
||||
if !active {
|
||||
cleanup()
|
||||
sess = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
checkSession()
|
||||
|
||||
switch req := req.Req.(type) {
|
||||
case *ChatAgentRequest_Accept:
|
||||
if sess != nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "already called accept, currently in chat session %q", sess.SessionId)
|
||||
}
|
||||
sess, ch, chCancel = s.acceptSession(ctx, agent, req.Accept.SessionId)
|
||||
if sess == nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "no session to accept")
|
||||
}
|
||||
err := stream.Send(&ChatAgentResponse{
|
||||
Resp: &ChatAgentResponse_AcceptedSession{
|
||||
AcceptedSession: sess.copySession(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// monitor the returned channel, sending incoming agent messages down the pipe
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case entry, ok := <-ch:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
stream.Send(&ChatAgentResponse{
|
||||
Resp: &ChatAgentResponse_SessionEnded{
|
||||
SessionEnded: Void_VOID,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if agentMsg, ok := entry.Entry.(*ChatEntry_AgentMsg); ok {
|
||||
if agentMsg.AgentMsg.AgentName == agent {
|
||||
continue
|
||||
}
|
||||
}
|
||||
stream.Send(&ChatAgentResponse{
|
||||
Resp: &ChatAgentResponse_Msg{
|
||||
Msg: entry,
|
||||
},
|
||||
})
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
case *ChatAgentRequest_Msg:
|
||||
if sess == nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "never called accept, no chat session for message")
|
||||
}
|
||||
|
||||
entry := &ChatEntry{
|
||||
Date: ptypes.TimestampNow(),
|
||||
Entry: &ChatEntry_AgentMsg{
|
||||
AgentMsg: &AgentMessage{
|
||||
AgentName: agent,
|
||||
Msg: req.Msg,
|
||||
},
|
||||
},
|
||||
}
|
||||
active := true
|
||||
func() {
|
||||
sess.mu.Lock()
|
||||
active = sess.active
|
||||
if active {
|
||||
sess.Session.History = append(sess.Session.History, entry)
|
||||
}
|
||||
sess.mu.Unlock()
|
||||
|
||||
if !active {
|
||||
return
|
||||
}
|
||||
|
||||
sess.mu.RLock()
|
||||
defer sess.mu.RUnlock()
|
||||
if sess.cust != nil {
|
||||
sess.cust.send(entry)
|
||||
}
|
||||
for otherAgent, l := range sess.agents {
|
||||
if otherAgent == agent {
|
||||
continue
|
||||
}
|
||||
l.send(entry)
|
||||
}
|
||||
}()
|
||||
if !active {
|
||||
return status.Errorf(codes.FailedPrecondition, "customer hung up on chat session %s", sess.SessionId)
|
||||
}
|
||||
|
||||
case *ChatAgentRequest_LeaveSession:
|
||||
if sess == nil {
|
||||
return status.Errorf(codes.FailedPrecondition, "never called init, no chat session to hang up")
|
||||
}
|
||||
s.closeSession(sess)
|
||||
cleanup()
|
||||
sess = nil
|
||||
|
||||
default:
|
||||
return status.Error(codes.InvalidArgument, "unknown request type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *chatServer) newSession(ctx context.Context, cust string) (*session, chan *ChatEntry, context.CancelFunc) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.lastSession++
|
||||
id := fmt.Sprintf("%06d", s.lastSession)
|
||||
s.chatsAwaitingAgent = append(s.chatsAwaitingAgent, id)
|
||||
|
||||
ch := make(chan *ChatEntry, 1)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
l := &listener{
|
||||
ch: ch,
|
||||
ctx: ctx,
|
||||
}
|
||||
sess := session{
|
||||
active: true,
|
||||
Session: Session{
|
||||
SessionId: id,
|
||||
CustomerName: cust,
|
||||
},
|
||||
cust: l,
|
||||
}
|
||||
s.chatsBySession[id] = &sess
|
||||
|
||||
return &sess, ch, cancel
|
||||
}
|
||||
|
||||
func (s *chatServer) resumeSession(ctx context.Context, cust, sessionID string) (*session, chan *ChatEntry, context.CancelFunc) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
sess := s.chatsBySession[sessionID]
|
||||
if sess.CustomerName != cust {
|
||||
// customer cannot join chat that they did not start
|
||||
return nil, nil, nil
|
||||
}
|
||||
if !sess.active {
|
||||
// chat has been closed
|
||||
return nil, nil, nil
|
||||
}
|
||||
if sess.cust != nil {
|
||||
// customer is active in the chat in another stream!
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
ch := make(chan *ChatEntry, 1)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
l := &listener{
|
||||
ch: ch,
|
||||
ctx: ctx,
|
||||
}
|
||||
sess.cust = l
|
||||
return sess, ch, cancel
|
||||
}
|
||||
|
||||
func (s *chatServer) closeSession(sess *session) {
|
||||
active := true
|
||||
func() {
|
||||
sess.mu.Lock()
|
||||
active = sess.active
|
||||
sess.active = false
|
||||
sess.mu.Unlock()
|
||||
|
||||
if !active {
|
||||
// already closed
|
||||
return
|
||||
}
|
||||
|
||||
sess.mu.RLock()
|
||||
defer sess.mu.RUnlock()
|
||||
for _, l := range sess.agents {
|
||||
l.send(nil)
|
||||
}
|
||||
}()
|
||||
|
||||
if !active {
|
||||
// already closed
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
delete(s.chatsBySession, sess.SessionId)
|
||||
for i, id := range s.chatsAwaitingAgent {
|
||||
if id == sess.SessionId {
|
||||
s.chatsAwaitingAgent = append(s.chatsAwaitingAgent[:i], s.chatsAwaitingAgent[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *chatServer) acceptSession(ctx context.Context, agent, sessionID string) (*session, chan *ChatEntry, context.CancelFunc) {
|
||||
var sess *session
|
||||
func() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if len(s.chatsAwaitingAgent) == 0 {
|
||||
return
|
||||
}
|
||||
if sessionID == "" {
|
||||
sessionID = s.chatsAwaitingAgent[0]
|
||||
s.chatsAwaitingAgent = s.chatsAwaitingAgent[1:]
|
||||
} else {
|
||||
found := false
|
||||
for i, id := range s.chatsAwaitingAgent {
|
||||
if id == sessionID {
|
||||
found = true
|
||||
s.chatsAwaitingAgent = append(s.chatsAwaitingAgent[:i], s.chatsAwaitingAgent[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
}
|
||||
sess = s.chatsBySession[sessionID]
|
||||
}()
|
||||
if sess == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
ch := make(chan *ChatEntry, 1)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
l := &listener{
|
||||
ch: ch,
|
||||
ctx: ctx,
|
||||
}
|
||||
sess.mu.Lock()
|
||||
if sess.agents == nil {
|
||||
sess.agents = map[string]*listener{}
|
||||
}
|
||||
sess.agents[agent] = l
|
||||
sess.mu.Unlock()
|
||||
return sess, ch, cancel
|
||||
}
|
||||
190
internal/testing/cmd/bankdemo/db.go
Normal file
190
internal/testing/cmd/bankdemo/db.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// In-memory database that is periodically saved to a JSON file.
|
||||
|
||||
type accounts struct {
|
||||
AccountNumbersByCustomer map[string][]uint64
|
||||
AccountsByNumber map[uint64]*account
|
||||
AccountNumbers []uint64
|
||||
Customers []string
|
||||
LastAccountNum uint64
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *accounts) openAccount(customer string, accountType Account_Type, initialBalanceCents int32) *Account {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
accountNums, ok := a.AccountNumbersByCustomer[customer]
|
||||
if !ok {
|
||||
// no accounts for this customer? it's a new customer
|
||||
a.Customers = append(a.Customers, customer)
|
||||
}
|
||||
num := a.LastAccountNum + 1
|
||||
a.LastAccountNum = num
|
||||
a.AccountNumbers = append(a.AccountNumbers, num)
|
||||
accountNums = append(accountNums, num)
|
||||
a.AccountNumbersByCustomer[customer] = accountNums
|
||||
var acct account
|
||||
acct.AccountNumber = num
|
||||
acct.Type = accountType
|
||||
acct.BalanceCents = initialBalanceCents
|
||||
acct.Transactions = append(acct.Transactions, &Transaction{
|
||||
AccountNumber: num,
|
||||
SeqNumber: 1,
|
||||
Date: ptypes.TimestampNow(),
|
||||
AmountCents: initialBalanceCents,
|
||||
Desc: "initial deposit",
|
||||
})
|
||||
a.AccountsByNumber[num] = &acct
|
||||
return &acct.Account
|
||||
}
|
||||
|
||||
func (a *accounts) closeAccount(customer string, accountNumber uint64) error {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
acctNums := a.AccountNumbersByCustomer[customer]
|
||||
found := -1
|
||||
for i, num := range acctNums {
|
||||
if num == accountNumber {
|
||||
found = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if found == -1 {
|
||||
return status.Errorf(codes.NotFound, "you have no account numbered %d", accountNumber)
|
||||
}
|
||||
|
||||
acct := a.AccountsByNumber[accountNumber]
|
||||
if acct.BalanceCents != 0 {
|
||||
return status.Errorf(codes.FailedPrecondition, "account %d cannot be closed because it has a non-zero balance: %s", accountNumber, dollars(acct.BalanceCents))
|
||||
}
|
||||
|
||||
for i, num := range a.AccountNumbers {
|
||||
if num == accountNumber {
|
||||
a.AccountNumbers = append(a.AccountNumbers[:i], a.AccountNumbers[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
a.AccountNumbersByCustomer[customer] = append(acctNums[:found], acctNums[found+1:]...)
|
||||
delete(a.AccountsByNumber, accountNumber)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *accounts) getAccount(customer string, accountNumber uint64) (*account, error) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
acctNums := a.AccountNumbersByCustomer[customer]
|
||||
for _, num := range acctNums {
|
||||
if num == accountNumber {
|
||||
return a.AccountsByNumber[num], nil
|
||||
}
|
||||
}
|
||||
return nil, status.Errorf(codes.NotFound, "you have no account numbered %d", accountNumber)
|
||||
}
|
||||
|
||||
func (a *accounts) getAllAccounts(customer string) []*Account {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
accountNums := a.AccountNumbersByCustomer[customer]
|
||||
var accounts []*Account
|
||||
for _, num := range accountNums {
|
||||
accounts = append(accounts, &a.AccountsByNumber[num].Account)
|
||||
}
|
||||
return accounts
|
||||
}
|
||||
|
||||
type account struct {
|
||||
Account
|
||||
Transactions []*Transaction
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func (a *account) getTransactions() []*Transaction {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.Transactions
|
||||
}
|
||||
|
||||
func (a *account) newTransaction(amountCents int32, desc string) (newBalance int32, err error) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
bal := a.BalanceCents + amountCents
|
||||
if bal < 0 {
|
||||
return 0, status.Errorf(codes.FailedPrecondition, "insufficient funds: cannot withdraw %s when balance is %s", dollars(amountCents), dollars(a.BalanceCents))
|
||||
}
|
||||
a.BalanceCents = bal
|
||||
a.Transactions = append(a.Transactions, &Transaction{
|
||||
AccountNumber: a.AccountNumber,
|
||||
Date: ptypes.TimestampNow(),
|
||||
AmountCents: amountCents,
|
||||
SeqNumber: uint64(len(a.Transactions) + 1),
|
||||
Desc: desc,
|
||||
})
|
||||
return bal, nil
|
||||
}
|
||||
|
||||
func (t *Transaction) MarshalJSON() ([]byte, error) {
|
||||
var jsm jsonpb.Marshaler
|
||||
var buf bytes.Buffer
|
||||
if err := jsm.Marshal(&buf, t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (t *Transaction) UnmarshalJSON(b []byte) error {
|
||||
return jsonpb.Unmarshal(bytes.NewReader(b), t)
|
||||
}
|
||||
|
||||
func (a *accounts) clone() *accounts {
|
||||
var clone accounts
|
||||
clone.AccountNumbersByCustomer = map[string][]uint64{}
|
||||
clone.AccountsByNumber = map[uint64]*account{}
|
||||
|
||||
a.mu.RLock()
|
||||
clone.Customers = a.Customers
|
||||
a.mu.RUnlock()
|
||||
|
||||
for _, cust := range clone.Customers {
|
||||
var acctNums []uint64
|
||||
a.mu.RLock()
|
||||
acctNums = a.AccountNumbersByCustomer[cust]
|
||||
a.mu.RUnlock()
|
||||
|
||||
clone.AccountNumbersByCustomer[cust] = acctNums
|
||||
clone.AccountNumbers = append(clone.AccountNumbers, acctNums...)
|
||||
|
||||
for _, acctNum := range acctNums {
|
||||
a.mu.RLock()
|
||||
acct := a.AccountsByNumber[acctNum]
|
||||
a.mu.RUnlock()
|
||||
|
||||
acct.mu.RLock()
|
||||
txns := acct.Transactions
|
||||
acct.mu.RUnlock()
|
||||
|
||||
clone.AccountsByNumber[acctNum] = &account{Transactions: txns}
|
||||
}
|
||||
}
|
||||
|
||||
return &clone
|
||||
}
|
||||
|
||||
func dollars(amountCents int32) string {
|
||||
return fmt.Sprintf("$%02f", float64(amountCents)/100)
|
||||
}
|
||||
168
internal/testing/cmd/bankdemo/main.go
Normal file
168
internal/testing/cmd/bankdemo/main.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
//go:generate protoc --go_out=plugins=grpc:./ bank.proto support.proto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func main() {
|
||||
grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stdout, os.Stdout, os.Stderr))
|
||||
|
||||
port := flag.Int("port", 12345, "The port on which bankdemo gRPC server will listen.")
|
||||
datafile := flag.String("datafile", "accounts.json", "The path and filename to which bank account data is saved and from which data will be loaded.")
|
||||
flag.Parse()
|
||||
|
||||
// create the server and load initial dataset
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s := &svr{
|
||||
datafile: *datafile,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
if err := s.load(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", *port))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
grpcSvr := gRPCServer()
|
||||
|
||||
// Register gRPC service implementations
|
||||
bankSvc := bankServer{
|
||||
allAccounts: &s.allAccounts,
|
||||
}
|
||||
RegisterBankServer(grpcSvr, &bankSvc)
|
||||
|
||||
chatSvc := chatServer{
|
||||
chatsBySession: map[string]*session{},
|
||||
}
|
||||
RegisterSupportServer(grpcSvr, &chatSvc)
|
||||
|
||||
go s.bgSaver()
|
||||
|
||||
// don't forget to include server reflection support!
|
||||
reflection.Register(grpcSvr)
|
||||
|
||||
defer func() {
|
||||
cancel()
|
||||
s.flush()
|
||||
}()
|
||||
|
||||
// trap SIGINT / SIGTERM to exit cleanly
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGINT)
|
||||
signal.Notify(c, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-c
|
||||
fmt.Println("Shutting down...")
|
||||
grpcSvr.GracefulStop()
|
||||
}()
|
||||
|
||||
grpclog.Infof("server starting, listening on %v", l.Addr())
|
||||
if err := grpcSvr.Serve(l); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func gRPCServer() *grpc.Server {
|
||||
var reqCounter uint64
|
||||
return grpc.NewServer(
|
||||
grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
||||
reqID := atomic.AddUint64(&reqCounter, 1)
|
||||
var client string
|
||||
if p, ok := peer.FromContext(ctx); ok {
|
||||
client = p.Addr.String()
|
||||
} else {
|
||||
client = "?"
|
||||
}
|
||||
grpclog.Infof("request %d started for %s from %s", reqID, info.FullMethod, client)
|
||||
|
||||
rsp, err := handler(ctx, req)
|
||||
|
||||
stat, _ := status.FromError(err)
|
||||
grpclog.Infof("request %d completed for %s from %s: %v %s", reqID, info.FullMethod, client, stat.Code(), stat.Message())
|
||||
return rsp, err
|
||||
|
||||
}),
|
||||
grpc.StreamInterceptor(func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
reqID := atomic.AddUint64(&reqCounter, 1)
|
||||
var client string
|
||||
if p, ok := peer.FromContext(ss.Context()); ok {
|
||||
client = p.Addr.String()
|
||||
} else {
|
||||
client = "?"
|
||||
}
|
||||
grpclog.Infof("request %d started for %s from %s", reqID, info.FullMethod, client)
|
||||
|
||||
err := handler(srv, ss)
|
||||
|
||||
stat, _ := status.FromError(err)
|
||||
grpclog.Infof("request %d completed for %s from %s: %v %s", reqID, info.FullMethod, client, stat.Code(), stat.Message())
|
||||
return err
|
||||
}))
|
||||
}
|
||||
|
||||
type svr struct {
|
||||
datafile string
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
allAccounts accounts
|
||||
}
|
||||
|
||||
func (s *svr) load() error {
|
||||
accts, err := ioutil.ReadFile(s.datafile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if len(accts) == 0 {
|
||||
s.allAccounts.AccountNumbersByCustomer = map[string][]uint64{}
|
||||
s.allAccounts.AccountsByNumber = map[uint64]*account{}
|
||||
} else if err := json.Unmarshal(accts, &s.allAccounts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *svr) bgSaver() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.flush()
|
||||
case <-s.ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *svr) flush() {
|
||||
accounts := s.allAccounts.clone()
|
||||
|
||||
if b, err := json.Marshal(accounts); err != nil {
|
||||
grpclog.Errorf("failed to save data to %q", s.datafile)
|
||||
} else if err := ioutil.WriteFile(s.datafile, b, 0666); err != nil {
|
||||
grpclog.Errorf("failed to save data to %q", s.datafile)
|
||||
}
|
||||
}
|
||||
1250
internal/testing/cmd/bankdemo/support.pb.go
Normal file
1250
internal/testing/cmd/bankdemo/support.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
118
internal/testing/cmd/bankdemo/support.proto
Normal file
118
internal/testing/cmd/bankdemo/support.proto
Normal file
@@ -0,0 +1,118 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "main";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// Support provides an interactive chat service, for customers to interact with
|
||||
// the bank's support agents. A single stream, for either of the two methods, is
|
||||
// a stateful connection to a single "chat session". Streams are initially disconnected
|
||||
// (not part of any session). A stream must be disconnected from a session (via customer
|
||||
// hang up or via agent leaving a session) before it can be connected to a new one.
|
||||
service Support {
|
||||
// ChatCustomer is used by a customer-facing app to send the customer's messages
|
||||
// to a chat session. The customer is how initiates and terminates (via "hangup")
|
||||
// a chat session. Only customers may invoke this method (e.g. requests must
|
||||
// include customer auth credentials).
|
||||
rpc ChatCustomer(stream ChatCustomerRequest) returns (stream ChatCustomerResponse);
|
||||
// ChatAgent is used by an agent-facing app to allow an agent to reply to a
|
||||
// customer's messages in a chat session. The agent may accept a chat session,
|
||||
// which defaults to the session awaiting an agent for the longest period of time
|
||||
// (FIFO queue).
|
||||
rpc ChatAgent(stream ChatAgentRequest) returns (stream ChatAgentResponse);
|
||||
}
|
||||
|
||||
enum Void {
|
||||
VOID = 0;
|
||||
}
|
||||
|
||||
message ChatCustomerRequest {
|
||||
oneof req {
|
||||
// init is used when a chat stream is not part of a
|
||||
// chat session. This is a stream's initial state, as well as
|
||||
// the state after a "hang_up" request is sent. This creates
|
||||
// a new state session or resumes an existing one.
|
||||
InitiateChat init = 1;
|
||||
// msg is used to send the customer's messages to support
|
||||
// agents.
|
||||
string msg = 2;
|
||||
// hang_up is used to terminate a chat session. If a stream
|
||||
// is broken, but the session was not terminated, the client
|
||||
// may initiate a new stream and use init to resume that
|
||||
// session. Sessions are not terminated unless done so
|
||||
// explicitly via sending this kind of request on the stream.
|
||||
Void hang_up = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message InitiateChat {
|
||||
string resume_session_id = 1;
|
||||
}
|
||||
|
||||
message AgentMessage {
|
||||
string agent_name = 1;
|
||||
string msg = 2;
|
||||
}
|
||||
|
||||
message ChatCustomerResponse {
|
||||
oneof resp {
|
||||
// session is sent from the server when the stream is connected
|
||||
// to a chat session. This happens after an init request is sent
|
||||
// and the stream is connected to either a new or resumed session.
|
||||
Session session = 1;
|
||||
// msg is sent from the server to convey agents' messages to the
|
||||
// customer.
|
||||
AgentMessage msg = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ChatAgentRequest {
|
||||
oneof req {
|
||||
// accept is used when an agent wants to join a customer chat
|
||||
// session. It can be used to connect to a specific session (by
|
||||
// ID), or to just accept the session for which the customer has
|
||||
// been waiting the longest (e.g. poll a FIFO queue of sessions
|
||||
// awaiting a support agent). It is possible for multiple agents
|
||||
// to be connected to the same chat session.
|
||||
AcceptChat accept = 1;
|
||||
// msg is used to send a message to the customer. It will also be
|
||||
// delivered to any other connected support agents.
|
||||
string msg = 2;
|
||||
// leave_session allows an agent to exit a chat session. They can
|
||||
// always re-enter later by sending an accept message for that
|
||||
// session ID.
|
||||
Void leave_session = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message AcceptChat {
|
||||
string session_id = 1;
|
||||
}
|
||||
|
||||
message ChatEntry {
|
||||
google.protobuf.Timestamp date = 1;
|
||||
oneof entry {
|
||||
string customer_msg = 2;
|
||||
AgentMessage agent_msg = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message ChatAgentResponse {
|
||||
oneof resp {
|
||||
// accepted_session provides the detail of a chat session. The server
|
||||
// sends this message after the agent has accepted a chat session.
|
||||
Session accepted_session = 1;
|
||||
// msg is sent by the server when the customer, or another support
|
||||
// agent, sends a message in stream's current session.
|
||||
ChatEntry msg = 2;
|
||||
// session_ended notifies the support agent that their currently
|
||||
// connected chat session has been terminated by the customer.
|
||||
Void session_ended = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message Session {
|
||||
string session_id = 1;
|
||||
string customer_name = 2;
|
||||
repeated ChatEntry history = 3;
|
||||
}
|
||||
6
internal/testing/cmd/testserver/README.md
Normal file
6
internal/testing/cmd/testserver/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# testserver
|
||||
|
||||
The `testserver` program is a simple server that can be used for testing RPC clients such
|
||||
as `grpcurl`. It implements an RPC interface that is defined in `grpcurl`'s [testing package](https://github.com/fullstorydev/grpcurl/blob/master/testing/example.proto) and also exposes [the implementation](https://godoc.org/github.com/fullstorydev/grpcurl/testing#TestServer) that is defined in that same package. This is the same test interface and implementation that is used in unit tests for `grpcurl`.
|
||||
|
||||
For a possibly more interesting test server, take a look at `bankdemo`, which is a demo gRPC app that provides a more concrete RPC interface, including full-duplex bidirectional streaming methods, plus an example implementation.
|
||||
176
internal/testing/cmd/testserver/testserver.go
Normal file
176
internal/testing/cmd/testserver/testserver.go
Normal file
@@ -0,0 +1,176 @@
|
||||
// Command testserver spins up a test GRPC server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/reflection"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/fullstorydev/grpcurl"
|
||||
grpcurl_testing "github.com/fullstorydev/grpcurl/internal/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
getUnixSocket func() string // nil when run on non-unix platforms
|
||||
|
||||
help = flag.Bool("help", false, "Print usage instructions and exit.")
|
||||
cacert = flag.String("cacert", "",
|
||||
`File containing trusted root certificates for verifying client certs. Ignored
|
||||
if TLS is not in use (e.g. no -cert or -key specified).`)
|
||||
cert = flag.String("cert", "",
|
||||
`File containing server certificate (public key). Must also provide -key option.
|
||||
Server uses plain-text if no -cert and -key options are given.`)
|
||||
key = flag.String("key", "",
|
||||
`File containing server private key. Must also provide -cert option. Server uses
|
||||
plain-text if no -cert and -key options are given.`)
|
||||
requirecert = flag.Bool("requirecert", false,
|
||||
`Require clients to authenticate via client certs. Must be using TLS (e.g. must
|
||||
also provide -cert and -key options).`)
|
||||
port = flag.Int("p", 0, "Port on which to listen. Ephemeral port used if not specified.")
|
||||
noreflect = flag.Bool("noreflect", false, "Indicates that server should not support server reflection.")
|
||||
quiet = flag.Bool("q", false, "Suppresses server request and stream logging.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *help {
|
||||
flag.PrintDefaults()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stdout, os.Stdout, os.Stderr))
|
||||
|
||||
if len(flag.Args()) > 0 {
|
||||
fmt.Fprintln(os.Stderr, "No arguments expected.")
|
||||
os.Exit(2)
|
||||
}
|
||||
if (*cert == "") != (*key == "") {
|
||||
fmt.Fprintln(os.Stderr, "The -cert and -key arguments must be used together and both be present.")
|
||||
os.Exit(2)
|
||||
}
|
||||
if *requirecert && *cert == "" {
|
||||
fmt.Fprintln(os.Stderr, "The -requirecert arg cannot be used without -cert and -key arguments.")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
var opts []grpc.ServerOption
|
||||
if *cert != "" {
|
||||
creds, err := grpcurl.ServerTransportCredentials(*cacert, *cert, *key, *requirecert)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to configure transport credentials: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
opts = []grpc.ServerOption{grpc.Creds(creds)}
|
||||
}
|
||||
if !*quiet {
|
||||
opts = append(opts, grpc.UnaryInterceptor(unaryLogger), grpc.StreamInterceptor(streamLogger))
|
||||
}
|
||||
|
||||
var network, addr string
|
||||
if getUnixSocket != nil && getUnixSocket() != "" {
|
||||
network = "unix"
|
||||
addr = getUnixSocket()
|
||||
} else {
|
||||
network = "tcp"
|
||||
addr = fmt.Sprintf("127.0.0.1:%d", *port)
|
||||
}
|
||||
l, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to listen on socket: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Listening on %v\n", l.Addr())
|
||||
|
||||
svr := grpc.NewServer(opts...)
|
||||
|
||||
grpcurl_testing.RegisterTestServiceServer(svr, grpcurl_testing.TestServer{})
|
||||
if !*noreflect {
|
||||
reflection.Register(svr)
|
||||
}
|
||||
|
||||
if err := svr.Serve(l); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "GRPC server returned error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var id int32
|
||||
|
||||
func unaryLogger(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
i := atomic.AddInt32(&id, 1) - 1
|
||||
grpclog.Infof("start <%d>: %s\n", i, info.FullMethod)
|
||||
start := time.Now()
|
||||
rsp, err := handler(ctx, req)
|
||||
var code codes.Code
|
||||
if stat, ok := status.FromError(err); ok {
|
||||
code = stat.Code()
|
||||
} else {
|
||||
code = codes.Unknown
|
||||
}
|
||||
grpclog.Infof("completed <%d>: %v (%d) %v\n", i, code, code, time.Since(start))
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func streamLogger(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
i := atomic.AddInt32(&id, 1) - 1
|
||||
start := time.Now()
|
||||
grpclog.Infof("start <%d>: %s\n", i, info.FullMethod)
|
||||
err := handler(srv, loggingStream{ss: ss, id: i})
|
||||
var code codes.Code
|
||||
if stat, ok := status.FromError(err); ok {
|
||||
code = stat.Code()
|
||||
} else {
|
||||
code = codes.Unknown
|
||||
}
|
||||
grpclog.Infof("completed <%d>: %v(%d) %v\n", i, code, code, time.Since(start))
|
||||
return err
|
||||
}
|
||||
|
||||
type loggingStream struct {
|
||||
ss grpc.ServerStream
|
||||
id int32
|
||||
}
|
||||
|
||||
func (l loggingStream) SetHeader(md metadata.MD) error {
|
||||
return l.ss.SetHeader(md)
|
||||
}
|
||||
|
||||
func (l loggingStream) SendHeader(md metadata.MD) error {
|
||||
return l.ss.SendHeader(md)
|
||||
}
|
||||
|
||||
func (l loggingStream) SetTrailer(md metadata.MD) {
|
||||
l.ss.SetTrailer(md)
|
||||
}
|
||||
|
||||
func (l loggingStream) Context() context.Context {
|
||||
return l.ss.Context()
|
||||
}
|
||||
|
||||
func (l loggingStream) SendMsg(m interface{}) error {
|
||||
err := l.ss.SendMsg(m)
|
||||
if err == nil {
|
||||
grpclog.Infof("stream <%d>: sent message\n", l.id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (l loggingStream) RecvMsg(m interface{}) error {
|
||||
err := l.ss.RecvMsg(m)
|
||||
if err == nil {
|
||||
grpclog.Infof("stream <%d>: received message\n", l.id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
17
internal/testing/cmd/testserver/unix.go
Normal file
17
internal/testing/cmd/testserver/unix.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package main
|
||||
|
||||
import "flag"
|
||||
|
||||
var (
|
||||
unix = flag.String("unix", "",
|
||||
`Use instead of -p to indicate listening on a Unix domain socket instead of a
|
||||
TCP port. If present, must be the path to a domain socket.`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
getUnixSocket = func() string {
|
||||
return *unix
|
||||
}
|
||||
}
|
||||
21
internal/testing/example.proto
Normal file
21
internal/testing/example.proto
Normal file
@@ -0,0 +1,21 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
import "google/protobuf/empty.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "example2.proto";
|
||||
|
||||
message TestRequest {
|
||||
repeated string file_names = 1;
|
||||
repeated Extension extensions = 2;
|
||||
}
|
||||
|
||||
message TestResponse {
|
||||
map<string, google.protobuf.FileDescriptorProto> file_protos = 1;
|
||||
google.protobuf.Timestamp last_update_date = 2;
|
||||
}
|
||||
|
||||
service TestService {
|
||||
rpc GetFiles (TestRequest) returns (TestResponse);
|
||||
rpc Ping (google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
}
|
||||
BIN
internal/testing/example.protoset
Normal file
BIN
internal/testing/example.protoset
Normal file
Binary file not shown.
8
internal/testing/example2.proto
Normal file
8
internal/testing/example2.proto
Normal file
@@ -0,0 +1,8 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
|
||||
message Extension {
|
||||
uint64 id = 1;
|
||||
google.protobuf.Any data = 2;
|
||||
}
|
||||
215
internal/testing/jsonpb_test_proto/test_objects.pb.go
Normal file
215
internal/testing/jsonpb_test_proto/test_objects.pb.go
Normal file
@@ -0,0 +1,215 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: testing/jsonpb_test_proto/test_objects.proto
|
||||
|
||||
package jsonpb
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
any "github.com/golang/protobuf/ptypes/any"
|
||||
duration "github.com/golang/protobuf/ptypes/duration"
|
||||
_struct "github.com/golang/protobuf/ptypes/struct"
|
||||
timestamp "github.com/golang/protobuf/ptypes/timestamp"
|
||||
wrappers "github.com/golang/protobuf/ptypes/wrappers"
|
||||
math "math"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type KnownTypes struct {
|
||||
An *any.Any `protobuf:"bytes,14,opt,name=an" json:"an,omitempty"`
|
||||
Dur *duration.Duration `protobuf:"bytes,1,opt,name=dur" json:"dur,omitempty"`
|
||||
St *_struct.Struct `protobuf:"bytes,12,opt,name=st" json:"st,omitempty"`
|
||||
Ts *timestamp.Timestamp `protobuf:"bytes,2,opt,name=ts" json:"ts,omitempty"`
|
||||
Lv *_struct.ListValue `protobuf:"bytes,15,opt,name=lv" json:"lv,omitempty"`
|
||||
Val *_struct.Value `protobuf:"bytes,16,opt,name=val" json:"val,omitempty"`
|
||||
Dbl *wrappers.DoubleValue `protobuf:"bytes,3,opt,name=dbl" json:"dbl,omitempty"`
|
||||
Flt *wrappers.FloatValue `protobuf:"bytes,4,opt,name=flt" json:"flt,omitempty"`
|
||||
I64 *wrappers.Int64Value `protobuf:"bytes,5,opt,name=i64" json:"i64,omitempty"`
|
||||
U64 *wrappers.UInt64Value `protobuf:"bytes,6,opt,name=u64" json:"u64,omitempty"`
|
||||
I32 *wrappers.Int32Value `protobuf:"bytes,7,opt,name=i32" json:"i32,omitempty"`
|
||||
U32 *wrappers.UInt32Value `protobuf:"bytes,8,opt,name=u32" json:"u32,omitempty"`
|
||||
Bool *wrappers.BoolValue `protobuf:"bytes,9,opt,name=bool" json:"bool,omitempty"`
|
||||
Str *wrappers.StringValue `protobuf:"bytes,10,opt,name=str" json:"str,omitempty"`
|
||||
Bytes *wrappers.BytesValue `protobuf:"bytes,11,opt,name=bytes" json:"bytes,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *KnownTypes) Reset() { *m = KnownTypes{} }
|
||||
func (m *KnownTypes) String() string { return proto.CompactTextString(m) }
|
||||
func (*KnownTypes) ProtoMessage() {}
|
||||
func (*KnownTypes) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_ab4422ec10550c41, []int{0}
|
||||
}
|
||||
|
||||
func (m *KnownTypes) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_KnownTypes.Unmarshal(m, b)
|
||||
}
|
||||
func (m *KnownTypes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_KnownTypes.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *KnownTypes) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_KnownTypes.Merge(m, src)
|
||||
}
|
||||
func (m *KnownTypes) XXX_Size() int {
|
||||
return xxx_messageInfo_KnownTypes.Size(m)
|
||||
}
|
||||
func (m *KnownTypes) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_KnownTypes.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_KnownTypes proto.InternalMessageInfo
|
||||
|
||||
func (m *KnownTypes) GetAn() *any.Any {
|
||||
if m != nil {
|
||||
return m.An
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetDur() *duration.Duration {
|
||||
if m != nil {
|
||||
return m.Dur
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetSt() *_struct.Struct {
|
||||
if m != nil {
|
||||
return m.St
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetTs() *timestamp.Timestamp {
|
||||
if m != nil {
|
||||
return m.Ts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetLv() *_struct.ListValue {
|
||||
if m != nil {
|
||||
return m.Lv
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetVal() *_struct.Value {
|
||||
if m != nil {
|
||||
return m.Val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetDbl() *wrappers.DoubleValue {
|
||||
if m != nil {
|
||||
return m.Dbl
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetFlt() *wrappers.FloatValue {
|
||||
if m != nil {
|
||||
return m.Flt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetI64() *wrappers.Int64Value {
|
||||
if m != nil {
|
||||
return m.I64
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetU64() *wrappers.UInt64Value {
|
||||
if m != nil {
|
||||
return m.U64
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetI32() *wrappers.Int32Value {
|
||||
if m != nil {
|
||||
return m.I32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetU32() *wrappers.UInt32Value {
|
||||
if m != nil {
|
||||
return m.U32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetBool() *wrappers.BoolValue {
|
||||
if m != nil {
|
||||
return m.Bool
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetStr() *wrappers.StringValue {
|
||||
if m != nil {
|
||||
return m.Str
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KnownTypes) GetBytes() *wrappers.BytesValue {
|
||||
if m != nil {
|
||||
return m.Bytes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*KnownTypes)(nil), "jsonpb.KnownTypes")
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterFile("testing/jsonpb_test_proto/test_objects.proto", fileDescriptor_ab4422ec10550c41)
|
||||
}
|
||||
|
||||
var fileDescriptor_ab4422ec10550c41 = []byte{
|
||||
// 402 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0xd0, 0xdf, 0x6b, 0xd5, 0x30,
|
||||
0x14, 0x07, 0x70, 0x96, 0xee, 0x4e, 0xcd, 0x44, 0x25, 0x88, 0x66, 0xd7, 0xa1, 0x22, 0x82, 0xc3,
|
||||
0x1f, 0x2d, 0xb6, 0xa5, 0xef, 0x0e, 0x11, 0x44, 0x9f, 0xba, 0xe9, 0xeb, 0x48, 0xd6, 0xac, 0x74,
|
||||
0x64, 0x49, 0x49, 0x4e, 0xee, 0xe8, 0xbf, 0xe6, 0x5f, 0x27, 0x49, 0x73, 0x45, 0x6e, 0xc9, 0xde,
|
||||
0xee, 0xcd, 0xf7, 0x73, 0xbe, 0x9c, 0x53, 0xfc, 0x11, 0x84, 0x85, 0x41, 0xf5, 0xc5, 0xb5, 0xd5,
|
||||
0x6a, 0xe4, 0x17, 0xfe, 0xef, 0xc5, 0x68, 0x34, 0xe8, 0x22, 0xfc, 0xd4, 0xfc, 0x5a, 0x5c, 0x82,
|
||||
0xcd, 0xc3, 0x13, 0x39, 0x98, 0xd5, 0xfa, 0xa8, 0xd7, 0xba, 0x97, 0xa2, 0x08, 0xaf, 0xdc, 0x5d,
|
||||
0x15, 0x4c, 0x4d, 0x33, 0x59, 0xbf, 0xdc, 0x8d, 0x3a, 0x67, 0x18, 0x0c, 0x5a, 0xc5, 0xfc, 0x78,
|
||||
0x37, 0xb7, 0x60, 0xdc, 0x25, 0xc4, 0xf4, 0xd5, 0x6e, 0x0a, 0xc3, 0x8d, 0xb0, 0xc0, 0x6e, 0xc6,
|
||||
0x54, 0xfd, 0xad, 0x61, 0xe3, 0x28, 0x4c, 0xdc, 0xf0, 0xcd, 0x9f, 0x15, 0xc6, 0x3f, 0x94, 0xbe,
|
||||
0x55, 0xe7, 0xd3, 0x28, 0x2c, 0x79, 0x8b, 0x11, 0x53, 0xf4, 0xd1, 0xeb, 0xbd, 0x93, 0xc3, 0xf2,
|
||||
0x69, 0x3e, 0xcf, 0xe6, 0xdb, 0xd9, 0xfc, 0x8b, 0x9a, 0x5a, 0xc4, 0x14, 0xf9, 0x80, 0xb3, 0xce,
|
||||
0x19, 0xba, 0x17, 0xd8, 0xd1, 0x82, 0x7d, 0x8d, 0x17, 0xb4, 0x5e, 0x91, 0x77, 0x18, 0x59, 0xa0,
|
||||
0x0f, 0x83, 0x7d, 0xbe, 0xb0, 0x67, 0xe1, 0x9a, 0x16, 0x59, 0x20, 0xef, 0x31, 0x02, 0x4b, 0x51,
|
||||
0x80, 0xeb, 0x05, 0x3c, 0xdf, 0x1e, 0xd6, 0x22, 0xb0, 0xde, 0xca, 0x0d, 0x7d, 0x9c, 0xb0, 0x3f,
|
||||
0x07, 0x0b, 0xbf, 0x99, 0x74, 0xa2, 0x45, 0x72, 0x43, 0x4e, 0x70, 0xb6, 0x61, 0x92, 0x3e, 0x09,
|
||||
0xf8, 0xd9, 0x02, 0xcf, 0xd0, 0x13, 0x92, 0xe3, 0xac, 0xe3, 0x92, 0x66, 0x41, 0x1e, 0x2f, 0xef,
|
||||
0xd2, 0x8e, 0x4b, 0x11, 0x7d, 0xc7, 0x25, 0xf9, 0x84, 0xb3, 0x2b, 0x09, 0x74, 0x3f, 0xf8, 0x17,
|
||||
0x0b, 0xff, 0x4d, 0x6a, 0x16, 0xf7, 0xf0, 0xce, 0xf3, 0xa1, 0xa9, 0xe9, 0x2a, 0xc1, 0xbf, 0x2b,
|
||||
0x68, 0xea, 0xc8, 0x87, 0xa6, 0xf6, 0xdb, 0xb8, 0xa6, 0xa6, 0x07, 0x89, 0x6d, 0x7e, 0xfd, 0xef,
|
||||
0x5d, 0x53, 0x87, 0xfa, 0xaa, 0xa4, 0xf7, 0xd2, 0xf5, 0x55, 0xb9, 0xad, 0xaf, 0xca, 0x50, 0x5f,
|
||||
0x95, 0xf4, 0xfe, 0x1d, 0xf5, 0xff, 0xbc, 0x0b, 0x7e, 0x9f, 0x6b, 0x2d, 0xe9, 0x83, 0xc4, 0x47,
|
||||
0x3f, 0xd5, 0x5a, 0xce, 0x3c, 0x38, 0xdf, 0x6f, 0xc1, 0x50, 0x9c, 0xe8, 0x3f, 0x03, 0x33, 0xa8,
|
||||
0x3e, 0xf6, 0x5b, 0x30, 0xe4, 0x33, 0x5e, 0xf1, 0x09, 0x84, 0xa5, 0x87, 0x89, 0x03, 0x4e, 0x7d,
|
||||
0x3a, 0x0f, 0xcc, 0xf2, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xab, 0x65, 0x99, 0x5a, 0x8d, 0x03,
|
||||
0x00, 0x00,
|
||||
}
|
||||
59
internal/testing/jsonpb_test_proto/test_objects.proto
Normal file
59
internal/testing/jsonpb_test_proto/test_objects.proto
Normal file
@@ -0,0 +1,59 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// 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.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/protobuf/wrappers.proto";
|
||||
|
||||
package jsonpb;
|
||||
|
||||
message KnownTypes {
|
||||
optional google.protobuf.Any an = 14;
|
||||
optional google.protobuf.Duration dur = 1;
|
||||
optional google.protobuf.Struct st = 12;
|
||||
optional google.protobuf.Timestamp ts = 2;
|
||||
optional google.protobuf.ListValue lv = 15;
|
||||
optional google.protobuf.Value val = 16;
|
||||
|
||||
optional google.protobuf.DoubleValue dbl = 3;
|
||||
optional google.protobuf.FloatValue flt = 4;
|
||||
optional google.protobuf.Int64Value i64 = 5;
|
||||
optional google.protobuf.UInt64Value u64 = 6;
|
||||
optional google.protobuf.Int32Value i32 = 7;
|
||||
optional google.protobuf.UInt32Value u32 = 8;
|
||||
optional google.protobuf.BoolValue bool = 9;
|
||||
optional google.protobuf.StringValue str = 10;
|
||||
optional google.protobuf.BytesValue bytes = 11;
|
||||
}
|
||||
1132
internal/testing/test.pb.go
Normal file
1132
internal/testing/test.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
176
internal/testing/test.proto
Normal file
176
internal/testing/test.proto
Normal file
@@ -0,0 +1,176 @@
|
||||
// NB: Copied from the gRPC Go repo: google.golang.org/grpc/interop/grpc_testing/test.proto
|
||||
|
||||
// Copyright 2017 gRPC authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// An integration test service that covers all the method signature permutations
|
||||
// of unary/streaming requests/responses.
|
||||
syntax = "proto3";
|
||||
|
||||
package 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.
|
||||
PayloadType type = 1;
|
||||
// Primary contents of payload.
|
||||
bytes body = 2;
|
||||
}
|
||||
|
||||
// A protobuf representation for grpc status. This is used by test
|
||||
// clients to specify a status that the server should attempt to return.
|
||||
message EchoStatus {
|
||||
int32 code = 1;
|
||||
string message = 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.
|
||||
PayloadType response_type = 1;
|
||||
|
||||
// Desired payload size in the response from the server.
|
||||
// If response_type is COMPRESSABLE, this denotes the size before compression.
|
||||
int32 response_size = 2;
|
||||
|
||||
// Optional input payload sent along with the request.
|
||||
Payload payload = 3;
|
||||
|
||||
// Whether SimpleResponse should include username.
|
||||
bool fill_username = 4;
|
||||
|
||||
// Whether SimpleResponse should include OAuth scope.
|
||||
bool fill_oauth_scope = 5;
|
||||
|
||||
// Whether server should return a given status
|
||||
EchoStatus response_status = 7;
|
||||
}
|
||||
|
||||
// Unary response, as configured by the request.
|
||||
message SimpleResponse {
|
||||
// Payload to increase message size.
|
||||
Payload payload = 1;
|
||||
|
||||
// The user the request came from, for verifying authentication was
|
||||
// successful when the client expected it.
|
||||
string username = 2;
|
||||
|
||||
// OAuth scope.
|
||||
string oauth_scope = 3;
|
||||
}
|
||||
|
||||
// Client-streaming request.
|
||||
message StreamingInputCallRequest {
|
||||
// Optional input payload sent along with the request.
|
||||
Payload payload = 1;
|
||||
|
||||
// Not expecting any payload from the response.
|
||||
}
|
||||
|
||||
// Client-streaming response.
|
||||
message StreamingInputCallResponse {
|
||||
// Aggregated size of payloads received from the client.
|
||||
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.
|
||||
int32 size = 1;
|
||||
|
||||
// Desired interval between consecutive responses in the response stream in
|
||||
// microseconds.
|
||||
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.
|
||||
PayloadType response_type = 1;
|
||||
|
||||
// Configuration for each expected response message.
|
||||
repeated ResponseParameters response_parameters = 2;
|
||||
|
||||
// Optional input payload sent along with the request.
|
||||
Payload payload = 3;
|
||||
|
||||
// Whether server should return a given status
|
||||
EchoStatus response_status = 7;
|
||||
}
|
||||
|
||||
// Server-streaming response, as configured by the request and parameters.
|
||||
message StreamingOutputCallResponse {
|
||||
// Payload to increase response size.
|
||||
Payload payload = 1;
|
||||
}
|
||||
|
||||
// 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(Empty) returns (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);
|
||||
}
|
||||
|
||||
// A simple service NOT implemented at servers so clients can test for
|
||||
// that case.
|
||||
service UnimplementedService {
|
||||
// A call that no server should implement
|
||||
rpc UnimplementedCall(Empty) returns (Empty);
|
||||
}
|
||||
BIN
internal/testing/test.protoset
Normal file
BIN
internal/testing/test.protoset
Normal file
Binary file not shown.
257
internal/testing/test_server.go
Normal file
257
internal/testing/test_server.go
Normal file
@@ -0,0 +1,257 @@
|
||||
package testing
|
||||
|
||||
//go:generate protoc --go_out=plugins=grpc:./ test.proto
|
||||
//go:generate protoc --descriptor_set_out=./test.protoset test.proto
|
||||
//go:generate protoc --descriptor_set_out=./example.protoset --include_imports example.proto
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/fullstorydev/grpcurl"
|
||||
)
|
||||
|
||||
// TestServer implements the TestService interface defined in example.proto.
|
||||
type TestServer struct{}
|
||||
|
||||
// EmptyCall accepts one empty request and issues one empty response.
|
||||
func (TestServer) EmptyCall(ctx context.Context, req *Empty) (*Empty, error) {
|
||||
headers, trailers, failEarly, failLate := processMetadata(ctx)
|
||||
grpc.SetHeader(ctx, headers)
|
||||
grpc.SetTrailer(ctx, trailers)
|
||||
if failEarly != codes.OK {
|
||||
return nil, status.Error(failEarly, "fail")
|
||||
}
|
||||
if failLate != codes.OK {
|
||||
return nil, status.Error(failLate, "fail")
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// UnaryCall accepts one request and issues one response. The response includes
|
||||
// the client's payload as-is.
|
||||
func (TestServer) UnaryCall(ctx context.Context, req *SimpleRequest) (*SimpleResponse, error) {
|
||||
headers, trailers, failEarly, failLate := processMetadata(ctx)
|
||||
grpc.SetHeader(ctx, headers)
|
||||
grpc.SetTrailer(ctx, trailers)
|
||||
if failEarly != codes.OK {
|
||||
return nil, status.Error(failEarly, "fail")
|
||||
}
|
||||
if failLate != codes.OK {
|
||||
return nil, status.Error(failLate, "fail")
|
||||
}
|
||||
|
||||
return &SimpleResponse{
|
||||
Payload: req.Payload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StreamingOutputCall accepts one request and issues a sequence of responses
|
||||
// (streamed download). The server returns the payload with client desired type
|
||||
// and sizes as specified in the request's ResponseParameters.
|
||||
func (TestServer) StreamingOutputCall(req *StreamingOutputCallRequest, str TestService_StreamingOutputCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
if failEarly != codes.OK {
|
||||
return status.Error(failEarly, "fail")
|
||||
}
|
||||
|
||||
rsp := &StreamingOutputCallResponse{Payload: &Payload{}}
|
||||
for _, param := range req.ResponseParameters {
|
||||
if str.Context().Err() != nil {
|
||||
return str.Context().Err()
|
||||
}
|
||||
delayMicros := int64(param.GetIntervalUs()) * int64(time.Microsecond)
|
||||
if delayMicros > 0 {
|
||||
time.Sleep(time.Duration(delayMicros))
|
||||
}
|
||||
sz := int(param.GetSize())
|
||||
buf := make([]byte, sz)
|
||||
for i := 0; i < sz; i++ {
|
||||
buf[i] = byte(i)
|
||||
}
|
||||
rsp.Payload.Type = req.ResponseType
|
||||
rsp.Payload.Body = buf
|
||||
if err := str.Send(rsp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if failLate != codes.OK {
|
||||
return status.Error(failLate, "fail")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StreamingInputCall accepts a sequence of requests and issues one response
|
||||
// (streamed upload). The server returns the aggregated size of client payloads
|
||||
// as the result.
|
||||
func (TestServer) StreamingInputCall(str TestService_StreamingInputCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
if failEarly != codes.OK {
|
||||
return status.Error(failEarly, "fail")
|
||||
}
|
||||
|
||||
sz := 0
|
||||
for {
|
||||
if str.Context().Err() != nil {
|
||||
return str.Context().Err()
|
||||
}
|
||||
if req, err := str.Recv(); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
sz += len(req.Payload.Body)
|
||||
}
|
||||
}
|
||||
if err := str.SendAndClose(&StreamingInputCallResponse{AggregatedPayloadSize: int32(sz)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if failLate != codes.OK {
|
||||
return status.Error(failLate, "fail")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FullDuplexCall accepts 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.
|
||||
func (TestServer) FullDuplexCall(str TestService_FullDuplexCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
if failEarly != codes.OK {
|
||||
return status.Error(failEarly, "fail")
|
||||
}
|
||||
|
||||
rsp := &StreamingOutputCallResponse{Payload: &Payload{}}
|
||||
for {
|
||||
if str.Context().Err() != nil {
|
||||
return str.Context().Err()
|
||||
}
|
||||
req, err := str.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, param := range req.ResponseParameters {
|
||||
sz := int(param.GetSize())
|
||||
buf := make([]byte, sz)
|
||||
for i := 0; i < sz; i++ {
|
||||
buf[i] = byte(i)
|
||||
}
|
||||
rsp.Payload.Type = req.ResponseType
|
||||
rsp.Payload.Body = buf
|
||||
if err := str.Send(rsp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if failLate != codes.OK {
|
||||
return status.Error(failLate, "fail")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HalfDuplexCall accepts a sequence of requests and issues a sequence of
|
||||
// responses. The server buffers all the client requests and then serves them
|
||||
// in order. A stream of responses is returned to the client once the client
|
||||
// half-closes the stream.
|
||||
func (TestServer) HalfDuplexCall(str TestService_HalfDuplexCallServer) error {
|
||||
headers, trailers, failEarly, failLate := processMetadata(str.Context())
|
||||
str.SetHeader(headers)
|
||||
str.SetTrailer(trailers)
|
||||
if failEarly != codes.OK {
|
||||
return status.Error(failEarly, "fail")
|
||||
}
|
||||
|
||||
var reqs []*StreamingOutputCallRequest
|
||||
for {
|
||||
if str.Context().Err() != nil {
|
||||
return str.Context().Err()
|
||||
}
|
||||
if req, err := str.Recv(); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
reqs = append(reqs, req)
|
||||
}
|
||||
}
|
||||
rsp := &StreamingOutputCallResponse{}
|
||||
for _, req := range reqs {
|
||||
rsp.Payload = req.Payload
|
||||
if err := str.Send(rsp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if failLate != codes.OK {
|
||||
return status.Error(failLate, "fail")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
// MetadataReplyHeaders is a request header that contains values that will
|
||||
// be echoed back to the client as response headers. The format of the value
|
||||
// is "key: val". To have the server reply with more than one response
|
||||
// header, supply multiple values in request metadata.
|
||||
MetadataReplyHeaders = "reply-with-headers"
|
||||
// MetadataReplyTrailers is a request header that contains values that will
|
||||
// be echoed back to the client as response trailers. Its format its the
|
||||
// same as MetadataReplyHeaders.
|
||||
MetadataReplyTrailers = "reply-with-trailers"
|
||||
// MetadataFailEarly is a request header that, if present and not zero,
|
||||
// indicates that the RPC should fail immediately with that code.
|
||||
MetadataFailEarly = "fail-early"
|
||||
// MetadataFailLate is a request header that, if present and not zero,
|
||||
// indicates that the RPC should fail at the end with that code. This is
|
||||
// different from MetadataFailEarly only for streaming calls. An early
|
||||
// failure means the call to fail before any request stream is read or any
|
||||
// response stream is generated. A late failure means the entire request and
|
||||
// response streams will be consumed/processed and only then will the error
|
||||
// code be sent.
|
||||
MetadataFailLate = "fail-late"
|
||||
)
|
||||
|
||||
func processMetadata(ctx context.Context) (metadata.MD, metadata.MD, codes.Code, codes.Code) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, nil, codes.OK, codes.OK
|
||||
}
|
||||
return grpcurl.MetadataFromHeaders(md[MetadataReplyHeaders]),
|
||||
grpcurl.MetadataFromHeaders(md[MetadataReplyTrailers]),
|
||||
toCode(md[MetadataFailEarly]),
|
||||
toCode(md[MetadataFailLate])
|
||||
}
|
||||
|
||||
func toCode(vals []string) codes.Code {
|
||||
if len(vals) == 0 {
|
||||
return codes.OK
|
||||
}
|
||||
i, err := strconv.Atoi(vals[len(vals)-1])
|
||||
if err != nil {
|
||||
return codes.Code(i)
|
||||
}
|
||||
return codes.Code(i)
|
||||
}
|
||||
|
||||
var _ TestServiceServer = TestServer{}
|
||||
16
internal/testing/tls/ca.crl
Normal file
16
internal/testing/tls/ca.crl
Normal file
@@ -0,0 +1,16 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIICfDBmAgEBMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNVBAMTAmNhFw0xNzA4MjUx
|
||||
NTQ1NTNaFw0yNzA4MjUxNTQ1NTNaMACgIzAhMB8GA1UdIwQYMBaAFM0FLuuYBwuA
|
||||
J+tocRlu+xUuOw6FMA0GCSqGSIb3DQEBCwUAA4ICAQCcN8WJKbvGrunXgRBjSnsM
|
||||
j/sejaX3CCZPmrXeditekSNMatO0JDXOjyoEvv7s9aZrAf3eFOU3Vr5N7PlbLRdj
|
||||
tovuKTeVp3ungqMoT70cFEf/7eMlpWMB2GkfpV9LtF5Tb8dOYT3kllqtMKv4TeZo
|
||||
2adu+GXdeQsqlz9fDEi0ZV4RBruuO0QyLWXpNrUB6fznUDfE4KVBsAIadjsg+Aew
|
||||
6jeTkYuUILWMwBM6MzOG/InTKqXpe4ghMufI9fO+phxY10gz4QQ44ZNOa18OuiJw
|
||||
IH8MoKzhrgUAPLs135hpdGbDePVw5SIKMHUAU2UEKtozAMVfCW45MZHREDdMV3NA
|
||||
w5QWDoBYl4jol08Orbccmhu4fbauXmB5Id4IPVgGEGFPpiH/QVyJgZIv1AD2dlRg
|
||||
Td26iz9I25hyrpEfF1gJMtOsDOklDsUiMo8ncQ3CL+pkKnMjhm54k6OFe0qlGsdO
|
||||
KSavNlEmW/F9h/gs5kaLeFv0v4JxLh12TY28pCE60yoB/UkuB1a+VTHcP3Fa6uUC
|
||||
uyv0T0f5yHujaM1isGjI3XGgVgLyJiFxtKMPsMEwRrsrEafqp7JCeLpnIWt1J0C/
|
||||
Zz3roGcCGj86Oq5zUjdguHS6Ra+uaX+IMJGohWq1cndzVzNfUNkRIyl8IdkCv9o3
|
||||
J8fVwBzN6sAu6zWd3BqT4A==
|
||||
-----END X509 CRL-----
|
||||
28
internal/testing/tls/ca.crt
Normal file
28
internal/testing/tls/ca.crt
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE2jCCAsKgAwIBAgIBATANBgkqhkiG9w0BAQsFADANMQswCQYDVQQDEwJjYTAe
|
||||
Fw0xNzA4MjUxNTQ1NTJaFw0yNzA4MjUxNTQ1NTNaMA0xCzAJBgNVBAMTAmNhMIIC
|
||||
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnjw7iZyn9EtjtK7zT+M59OxS
|
||||
J43a3kMm11Vnh1Fw8oQ7tH0kQW6COyAwlBhAzWGtfDC6jG7A2n+8mPWoinsxoLA5
|
||||
viSZrEkWZ4tGxWWZ5y/xWh5NBrHa3Jsg1rZ5dstAl08gJPwl0v32aYkMdtk16K0k
|
||||
jtdPpKtO98v1N6ea7fvQKdyrAaUHY/BY+onoqmSBGPX6vVV7FGybWDf72J5vcwyA
|
||||
07wdaZ7TrUYbD28nhXuc4Dt2KbZbWvkT4OYZ+4c+eiehRFVGtuXXi0cKG0xF516b
|
||||
DnrSO1/2ZbIB2xmcgKvcay0jLzWqhnsSc+qTqODVLODQAMvrMlY0RUXQPn/WTfAw
|
||||
aQ+u/j7qIR1KFZcLn7uVq8bCM9g+VeyLjq+6XUvgGL9KhvrR/FA4R4DUka+ZVQqh
|
||||
s262Qs7pNFdoIIrTsJyPd7/UYWCcQbkCKw0aRoUfBeZgkg4bylcABygZqY9+apRF
|
||||
NBEhpycAEvWFarr6rosqII9kLm1LpnPNEgSvQ/CIRIHq5z5iKeSvHFYOVxLS44HH
|
||||
M16Mry5UF/jW7Vg8JY5Jrg5YwyOhdGoOSJ3+c/pbLq1TRkwK9bwEJwGr7FdkrjPZ
|
||||
uNJ7HQiFn2IaeLlbtzwJ+q3vGSFEjlujOigwUJVzXz16Q93vYIuK3FcyDuOMzpwW
|
||||
HHgSLH/+rdtd+7hoLwMCAwEAAaNFMEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB
|
||||
/wQIMAYBAf8CAQAwHQYDVR0OBBYEFM0FLuuYBwuAJ+tocRlu+xUuOw6FMA0GCSqG
|
||||
SIb3DQEBCwUAA4ICAQBLNuP8ovTgFcYf7Ydgc+aDB2v+qMYVIqJMrnp8DuYy20yv
|
||||
64jYcIxh35IbQWOZxZshJsebRKM9vr6huEo2c/SuHLQ5HZGPxSt++aG+iY4Y1zL5
|
||||
KHtG558lK4S5VsXymMkUjGZtm+ZuJida9ZcV+jz/kePMHpErWPeMvH2jDmD4mWgA
|
||||
YdjipD4cxEn+9O3lBSCkeSjaAd5rQeD9XomV4a2/uL4Y7RDbn9BNt+jdLvfu2pmo
|
||||
O1zcp0f578oFlUIg0H9fb6YzL3MKOXiuh7KE1/W9el5zsN/kLlyWFbopN34A6PlO
|
||||
ZHEvZZcQW06bmy2FRWgqkqWMqBwzWk7JKGp+ozv8IBvimhgjNun068FQAZV9nfKU
|
||||
6U728P6T1USDhgwtpX7/2IaukXcmO2FE9XzKZyYAbmAcOhPLzFO4pdwapU2lPbFE
|
||||
l2HLkYaHLXzMxB30kQQHW2l8+8xr+MAa+bBcD9Jaxaz/t3ZpLt62/1nxT7SWNwH4
|
||||
Sa83BaG3EHBotlBc18hqrFWEKR4KYenqY8xa7kblDI0rXqlXBblUXp0TwIctOmzR
|
||||
coqR8q6/R4VXhD9FZBIW1/uX2KKEPfTM46aQdaTtdzjd3UzwTP0SRwkvZ4oFftW6
|
||||
s1GljfCGsrOpi6O/Uy/IVTE7Xn/oVnlJvGbaP+AHexLytBiBVUBukLBwvpJ8bg==
|
||||
-----END CERTIFICATE-----
|
||||
51
internal/testing/tls/ca.key
Normal file
51
internal/testing/tls/ca.key
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
24
internal/testing/tls/client.crt
Normal file
24
internal/testing/tls/client.crt
Normal file
@@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEGjCCAgKgAwIBAgIRAPt0KCF12GYbCoUj7klj5/AwDQYJKoZIhvcNAQELBQAw
|
||||
DTELMAkGA1UEAxMCY2EwHhcNMTcwODI1MTU0NTUzWhcNMjcwODI1MTU0NTUyWjAR
|
||||
MQ8wDQYDVQQDEwZjbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQC1JxEPOsZyf8883tlPBEajotyECtrYMZ48FsYEmQ1XvKPoH3eb7+Ev7tRBVAup
|
||||
yB87XQ5PU/oNqAtpo/6WD5JGnKSVs+EAMESXmzEF04T9hK8uSd0cVEEkd0tbVNpX
|
||||
bWMbivHnx5Vp8o2mIx0sVrgGsJW3t+cYbNTp3bOTdmz7LKbiQN2Ix0wH+2/sPXYa
|
||||
cZsgbI0Ydo9KnqykPm2TqBYCL1kzhGlvaAotjdDIm7OgnaGCFe4CbK4QZB4uFw3e
|
||||
M+PmLG0TsaH9CT/ZRrE21iBfg0rqgpKZKMcqYQftXdLqlikuV69F+0L84xRfeVqB
|
||||
1E4j0RwBGWW8EwY4WHK3VE25AgMBAAGjcTBvMA4GA1UdDwEB/wQEAwIDuDAdBgNV
|
||||
HSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFMs+/QF/ZJaRu8Wv
|
||||
vcaMC7jGmPwxMB8GA1UdIwQYMBaAFM0FLuuYBwuAJ+tocRlu+xUuOw6FMA0GCSqG
|
||||
SIb3DQEBCwUAA4ICAQB0gPDs86Rjy/O2+l8QyaYfwmmyTMPjNVqKgVP1uuikWErN
|
||||
5hTAlwtDI9FuiMFBqeBdeiT8IQvzEEQPYu69kAX2XYBWBMWDa85co5fJztAzV7Yz
|
||||
VL1byhxd2jgM14usyx6PbzkhYKBNesujHj7wQ0ur+85Kp66HqKCuNCvbj0zv58PH
|
||||
RWkojRPgyTpbLdXXCOWJXp62XfddL1Bf7NJCW5QTyHoHoOsOeoPajb4OOmQehzqv
|
||||
b9FPAHVFBPrU53Xn1CURAzTeBQ2T/OK4nx6EdQgxP9+VVurBQ9N2YBM9VEJmfQK8
|
||||
Lf5/+EJHe5ctOy1Xm4A3A52zZ1kGjfvWUtGJUSnJ5ahhMm6Dx63wk7oYNCTXnPup
|
||||
aVtINWygNlS/dQsWubHaWSFwB9/QwK074+H/4EpDq9HCMMl8yPMktOmv69Hyaju3
|
||||
MvGshz/DLNZf9oYpO+lbU8X124Z6XifEztMiBlUPW75KYv9X4CTbKTdE45QaRMiO
|
||||
ZXcH4HE1/iQ9IOGg7CplMlMcHg+lQ7CpXQjtUUjCEpkj8BAs8YLDodLnjigs56/8
|
||||
75+3cVZu0+dY+9eNt/EIqzjaFwEx72hbLyhk2IeS++7QloJDhYqlq+ng54Ul957A
|
||||
8e7TsSVHlLZVGXw8yqjyxxOwWaFx6mvFzGrcBtvCgK2HwEiYQ9qXJ5VPkdo42w==
|
||||
-----END CERTIFICATE-----
|
||||
15
internal/testing/tls/client.csr
Normal file
15
internal/testing/tls/client.csr
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICVjCCAT4CAQAwETEPMA0GA1UEAxMGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEAtScRDzrGcn/PPN7ZTwRGo6LchAra2DGePBbGBJkNV7yj
|
||||
6B93m+/hL+7UQVQLqcgfO10OT1P6DagLaaP+lg+SRpyklbPhADBEl5sxBdOE/YSv
|
||||
LkndHFRBJHdLW1TaV21jG4rx58eVafKNpiMdLFa4BrCVt7fnGGzU6d2zk3Zs+yym
|
||||
4kDdiMdMB/tv7D12GnGbIGyNGHaPSp6spD5tk6gWAi9ZM4Rpb2gKLY3QyJuzoJ2h
|
||||
ghXuAmyuEGQeLhcN3jPj5ixtE7Gh/Qk/2UaxNtYgX4NK6oKSmSjHKmEH7V3S6pYp
|
||||
LlevRftC/OMUX3lagdROI9EcARllvBMGOFhyt1RNuQIDAQABoAAwDQYJKoZIhvcN
|
||||
AQELBQADggEBAFnxmVCuM3J2bt79JcFOqsXNsvGUUT+4kMl3BcfSWaf1pviuhiXT
|
||||
fsKkk1WItvaRQvpNdQoFQDjKHGcd6+0vCz+Q6Nni2Vniz3+f3+h/rOzWGA656Xxm
|
||||
lgByryixnngWZBNLZkLWCz/H1MAlQYu8PTdy0N+JBF/E5SAGfaaXtfTC6tjnnZIm
|
||||
3rjxC7C3EyELpo3X3erTcHpnFvhl6ZSkViVWfhOjxU0n+TGGohczesbHZc8YC37y
|
||||
JrkrnRDrNKnca1XkXWUnbV6rH8cVDnJ0Fvs54RI686Tlv+LxW2xa3D2+pV7Koduj
|
||||
Ru+PguJ3BbaRpieGTxHg7hH/1T5HsZnD2E0=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
27
internal/testing/tls/client.key
Normal file
27
internal/testing/tls/client.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAtScRDzrGcn/PPN7ZTwRGo6LchAra2DGePBbGBJkNV7yj6B93
|
||||
m+/hL+7UQVQLqcgfO10OT1P6DagLaaP+lg+SRpyklbPhADBEl5sxBdOE/YSvLknd
|
||||
HFRBJHdLW1TaV21jG4rx58eVafKNpiMdLFa4BrCVt7fnGGzU6d2zk3Zs+yym4kDd
|
||||
iMdMB/tv7D12GnGbIGyNGHaPSp6spD5tk6gWAi9ZM4Rpb2gKLY3QyJuzoJ2hghXu
|
||||
AmyuEGQeLhcN3jPj5ixtE7Gh/Qk/2UaxNtYgX4NK6oKSmSjHKmEH7V3S6pYpLlev
|
||||
RftC/OMUX3lagdROI9EcARllvBMGOFhyt1RNuQIDAQABAoIBACE5jRNyADu33VaY
|
||||
uNqZOiuBD1jYdNL6Jr92ndLyD1RsMNO+Eb3z/SVBdISW2ZzGK5RDuQArss0WaSFz
|
||||
BpqXOIji6fzbBQV31NzJhfA/n0CwOUEQIxGzEk+R4axan8ExOuAuV7ffDzRjXD+A
|
||||
aTVcolv3vz326Ne9/j72fp0pN0vJ0b8mk1xmDWNOHhfoWmIGrUZAjqAkA1kh5aLk
|
||||
Q8MCjVyjT+KYDkFT6NscFVxKslDVhb2OFC7oy+9l/hBru12bsi9eBdYpPT9E1cpR
|
||||
U9N8+9XS9d7wgVnmVh8CIrFToLsvSrwD8SG0Indot0C6dsy0PkoMUekVxvM5/wXm
|
||||
YLZnZEECgYEA28JfZxFxO+bjd+zBC+yrusHCVfZK6MZZV0u/V82Bn+gftwgxagI7
|
||||
q2h7m56WBtq9MeLlhicZ+em4BtA3yHwVGhqr+d5CaXjkYype1EditqGx8JYRUwlG
|
||||
9z7W6jCEDJsjrzvGgua1qsyCZFePG78i4rLumK7UVvWEK/OaiZu3Pr0CgYEA0wbT
|
||||
3STBc4THLXR8nx39b6RP+qH8jO9UcD/V7Hi/SWTCcGB8IIlTV2EJVndKHPregcmI
|
||||
dN61uH3d+3UtI/WxEPMcfrSlEwVrjF2m5szYjLIAeFynw7pQY95qIhgKi6OH0Yn6
|
||||
9OCmieL0x1ez5zOXiv+GVjmn9tDCxXvqfsW9CK0CgYEApOd0Y4kpKUQWyPT135bX
|
||||
PqsKwyqwB4BfpiwHB0IE1ROASP5y5hOK5xLePmaAOeCGPBsBFOveiDQjjalNUroZ
|
||||
s570EeoAd9jpuKggxLZUkqs/NUPG+EJr6DhVWSLS1ArOej4mti+dfu87oUQ69R02
|
||||
dlrCw/vdBuvxJHIGMuCQXxkCgYEAinSFVygBgQCSCkHObjuoB7LgAsp7QCDa3tcT
|
||||
TZafstDYPhEf/9z6AG+bR872onL6wF7xF/Tzd7ulhJGJ73kJFtzbSkrNr+AzgyID
|
||||
GpU2U4GKi24HaIT6r7vDGOF7Mck2mIWWUUqAGiH9hjkFwWD5QeqLQlGL4YVw9U9r
|
||||
OIgWkfUCgYAoMub8wHJe9zhq7UCBCa4zPXqWVQcN7ANjL6fESvyK+A6TfwL5j782
|
||||
CNIVl8ewU9TthEY+AdbJeiAevz+pIazSqi0ln1JKO5YpCOC1Y0UxcEpghplBTlPU
|
||||
yoQyTJP81iLynwOM4pC322ptJISXIndL7Ig/9AoRZtAJV4Ot6z9b1Q==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
25
internal/testing/tls/expired.crt
Normal file
25
internal/testing/tls/expired.crt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEODCCAiCgAwIBAgIQP6vqM4GEKFdwrZvr/s/5KTANBgkqhkiG9w0BAQsFADAN
|
||||
MQswCQYDVQQDEwJjYTAeFw0xNzA4MjUxNTQ1NThaFw0xNzA4MjUxNTQ1NThaMBIx
|
||||
EDAOBgNVBAMTB2V4cGlyZWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQDDlGu08Ye7IGMnh6zyiVbW64btT2BUfj4fZlyTraLNzVPfULOXezgJRAwAXEnY
|
||||
eYA5j6e7iAO/yGXq+Pxh9rbGlXTVC2BsPIfKzTOcCTxgO4tfrdfSWFRDGgcFtpAA
|
||||
/4b8fYQ8BMw7khVpOqj9n8TyqsN8DGyZTyNhQuc23zmmY1eVYtDIwVvVBp8/YzDT
|
||||
3RzO+FyoRpKgdngX6kdqqZ+vaPxP078JM5zAMnysjSkpQCdAdu4EupvA5xRtOrDt
|
||||
jJoAi4iUf4sG20RiF9XFpiDkyUiOa0ysFC4wrWbXkaT1xHZJJPwwJZ8VyGDRFlFa
|
||||
POEaM9dtLPKtEV2af3G9Q9s/AgMBAAGjgY4wgYswDgYDVR0PAQH/BAQDAgO4MB0G
|
||||
A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUNX8nKbJ+G0bv
|
||||
k88bTHkULUX1U5AwHwYDVR0jBBgwFoAUzQUu65gHC4An62hxGW77FS47DoUwGgYD
|
||||
VR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQBHZsxC
|
||||
GGOUhNi1b0xUYmKwFaRP641R69NydzVMma+2rpLZtL8MQq7lWAgWGE8EiKfTewk8
|
||||
2Gm+rNwReBRkw56zka8WtLomO4GKKJHvfRsbSempsp9MShc3vcsEtzXGjDQi9c4Y
|
||||
BJjWbh666jCKPRJ09RewcTyTYPtZ96lVeYRj9HhXF1EGnURG5sM/zCantlZXxInk
|
||||
1FIOKDuawMbgf3GR6n/bM2oybYCs4Skv3yOp8x3lyhlJ/zmSPGVVkPa7Vki5+sPh
|
||||
/eIZJ9mzEXsu7IXfg1isZ01iB28+6UgpZt/3017PvgopiYrL0gMKO3EL8UqJDweN
|
||||
GzJh5VYOqsjTbsrYYsWGqM66vJmfPvqDyYA1jj/EH+q6TBz0s99rzF44bBKKie6T
|
||||
0KrZT7ohXQ18Vhl2UpqahMqxMeAW/QP5asGuzS5EalUir5mNcOljtq1NnfmYvqok
|
||||
aDC7rURoANwZ2L1oKN0oB6jn36g791pFdKycw+HdsrGgQGPOMak9P32z5kmDsODH
|
||||
6aVrfio2WwSGg+1CIBH0QsclHAUgLsAXjyGbRWxPsvMcsLB6OOymuTP2UfriT05X
|
||||
duabvbEP5IIRehVUfrP5uvoo29xnoPL4UB0C8gwr21IDn7Zew5/ALekN+s6IgsfL
|
||||
9yKTGSD+6Ir3NqBgL8T+uhOAekyLE5S4CcwCHw==
|
||||
-----END CERTIFICATE-----
|
||||
16
internal/testing/tls/expired.csr
Normal file
16
internal/testing/tls/expired.csr
Normal file
@@ -0,0 +1,16 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIChDCCAWwCAQAwEjEQMA4GA1UEAxMHZXhwaXJlZDCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAMOUa7Txh7sgYyeHrPKJVtbrhu1PYFR+Ph9mXJOtos3N
|
||||
U99Qs5d7OAlEDABcSdh5gDmPp7uIA7/IZer4/GH2tsaVdNULYGw8h8rNM5wJPGA7
|
||||
i1+t19JYVEMaBwW2kAD/hvx9hDwEzDuSFWk6qP2fxPKqw3wMbJlPI2FC5zbfOaZj
|
||||
V5Vi0MjBW9UGnz9jMNPdHM74XKhGkqB2eBfqR2qpn69o/E/TvwkznMAyfKyNKSlA
|
||||
J0B27gS6m8DnFG06sO2MmgCLiJR/iwbbRGIX1cWmIOTJSI5rTKwULjCtZteRpPXE
|
||||
dkkk/DAlnxXIYNEWUVo84Roz120s8q0RXZp/cb1D2z8CAwEAAaAtMCsGCSqGSIb3
|
||||
DQEJDjEeMBwwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEB
|
||||
CwUAA4IBAQA0GTIB6PxgmHBa234rSYqIew4qRfY9MeUkVQEFRDwodqxa+LWvZx2T
|
||||
5JmTZYyXBfQwnSye18fDjQuHv1KaI7bnJuMRv9KU8L6ynLkAqrFWRSBjt3eCum01
|
||||
IWZFyWu+dUN2c12C79zUQh8uZc15oDNFrD8ivBbGRpWvR1CSG/DH52kJ8nckgEsh
|
||||
SwxbzSPOXBgLH6ke5z9QGHJMK2rhRFutFOecAId7VBiWqfZJv15+P2ZcyJNQGThs
|
||||
V2sT974YGFkc0Y1MWlCgi3XhyQzIzqV1tEILGSTDE8biuKlm1nX3H0K8oI3hoGcM
|
||||
CBjE3HQ1/rs10IY/WvkpIfAU71D/ExMc
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
27
internal/testing/tls/expired.key
Normal file
27
internal/testing/tls/expired.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAw5RrtPGHuyBjJ4es8olW1uuG7U9gVH4+H2Zck62izc1T31Cz
|
||||
l3s4CUQMAFxJ2HmAOY+nu4gDv8hl6vj8Yfa2xpV01QtgbDyHys0znAk8YDuLX63X
|
||||
0lhUQxoHBbaQAP+G/H2EPATMO5IVaTqo/Z/E8qrDfAxsmU8jYULnNt85pmNXlWLQ
|
||||
yMFb1QafP2Mw090czvhcqEaSoHZ4F+pHaqmfr2j8T9O/CTOcwDJ8rI0pKUAnQHbu
|
||||
BLqbwOcUbTqw7YyaAIuIlH+LBttEYhfVxaYg5MlIjmtMrBQuMK1m15Gk9cR2SST8
|
||||
MCWfFchg0RZRWjzhGjPXbSzyrRFdmn9xvUPbPwIDAQABAoIBADa1IZuvpCP330SD
|
||||
cyE0wZHEuC1RcsSvu3jVDThR7aRbtwZUcKgC053j5ueC6TUgZ3mycVzHoyTWTYv4
|
||||
scBFXsMVs2SUlhgwpltYIwOWocjZXxcYbbJs+sT6VtSGSKm+0Gd4RLD1NpvDNTIG
|
||||
MpcfRdwLYDsmzonj1SWzrTFwJ5Qe33cHSR8Oi9OarrxzIHWLDNgp8x+5j2l0T3AG
|
||||
RPQMXj7jaK6qEdHGD6geg+ButeyjYyxu64l8Ooax11/jfdYHcK+BmmtmP3Bd/6FQ
|
||||
DCtrXTBv5Txl/T6D/6OsyabSlwGWFDNEAaS47hKCFiYJBR+Az6r7d1QFBIzuqF0+
|
||||
T7EwaKECgYEA/tUMcTXnBRquAtQHW4cSvHH2qoZAhGjXCmj/pk4UCOZDT1DP/K5u
|
||||
m7tOZB4RJBmzg0a1QS89aJ6lhY0Oy1Y9MZTM6ofkBKd8+P0Rgo04UDVeQ8sA9c8x
|
||||
4bFOrOEqe7NoK7Mwxyn5NOmNi/wxy+tpiMH4Jt3y4TLDEZPg0Tsfu90CgYEAxHnc
|
||||
fN3gKeY3SV6nHi+johBirSNazhr4n0fx/TCsy02e5Rrn0rksGVfPij5Y2g8HQLel
|
||||
hdbG7tVyA7UtGgVjwiT+4j0VXmWpCIqfPsibRoy29oPqO4p5pULsz+ueb4biRW2Q
|
||||
tdLqS4OldM8YUFwkS7k3Pp13SAY+Ir9rHL6wv8sCgYEA/AZaYtCrZMnpFNT7XdLt
|
||||
fb+78xQJVKqXGi2TwLbxa4fHQ/cpa75bl9scAToXO7vLZOaWNhxxQDm+e6Fw4zqs
|
||||
FJAURVMV+GBo4ZrvKU1fRzwwuR1ZGsHKlGoV5DZgHKznNmjmseJaG7FsEujdms58
|
||||
tgsXz+Cr53qbn5O/wU4W6WUCgYBA16sB9sPtcBIc/8UNvFE3wkqes4VbciFNiBQA
|
||||
KJlOe26OVCPgMsawEn/nMw5l4QHWxQU2t5xt5Dm9qYSaCt9Sip0oE1rDDbAMpptJ
|
||||
wDEmxnf3wa+DOP9OoFjBghSG4DA7E57nsxUqGOd5NoPiuZYs+5KU8qkUNyM4mo4C
|
||||
LZjtowKBgCWuDPr8MBL1S3ym55VTNUWcuMKUHZkMg0HjRpi8ABXeARoM5FAWykbk
|
||||
hFnI/Waj6EHNGoVVpKttJldP+uRRA+S3wZYKH4gRQcsWI/BVdiBkZgoTG7cwnixs
|
||||
CqMQ4p6In1Q/6EafPqkVQOY1abNKQ7ZGbhksB/fVbf37XfOnDj3H
|
||||
-----END RSA PRIVATE KEY-----
|
||||
25
internal/testing/tls/other.crt
Normal file
25
internal/testing/tls/other.crt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEODCCAiCgAwIBAgIRAMcIOg3oRf7n7mR1BUqNnlcwDQYJKoZIhvcNAQELBQAw
|
||||
DTELMAkGA1UEAxMCY2EwHhcNMTcwODI1MTU0NTUzWhcNMjcwODI1MTU0NTUyWjAQ
|
||||
MQ4wDAYDVQQDEwVvdGhlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AM4lPXHK6YV7GFMeSsHKVq33LDn8Zt0IkY5pGNCuuksPa3vYBFRkwTrrobKQDnmw
|
||||
jLeaQISyxyelhT4aIU34OQpGjOmCzceYPG0uOcecq7noVvbgw0UeQHkEL0p4NOlb
|
||||
9LMNVhKTgQUhZC5mYSIpXNxlE/LlGmqrFX/8peSaJ1oAkkB5FYN0gAbYhd8kpJX3
|
||||
9Nr2A+f88oicSX5K0L73LUFUrxoTcrFP1pWnFgg28vLvvzrW/VZtq4qJPl3GTLbM
|
||||
xHOu000LaHK8TgIJMCQUalh2q2nz+Htmtv+g/b27YEMGcDBW1qBX9oMKgoM/Z1Hv
|
||||
zjVhAm9I649heDUguFXCaoMCAwEAAaOBjzCBjDAOBgNVHQ8BAf8EBAMCA7gwHQYD
|
||||
VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBSUR1DE/STLJwf2
|
||||
hW4qXPrOSLqlmDAfBgNVHSMEGDAWgBTNBS7rmAcLgCfraHEZbvsVLjsOhTAbBgNV
|
||||
HREEFDASggpmb29iYXIuY29thwQBAgMEMA0GCSqGSIb3DQEBCwUAA4ICAQBG87BU
|
||||
UuDuUCnvcwUNbga8fhe1PR6z2jueKQiI10SxkYG/g6PMQGGYDNO9DZFKu9l/TMTf
|
||||
LnuEozN+Csig+wC+sc32/MdR35XTmkKtNhL0cVgvKP0Q6zNk97/QJErLtDpYb9VR
|
||||
Gm2Ky6FGDp+/EEUvQUKRpGBmWIqOtjxqQu8lLoJlt/TPhxJ0lGDd3c8WwaVFYTbS
|
||||
isBKdHpS2hkn/O1Yd4QtNk06pCpUDQuPumUOBoa+dK3y0jZ+e34h1NoR5EZvfRJy
|
||||
p3n7CLD2eZSNc4oWKb67X0RDao5LD0b51crjgsFYHhCTS+Mgh0YkgukQZ8uBKpUJ
|
||||
IBhz2Nr1QXykrJUhal9MrKukjczEikGxzK1VsDgxYY1kLBURhM9/TfvICmcAaQqv
|
||||
MF9B78lnoJiPZZxD+a5N9MawzN6QBqX8GpvhZoAnj6iAuNwKJVyENpZqravxeq2o
|
||||
buNjgQ+SmfqxQDfMD3lu95yidqD7bcDipJsXEPQzdBjZ1JOJCGi2eiAm50e6bq94
|
||||
CMKmmRjtIbF1hJnHeEFPvXqdPpqcyEvcaDebph/f+54wubTgwFI3VMnhhlv2EPIe
|
||||
rwcbZV3kNpUZWXAZVzYlQcbK+9US8PocOUzmqzA7ZZRO+rCWNxahHjdgrMK3fG6r
|
||||
WudSHHXawj3dkPeWrQde6SILSK/myhGLdjg1fg==
|
||||
-----END CERTIFICATE-----
|
||||
16
internal/testing/tls/other.csr
Normal file
16
internal/testing/tls/other.csr
Normal file
@@ -0,0 +1,16 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICgzCCAWsCAQAwEDEOMAwGA1UEAxMFb3RoZXIwggEiMA0GCSqGSIb3DQEBAQUA
|
||||
A4IBDwAwggEKAoIBAQDOJT1xyumFexhTHkrBylat9yw5/GbdCJGOaRjQrrpLD2t7
|
||||
2ARUZME666GykA55sIy3mkCEsscnpYU+GiFN+DkKRozpgs3HmDxtLjnHnKu56Fb2
|
||||
4MNFHkB5BC9KeDTpW/SzDVYSk4EFIWQuZmEiKVzcZRPy5RpqqxV//KXkmidaAJJA
|
||||
eRWDdIAG2IXfJKSV9/Ta9gPn/PKInEl+StC+9y1BVK8aE3KxT9aVpxYINvLy7786
|
||||
1v1WbauKiT5dxky2zMRzrtNNC2hyvE4CCTAkFGpYdqtp8/h7Zrb/oP29u2BDBnAw
|
||||
VtagV/aDCoKDP2dR7841YQJvSOuPYXg1ILhVwmqDAgMBAAGgLjAsBgkqhkiG9w0B
|
||||
CQ4xHzAdMBsGA1UdEQQUMBKCCmZvb2Jhci5jb22HBAECAwQwDQYJKoZIhvcNAQEL
|
||||
BQADggEBABuaL2t2Zcv9R72OH93EpIzgExL37odLUiIjTIIykK2TT/gb1LtnE1WK
|
||||
THdqaLpnPot9IqBofppfkXPMrG7vavJoPlAp0lU2FHYIz64PHou8lj9yiXezDKDn
|
||||
Jia3TOxCu5VTRnYT7Ypt8kSull/jlyBQgTP+P0YXwoYAXJteQr9O6yD75yWOAx3A
|
||||
f/oQS77xoe0jdU4RkEMQRQQUIiaNyH8Bx4CeETmPoJDzEiIvnC5xDoySks1VJK7b
|
||||
w11IANF7zO5UWtYv/i3+Wh5XLMJ0GIIVTpuGkeVaZCjB8goiBJXoaHOKsO5ygJo5
|
||||
N+nxwiDTwUIZM+dU88mtCh0dvJeMMfA=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
27
internal/testing/tls/other.key
Normal file
27
internal/testing/tls/other.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAziU9ccrphXsYUx5KwcpWrfcsOfxm3QiRjmkY0K66Sw9re9gE
|
||||
VGTBOuuhspAOebCMt5pAhLLHJ6WFPhohTfg5CkaM6YLNx5g8bS45x5yruehW9uDD
|
||||
RR5AeQQvSng06Vv0sw1WEpOBBSFkLmZhIilc3GUT8uUaaqsVf/yl5JonWgCSQHkV
|
||||
g3SABtiF3ySklff02vYD5/zyiJxJfkrQvvctQVSvGhNysU/WlacWCDby8u+/Otb9
|
||||
Vm2riok+XcZMtszEc67TTQtocrxOAgkwJBRqWHarafP4e2a2/6D9vbtgQwZwMFbW
|
||||
oFf2gwqCgz9nUe/ONWECb0jrj2F4NSC4VcJqgwIDAQABAoIBAQCmygix5gwU/KiM
|
||||
r6iqrOx+6sq0y9vqIIGsaKo0RfriukIrvHacVbzl0DpPADFGEit4beyfsQpjsI9i
|
||||
1L93l0uHXderIzMdt7XEXK9RKxjiXPLn4qj7ZmOhxloA9ctRuB3/NN4cP44XOZIV
|
||||
3K3gdvj0NS/zyZwbC/tkR2Vt1a/bJ8DFfaFrSdk/btpVY2BH/uWjMls1BYIs3tEk
|
||||
nroJYb+fyliC+n/QXrLQAPTVLfI3jyVjRYpW5b6mZHQk4SGDde1XbtqRCp/f6PLu
|
||||
H8FQumd+SfrjgTwefphSWCW2H/aMNsZpL9NK/hmglKg3OcGKhwQswdmL9rDVFQmy
|
||||
AqxXwxk5AoGBAPPmijvwg6bKwqeSCdhoYjQVNXdTbdda3arjC/G/vxhsVntXluOX
|
||||
wGF2jInAu+sAShBFdhG4JlL+itlpA/aDDIMUxsF+YnhTOX5pofL8Nr4sstY6wjBf
|
||||
4jVHQKLnaOm/mVpHqWABVv/M085XK7HHZR9e9y32ry1geRSiPF/E5I03AoGBANhf
|
||||
OPL5WWO7TOchWci6qRcv0kZ47iSE1JxSGnXr+KIIBPIAgrwhYkqgrl6noSe3x1qB
|
||||
tP7ZvmFWewxmo3mN2OwPTsxAhnjQK4D1PGJcsvf2A3f1uiwG+emqWpcMshTQnjda
|
||||
Hi2krfMaHwErE+dEbZiilLDRYAMAWlQodXnhllMVAoGAKdDIumYN7DavENO05Glh
|
||||
DNTmCcM//cASaQ3sKlJZjPJmEVd/Ax4tWYhdp/BnR28RQ6DlETylNW12mLesekMV
|
||||
jhOtz9a/QynhnY62uVYMfKZlMt14FZsayU+iAUvzbL/wps3KeC9CnzCaz7GaSCyL
|
||||
Zcl+T18PwZPcrnDyMOks1hkCgYEArrQUE3tpxbER4v12tTCiHuqp6eTyw+HMmXth
|
||||
ih1B3/KBq7Tl2mlKJ9+daygGYz9sY5OfRLcjlQxyxgyJqjfyEog5o4nmCd5rgfCB
|
||||
FRqsFrI5Er8B11K6rwSxqIzDrTLUzPSisU/qdAN/TT4vD+icZUXAsRQdZc7/IDya
|
||||
vhJ7ghECgYEAiZ6v380l2jcEk+tm4UYZ+nXq3c2wq8mZkQcEDVpqvDGL8k/dPEq9
|
||||
xOy7PVooQHQJyC2bgTbEKs6JYvzAYQmohiq7L3y9WxDQFJeImtzuNi0s/dwP+mBh
|
||||
R6htM5JwAO3JnE9V863M6kvtLEIk7XptI5gC0kN3Thi70yT3lnU+emk=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
25
internal/testing/tls/server.crt
Normal file
25
internal/testing/tls/server.crt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIENzCCAh+gAwIBAgIQSCdvhIY3KZ9w9YRcDH1oRzANBgkqhkiG9w0BAQsFADAN
|
||||
MQswCQYDVQQDEwJjYTAeFw0xNzA4MjUxNTQ1NTNaFw0yNzA4MjUxNTQ1NTJaMBEx
|
||||
DzANBgNVBAMTBnNlcnZlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
ANlVMhHZHjRWiGqmpOse1KZTmdUaiSgl3T88+Mie17UbiLmsOfnkd3PuEnKlXRXM
|
||||
sqOg15k9xfnV6SQebpBfkcwqJVH/USjWY4C1e2vDrva+j95L+uzDMZDF9nxpnjHE
|
||||
uHT9+hnrmB3Xted0tzxRC/77Cht8Kn4gaoljbBoZsRnv0vRRUYKA2OJHJRRCHhzZ
|
||||
AN6A+lWbWyvyvd3UeOLl3oFhk4lS5fwYY6RY8W9ZTJVxetVycvro42kK2jtpqUeF
|
||||
NercfjOg8VBWYjqB3Ey1wHDpjS407TW7RWEGA+3mP8ZFsuoYr7Rs+z8LprbYEPpF
|
||||
ojgvss+vMvjGkrcU8v0MR/sCAwEAAaOBjjCBizAOBgNVHQ8BAf8EBAMCA7gwHQYD
|
||||
VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRS8x8oOcBJnrrW
|
||||
jiS0t3qjKee63TAfBgNVHSMEGDAWgBTNBS7rmAcLgCfraHEZbvsVLjsOhTAaBgNV
|
||||
HREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQELBQADggIBAI9t4Pvn
|
||||
o0cW0SrC4EFNJqUaffbqUNd3i+dBn5/EQc8EGYVY3k8E8iUMHzRDyH+/VRp3RUOH
|
||||
oJDDS2uyeJ2InkC193MOpNJDQV5qf3yOmQCeVmmjcXkg1Nc+3oe8ttWiVW0ArFXS
|
||||
oud6V7/6qzIz3850ypi0Zz4KLVhSGzaI8dnzPopqNEG+ZbgIwvQLqWhv2bLpqycY
|
||||
5ANECpjH6QIEp8JNjga9Sp/LspNCqMDmVswBGarySNWZ1+uflg9X30hsdCzVPgX3
|
||||
KMy0wVelT/4y893BCTo2KendGh70jaGxm8nBH7OXkeki2TI6boAvsn2Iash0HSZ4
|
||||
hPacZ4QWFEYW4jZeu3ZNTJ9tc2u2jgpAGueOcWRY75CraLid1V5t1kpGxAtX4NvX
|
||||
X56e/IlmEI6qPsaoPouQ1riAWMdRQUT1FLNPanv4vElDYXBNcFl7knuS14JDCC0M
|
||||
K6ttSb2MxJYfC6J+OJpHQd2GWU5aO2uZcgi9jRMslNwR+R94bxI+q/bjrI31JsDz
|
||||
1pVRnGRWH7cDejA2f+q7X8/uRuA8bfnqBcu0uI9YR64W0VMMLe41+iR0wt5N3yWr
|
||||
/DalWIvmUvE8LoaGDwxV9T3xq1I1dWnASX+Xmb1SQ0CnhEEooZfQYfb3ffciG6mU
|
||||
UvVC0YW1cjOgb193W/N+Dgju7/a/e+XgbsgT
|
||||
-----END CERTIFICATE-----
|
||||
16
internal/testing/tls/server.csr
Normal file
16
internal/testing/tls/server.csr
Normal file
@@ -0,0 +1,16 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICgzCCAWsCAQAwETEPMA0GA1UEAxMGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEA2VUyEdkeNFaIaqak6x7UplOZ1RqJKCXdPzz4yJ7XtRuI
|
||||
uaw5+eR3c+4ScqVdFcyyo6DXmT3F+dXpJB5ukF+RzColUf9RKNZjgLV7a8Ou9r6P
|
||||
3kv67MMxkMX2fGmeMcS4dP36GeuYHde153S3PFEL/vsKG3wqfiBqiWNsGhmxGe/S
|
||||
9FFRgoDY4kclFEIeHNkA3oD6VZtbK/K93dR44uXegWGTiVLl/BhjpFjxb1lMlXF6
|
||||
1XJy+ujjaQraO2mpR4U16tx+M6DxUFZiOoHcTLXAcOmNLjTtNbtFYQYD7eY/xkWy
|
||||
6hivtGz7PwumttgQ+kWiOC+yz68y+MaStxTy/QxH+wIDAQABoC0wKwYJKoZIhvcN
|
||||
AQkOMR4wHDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEL
|
||||
BQADggEBAA76r3SIQUER3XyGp4MOrfKGwBE7RnALcxW1XkrhID2bng2hzovrZZNO
|
||||
xutL1zqPFCxClIKUxYAXpMeY8lnS6H8I6FoM6ALCZbK7q9rmMK198LPMo3zC6TsO
|
||||
rEP8HOF9wxaYubg8xaq8iDlaL4e418M0UPOlE75PtkDAjhY++7ZTsjPVr/9WsJpZ
|
||||
MmEZ5kSS59PZbMbyqXn5MxE0iSD0LfM+lmkIBwSvD8rjq3SQ5NKCg6CJkRRq7BVe
|
||||
bujA2pPb6ivS5pujjIxkdUoz6S0G+ewZG16kbBoygWuRVFD8xqR9Pa41KSPhpx85
|
||||
1qSrqR4zHvS3r+RS9UVIXnbh9ejW6Vg=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
27
internal/testing/tls/server.key
Normal file
27
internal/testing/tls/server.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA2VUyEdkeNFaIaqak6x7UplOZ1RqJKCXdPzz4yJ7XtRuIuaw5
|
||||
+eR3c+4ScqVdFcyyo6DXmT3F+dXpJB5ukF+RzColUf9RKNZjgLV7a8Ou9r6P3kv6
|
||||
7MMxkMX2fGmeMcS4dP36GeuYHde153S3PFEL/vsKG3wqfiBqiWNsGhmxGe/S9FFR
|
||||
goDY4kclFEIeHNkA3oD6VZtbK/K93dR44uXegWGTiVLl/BhjpFjxb1lMlXF61XJy
|
||||
+ujjaQraO2mpR4U16tx+M6DxUFZiOoHcTLXAcOmNLjTtNbtFYQYD7eY/xkWy6hiv
|
||||
tGz7PwumttgQ+kWiOC+yz68y+MaStxTy/QxH+wIDAQABAoIBAEzhDVAw/LVI8wK/
|
||||
JlGh21lm82Dl/SS9mDE5kUvunKGNNuVvXibewb65tb7mbjI68epeCEZGCtVg7RMA
|
||||
zN23YOzW79K8vWnzxMkP6bPqSecw69WYDRBZ0BvFW3cRKYuzagjAmws2Qt4zoz5Y
|
||||
FEV66gJtrVqhpqptLyKgj+n/sp1YiHMjkcJF5PGnAPudYxpiDHiPFked7vZtigyq
|
||||
eE2GCVbaOmRGPMe28JzRmOuFqEN9GccRjlq+AuzYe14lWKa6fZLVke78luByF//M
|
||||
RA+gGoKfHq859wACOEGbqShMWxC++y1HJ2Mu4adUlikzVq8rboinaAxzv56kc1n6
|
||||
EDc/qPECgYEA2x+qxHKSJnQuItZp0CpsDk51yr1fJcWtdEbcgmomiwMwl/Nk0OAB
|
||||
rplrW5gezyVRfMDsoqAnmBS301JEL/7QuEWvedTpptKC503Z+mCINPTZAaJfa7Jb
|
||||
KUCSQHO3hThfOXFMkb9mcJEVVNqtobnK61tC+JNOSd00Z9TdLQAbcokCgYEA/ehf
|
||||
Vq3md8bkQFlnMtFib8SIr8IP5j4JecFSeqa/OnBFfHJr0trUhoMO04uaQOl6lTT7
|
||||
9Ca36WfJsv3EGkMHEFeceOsPswf65bI/qgxgUS7Qmu+NZTjvXL0gCpeHLPzEaLTV
|
||||
CDXoo+YAJzzv9yWwVEvIIru5SJPVud6Gap5L1WMCgYAjfO91PXD6FVrbfYpJknVJ
|
||||
o99j5GOihG9hI5DW9kYjwXJ/SYYMZhsfoe1HOk3TEqIt6Djq5bFD6icTbIFqnIRF
|
||||
M9QFkTv+Lp3QxEUHTdcBbJ4wq5F0qcAl4DVPhu4z/zs83GKgQDVhCb5AreHtDWAV
|
||||
2gPwqjrFr7OrFUh0302SsQKBgQCOHKZn9HNfLOIKJj/9kHYhCoZaoSqW+rgA/rQ0
|
||||
U+oKQlaR/dTdsn9rPiVpP+S5WjSzGHHAyH79U4rv9Nryu/tTKUY5447o7JmAQJEj
|
||||
k0PBjItTfKrOMdy/MlehtggBpQQlerkVnF62hYAmdhP1Z5HWzIea8SkWNzBTlPn0
|
||||
6N6W8wKBgQCDBPXqGii/ur5IBQ+RASIL78RgIGH49XYcq7IrYFOuztGBbqf4qorR
|
||||
BTl/mWFFHr+fAcLHlC/qTSBBflGClElqcg+j+92RAI6HbCFemdSbnsv5FCmciL6e
|
||||
09x+oprKq3/WAVARUBif3RtDq92SFwSBfrx8JFhGIa9kUcsDFSBaDQ==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
16
internal/testing/tls/wrong-ca.crl
Normal file
16
internal/testing/tls/wrong-ca.crl
Normal file
@@ -0,0 +1,16 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIICgjBsAgEBMA0GCSqGSIb3DQEBCwUAMBMxETAPBgNVBAMTCHdyb25nLWNhFw0x
|
||||
NzA4MjUxNTQ1NTdaFw0yNzA4MjUxNTQ1NTdaMACgIzAhMB8GA1UdIwQYMBaAFGM6
|
||||
559SRpX6VJlKbIObtrLXvc1rMA0GCSqGSIb3DQEBCwUAA4ICAQA+6zsHCq9YRZ+a
|
||||
fwsZmbGQqDUBVp5TWtDsy+qvKf/084CgTn0sR28HKEONQvX+R1CyzAaCGrkm081k
|
||||
yDUizdyGrVR8zmCc7O3ztPobfZBmQXbR0pcxwweFiELBO1exEQ5IpM4J0KOPc+CB
|
||||
AwVA227Q4oKrKyUtNQ9d3qr0/2E2HE8W0aV7Ax38MvXsUfafWk0SNPDusFiYNsTt
|
||||
v50Gmd2yBlaMzT9Dsze9wuoTvT42lpCby/NSSDYynG9Cra2y0VoIpVxvPqVzn77L
|
||||
1otkaQRatbfktaa1WVufEK81FXEyeYdM/T4bKCbB5oBKxmwwiS8ukvunc9gdrosx
|
||||
/7QIlpr3iBEu+X+GeOdGyPA+a+S566Hil38QCyMUX4fI7xjYt3ek2MI1YeNEv576
|
||||
CwEihc7NvPh5MI0OQq0nIjTcIeEGQag0eAaGwmJ+AmrZ+4bop5QavuwHVh/7sNel
|
||||
rNhFucmx+gEPeS/Ae0cp2BeXjEXHmdfwKDCT1n1Rdd5wfLeFuKlBG8NdZKwZ7HH7
|
||||
vwi+JwIBx/WJ/f1qcPlWAyF4Y/HUJIRRNSXJOUQCWhCvfGtLQ4xs7uo4ViE8CtyO
|
||||
RE/3xHrX9UwJUymw50Efj3PpOxWPvJ9B7A+8ED1kJR29HQz5gVZhEcW6nDTntwDQ
|
||||
5nrvhzi93xzaTQIhEe53uz+rTGs/fw==
|
||||
-----END X509 CRL-----
|
||||
29
internal/testing/tls/wrong-ca.crt
Normal file
29
internal/testing/tls/wrong-ca.crt
Normal file
@@ -0,0 +1,29 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE5jCCAs6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwh3cm9u
|
||||
Zy1jYTAeFw0xNzA4MjUxNTQ1NTRaFw0yNzA4MjUxNTQ1NTdaMBMxETAPBgNVBAMT
|
||||
CHdyb25nLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzs91DHKf
|
||||
enUfIPigN6n4CsTSHGMkRpWQw9T5vIqUd3ZgMJLCoEnj0OpVlFVcb9+P2/f5RYg8
|
||||
H0dmM1iHGi5uCoDnJk7K5zaLMgxGtRy8dwZ1nHri3sZwM6Z9AyvktuSVbuElzp08
|
||||
utwB+fstR2METN5Dovp738rdx2q3zYYYcfnEXcYvxBdxVeitFgrXauRDxuqMio+5
|
||||
5bHVUpIWlpu8Fd3CqnMUS4N6McijIn6T2wiyALJDf9xE7edAYl4ExYVKaOyR3Ff3
|
||||
+BhiS+IEPd9AoctU9JYFpDavfaiZz77AukwwfU+W93NTTFQ+rf/ev8XzsgkFyZPw
|
||||
CCfKuet+o2/8MIxv4nwKxv6GGMFbQz1gNw3RqG5m19zppqVzp1vgMcNXSeRPFlQI
|
||||
fXYFN9BY9bvx2L2ZpTn3gsdgzDGNzYU5fGro3YzmtelNBCY3sAz/riG6+wMDHCId
|
||||
5K5NxrJBW3tTvEZQyKVZA1W22/F/Wz2LxA+4ZLhUoUuXTkJxLS75EWLkK2xK+IXv
|
||||
h4s8n8CwfhFV/De7u18Pho0XKTm2IPir1nL0WNhjy4jvBYDN/Jy4fE4QALt4oH9t
|
||||
+GITkDo0Zd/USZSkAOXgEb+Ks5F/fztI9yVp7/nhhj1KnSIrkObBaltlfEOe4vgz
|
||||
3dNFAG5kH5lyG90uffKR7h1Vo7UkYcpJ3IMCAwEAAaNFMEMwDgYDVR0PAQH/BAQD
|
||||
AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGM6559SRpX6VJlKbIOb
|
||||
trLXvc1rMA0GCSqGSIb3DQEBCwUAA4ICAQCIm4pRbel8V/W1OKSzJXg3F76ORIjz
|
||||
zN4mAtCX8YtFQwawBlES25Ju2IQ9XfvqM0CPO5LEe1v8ZTXeke9Vjf/XGReBCCqy
|
||||
/STzLSBHflQqvybMYH87K5h5e91Ow9T2HjyPtzS3RdyaahU/Y8/EnOTG89uJlpN3
|
||||
0k0/KXfwVKAyjrOaoTeGPM9BjDssNq2S07h5C8sCby3MpR26CIMGbFnotwTjmSww
|
||||
qkDSVd63/ZIB5/dOcOlBd1+rE3LOzYxDiZtKWu0NM+o7N0m4Y+gD4siyxRWuKslz
|
||||
cTwiwnLmzZG5BUvRT2FmzCwejp45+LjrXmUZ8hCznk68hnkilx9XLdkBL/1qyk40
|
||||
I1IUFQtkkcyznwUyKpC0z4VJZAVL8xi6KO60TOYtidxFTJxkPrWcAHvgzItao3XZ
|
||||
C4hLlNk7RD6BJ8oyMtpXFq7MHAAb8MWSLu/rSAhQHoKqlCEK4Iks9nWLmRP0OdAw
|
||||
BcXGMuTIn1jFRM0CQvg68GPFOH3FKv+cyUbjPoXvCBYiXKmxA/WX3rYvDo2paZKU
|
||||
/mDMu+EdAR3Zk/wYXl4738ujqzO88Nw2LBHLKhXytHMaSbfmWf085r0L+fqHLuVM
|
||||
jlpPEi6vQum25j9tvGnp6GyO8lUDAUqk5gtYIp+D67+NG+9eBocA1ADVpeKZHBQV
|
||||
xGgCdjnoP+nDVw==
|
||||
-----END CERTIFICATE-----
|
||||
51
internal/testing/tls/wrong-ca.key
Normal file
51
internal/testing/tls/wrong-ca.key
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
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
|
||||
-----END RSA PRIVATE KEY-----
|
||||
25
internal/testing/tls/wrong-client.crt
Normal file
25
internal/testing/tls/wrong-client.crt
Normal file
@@ -0,0 +1,25 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEJTCCAg2gAwIBAgIQINVcdjlrWhsKxwcXL4vbUDANBgkqhkiG9w0BAQsFADAT
|
||||
MREwDwYDVQQDEwh3cm9uZy1jYTAeFw0xNzA4MjUxNTQ1NThaFw0yNzA4MjUxNTQ1
|
||||
NTZaMBcxFTATBgNVBAMTDHdyb25nLWNsaWVudDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBANHAXfyT8vuNKc3FeedZH3865bF5PLbRs2R8CaUOdQj/HTM5
|
||||
xSsb5Tr3x6IkWK+5SwtnGdaLY7GktSktXyUNf2uZflXHCLAqiqcBpLNO9mcFAACz
|
||||
pRb7C18ZZ6d9b7UtPA5oK1Vt45iUzI+mdCC0BtRTWeyKdtTF4muD2TtF7RMQwnjf
|
||||
RGV1EkfQ3sKpX3P7daiA/W116NlESpX3J/VAzoQu+3BrDeXrqgEbNVl+/NN/7uA0
|
||||
RY0HxE75RNhL9yuz1VFP4/NaFOdWN3pSMKwcmiIeNC5n0eyW/fodQpOT/dcscfbP
|
||||
3a0XeoZkfB8nuVcGkmtUkuw0jPy+R64vnQvlugsCAwEAAaNxMG8wDgYDVR0PAQH/
|
||||
BAQDAgO4MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQU
|
||||
0pX6qc7YwiehN5AYh+p4JNkFEXwwHwYDVR0jBBgwFoAUYzrnn1JGlfpUmUpsg5u2
|
||||
ste9zWswDQYJKoZIhvcNAQELBQADggIBALH5pFNhbnro2vFE+8RqbRqOZZNoyKqL
|
||||
INY/e0MNPmhp4CE2BQcrxgFcRgJmyh4lOP8gmIHT8q/9kOvYqMmfU1vbVF/XLFsa
|
||||
NJxkQSX9uilV1LDykyRbwlI4McjdTW7rLEkW8YrZueMXnDYQHGx9L2qYWgXzA5yA
|
||||
Mfsgq3pr39sDVDfYg1H+0daA3nIw+OWDsWjORXvzo5TQzjUXLhREp6WuuRKBT1+p
|
||||
VHGAnUcwDEb6L1bWEloG9ogXJfsXuCUxF+/II1RYSKiAmjge1nDOM2USfIKfD5nz
|
||||
tLJn0pn0B5dyceJTgOK6dwCXwn0Gc99qVzBSSHtPe+abSuY5dNoIwtL4R4rDE4U+
|
||||
+y2vQwzum+GhHn/ZEDuYT/0+IDqkVxeWBiZ5IFEkRpBEFpmEJOdKWaSrIQWMpIjf
|
||||
FIlxY3VzUD8H5M65kMSRKXbRJ1zSHMcIFKK2R98SPuYnYmgc4kOh49WkEr6dj2B1
|
||||
0QNZxPg70HP3qWgCxf8F5Mxg5YOtz7gN6N3AutrlYV0KB/OT0h4lhtvW3inxRgID
|
||||
iAHw0A4X/1qbFUeSUpINZaVQFtBh6fT/JfYDDTFFBcoqrLOZpFaS7r6FbGP3FDS4
|
||||
v9MqsYOSA6LrOHMdRop+eDV718iGDcUVtIItRZZV0s4UTo5q0JpGBtVPo/R8ui19
|
||||
eaGxvLJT1Gd+
|
||||
-----END CERTIFICATE-----
|
||||
15
internal/testing/tls/wrong-client.csr
Normal file
15
internal/testing/tls/wrong-client.csr
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIICXDCCAUQCAQAwFzEVMBMGA1UEAxMMd3JvbmctY2xpZW50MIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0cBd/JPy+40pzcV551kffzrlsXk8ttGzZHwJ
|
||||
pQ51CP8dMznFKxvlOvfHoiRYr7lLC2cZ1otjsaS1KS1fJQ1/a5l+VccIsCqKpwGk
|
||||
s072ZwUAALOlFvsLXxlnp31vtS08DmgrVW3jmJTMj6Z0ILQG1FNZ7Ip21MXia4PZ
|
||||
O0XtExDCeN9EZXUSR9Dewqlfc/t1qID9bXXo2URKlfcn9UDOhC77cGsN5euqARs1
|
||||
WX7803/u4DRFjQfETvlE2Ev3K7PVUU/j81oU51Y3elIwrByaIh40LmfR7Jb9+h1C
|
||||
k5P91yxx9s/drRd6hmR8Hye5VwaSa1SS7DSM/L5Hri+dC+W6CwIDAQABoAAwDQYJ
|
||||
KoZIhvcNAQELBQADggEBAMxscjfVRQ0/0c6f0MWtJJe+vy5Gj26XHVy5EsbH1ofq
|
||||
eWF00CFlVw5CdznGV0NL6LOE+sz5sBKsN2sZU7xPeV5XRHVXpAuECcOcgWK6FkqA
|
||||
wSmwVWZ93o+kJXrUZTyZBMkvQMUUr30JIpXIXJmLWKPBq5KRBJLirHZYw4FmbARv
|
||||
iZdNlQ1rLOZKZl7yUVkAfyfw+ueb0OPFp/fzuCerNB0ySSmYdHzqDFsMLm4Bq/2z
|
||||
FJOgasAU2RvxFVoRp7P/ZUSvtOKACMUHaYBnKZAvkKv3MIS/qLCSkzxNwsclT8uF
|
||||
aKRFZnOkZZHvEaXVAwCfJB7tI3TELb+L2KyqU36Q1Oc=
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
27
internal/testing/tls/wrong-client.key
Normal file
27
internal/testing/tls/wrong-client.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEA0cBd/JPy+40pzcV551kffzrlsXk8ttGzZHwJpQ51CP8dMznF
|
||||
KxvlOvfHoiRYr7lLC2cZ1otjsaS1KS1fJQ1/a5l+VccIsCqKpwGks072ZwUAALOl
|
||||
FvsLXxlnp31vtS08DmgrVW3jmJTMj6Z0ILQG1FNZ7Ip21MXia4PZO0XtExDCeN9E
|
||||
ZXUSR9Dewqlfc/t1qID9bXXo2URKlfcn9UDOhC77cGsN5euqARs1WX7803/u4DRF
|
||||
jQfETvlE2Ev3K7PVUU/j81oU51Y3elIwrByaIh40LmfR7Jb9+h1Ck5P91yxx9s/d
|
||||
rRd6hmR8Hye5VwaSa1SS7DSM/L5Hri+dC+W6CwIDAQABAoIBAHml4JyRTdX4q+sM
|
||||
gcPcG3lVtkt0rfK1sh4wFgPlW5kpJE1GTwTOe+b0N5LhE5Jum4h0djbIxrwLc4n7
|
||||
J3g82M6VygCDm5VYRuvO9y+LNzrOWo8NoUyvsouoF0a7aCMipfcRETjNr7cZbX5O
|
||||
ooEpB+Dyqm+Wao7CaavDXySSTInGHG4AD9HM5nQsVIebS1HOkhI7SmNkZTOd3gzp
|
||||
bR/iZgaYI5eC7Zj7hHNr4gWdRBuefU8wLZZGoqByHRTSrKwICRLIkGyoMRAD9p9r
|
||||
S48lyUmd3BGHRPLmNl4u0kfsVlcCYBNKVZV6kkSVv4Wht/KVr3tl0+2wrNUe17w7
|
||||
vlCsCPECgYEA9AdHq3UjswD6PYITCdrDOGVLMpgL0obA6X2Fi6GsMiRXQgsvxMVY
|
||||
a4D2vtfZvLa8TSA+b8bK6uSMyD3mOHLxBkUPMQZxiHzq/ldK9vSIvzky0woF6suT
|
||||
J8fa2F0QfjlKWhFMwf6JVGyZl+vmYqqF55lxRJSWGZHSSxaxYPU7dbkCgYEA3Aqb
|
||||
YInpCp7zSlWYfXorwnyvsHsTdFbsoGGgMrc5l2B7PcP+Na7lm0dsNCrp7n7TSrE0
|
||||
8iIoqhYq5u7RlGf5QXzXcGgZQMHcxLcrqBPviEktuUifXZEwfw9NXrlt4iF/oVTc
|
||||
++7jqUZ+iH9NIMoxrQPXVdXJSJN9iwc7/yG6j+MCgYA+ehqsWCpSqx5mXwYW0M6I
|
||||
gs6U3n6wYNXFMeDeFf9rOwioHQsW2tu/cl46EDNr8HEXYfj6TzAmoWs13TszGqKA
|
||||
02+HQroQksLraVgFEChupOtRQtCvA33ignWSTYlqd6qEksdPJ6brWX6decUbX8M2
|
||||
v39TaqNfWok3tlClnUOi6QKBgQCIhfQ9g5OZyWE937nLMH/yHZaMMvCxIDWUlL3m
|
||||
eZQ7/dq5Sd9xw2AmZbwW6gFWvk2ubCBjkxoT3ckkm0xhfdlC7ohk79GrQh0N2HA3
|
||||
ypa1wmGiMhLe5PRoAUCJ4xbwVMRxfsvVbDTIlDpxyjo6e/kyVc3HLevDIe+k0QpC
|
||||
k9TC7QKBgDyghEPAM5euQHk2o7cMr0YK52vzoM1FGhrRAF5MhTYJEYBNu0Z+0McB
|
||||
G8kwy4WH5zMuvKj1zZckAzkbpD/iL3XQuzs9pZdNnXzdf25/us0pZ2a/6v5+fpmF
|
||||
JEuwQ1AztPEv4tLd3+xrmE+j+qd3xDqYt8eaWFswcuxchB6nUdqq
|
||||
-----END RSA PRIVATE KEY-----
|
||||
Reference in New Issue
Block a user