snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
This commit is contained in:
28
examples/features/advancedtls/README.md
Normal file
28
examples/features/advancedtls/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# gRPC Advanced Security Examples
|
||||
This repo contains example code for different security configurations for grpc-go using `advancedtls`.
|
||||
|
||||
The servers run a basic echo server with the following setups:
|
||||
* Port 8885: A server with a good certificate using certificate providers and crl providers.
|
||||
* Port 8884: A server with a revoked certificate using certificate providers and crl providers.
|
||||
* Port 8883: A server running using InsecureCredentials.
|
||||
|
||||
The clients are designed to call these servers with varying configurations of credentials and revocation configurations.
|
||||
* mTLS with certificate providers and CRLs
|
||||
* mTLS with custom verification
|
||||
* mTLS with credentials from credentials.NewTLS (directly using the tls.Config)
|
||||
* Insecure Credentials
|
||||
|
||||
## Building and Running
|
||||
```
|
||||
# Run the server
|
||||
$ go run server/main.go -credentials_directory $(pwd)/creds
|
||||
# Run the clients from the `grpc-go/examples/features/advancedtls` directory
|
||||
$ go run client/main.go -credentials_directory $(pwd)/creds
|
||||
```
|
||||
|
||||
Stop the servers with ctrl-c or by killing the process.
|
||||
|
||||
## Developer Note - Generate the credentials used in the examples
|
||||
The credentials used for these examples were generated by running the `examples/features/advancedtls/generate.sh` script.
|
||||
|
||||
If the credentials need to be re-generated, run `./generate.sh` from `/path/to/grpc-go/examples/features/advancedtls` to re-create the `creds` directory containing the certificates and CRLs needed for these examples.
|
||||
306
examples/features/advancedtls/client/main.go
Normal file
306
examples/features/advancedtls/client/main.go
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client is an example client demonstrating use of advancedtls, to set
|
||||
// up a secure gRPC client connection with various TLS authentication methods.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/credentials/tls/certprovider"
|
||||
"google.golang.org/grpc/credentials/tls/certprovider/pemfile"
|
||||
"google.golang.org/grpc/security/advancedtls"
|
||||
)
|
||||
|
||||
const credRefreshInterval = 1 * time.Minute
|
||||
const serverAddr = "localhost"
|
||||
const goodServerPort string = "50051"
|
||||
const revokedServerPort string = "50053"
|
||||
const insecurePort string = "50054"
|
||||
const message string = "Hello"
|
||||
|
||||
// -- TLS --
|
||||
|
||||
func makeRootProvider(credsDirectory string) certprovider.Provider {
|
||||
rootOptions := pemfile.Options{
|
||||
RootFile: filepath.Join(credsDirectory, "ca_cert.pem"),
|
||||
RefreshDuration: credRefreshInterval,
|
||||
}
|
||||
rootProvider, err := pemfile.NewProvider(rootOptions)
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return rootProvider
|
||||
}
|
||||
|
||||
func makeIdentityProvider(revoked bool, credsDirectory string) certprovider.Provider {
|
||||
var certFile string
|
||||
if revoked {
|
||||
certFile = filepath.Join(credsDirectory, "client_cert_revoked.pem")
|
||||
} else {
|
||||
certFile = filepath.Join(credsDirectory, "client_cert.pem")
|
||||
}
|
||||
identityOptions := pemfile.Options{
|
||||
CertFile: certFile,
|
||||
KeyFile: filepath.Join(credsDirectory, "client_key.pem"),
|
||||
RefreshDuration: credRefreshInterval,
|
||||
}
|
||||
identityProvider, err := pemfile.NewProvider(identityOptions)
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return identityProvider
|
||||
}
|
||||
|
||||
func runClientWithProviders(rootProvider certprovider.Provider, identityProvider certprovider.Provider, crlProvider advancedtls.CRLProvider, port string, shouldFail bool) {
|
||||
options := &advancedtls.Options{
|
||||
// Setup the certificates to be used
|
||||
IdentityOptions: advancedtls.IdentityCertificateOptions{
|
||||
IdentityProvider: identityProvider,
|
||||
},
|
||||
// Setup the roots to be used
|
||||
RootOptions: advancedtls.RootCertificateOptions{
|
||||
RootProvider: rootProvider,
|
||||
},
|
||||
// Tell the client to verify the server cert
|
||||
VerificationType: advancedtls.CertVerification,
|
||||
}
|
||||
|
||||
// Configure revocation and CRLs
|
||||
options.RevocationOptions = &advancedtls.RevocationOptions{
|
||||
CRLProvider: crlProvider,
|
||||
}
|
||||
|
||||
clientTLSCreds, err := advancedtls.NewClientCreds(options)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fullServerAddr := serverAddr + ":" + port
|
||||
runWithCredentials(clientTLSCreds, fullServerAddr, !shouldFail)
|
||||
}
|
||||
|
||||
func tlsWithCRLsToGoodServer(credsDirectory string) {
|
||||
rootProvider := makeRootProvider(credsDirectory)
|
||||
defer rootProvider.Close()
|
||||
identityProvider := makeIdentityProvider(false, credsDirectory)
|
||||
defer identityProvider.Close()
|
||||
crlProvider := makeCRLProvider(credsDirectory)
|
||||
defer crlProvider.Close()
|
||||
|
||||
runClientWithProviders(rootProvider, identityProvider, crlProvider, goodServerPort, false)
|
||||
}
|
||||
|
||||
func tlsWithCRLsToRevokedServer(credsDirectory string) {
|
||||
rootProvider := makeRootProvider(credsDirectory)
|
||||
defer rootProvider.Close()
|
||||
identityProvider := makeIdentityProvider(false, credsDirectory)
|
||||
defer identityProvider.Close()
|
||||
crlProvider := makeCRLProvider(credsDirectory)
|
||||
defer crlProvider.Close()
|
||||
|
||||
runClientWithProviders(rootProvider, identityProvider, crlProvider, revokedServerPort, true)
|
||||
}
|
||||
|
||||
func tlsWithCRLs(credsDirectory string) {
|
||||
tlsWithCRLsToGoodServer(credsDirectory)
|
||||
tlsWithCRLsToRevokedServer(credsDirectory)
|
||||
}
|
||||
|
||||
func makeCRLProvider(crlDirectory string) *advancedtls.FileWatcherCRLProvider {
|
||||
options := advancedtls.FileWatcherOptions{
|
||||
CRLDirectory: crlDirectory,
|
||||
}
|
||||
provider, err := advancedtls.NewFileWatcherCRLProvider(options)
|
||||
if err != nil {
|
||||
fmt.Printf("Error making CRL Provider: %v\nExiting...", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
// --- Custom Verification ---
|
||||
func customVerificationSucceed(info *advancedtls.HandshakeVerificationInfo) (*advancedtls.PostHandshakeVerificationResults, error) {
|
||||
// Looks at info for what you care about as the custom verification implementer
|
||||
if info.ServerName != "localhost:50051" {
|
||||
return nil, fmt.Errorf("expected servername of localhost:50051, got %v", info.ServerName)
|
||||
}
|
||||
return &advancedtls.PostHandshakeVerificationResults{}, nil
|
||||
}
|
||||
|
||||
func customVerificationFail(info *advancedtls.HandshakeVerificationInfo) (*advancedtls.PostHandshakeVerificationResults, error) {
|
||||
// Looks at info for what you care about as the custom verification implementer
|
||||
if info.ServerName != "ExampleDesignedToFail" {
|
||||
return nil, fmt.Errorf("expected servername of ExampleDesignedToFail, got %v", info.ServerName)
|
||||
}
|
||||
return &advancedtls.PostHandshakeVerificationResults{}, nil
|
||||
}
|
||||
|
||||
func customVerification(credsDirectory string) {
|
||||
runClientWithCustomVerification(credsDirectory, goodServerPort)
|
||||
|
||||
}
|
||||
|
||||
func runClientWithCustomVerification(credsDirectory string, port string) {
|
||||
rootProvider := makeRootProvider(credsDirectory)
|
||||
defer rootProvider.Close()
|
||||
identityProvider := makeIdentityProvider(false, credsDirectory)
|
||||
defer identityProvider.Close()
|
||||
fullServerAddr := serverAddr + ":" + port
|
||||
{
|
||||
// Run with the custom verification func that will succeed
|
||||
options := &advancedtls.Options{
|
||||
// Setup the certificates to be used
|
||||
IdentityOptions: advancedtls.IdentityCertificateOptions{
|
||||
IdentityProvider: identityProvider,
|
||||
},
|
||||
// Setup the roots to be used
|
||||
RootOptions: advancedtls.RootCertificateOptions{
|
||||
RootProvider: rootProvider,
|
||||
},
|
||||
// Tell the client to verify the server cert
|
||||
VerificationType: advancedtls.CertVerification,
|
||||
AdditionalPeerVerification: customVerificationSucceed,
|
||||
}
|
||||
|
||||
clientTLSCreds, err := advancedtls.NewClientCreds(options)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
runWithCredentials(clientTLSCreds, fullServerAddr, true)
|
||||
}
|
||||
{
|
||||
// Run with the custom verification func that will fail
|
||||
options := &advancedtls.Options{
|
||||
// Setup the certificates to be used
|
||||
IdentityOptions: advancedtls.IdentityCertificateOptions{
|
||||
IdentityProvider: identityProvider,
|
||||
},
|
||||
// Setup the roots to be used
|
||||
RootOptions: advancedtls.RootCertificateOptions{
|
||||
RootProvider: rootProvider,
|
||||
},
|
||||
// Tell the client to verify the server cert
|
||||
VerificationType: advancedtls.CertVerification,
|
||||
AdditionalPeerVerification: customVerificationFail,
|
||||
}
|
||||
|
||||
clientTLSCreds, err := advancedtls.NewClientCreds(options)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
runWithCredentials(clientTLSCreds, fullServerAddr, false)
|
||||
}
|
||||
}
|
||||
|
||||
// -- credentials.NewTLS example --
|
||||
func credentialsNewTLSExample(credsDirectory string) {
|
||||
cert, err := tls.LoadX509KeyPair(filepath.Join(credsDirectory, "client_cert.pem"), filepath.Join(credsDirectory, "client_key.pem"))
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
rootPem, err := os.ReadFile(filepath.Join(credsDirectory, "ca_cert.pem"))
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
root := x509.NewCertPool()
|
||||
if !root.AppendCertsFromPEM(rootPem) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config := &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: root,
|
||||
}
|
||||
|
||||
// Directly create credentials from a tls.Config.
|
||||
creds := credentials.NewTLS(config)
|
||||
port := goodServerPort
|
||||
fullServerAddr := serverAddr + ":" + port
|
||||
runWithCredentials(creds, fullServerAddr, true)
|
||||
|
||||
}
|
||||
|
||||
// -- Insecure --
|
||||
func insecureCredentialsExample() {
|
||||
creds := insecure.NewCredentials()
|
||||
port := insecurePort
|
||||
fullServerAddr := serverAddr + ":" + port
|
||||
runWithCredentials(creds, fullServerAddr, true)
|
||||
}
|
||||
|
||||
// -- Main and Runner --
|
||||
|
||||
// All of these examples differ in how they configure the
|
||||
// credentials.TransportCredentials object. Once we have that, actually making
|
||||
// the calls with gRPC is the same.
|
||||
func runWithCredentials(creds credentials.TransportCredentials, fullServerAddr string, shouldSucceed bool) {
|
||||
conn, err := grpc.NewClient(fullServerAddr, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
fmt.Printf("Error during grpc.NewClient %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer conn.Close()
|
||||
client := pb.NewEchoClient(conn)
|
||||
req := &pb.EchoRequest{
|
||||
Message: message,
|
||||
}
|
||||
context, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
resp, err := client.UnaryEcho(context, req)
|
||||
defer cancel()
|
||||
|
||||
if shouldSucceed && err != nil {
|
||||
fmt.Printf("Error during client.UnaryEcho %v\n", err)
|
||||
} else if !shouldSucceed && err == nil {
|
||||
fmt.Printf("Should have failed but didn't, got response: %v\n", resp)
|
||||
}
|
||||
|
||||
}
|
||||
func main() {
|
||||
credsDirectory := flag.String("credentials_directory", "", "Path to the creds directory of this example repo")
|
||||
flag.Parse()
|
||||
|
||||
if *credsDirectory == "" {
|
||||
fmt.Println("Must set credentials_directory argument to this repo's creds directory")
|
||||
os.Exit(1)
|
||||
}
|
||||
tlsWithCRLs(*credsDirectory)
|
||||
customVerification(*credsDirectory)
|
||||
credentialsNewTLSExample(*credsDirectory)
|
||||
insecureCredentialsExample()
|
||||
}
|
||||
35
examples/features/advancedtls/creds/ca_cert.pem
Normal file
35
examples/features/advancedtls/creds/ca_cert.pem
Normal file
@@ -0,0 +1,35 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGJTCCBA2gAwIBAgIUQIWlFBWaWCYUunTANnlB4XZeFeUwDQYJKoZIhvcNAQEL
|
||||
BQAwgaExCzAJBgNVBAYTAlVTMRAwDgYDVQQIDAdHZW9yZ2lhMRAwDgYDVQQHDAdB
|
||||
dGxhbnRhMRAwDgYDVQQKDAdUZXN0IENBMRwwGgYDVQQLDBNUZXN0IENBIE9yZ2Fu
|
||||
emF0aW9uMR0wGwYDVQQDDBRUZXN0IENBIE9yZ2FuaXphdGlvbjEfMB0GCSqGSIb3
|
||||
DQEJARYQdGVzdEBleGFtcGxlLmNvbTAeFw0yNDA4MDgxODA2MjFaFw0zNDA4MDYx
|
||||
ODA2MjFaMIGhMQswCQYDVQQGEwJVUzEQMA4GA1UECAwHR2VvcmdpYTEQMA4GA1UE
|
||||
BwwHQXRsYW50YTEQMA4GA1UECgwHVGVzdCBDQTEcMBoGA1UECwwTVGVzdCBDQSBP
|
||||
cmdhbnphdGlvbjEdMBsGA1UEAwwUVGVzdCBDQSBPcmdhbml6YXRpb24xHzAdBgkq
|
||||
hkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC
|
||||
DwAwggIKAoICAQCs+Px6CMv0x3dmmK9PEdIq95J0JQ7Y6NojD93oosZxqi0QLzxU
|
||||
LiRamNOvoMSBgbUl1GtC8xcQQ/YiaBS0A+tc+7NxZ6SJXIa/i7tbJcebPY5bnbHc
|
||||
ILXPOt4FLEgcBqyv9UquPstkYytJje4J0N+G/nqfKsh+mo+emnKFSy1QS7NoPr/T
|
||||
fDKemnf2DBk0HOiBnIr2gh3gqThXqUt/dZlDNJALeJU+7IpLDThOM3sf1QOOkSF9
|
||||
O1IM1YJt3B9GeTDwPnqKbXVOKf23eBi51QyvWde1ZscTRh0p9HX4VRCYOGfkQnWw
|
||||
0d3BpFg/a6rGVNLSPBGE2H6O68L4K1bBDV0CvdTjVD8/vgrLm/7NAOlg/58TKIaq
|
||||
NxFalXeLmdKr0c5d4JZEbbPgg26O8Fsq769s8Jc1dtnAiFwB2opIOvOLZkNwzPG8
|
||||
EjAET9HmjWHHzZ/OmswWamqywPukW8jdLH5f4RsuGpGHsUvs/53fUUeAdAlceJ+1
|
||||
KuLNuk7ULRU59TRbppt6m/Ws81bWJLQtw79BdyDNgJ4q7Vyl5tCuC2mZzDqOb/uK
|
||||
py5Gx6Upoy0klAsMjvUBiw3cpkVCl1/RCSx2HmV85itS20QCiFcT+KeJ3xSbIc+P
|
||||
ScNvinnbwtRhENQY+fy5MAfy9kvEdlYlsM2yp3l1B+Z4My6w8e2CcQO4RwIDAQAB
|
||||
o1MwUTAdBgNVHQ4EFgQUUHbDXGsS4ZIPKPjyQ6aAwpzoVtYwHwYDVR0jBBgwFoAU
|
||||
UHbDXGsS4ZIPKPjyQ6aAwpzoVtYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
|
||||
AQsFAAOCAgEAe7/P+MvYYM8gBN0AHQtmG4SaPpiE+Wi8TU4KSU6n+gzM347bPUnH
|
||||
TbxMs1gYkiQ4IsYnU/uY2L+lCVvBpd66aIM9dJ5WGHS1RyRjRCUwZNEu9UIizemp
|
||||
JSWu6hql96ib1AFAnXbjC8uNFG8OK+aF/NhChnu1pWKLLAMgBXhG8e5z7wNjQHqB
|
||||
D/FOOBEn6ljR6MhBsRyPZxz/tqEt5hGflgeQnZXC2dzmQQDRfEWq9jjDgIVGpOjZ
|
||||
VNYnua0GxdJGmRtExPHCf4bmClGf9uW1GK1ViCnj6Qlsvln0eOgNkI/m/VxjuSvE
|
||||
NDUF+jWK7z+O0nagDSDTIGUU/enSFpdAHrUQuyqKS1S8WHhf4AIi0DNkUhHVojk6
|
||||
40nUPxVHl8R7wPXu3K7sTCfNJFJsqY8+oMhS3lk05voDuPJAgWnvG3wnE5rDWi/Q
|
||||
R7CLMnnYQ7oIyJ9mE8ZLDWd9Udov+n/y5VkFVh8WFbu9Vidvlpy9xXQKaJVP4EHa
|
||||
K0nLHGSw1zRrB+zx0Ep7ow/zGDxT8kCcKMQ/Uonv6kRxpi90oBdvNNXzsTkQ+FZ3
|
||||
168nBjWf+X6XX/HalbRiKmgww6SqG+hoVXP0cFw3vJwgESeXJHbxCcu1mJdzSbr3
|
||||
HzRkGKgTKIBV0z2AMG3cLCW/DO4+45GKi/DYibz0GjvFkXT8cGhN5vM=
|
||||
-----END CERTIFICATE-----
|
||||
52
examples/features/advancedtls/creds/ca_key.pem
Normal file
52
examples/features/advancedtls/creds/ca_key.pem
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCs+Px6CMv0x3dm
|
||||
mK9PEdIq95J0JQ7Y6NojD93oosZxqi0QLzxULiRamNOvoMSBgbUl1GtC8xcQQ/Yi
|
||||
aBS0A+tc+7NxZ6SJXIa/i7tbJcebPY5bnbHcILXPOt4FLEgcBqyv9UquPstkYytJ
|
||||
je4J0N+G/nqfKsh+mo+emnKFSy1QS7NoPr/TfDKemnf2DBk0HOiBnIr2gh3gqThX
|
||||
qUt/dZlDNJALeJU+7IpLDThOM3sf1QOOkSF9O1IM1YJt3B9GeTDwPnqKbXVOKf23
|
||||
eBi51QyvWde1ZscTRh0p9HX4VRCYOGfkQnWw0d3BpFg/a6rGVNLSPBGE2H6O68L4
|
||||
K1bBDV0CvdTjVD8/vgrLm/7NAOlg/58TKIaqNxFalXeLmdKr0c5d4JZEbbPgg26O
|
||||
8Fsq769s8Jc1dtnAiFwB2opIOvOLZkNwzPG8EjAET9HmjWHHzZ/OmswWamqywPuk
|
||||
W8jdLH5f4RsuGpGHsUvs/53fUUeAdAlceJ+1KuLNuk7ULRU59TRbppt6m/Ws81bW
|
||||
JLQtw79BdyDNgJ4q7Vyl5tCuC2mZzDqOb/uKpy5Gx6Upoy0klAsMjvUBiw3cpkVC
|
||||
l1/RCSx2HmV85itS20QCiFcT+KeJ3xSbIc+PScNvinnbwtRhENQY+fy5MAfy9kvE
|
||||
dlYlsM2yp3l1B+Z4My6w8e2CcQO4RwIDAQABAoICAA//iW6KEL8nkcIR/ijsh4lE
|
||||
061dXhWu17oldgtVvs/1gux7yfMpP2CHwRB96J7nzcbdcjxDeo8dEg9VnBCYSjUT
|
||||
7KFhCiVQQwBFXsNL573SgC+2EqS++8Haen10/ohlD6TIpasfELXMvEy1zV3oDTyR
|
||||
nerJzLh0+DKdq1jrvpmuHr5WC2z2kEH+HHlL3irlP5X5UhsBptzIGfd1p49244GF
|
||||
Q4tkED29J/9QDjSha1Ji48zUXIoWKf0Y5FLf6J6eh+m4haH3BMIBfT9yYqsRavZu
|
||||
81YKVwBP3FOskhqxV3MUyHsisHr1tjJ6TlUzUpy8bLFYL/CfC3mRkbtdWs1JPKBk
|
||||
2BFZVBU0JeTS4SB2kSSjxHMDTi5lhCzTgdzNk9z3FvrwPYAYV9eTdEoWnwRlGjQo
|
||||
IAwde0EQk508JCBG7RXpn+yp7ye0y1WvmxvwTx5mshSf9S90wrquaFryOZzAO+qa
|
||||
FbQBPhWdtz/NBEqZa3teNo+kvhm90Ey6BcoO75EFVVPJaDlCjZ7jrzSy4XuYi99F
|
||||
NjgmXUnGTRgYu+aOItBX1ckBNUg6kSVXk4iIVpXD65wANTjNladeK8ZWBq1k1JEd
|
||||
V+VBdQu5H0JzOi01i4jDzzb/6T7lIj1NFpi5PL7T6q6EelC7QpxJpoKGd3YVQPWr
|
||||
zvLR1bS2Fsg3hNlkFr6RAoIBAQDYsZjJcnlGREg6aNmjyY+s2jQhk3SU65VmBH+z
|
||||
IKg8Nk1erW0eVtY/nYdFVcuyH84VxpINDDIfbVzwr7tz6qAWb3+dgEKZUVv1MHv0
|
||||
S12snsO3NdZ0UKdrqr16K0d9oa3OyaCSi89zKKtfPhIPMxLRehZipXecyCM+1Rda
|
||||
AHCAjmJWD9VA+izHtLQB92+d+pl3BUpWi5wxBomOP5VqJ2slOdpd2sD94EhumQbR
|
||||
Gk0/4kj5PHx3r7nhgSJoAvO2HMV/PvvoiGXf+4Oi3vhgACnBA8N+zEaKvLHBRZNF
|
||||
nIkoxAgu5erEsnuJJEOEgpnsiLD7ZChbnPQgpD4p9t7Da7jXAoIBAQDMWSb28T4m
|
||||
DIm50G4Q2Hnhz6EEaGZSEbyq5AejWLrhgvJHpo/mOaFoNOPQuzV3WpBgM6Uvltcq
|
||||
Kk6uthA6Vr3haiJkxWMXI5rnszHnEd2VMKBfsui60Z2leWOdiEH2CkBroU67ZV0x
|
||||
9X7P3LlxClJ91yP9iPLz6Cx8QZWZ2WMQabODb86K8aQ5pqLcTgclujCk7NmSM9T3
|
||||
eBz8mlVMFBsnudIe1A04e4EYHkVUvWtAltQbILsvbxLVMGRGNe2rKZ+wkke/xbf+
|
||||
Agv1LL6LwyGOAdt+71DWzFsdML5UEAkJ5EA3ERiOthhpFllvmV3pr9cMCIS0ivSH
|
||||
S925tO1rvt4RAoIBAGEWOSFQs7tizoW1AoYawc+tOBwvB9XNM3Ow4lIseJP5tHKN
|
||||
+0zTlUyNVNUg2pHlJB2niTplU3O3OSPxaGhIIA/NRv0XQT+WL0BMx8ytk7vKql/E
|
||||
tGAK3ugjaJ97Ep3cOZZjyhi+oWS0PQwAMHE07eKC89Kg1lWdagU1zi+Z8M34fWCX
|
||||
2XEyZavYb6pN5Wl/pRCpgyQBiyqABlOAc35LSPs1z3urjjpxKaK7100Knr/Xr+BT
|
||||
VGT/i6XYiMTXRcA7ZdVcL9uAeTyAYPsxMVE54XtEJ2wBND3myzGP7asLtnxYUF5K
|
||||
zwPv/99zKvkM1tAeckVAG8DoMo0JaXy9yhL+iaMCggEAXFqSfJqM/v89o4fqppxf
|
||||
gUmoOOjCDadMgGNsfEuWsmLPAsjpUiCLrR/yMhzZziZVB9Vve3GNrtXOF7Ha5bLc
|
||||
QCsKfkajQQrrcHoRPKBbZ5jBcl7WRdCEkguplMHHJd5+POZ7QcBO/Uw5UtIr0UXc
|
||||
AFmiP2yMeOVebY3qgcy4s+tBoU5/p1YMZa3E/xIYstlSMMeGkUfxoSJc32EU2bxg
|
||||
hXS63QnzK6rNrkvIA8NT3K4OEHCbiJWHimhDeWPYFTpLnK6P1MEUJa1hIB5nw5yd
|
||||
5qM6Q0T/YQSczTWBX1ab7yeESh7k3WK4542dQA2tXvcElsCm0T3Xw+nqvIpjnwV1
|
||||
MQKCAQAbK3pgm6uE4x7iC7KuFm+j1ccm1BX9t80XlSqMv3aPTufF2wbv+OIOJgLA
|
||||
HmY14nobAxt1f1AQ/9NfbZxiw8lzQMo407aXpubzHLSX4+S700quSAGBpexy2cqM
|
||||
Sft9gHWiblHw7NNC1IWG/H7MUv7UA+b8GQuVhRYvVi/YeErk4eb+tAvp8T3jg465
|
||||
PwQBCO4hkXZhUDYS8S0dL04vEeMSo8eh252LrNkjho/iU58ZDmGiyHr9XDq3awDR
|
||||
mfdkufXVKVigaCan7HmEOJUt2Dt2sIVn1dQ3qFzulG5FDTrUi/mefsH/FaAJoNsa
|
||||
/9XEh58NyCCGvjV0a6MHrcZRMl3y
|
||||
-----END PRIVATE KEY-----
|
||||
127
examples/features/advancedtls/creds/client_cert.pem
Normal file
127
examples/features/advancedtls/creds/client_cert.pem
Normal file
@@ -0,0 +1,127 @@
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 3 (0x3)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C=US, ST=Georgia, L=Atlanta, O=Test CA, OU=Test CA Organzation, CN=Test CA Organization/emailAddress=test@example.com
|
||||
Validity
|
||||
Not Before: Aug 8 18:06:22 2024 GMT
|
||||
Not After : Dec 25 18:06:22 2051 GMT
|
||||
Subject: C=US, ST=Georgia, L=Atlanta, O=Test client, OU=Test client Organzation, CN=Test client Organization
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Modulus:
|
||||
00:bf:8d:88:b7:20:0e:04:3e:5e:3a:f1:a3:78:2d:
|
||||
a5:44:f6:68:b3:f3:ec:3c:7e:8f:cd:e2:cd:55:9c:
|
||||
2c:a2:a6:a0:31:41:b4:10:cb:3a:a8:8e:9e:ae:b5:
|
||||
65:13:18:02:fc:35:38:7c:5e:6d:ba:e0:13:31:f0:
|
||||
65:bb:a6:d3:61:7c:7f:86:bd:d6:84:d2:b1:06:92:
|
||||
fe:47:5d:dd:3e:1f:99:6c:55:6f:67:eb:44:eb:d8:
|
||||
da:79:70:2e:d7:48:75:6f:1d:cb:bd:e6:59:17:22:
|
||||
d7:d9:23:26:90:0c:b9:63:85:91:9f:8e:58:92:52:
|
||||
b6:09:3a:80:b7:40:91:fe:47:b6:e8:3c:4d:44:97:
|
||||
ef:1c:11:a7:75:e0:19:d2:79:cb:3e:5d:f9:0c:81:
|
||||
95:63:6d:df:58:43:e5:03:62:78:52:0b:5b:5a:5c:
|
||||
c3:d9:8e:39:15:e5:72:37:b0:3a:ce:99:67:c0:72:
|
||||
ca:9f:65:25:7b:23:bf:87:bf:1f:a9:f5:0f:f2:bf:
|
||||
a1:ec:43:3b:8a:67:d0:5f:61:d8:03:74:e6:b1:25:
|
||||
91:45:70:85:d0:a2:70:65:df:4d:ed:39:6c:4d:c4:
|
||||
fd:fe:8d:71:92:06:90:ad:19:8e:de:0b:35:e1:50:
|
||||
79:30:6f:f6:bb:3d:74:a7:66:dd:0e:7b:d0:63:f2:
|
||||
5d:58:dc:17:a1:a2:e4:45:4e:b7:9c:32:b8:bc:56:
|
||||
88:31:de:6f:27:f3:56:29:54:45:07:68:f3:76:9d:
|
||||
b7:63:c0:d7:cf:6b:11:c5:3a:d2:9f:1a:34:96:2a:
|
||||
df:64:e1:df:fe:be:1d:4a:48:58:33:be:2e:c7:ac:
|
||||
c7:12:6f:9a:a6:10:e5:ef:a4:ae:0b:8d:c9:56:2c:
|
||||
49:60:ff:54:91:2c:41:05:90:74:70:3e:dd:54:58:
|
||||
b3:83:ae:c4:b4:4e:91:0b:a5:f1:3d:e4:5a:6d:34:
|
||||
5c:3b:ee:f6:d7:62:0b:a8:55:8f:5d:8a:ed:56:9a:
|
||||
8d:e7:80:16:0f:97:1b:f5:eb:0d:7f:1f:9a:51:e1:
|
||||
9b:3e:14:ac:f7:c3:36:42:06:11:7c:e9:ef:75:54:
|
||||
ae:1b:3b:68:b7:c4:79:fd:67:5c:26:9e:a5:d4:55:
|
||||
6c:c7:92:15:51:73:57:99:bc:de:fb:56:ab:70:db:
|
||||
98:10:1a:63:71:9c:c3:9f:11:9f:c2:c5:8b:ac:5c:
|
||||
52:69:c7:58:a1:b1:26:86:e3:68:85:23:17:68:62:
|
||||
30:01:79:1a:51:d7:e9:1b:a4:da:81:b6:46:33:1e:
|
||||
9a:2b:9b:f6:20:26:d0:21:10:b0:15:58:91:08:b5:
|
||||
bd:b7:c0:05:c1:cf:2f:bd:3b:18:40:17:08:92:58:
|
||||
6e:bb:bb
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
DE:03:BD:A3:0E:63:F4:97:C2:52:70:63:E8:BE:A9:DF:F1:9A:7B:56
|
||||
X509v3 Authority Key Identifier:
|
||||
50:76:C3:5C:6B:12:E1:92:0F:28:F8:F2:43:A6:80:C2:9C:E8:56:D6
|
||||
X509v3 Basic Constraints:
|
||||
CA:FALSE
|
||||
X509v3 Key Usage:
|
||||
Digital Signature, Key Encipherment
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:localhost, IP Address:0.0.0.0
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Signature Value:
|
||||
92:69:25:55:69:46:6e:3b:c3:a1:9d:00:b2:6e:b5:ae:1b:5a:
|
||||
19:2a:77:7f:12:e3:f7:84:72:37:35:26:78:45:5e:90:3d:0b:
|
||||
57:6f:1f:42:05:77:ec:4b:0c:29:dd:d7:db:02:cb:b7:2f:7b:
|
||||
cc:81:4a:cc:71:2f:54:aa:3a:27:e3:8e:cd:87:76:c1:5f:60:
|
||||
b6:34:0c:16:ef:fc:b6:ae:61:44:6b:b2:e1:db:86:15:e4:24:
|
||||
db:47:48:f2:29:14:fb:61:0b:10:97:b1:b2:79:c3:69:dc:f3:
|
||||
65:e9:15:a6:89:17:34:46:83:b1:a6:89:4f:12:e0:69:27:66:
|
||||
f8:89:df:36:21:59:a9:a5:e5:6a:8b:10:8c:19:39:cf:6e:61:
|
||||
a5:43:6f:34:b4:e1:79:7a:0a:f9:1d:2d:06:66:cb:a0:91:9c:
|
||||
04:85:4f:0b:3d:c1:54:a8:06:d3:89:2e:16:5c:f2:29:c5:f7:
|
||||
6e:d9:4b:ca:81:65:96:3c:ba:66:8e:40:16:a3:20:ca:ed:5a:
|
||||
ea:72:97:7a:2c:c4:b6:b5:c2:00:83:fb:1b:8a:d0:72:85:49:
|
||||
88:ad:81:9e:87:42:31:99:1a:39:ad:b5:ff:24:b5:e0:90:07:
|
||||
08:2e:1d:4a:a7:01:ef:97:9a:07:d4:e6:09:f5:c8:36:37:ce:
|
||||
e3:b2:94:2a:5e:95:e1:6a:06:68:d1:31:24:da:b4:fe:ce:af:
|
||||
a5:23:87:bc:7e:35:54:dd:c3:77:a5:44:95:43:a0:b1:f5:c4:
|
||||
f8:98:4d:a3:fc:33:ef:7a:d7:4b:5b:ae:de:2b:1f:7a:a1:3f:
|
||||
df:85:6b:97:57:4d:fa:b1:1a:79:4b:a7:96:62:09:99:b0:54:
|
||||
f1:46:65:dd:3a:31:bc:1b:07:97:ff:e7:1b:0a:d4:82:68:62:
|
||||
cc:66:9c:06:d4:18:70:3b:71:82:2d:76:bf:e7:56:88:4f:d9:
|
||||
5e:1b:46:9c:f9:9c:15:bc:73:ca:f5:e5:44:3d:f1:e4:b9:55:
|
||||
e6:06:80:e2:0d:4f:ba:19:e2:01:29:da:5b:6f:1f:79:6a:6c:
|
||||
d4:e8:c2:e1:12:c2:13:d0:5a:63:1d:35:f1:36:d4:1b:48:26:
|
||||
72:18:df:5f:7e:30:8d:86:42:cf:22:90:db:f8:6c:9d:b0:e7:
|
||||
3b:a1:0d:8a:b1:d9:de:a1:d0:4b:de:33:a2:fc:6c:cc:b0:7d:
|
||||
a6:57:43:fe:db:2a:44:e3:6c:68:ff:c8:82:91:19:68:f0:c5:
|
||||
6b:9d:3b:4c:f8:2d:8f:0e:44:04:79:4e:99:ec:08:c6:e6:25:
|
||||
90:5b:2d:16:18:94:fe:0b:86:9b:01:f2:40:66:ec:fa:ac:28:
|
||||
ba:33:fc:58:c1:8e:a2:06
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGIjCCBAqgAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCVVMx
|
||||
EDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExEDAOBgNVBAoMB1Rl
|
||||
c3QgQ0ExHDAaBgNVBAsME1Rlc3QgQ0EgT3JnYW56YXRpb24xHTAbBgNVBAMMFFRl
|
||||
c3QgQ0EgT3JnYW5pemF0aW9uMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUu
|
||||
Y29tMCAXDTI0MDgwODE4MDYyMloYDzIwNTExMjI1MTgwNjIyWjCBjDELMAkGA1UE
|
||||
BhMCVVMxEDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExFDASBgNV
|
||||
BAoMC1Rlc3QgY2xpZW50MSAwHgYDVQQLDBdUZXN0IGNsaWVudCBPcmdhbnphdGlv
|
||||
bjEhMB8GA1UEAwwYVGVzdCBjbGllbnQgT3JnYW5pemF0aW9uMIICIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAg8AMIICCgKCAgEAv42ItyAOBD5eOvGjeC2lRPZos/PsPH6PzeLN
|
||||
VZwsoqagMUG0EMs6qI6errVlExgC/DU4fF5tuuATMfBlu6bTYXx/hr3WhNKxBpL+
|
||||
R13dPh+ZbFVvZ+tE69jaeXAu10h1bx3LveZZFyLX2SMmkAy5Y4WRn45YklK2CTqA
|
||||
t0CR/ke26DxNRJfvHBGndeAZ0nnLPl35DIGVY23fWEPlA2J4UgtbWlzD2Y45FeVy
|
||||
N7A6zplnwHLKn2UleyO/h78fqfUP8r+h7EM7imfQX2HYA3TmsSWRRXCF0KJwZd9N
|
||||
7TlsTcT9/o1xkgaQrRmO3gs14VB5MG/2uz10p2bdDnvQY/JdWNwXoaLkRU63nDK4
|
||||
vFaIMd5vJ/NWKVRFB2jzdp23Y8DXz2sRxTrSnxo0lirfZOHf/r4dSkhYM74ux6zH
|
||||
Em+aphDl76SuC43JVixJYP9UkSxBBZB0cD7dVFizg67EtE6RC6XxPeRabTRcO+72
|
||||
12ILqFWPXYrtVpqN54AWD5cb9esNfx+aUeGbPhSs98M2QgYRfOnvdVSuGztot8R5
|
||||
/WdcJp6l1FVsx5IVUXNXmbze+1arcNuYEBpjcZzDnxGfwsWLrFxSacdYobEmhuNo
|
||||
hSMXaGIwAXkaUdfpG6TagbZGMx6aK5v2ICbQIRCwFViRCLW9t8AFwc8vvTsYQBcI
|
||||
klhuu7sCAwEAAaN2MHQwHQYDVR0OBBYEFN4DvaMOY/SXwlJwY+i+qd/xmntWMB8G
|
||||
A1UdIwQYMBaAFFB2w1xrEuGSDyj48kOmgMKc6FbWMAkGA1UdEwQCMAAwCwYDVR0P
|
||||
BAQDAgWgMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEAAAAADANBgkqhkiG9w0BAQsF
|
||||
AAOCAgEAkmklVWlGbjvDoZ0Asm61rhtaGSp3fxLj94RyNzUmeEVekD0LV28fQgV3
|
||||
7EsMKd3X2wLLty97zIFKzHEvVKo6J+OOzYd2wV9gtjQMFu/8tq5hRGuy4duGFeQk
|
||||
20dI8ikU+2ELEJexsnnDadzzZekVpokXNEaDsaaJTxLgaSdm+InfNiFZqaXlaosQ
|
||||
jBk5z25hpUNvNLTheXoK+R0tBmbLoJGcBIVPCz3BVKgG04kuFlzyKcX3btlLyoFl
|
||||
ljy6Zo5AFqMgyu1a6nKXeizEtrXCAIP7G4rQcoVJiK2BnodCMZkaOa21/yS14JAH
|
||||
CC4dSqcB75eaB9TmCfXINjfO47KUKl6V4WoGaNExJNq0/s6vpSOHvH41VN3Dd6VE
|
||||
lUOgsfXE+JhNo/wz73rXS1uu3isfeqE/34Vrl1dN+rEaeUunlmIJmbBU8UZl3Tox
|
||||
vBsHl//nGwrUgmhizGacBtQYcDtxgi12v+dWiE/ZXhtGnPmcFbxzyvXlRD3x5LlV
|
||||
5gaA4g1PuhniASnaW28feWps1OjC4RLCE9BaYx018TbUG0gmchjfX34wjYZCzyKQ
|
||||
2/hsnbDnO6ENirHZ3qHQS94zovxszLB9pldD/tsqRONsaP/IgpEZaPDFa507TPgt
|
||||
jw5EBHlOmewIxuYlkFstFhiU/guGmwHyQGbs+qwoujP8WMGOogY=
|
||||
-----END CERTIFICATE-----
|
||||
127
examples/features/advancedtls/creds/client_cert_revoked.pem
Normal file
127
examples/features/advancedtls/creds/client_cert_revoked.pem
Normal file
@@ -0,0 +1,127 @@
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 4 (0x4)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C=US, ST=Georgia, L=Atlanta, O=Test CA, OU=Test CA Organzation, CN=Test CA Organization/emailAddress=test@example.com
|
||||
Validity
|
||||
Not Before: Aug 8 18:06:22 2024 GMT
|
||||
Not After : Dec 25 18:06:22 2051 GMT
|
||||
Subject: C=US, ST=Georgia, L=Atlanta, O=Test client, OU=Test client Organzation, CN=Test client Organization
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Modulus:
|
||||
00:bf:8d:88:b7:20:0e:04:3e:5e:3a:f1:a3:78:2d:
|
||||
a5:44:f6:68:b3:f3:ec:3c:7e:8f:cd:e2:cd:55:9c:
|
||||
2c:a2:a6:a0:31:41:b4:10:cb:3a:a8:8e:9e:ae:b5:
|
||||
65:13:18:02:fc:35:38:7c:5e:6d:ba:e0:13:31:f0:
|
||||
65:bb:a6:d3:61:7c:7f:86:bd:d6:84:d2:b1:06:92:
|
||||
fe:47:5d:dd:3e:1f:99:6c:55:6f:67:eb:44:eb:d8:
|
||||
da:79:70:2e:d7:48:75:6f:1d:cb:bd:e6:59:17:22:
|
||||
d7:d9:23:26:90:0c:b9:63:85:91:9f:8e:58:92:52:
|
||||
b6:09:3a:80:b7:40:91:fe:47:b6:e8:3c:4d:44:97:
|
||||
ef:1c:11:a7:75:e0:19:d2:79:cb:3e:5d:f9:0c:81:
|
||||
95:63:6d:df:58:43:e5:03:62:78:52:0b:5b:5a:5c:
|
||||
c3:d9:8e:39:15:e5:72:37:b0:3a:ce:99:67:c0:72:
|
||||
ca:9f:65:25:7b:23:bf:87:bf:1f:a9:f5:0f:f2:bf:
|
||||
a1:ec:43:3b:8a:67:d0:5f:61:d8:03:74:e6:b1:25:
|
||||
91:45:70:85:d0:a2:70:65:df:4d:ed:39:6c:4d:c4:
|
||||
fd:fe:8d:71:92:06:90:ad:19:8e:de:0b:35:e1:50:
|
||||
79:30:6f:f6:bb:3d:74:a7:66:dd:0e:7b:d0:63:f2:
|
||||
5d:58:dc:17:a1:a2:e4:45:4e:b7:9c:32:b8:bc:56:
|
||||
88:31:de:6f:27:f3:56:29:54:45:07:68:f3:76:9d:
|
||||
b7:63:c0:d7:cf:6b:11:c5:3a:d2:9f:1a:34:96:2a:
|
||||
df:64:e1:df:fe:be:1d:4a:48:58:33:be:2e:c7:ac:
|
||||
c7:12:6f:9a:a6:10:e5:ef:a4:ae:0b:8d:c9:56:2c:
|
||||
49:60:ff:54:91:2c:41:05:90:74:70:3e:dd:54:58:
|
||||
b3:83:ae:c4:b4:4e:91:0b:a5:f1:3d:e4:5a:6d:34:
|
||||
5c:3b:ee:f6:d7:62:0b:a8:55:8f:5d:8a:ed:56:9a:
|
||||
8d:e7:80:16:0f:97:1b:f5:eb:0d:7f:1f:9a:51:e1:
|
||||
9b:3e:14:ac:f7:c3:36:42:06:11:7c:e9:ef:75:54:
|
||||
ae:1b:3b:68:b7:c4:79:fd:67:5c:26:9e:a5:d4:55:
|
||||
6c:c7:92:15:51:73:57:99:bc:de:fb:56:ab:70:db:
|
||||
98:10:1a:63:71:9c:c3:9f:11:9f:c2:c5:8b:ac:5c:
|
||||
52:69:c7:58:a1:b1:26:86:e3:68:85:23:17:68:62:
|
||||
30:01:79:1a:51:d7:e9:1b:a4:da:81:b6:46:33:1e:
|
||||
9a:2b:9b:f6:20:26:d0:21:10:b0:15:58:91:08:b5:
|
||||
bd:b7:c0:05:c1:cf:2f:bd:3b:18:40:17:08:92:58:
|
||||
6e:bb:bb
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
DE:03:BD:A3:0E:63:F4:97:C2:52:70:63:E8:BE:A9:DF:F1:9A:7B:56
|
||||
X509v3 Authority Key Identifier:
|
||||
50:76:C3:5C:6B:12:E1:92:0F:28:F8:F2:43:A6:80:C2:9C:E8:56:D6
|
||||
X509v3 Basic Constraints:
|
||||
CA:FALSE
|
||||
X509v3 Key Usage:
|
||||
Digital Signature, Key Encipherment
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:localhost, IP Address:0.0.0.0
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Signature Value:
|
||||
58:b7:35:45:3b:6b:5e:7d:6b:58:70:be:e6:39:96:14:2e:69:
|
||||
17:fc:a4:8e:1b:ae:ca:62:73:ec:12:92:ca:a8:1f:92:b8:1e:
|
||||
09:a5:7e:c0:49:d2:a3:29:48:2f:4c:67:ae:a6:fb:ad:7a:1b:
|
||||
2a:29:0b:75:6d:11:0f:99:8c:1d:dc:af:1c:a8:e7:cb:7c:66:
|
||||
34:de:7e:8f:e6:aa:26:6e:56:17:aa:1f:34:e9:1f:ff:7a:58:
|
||||
d2:7e:7c:65:62:56:d1:de:04:bd:71:cf:a2:6c:ad:47:cb:10:
|
||||
e8:72:b0:0a:9e:24:79:e0:1a:b6:e2:61:6f:fd:94:8b:3c:19:
|
||||
d0:8e:62:4f:a2:3a:fd:3d:97:c2:e7:93:1f:2c:aa:13:f5:c6:
|
||||
d0:03:4c:ee:90:48:94:3b:03:d9:2c:80:59:97:fb:a2:7f:00:
|
||||
23:19:51:0b:89:2a:92:36:57:94:0b:73:8b:f3:ae:5d:f0:68:
|
||||
29:ea:a1:f3:eb:83:48:f5:19:d1:42:fe:94:cd:13:37:c9:9a:
|
||||
c1:65:b3:97:eb:7e:82:f1:e3:98:c8:da:0c:41:c0:6f:4f:42:
|
||||
49:38:8b:c4:57:f4:07:cb:7f:f5:70:81:f0:72:3e:c7:e1:69:
|
||||
e3:38:e5:d0:4a:97:b2:b6:bf:25:c9:fe:91:79:39:d0:eb:04:
|
||||
a5:5d:b6:ca:4a:83:6e:9a:32:a2:6f:b1:ed:34:71:6f:9e:ee:
|
||||
ed:e4:c3:1b:07:ec:e1:d2:19:9f:f8:b0:a0:91:e6:dd:92:cf:
|
||||
2a:dd:45:b5:29:12:57:1b:6c:f2:04:37:be:4d:20:e8:f4:f4:
|
||||
2c:f1:bc:3e:76:ed:85:64:26:0f:81:c5:dc:63:f6:6e:77:fc:
|
||||
32:18:0b:a0:e4:8a:b5:af:93:d3:55:26:5d:7f:5d:a1:5d:1d:
|
||||
2e:f2:11:66:bd:5a:32:cc:80:6d:cf:c2:45:17:b4:bf:46:c6:
|
||||
99:2d:ae:1e:20:b8:21:b0:80:8f:72:25:9d:62:b6:80:71:9e:
|
||||
90:80:ef:52:19:a3:68:05:80:f9:8b:dc:f5:89:57:35:5c:1b:
|
||||
11:f0:e0:15:4e:ca:19:3c:19:61:86:8f:6b:3c:c3:d1:cf:6f:
|
||||
c5:28:88:35:7d:c8:ae:1b:98:a1:7c:b8:e8:df:36:a9:9a:9b:
|
||||
bd:71:48:c2:89:d6:5c:27:31:c9:c3:4c:71:95:67:aa:7a:c4:
|
||||
2e:7e:05:6f:d2:53:16:cc:6b:5b:64:43:ff:e5:1a:d5:47:d9:
|
||||
ff:47:1f:28:91:43:88:5d:34:ca:61:fe:38:b7:8f:35:43:51:
|
||||
78:b1:c1:2b:e2:29:2a:a1:69:bb:1f:14:2e:c5:f3:18:9d:81:
|
||||
ee:bc:d6:fc:e7:52:d6:d6
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGIjCCBAqgAwIBAgIBBDANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCVVMx
|
||||
EDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExEDAOBgNVBAoMB1Rl
|
||||
c3QgQ0ExHDAaBgNVBAsME1Rlc3QgQ0EgT3JnYW56YXRpb24xHTAbBgNVBAMMFFRl
|
||||
c3QgQ0EgT3JnYW5pemF0aW9uMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUu
|
||||
Y29tMCAXDTI0MDgwODE4MDYyMloYDzIwNTExMjI1MTgwNjIyWjCBjDELMAkGA1UE
|
||||
BhMCVVMxEDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExFDASBgNV
|
||||
BAoMC1Rlc3QgY2xpZW50MSAwHgYDVQQLDBdUZXN0IGNsaWVudCBPcmdhbnphdGlv
|
||||
bjEhMB8GA1UEAwwYVGVzdCBjbGllbnQgT3JnYW5pemF0aW9uMIICIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAg8AMIICCgKCAgEAv42ItyAOBD5eOvGjeC2lRPZos/PsPH6PzeLN
|
||||
VZwsoqagMUG0EMs6qI6errVlExgC/DU4fF5tuuATMfBlu6bTYXx/hr3WhNKxBpL+
|
||||
R13dPh+ZbFVvZ+tE69jaeXAu10h1bx3LveZZFyLX2SMmkAy5Y4WRn45YklK2CTqA
|
||||
t0CR/ke26DxNRJfvHBGndeAZ0nnLPl35DIGVY23fWEPlA2J4UgtbWlzD2Y45FeVy
|
||||
N7A6zplnwHLKn2UleyO/h78fqfUP8r+h7EM7imfQX2HYA3TmsSWRRXCF0KJwZd9N
|
||||
7TlsTcT9/o1xkgaQrRmO3gs14VB5MG/2uz10p2bdDnvQY/JdWNwXoaLkRU63nDK4
|
||||
vFaIMd5vJ/NWKVRFB2jzdp23Y8DXz2sRxTrSnxo0lirfZOHf/r4dSkhYM74ux6zH
|
||||
Em+aphDl76SuC43JVixJYP9UkSxBBZB0cD7dVFizg67EtE6RC6XxPeRabTRcO+72
|
||||
12ILqFWPXYrtVpqN54AWD5cb9esNfx+aUeGbPhSs98M2QgYRfOnvdVSuGztot8R5
|
||||
/WdcJp6l1FVsx5IVUXNXmbze+1arcNuYEBpjcZzDnxGfwsWLrFxSacdYobEmhuNo
|
||||
hSMXaGIwAXkaUdfpG6TagbZGMx6aK5v2ICbQIRCwFViRCLW9t8AFwc8vvTsYQBcI
|
||||
klhuu7sCAwEAAaN2MHQwHQYDVR0OBBYEFN4DvaMOY/SXwlJwY+i+qd/xmntWMB8G
|
||||
A1UdIwQYMBaAFFB2w1xrEuGSDyj48kOmgMKc6FbWMAkGA1UdEwQCMAAwCwYDVR0P
|
||||
BAQDAgWgMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEAAAAADANBgkqhkiG9w0BAQsF
|
||||
AAOCAgEAWLc1RTtrXn1rWHC+5jmWFC5pF/ykjhuuymJz7BKSyqgfkrgeCaV+wEnS
|
||||
oylIL0xnrqb7rXobKikLdW0RD5mMHdyvHKjny3xmNN5+j+aqJm5WF6ofNOkf/3pY
|
||||
0n58ZWJW0d4EvXHPomytR8sQ6HKwCp4keeAatuJhb/2UizwZ0I5iT6I6/T2XwueT
|
||||
HyyqE/XG0ANM7pBIlDsD2SyAWZf7on8AIxlRC4kqkjZXlAtzi/OuXfBoKeqh8+uD
|
||||
SPUZ0UL+lM0TN8mawWWzl+t+gvHjmMjaDEHAb09CSTiLxFf0B8t/9XCB8HI+x+Fp
|
||||
4zjl0EqXsra/Jcn+kXk50OsEpV22ykqDbpoyom+x7TRxb57u7eTDGwfs4dIZn/iw
|
||||
oJHm3ZLPKt1FtSkSVxts8gQ3vk0g6PT0LPG8PnbthWQmD4HF3GP2bnf8MhgLoOSK
|
||||
ta+T01UmXX9doV0dLvIRZr1aMsyAbc/CRRe0v0bGmS2uHiC4IbCAj3IlnWK2gHGe
|
||||
kIDvUhmjaAWA+Yvc9YlXNVwbEfDgFU7KGTwZYYaPazzD0c9vxSiINX3IrhuYoXy4
|
||||
6N82qZqbvXFIwonWXCcxycNMcZVnqnrELn4Fb9JTFsxrW2RD/+Ua1UfZ/0cfKJFD
|
||||
iF00ymH+OLePNUNReLHBK+IpKqFpux8ULsXzGJ2B7rzW/OdS1tY=
|
||||
-----END CERTIFICATE-----
|
||||
52
examples/features/advancedtls/creds/client_key.pem
Normal file
52
examples/features/advancedtls/creds/client_key.pem
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC/jYi3IA4EPl46
|
||||
8aN4LaVE9miz8+w8fo/N4s1VnCyipqAxQbQQyzqojp6utWUTGAL8NTh8Xm264BMx
|
||||
8GW7ptNhfH+GvdaE0rEGkv5HXd0+H5lsVW9n60Tr2Np5cC7XSHVvHcu95lkXItfZ
|
||||
IyaQDLljhZGfjliSUrYJOoC3QJH+R7boPE1El+8cEad14BnSecs+XfkMgZVjbd9Y
|
||||
Q+UDYnhSC1taXMPZjjkV5XI3sDrOmWfAcsqfZSV7I7+Hvx+p9Q/yv6HsQzuKZ9Bf
|
||||
YdgDdOaxJZFFcIXQonBl303tOWxNxP3+jXGSBpCtGY7eCzXhUHkwb/a7PXSnZt0O
|
||||
e9Bj8l1Y3BehouRFTrecMri8Vogx3m8n81YpVEUHaPN2nbdjwNfPaxHFOtKfGjSW
|
||||
Kt9k4d/+vh1KSFgzvi7HrMcSb5qmEOXvpK4LjclWLElg/1SRLEEFkHRwPt1UWLOD
|
||||
rsS0TpELpfE95FptNFw77vbXYguoVY9diu1Wmo3ngBYPlxv16w1/H5pR4Zs+FKz3
|
||||
wzZCBhF86e91VK4bO2i3xHn9Z1wmnqXUVWzHkhVRc1eZvN77Vqtw25gQGmNxnMOf
|
||||
EZ/CxYusXFJpx1ihsSaG42iFIxdoYjABeRpR1+kbpNqBtkYzHporm/YgJtAhELAV
|
||||
WJEItb23wAXBzy+9OxhAFwiSWG67uwIDAQABAoICAA9XpZV5UUtrYr5uNULTxnLV
|
||||
blcfgz091oR4lSwKuWkrrGqq5w0Yogb3KWQ37jO5zsoRnzJtmR9d1tu/SVQJjXu3
|
||||
mOVGFkUG38agMRKumiEMLzcwnp7gXAxXOaR+ukRkWYJADN4Vxu6l9qDoglcJAIj6
|
||||
nUp+KxTz4ABcObazyshm87z6e3vc1CTXkHtOAEjN7YaoyIuvuLNBN8wzip0bSXoq
|
||||
I7PYo0KCdDW57iHiESEUGMMezqDhXT+q13/mwdap5Lrzjg4aPSNy+OBy5KDeP6NS
|
||||
WCY2LYRORm9lE9zw0P5noqpn/NU5MQ7+8Y+nrotF7UxWY2462A7tG76PEdwM1ly7
|
||||
GyslA5o+V9RE1CsQJfK0yInD0W2cC9cggdcGlzm5p0jhWKk3Z3QaEXv6KxlkQjct
|
||||
q9uX7xdQff01WQVnS96VG4I5YlJP8vdTww3KL+7fpsBS1uNn1/pxDZW3LgT1XEHf
|
||||
lJxRSHKwyViHJtQyVaKzuzS9AOztOO1OWJvIwLAwImdjyXZeoKdBptkVLRfbX4+U
|
||||
4zCE0fgwkkmuFrvFQSoXvQsul+BXULiUhRTilUaN6VweP1Q99g9dzVqkyNLmm6+2
|
||||
vaOR4F3y+DYgu5wa7wQdULnpkzpHwiPUcqulOgg5xLo7tr5Q9nVrwyQy5zvuvYas
|
||||
N/LsYR2xBrMqRZRbxSWVAoIBAQD5mBydPI2jtw2jw6NBs9lqZasmUXoFkUO1IPuG
|
||||
oNauIzQdhQ7OCdBmwyWVyI7QnM0ZNB495qOg+lmxMpkg3hoQlN0PFKEBvVZde6Yz
|
||||
sB4eCL95x5bdp3oblUvq7h/0d4i+1I8OVKZz56zSn6CyoEH5zIxq+dXgGVsdfWOW
|
||||
tqnLSGM0apZlfKWoHTPc5OtJAiXXwWA0aGBGCa3M438HJ8V6qy4QREOOxR6HapUJ
|
||||
VODPsj9h6XL5jxFqcbaojjpckpb3SYh7rB3kgoriuSBgPGKM+PLjW1mCGwcLgGYB
|
||||
cn+2EHxKLvm+ShzZGdwjrpCv0aRuiYIGEb1/Afu4nOGnnJXnAoIBAQDEeBQBez5E
|
||||
SBiy6W5ZCIUXMS8DHFVei/GYDsAYMH0+vnbwUxBWNDKpUbiVVE6yrTjiRPfNamf0
|
||||
0zKSsI2MOMLNnb8nIkEWeVweHfQF5R1xxubemqmQ55Rd11ep59p9Er7O1WtR0968
|
||||
KWwT9ZN4OoXSRIFHV0fjY2V2Ns0VDyHl8vWgDk80JpEX/4BWFtRzuPaRCGuxFAJY
|
||||
3Md5ynCwOPaPKPXMc1by1ZCKGBxpXoeErWM0/hvbt2YUfI8+n80W0ws2p1/YDWE+
|
||||
9PhCHUFPxxTvb7+rSYYHJs3tcnS7c3K3jhkxbbR9FS95IVMTopjD24nl3G6nSyPT
|
||||
UJP+qnE3WgkNAoIBAEKqt5HkF60P+uuwGM423K7HozRj9OTBzUT3H1fxZAY1TvlH
|
||||
jhHIm0qne0WLwWHFUB8YRa+hCDm6RPTIoBAgYvPk3zrk9rCBQy1LFrSdqR26lnJP
|
||||
tmNUFZCCizmgCxcASp02J1Pblm5FBmtnycOMfLLdSPBV9SObgjPZRx19gtLSbfUV
|
||||
N0C6T4Ec87pfxtzEXxlHBIxbCMQMV8jvRwHBRMUkLfSYVzcuPZ5MAKzyZ+3yHW3o
|
||||
rhYseall4DUbcElDumEo2fS2n3Fm0PQIILazylr/L9k8kCbpUzNmQ1jFnYki1B/4
|
||||
diq2nwf6GUvKl8juhS4lOn6mhGgFPpgsBzX+5CcCggEAL/1Epbf81aDmp4ztL0It
|
||||
gCS7Xv8kuxtjv8iak04EybasRreDXgsR9NnJRHB7aJl3M421Ga/MBLkxuTL24DFd
|
||||
I+xMLLrpOxwZrCGU4Xu9XXVAH0+X65UlYGahOxcu/y38/XiT5kDiPwO/KoDprIxe
|
||||
86VYDpz7Kke1GNL59RLlLM3TwWy9W/evqTT3nA+nhTzAvVxZMb+5cws6jj0smV7Q
|
||||
mtdecroZmucfjxuklPhKEdZoTSFknJ6HiKmEM7/E0LZsHsVzW8qo3j/oA/4xXdM7
|
||||
AeFB6AzleAm6cy1p5f+lHcDP1or9czAhkGzbZghpWC3f2Q2m2aY48fzUqXfof6S2
|
||||
YQKCAQEAhnYgWar6sj/llQiKGmwQxxw9PkJMHGLAX44H3P9xAPwDIsgkL+hGmVIR
|
||||
7/ILhEbPtBGCvaoI/bqR5zh8VdMbqnm8ocZqz4xnu4WMpMfmF07TxF8aVC9TBoas
|
||||
Ad6khQfL4c91YrwTThvfyZ6im3aP/e8CSiZrkg89tF3a6rLvsJRx1qlj0DciLxW5
|
||||
/7soumtv9DCa1YmuuBAad14WprxEvAG7OVpH6SJPx6V3Di7LdWZUJQ79xeWn3Coh
|
||||
jfC/JlCEkRvxmzW8oDPWxHzbIJ194ukXPQot8eFzH+oOWtGkODxjhbdiTBl6ty2f
|
||||
egtZ+t//dA1KBWPMdPk3MoWZopTVgw==
|
||||
-----END PRIVATE KEY-----
|
||||
20
examples/features/advancedtls/creds/crl/client_revoked.crl
Normal file
20
examples/features/advancedtls/creds/crl/client_revoked.crl
Normal file
@@ -0,0 +1,20 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIIDOzCCASMCAQEwDQYJKoZIhvcNAQELBQAwgaExCzAJBgNVBAYTAlVTMRAwDgYD
|
||||
VQQIDAdHZW9yZ2lhMRAwDgYDVQQHDAdBdGxhbnRhMRAwDgYDVQQKDAdUZXN0IENB
|
||||
MRwwGgYDVQQLDBNUZXN0IENBIE9yZ2FuemF0aW9uMR0wGwYDVQQDDBRUZXN0IENB
|
||||
IE9yZ2FuaXphdGlvbjEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbRcN
|
||||
MjQwODA4MTgwNjIyWhcNMjQwOTA3MTgwNjIyWjAoMBICAQIXDTI0MDgwODE4MDYy
|
||||
MVowEgIBBBcNMjQwODA4MTgwNjIyWqAjMCEwHwYDVR0jBBgwFoAUUHbDXGsS4ZIP
|
||||
KPjyQ6aAwpzoVtYwDQYJKoZIhvcNAQELBQADggIBAArFuFeXXCWCCNLy8qk0UG5r
|
||||
CljVMSWrOPTy3eyQH+pSbzdwA5PYW2i5BOBcr6ULKW5aamFjhYMviqroFXrib7yU
|
||||
hNhiK8FtH9cl2O7pbdFGBdjqHoGOSOWXG++0LU+Hhh5kTr/iZrgkYvB3RHycofC1
|
||||
85nY01t//fGZZJ3e8hBwf8sNdR4L7vQ2WJtbzj8mj6mU4K//UkTiqZv2yGlbDXmh
|
||||
p0HDdu9/nBFLrLE35N/0m/1R4pW7AXm3R6WBiqxY8KdA4Us9tC9+qvtsWwEe/klN
|
||||
5E9FLcARMTl9kwJLNJZpVoe6tyt/S4WXs4nh+XEpiD5uZgbMh0N0jwaCMWyz3wo6
|
||||
tLkMmg+4mXEViAKQZTGVU2fTVaBH1C6A4ugB7IcFG1gXVw2DnF6I1XQB9+EcPbpb
|
||||
6ZTBo1msSR0Bzr0sUOdCiKhSc60DTjeNjcLhNT4k06cVvzQcyb2KePG+NnA/Tfbz
|
||||
yMuDcx62T2BTL1X2aVMUSLY3mwWnqyFdHbEQOoKH084Nrhizq7H2YwdoL992UTuH
|
||||
PzjyEqJN3hIePthlHl2g9fGh9dIJtxu6didm2M4WoHKeCfpWPH8fc37zhX8QYpqj
|
||||
U9vDvc2F567lRpAGwyqKZti+2xg2L2K/qBSGvKdtf5hPsOvVlEnWC4mTbjo19aUn
|
||||
YvLKT6e3D16ao5jVKITj
|
||||
-----END X509 CRL-----
|
||||
24
examples/features/advancedtls/creds/localhost-openssl.cnf
Normal file
24
examples/features/advancedtls/creds/localhost-openssl.cnf
Normal file
@@ -0,0 +1,24 @@
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
|
||||
[req_distinguished_name]
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_default = US
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
stateOrProvinceName_default = Georgia
|
||||
localityName = Locality Name (eg, city)
|
||||
localityName_default = Atlanta
|
||||
organizationName = Organization Name (eg, company)
|
||||
organizationName_default = Test Department
|
||||
commonName = Common Name (eg, YOUR name)
|
||||
commonName_max = 64
|
||||
|
||||
[v3_req]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
IP.1 = 0.0.0.0
|
||||
94
examples/features/advancedtls/creds/openssl-ca.cnf
Normal file
94
examples/features/advancedtls/creds/openssl-ca.cnf
Normal file
@@ -0,0 +1,94 @@
|
||||
base_dir = .
|
||||
certificate = $base_dir/ca_cert.pem # The CA certificate
|
||||
private_key = $base_dir/ca_key.pem # The CA private key
|
||||
new_certs_dir = $base_dir # Location for new certs after signing
|
||||
database = $base_dir/index.txt # Database index file
|
||||
serial = $base_dir/serial.txt # The current serial number
|
||||
|
||||
unique_subject = no # Set to 'no' to allow creation of
|
||||
# several certificates with same subject.
|
||||
|
||||
HOME = .
|
||||
RANDFILE = $ENV::HOME/.rnd
|
||||
|
||||
####################################################################
|
||||
[ ca ]
|
||||
default_ca = CA_default # The default ca section
|
||||
|
||||
[ CA_default ]
|
||||
|
||||
default_days = 10000 # How long to certify for
|
||||
default_crl_days = 30 # How long before next CRL
|
||||
default_md = sha256 # Use public key default MD
|
||||
preserve = no # Keep passed DN ordering
|
||||
|
||||
x509_extensions = ca_extensions # The extensions to add to the cert
|
||||
crl_extensions = crl_ext
|
||||
|
||||
email_in_dn = no # Don't concat the email in the DN
|
||||
copy_extensions = copy # Required to copy SANs from CSR to cert
|
||||
|
||||
####################################################################
|
||||
[ req ]
|
||||
default_bits = 4096
|
||||
default_keyfile = ca_key.pem
|
||||
distinguished_name = ca_distinguished_name
|
||||
x509_extensions = ca_extensions
|
||||
string_mask = utf8only
|
||||
|
||||
####################################################################
|
||||
[ ca_distinguished_name ]
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_default = US
|
||||
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
stateOrProvinceName_default = Georgia
|
||||
|
||||
localityName = Locality Name (eg, city)
|
||||
localityName_default = Atlanta
|
||||
|
||||
organizationName = Organization Name (eg, company)
|
||||
organizationName_default = Test CA
|
||||
|
||||
organizationalUnitName = Organizational Unit (eg, division)
|
||||
organizationalUnitName_default = Test CA Organization
|
||||
|
||||
commonName = Common Name (e.g. server FQDN or YOUR name)
|
||||
commonName_default = Test CA Organization
|
||||
|
||||
emailAddress = Email Address
|
||||
emailAddress_default = test@example.com
|
||||
|
||||
####################################################################
|
||||
[ ca_extensions ]
|
||||
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always, issuer
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
|
||||
|
||||
|
||||
####################################################################
|
||||
[ signing_policy ]
|
||||
countryName = optional
|
||||
stateOrProvinceName = optional
|
||||
localityName = optional
|
||||
organizationName = optional
|
||||
organizationalUnitName = optional
|
||||
commonName = supplied
|
||||
emailAddress = optional
|
||||
|
||||
####################################################################
|
||||
[ signing_req ]
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
|
||||
[ crl_ext ]
|
||||
# CRL extensions.
|
||||
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
|
||||
#issuerAltName=issuer:copy
|
||||
authorityKeyIdentifier=keyid:always
|
||||
127
examples/features/advancedtls/creds/server_cert.pem
Normal file
127
examples/features/advancedtls/creds/server_cert.pem
Normal file
@@ -0,0 +1,127 @@
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 1 (0x1)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C=US, ST=Georgia, L=Atlanta, O=Test CA, OU=Test CA Organzation, CN=Test CA Organization/emailAddress=test@example.com
|
||||
Validity
|
||||
Not Before: Aug 8 18:06:21 2024 GMT
|
||||
Not After : Dec 25 18:06:21 2051 GMT
|
||||
Subject: C=US, ST=Georgia, L=Atlanta, O=Test Server, OU=Test Server Organzation, CN=Test Server Organization
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Modulus:
|
||||
00:bd:47:df:b7:d5:38:92:af:b4:69:e7:48:3b:a0:
|
||||
7b:9e:6b:83:0e:76:91:06:06:94:a3:80:a3:73:8f:
|
||||
50:e5:43:80:f8:f7:fb:65:7b:f0:a3:94:cc:8e:a6:
|
||||
7e:fe:59:43:ce:80:68:6d:55:67:8e:33:aa:90:79:
|
||||
21:ac:de:6e:f0:03:27:1e:6f:50:31:cf:d2:3e:c3:
|
||||
8e:98:f5:bb:f9:e9:44:3f:3f:59:ae:7c:a3:b8:a7:
|
||||
ae:94:ff:68:70:d0:fb:7b:cb:cc:35:7d:04:81:f5:
|
||||
2b:12:78:bf:6e:1b:a3:cd:d1:74:41:41:9f:ee:02:
|
||||
1f:b3:42:fd:c9:01:b5:28:43:ee:31:03:3a:5d:60:
|
||||
d3:df:8f:69:1e:73:4a:c4:83:35:95:00:93:83:6e:
|
||||
d6:b0:d2:0b:30:31:7f:95:eb:ce:c9:73:83:b9:76:
|
||||
eb:45:f1:20:8b:75:de:81:a3:32:b0:f7:0f:21:64:
|
||||
a7:1d:cc:3b:00:82:c8:48:74:c9:3a:0b:f9:cb:6e:
|
||||
8c:ab:fc:b0:94:20:bd:60:06:eb:d0:12:15:55:48:
|
||||
d7:d3:30:ef:59:67:98:df:f6:31:92:6d:63:1c:4a:
|
||||
93:7c:97:a8:99:f6:61:e5:78:12:36:a2:24:56:37:
|
||||
4b:38:ce:63:00:a2:26:b3:31:05:93:23:3c:c1:ed:
|
||||
b1:fb:25:7d:fc:54:04:3a:b9:3a:f7:17:a4:58:10:
|
||||
4f:e8:6d:90:69:49:b6:1f:1b:81:fb:f5:c7:6c:aa:
|
||||
b3:e0:4a:b1:38:40:77:83:a2:aa:8c:e2:7c:91:a9:
|
||||
3e:cd:43:be:90:c3:e7:b1:23:94:47:f9:68:db:e4:
|
||||
2c:df:65:e7:88:b6:64:dc:62:d0:86:33:9b:13:64:
|
||||
94:37:aa:0e:56:9f:a3:42:19:67:30:a1:e9:3b:5b:
|
||||
4a:e6:e1:81:52:81:21:2a:78:ac:c1:77:77:52:fc:
|
||||
4a:95:b9:3f:f7:e6:32:9e:59:5b:46:4c:a9:8a:12:
|
||||
d3:2c:fc:33:73:3a:28:26:28:22:4c:1c:a9:b1:59:
|
||||
96:ab:a5:f6:e9:e7:55:32:a8:2b:a2:33:de:a0:e2:
|
||||
5f:77:d8:cd:d1:aa:1f:4f:c6:69:10:66:4e:9d:aa:
|
||||
77:83:82:78:96:5a:07:21:12:db:4c:97:51:cd:ba:
|
||||
ea:00:cd:94:97:40:b8:50:62:90:2b:8c:b0:1b:2c:
|
||||
aa:a5:63:0c:bb:7d:d5:7d:3f:c1:4a:00:6b:cb:74:
|
||||
fa:23:35:26:1e:26:1a:30:b2:96:bc:1b:16:2a:62:
|
||||
96:1f:51:20:72:95:36:1a:87:20:26:9f:76:d6:84:
|
||||
1b:67:2a:32:68:b7:e0:c7:80:75:a3:fa:b7:da:a3:
|
||||
03:71:c1
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
4C:57:E2:72:97:CF:DC:C4:B8:4E:DB:D4:C1:C6:3D:AE:EF:D7:0A:19
|
||||
X509v3 Authority Key Identifier:
|
||||
50:76:C3:5C:6B:12:E1:92:0F:28:F8:F2:43:A6:80:C2:9C:E8:56:D6
|
||||
X509v3 Basic Constraints:
|
||||
CA:FALSE
|
||||
X509v3 Key Usage:
|
||||
Digital Signature, Key Encipherment
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:localhost, IP Address:0.0.0.0
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Signature Value:
|
||||
10:9b:66:d5:4b:8f:e2:7b:25:8b:fe:5b:9c:a6:dd:4e:d5:ee:
|
||||
27:ad:a9:e5:c4:5d:9b:f9:2c:f1:d6:8d:0e:d6:9b:e6:9f:87:
|
||||
0b:14:1b:c9:a3:dc:da:82:d0:1e:e8:c5:f7:f4:ea:99:ea:01:
|
||||
f1:2e:7c:f0:07:15:28:74:15:b0:36:27:a5:3f:2d:c7:32:fc:
|
||||
81:61:44:15:9a:9a:88:20:fb:c6:d9:8a:26:61:df:e2:04:a2:
|
||||
54:98:76:90:40:98:80:d3:eb:ff:73:29:d7:2f:3f:79:ca:ba:
|
||||
c3:1b:34:53:6e:f0:da:06:f8:19:3e:97:de:34:74:d1:4c:90:
|
||||
e1:ce:6a:36:31:6e:58:d2:22:b1:5a:05:71:d8:0b:d9:c2:03:
|
||||
17:0d:98:78:f5:e2:24:7c:0a:7d:7b:49:4f:fe:31:a6:c3:0e:
|
||||
11:9e:af:6e:88:83:72:5a:34:a9:34:94:ef:6b:ee:cc:c1:71:
|
||||
5c:53:c6:dd:52:7e:a7:4c:9a:48:76:e9:72:b9:c4:26:74:87:
|
||||
64:c9:89:34:7d:bc:f2:ff:8a:ac:32:b5:3d:50:19:09:5f:30:
|
||||
19:49:6e:86:4e:84:e3:13:cc:9f:4c:a9:4a:20:89:5e:e3:91:
|
||||
ad:8d:5e:3f:ac:ea:63:f1:48:18:f2:22:e9:b6:c3:6f:dc:b4:
|
||||
46:fc:41:71:33:ee:a7:4b:33:79:11:0f:c9:81:4d:10:c3:df:
|
||||
b6:4d:75:62:74:39:e4:8d:5d:33:37:1b:91:ce:23:a3:47:15:
|
||||
58:57:5b:09:ba:4f:d5:1b:0f:4f:7b:03:10:d7:49:76:86:e0:
|
||||
69:7f:1a:7e:cb:6c:2a:80:b4:d8:9e:03:66:5c:89:3c:d3:82:
|
||||
86:d9:50:65:d9:15:51:e1:0b:3b:2f:e8:c7:44:6d:27:e3:09:
|
||||
2d:58:ce:a1:af:f9:d9:2f:0a:fd:fb:65:3d:3b:30:5a:42:b1:
|
||||
ab:34:28:20:0d:a4:31:dd:84:65:eb:87:d1:59:33:1d:db:b1:
|
||||
64:e3:e5:6f:25:1a:15:ae:f1:39:b6:cc:91:d0:82:6e:e6:82:
|
||||
9e:f0:fc:c9:41:2b:a4:d7:b5:e7:af:1e:13:46:c0:e6:04:ac:
|
||||
98:53:ab:52:f3:85:bb:95:0d:b0:fb:e0:0a:c9:5e:da:99:ec:
|
||||
63:6c:7c:78:21:12:8d:21:6b:c3:bf:6c:cb:88:dc:c3:7a:24:
|
||||
b9:4b:ba:36:63:b3:01:91:b3:07:a9:b0:1f:2c:ab:ae:d4:cd:
|
||||
a7:a2:46:c0:29:df:1f:c2:29:d4:f9:49:9e:c5:e0:ca:02:f7:
|
||||
eb:de:b8:b9:6e:1f:18:3a:6d:0f:07:0d:97:d2:16:0d:84:2c:
|
||||
81:24:c6:e6:e5:f5:e4:59
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGIjCCBAqgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCVVMx
|
||||
EDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExEDAOBgNVBAoMB1Rl
|
||||
c3QgQ0ExHDAaBgNVBAsME1Rlc3QgQ0EgT3JnYW56YXRpb24xHTAbBgNVBAMMFFRl
|
||||
c3QgQ0EgT3JnYW5pemF0aW9uMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUu
|
||||
Y29tMCAXDTI0MDgwODE4MDYyMVoYDzIwNTExMjI1MTgwNjIxWjCBjDELMAkGA1UE
|
||||
BhMCVVMxEDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExFDASBgNV
|
||||
BAoMC1Rlc3QgU2VydmVyMSAwHgYDVQQLDBdUZXN0IFNlcnZlciBPcmdhbnphdGlv
|
||||
bjEhMB8GA1UEAwwYVGVzdCBTZXJ2ZXIgT3JnYW5pemF0aW9uMIICIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAg8AMIICCgKCAgEAvUfft9U4kq+0aedIO6B7nmuDDnaRBgaUo4Cj
|
||||
c49Q5UOA+Pf7ZXvwo5TMjqZ+/llDzoBobVVnjjOqkHkhrN5u8AMnHm9QMc/SPsOO
|
||||
mPW7+elEPz9ZrnyjuKeulP9ocND7e8vMNX0EgfUrEni/bhujzdF0QUGf7gIfs0L9
|
||||
yQG1KEPuMQM6XWDT349pHnNKxIM1lQCTg27WsNILMDF/levOyXODuXbrRfEgi3Xe
|
||||
gaMysPcPIWSnHcw7AILISHTJOgv5y26Mq/ywlCC9YAbr0BIVVUjX0zDvWWeY3/Yx
|
||||
km1jHEqTfJeomfZh5XgSNqIkVjdLOM5jAKImszEFkyM8we2x+yV9/FQEOrk69xek
|
||||
WBBP6G2QaUm2HxuB+/XHbKqz4EqxOEB3g6KqjOJ8kak+zUO+kMPnsSOUR/lo2+Qs
|
||||
32XniLZk3GLQhjObE2SUN6oOVp+jQhlnMKHpO1tK5uGBUoEhKniswXd3UvxKlbk/
|
||||
9+YynllbRkypihLTLPwzczooJigiTBypsVmWq6X26edVMqgrojPeoOJfd9jN0aof
|
||||
T8ZpEGZOnap3g4J4lloHIRLbTJdRzbrqAM2Ul0C4UGKQK4ywGyyqpWMMu33VfT/B
|
||||
SgBry3T6IzUmHiYaMLKWvBsWKmKWH1EgcpU2GocgJp921oQbZyoyaLfgx4B1o/q3
|
||||
2qMDccECAwEAAaN2MHQwHQYDVR0OBBYEFExX4nKXz9zEuE7b1MHGPa7v1woZMB8G
|
||||
A1UdIwQYMBaAFFB2w1xrEuGSDyj48kOmgMKc6FbWMAkGA1UdEwQCMAAwCwYDVR0P
|
||||
BAQDAgWgMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEAAAAADANBgkqhkiG9w0BAQsF
|
||||
AAOCAgEAEJtm1UuP4nsli/5bnKbdTtXuJ62p5cRdm/ks8daNDtab5p+HCxQbyaPc
|
||||
2oLQHujF9/TqmeoB8S588AcVKHQVsDYnpT8txzL8gWFEFZqaiCD7xtmKJmHf4gSi
|
||||
VJh2kECYgNPr/3Mp1y8/ecq6wxs0U27w2gb4GT6X3jR00UyQ4c5qNjFuWNIisVoF
|
||||
cdgL2cIDFw2YePXiJHwKfXtJT/4xpsMOEZ6vboiDclo0qTSU72vuzMFxXFPG3VJ+
|
||||
p0yaSHbpcrnEJnSHZMmJNH288v+KrDK1PVAZCV8wGUluhk6E4xPMn0ypSiCJXuOR
|
||||
rY1eP6zqY/FIGPIi6bbDb9y0RvxBcTPup0szeREPyYFNEMPftk11YnQ55I1dMzcb
|
||||
kc4jo0cVWFdbCbpP1RsPT3sDENdJdobgaX8afstsKoC02J4DZlyJPNOChtlQZdkV
|
||||
UeELOy/ox0RtJ+MJLVjOoa/52S8K/ftlPTswWkKxqzQoIA2kMd2EZeuH0VkzHdux
|
||||
ZOPlbyUaFa7xObbMkdCCbuaCnvD8yUErpNe1568eE0bA5gSsmFOrUvOFu5UNsPvg
|
||||
Csle2pnsY2x8eCESjSFrw79sy4jcw3okuUu6NmOzAZGzB6mwHyyrrtTNp6JGwCnf
|
||||
H8Ip1PlJnsXgygL36964uW4fGDptDwcNl9IWDYQsgSTG5uX15Fk=
|
||||
-----END CERTIFICATE-----
|
||||
127
examples/features/advancedtls/creds/server_cert_revoked.pem
Normal file
127
examples/features/advancedtls/creds/server_cert_revoked.pem
Normal file
@@ -0,0 +1,127 @@
|
||||
Certificate:
|
||||
Data:
|
||||
Version: 3 (0x2)
|
||||
Serial Number: 2 (0x2)
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Issuer: C=US, ST=Georgia, L=Atlanta, O=Test CA, OU=Test CA Organzation, CN=Test CA Organization/emailAddress=test@example.com
|
||||
Validity
|
||||
Not Before: Aug 8 18:06:21 2024 GMT
|
||||
Not After : Dec 25 18:06:21 2051 GMT
|
||||
Subject: C=US, ST=Georgia, L=Atlanta, O=Test server, OU=Test server Organzation, CN=Test server Organization
|
||||
Subject Public Key Info:
|
||||
Public Key Algorithm: rsaEncryption
|
||||
Public-Key: (4096 bit)
|
||||
Modulus:
|
||||
00:bd:47:df:b7:d5:38:92:af:b4:69:e7:48:3b:a0:
|
||||
7b:9e:6b:83:0e:76:91:06:06:94:a3:80:a3:73:8f:
|
||||
50:e5:43:80:f8:f7:fb:65:7b:f0:a3:94:cc:8e:a6:
|
||||
7e:fe:59:43:ce:80:68:6d:55:67:8e:33:aa:90:79:
|
||||
21:ac:de:6e:f0:03:27:1e:6f:50:31:cf:d2:3e:c3:
|
||||
8e:98:f5:bb:f9:e9:44:3f:3f:59:ae:7c:a3:b8:a7:
|
||||
ae:94:ff:68:70:d0:fb:7b:cb:cc:35:7d:04:81:f5:
|
||||
2b:12:78:bf:6e:1b:a3:cd:d1:74:41:41:9f:ee:02:
|
||||
1f:b3:42:fd:c9:01:b5:28:43:ee:31:03:3a:5d:60:
|
||||
d3:df:8f:69:1e:73:4a:c4:83:35:95:00:93:83:6e:
|
||||
d6:b0:d2:0b:30:31:7f:95:eb:ce:c9:73:83:b9:76:
|
||||
eb:45:f1:20:8b:75:de:81:a3:32:b0:f7:0f:21:64:
|
||||
a7:1d:cc:3b:00:82:c8:48:74:c9:3a:0b:f9:cb:6e:
|
||||
8c:ab:fc:b0:94:20:bd:60:06:eb:d0:12:15:55:48:
|
||||
d7:d3:30:ef:59:67:98:df:f6:31:92:6d:63:1c:4a:
|
||||
93:7c:97:a8:99:f6:61:e5:78:12:36:a2:24:56:37:
|
||||
4b:38:ce:63:00:a2:26:b3:31:05:93:23:3c:c1:ed:
|
||||
b1:fb:25:7d:fc:54:04:3a:b9:3a:f7:17:a4:58:10:
|
||||
4f:e8:6d:90:69:49:b6:1f:1b:81:fb:f5:c7:6c:aa:
|
||||
b3:e0:4a:b1:38:40:77:83:a2:aa:8c:e2:7c:91:a9:
|
||||
3e:cd:43:be:90:c3:e7:b1:23:94:47:f9:68:db:e4:
|
||||
2c:df:65:e7:88:b6:64:dc:62:d0:86:33:9b:13:64:
|
||||
94:37:aa:0e:56:9f:a3:42:19:67:30:a1:e9:3b:5b:
|
||||
4a:e6:e1:81:52:81:21:2a:78:ac:c1:77:77:52:fc:
|
||||
4a:95:b9:3f:f7:e6:32:9e:59:5b:46:4c:a9:8a:12:
|
||||
d3:2c:fc:33:73:3a:28:26:28:22:4c:1c:a9:b1:59:
|
||||
96:ab:a5:f6:e9:e7:55:32:a8:2b:a2:33:de:a0:e2:
|
||||
5f:77:d8:cd:d1:aa:1f:4f:c6:69:10:66:4e:9d:aa:
|
||||
77:83:82:78:96:5a:07:21:12:db:4c:97:51:cd:ba:
|
||||
ea:00:cd:94:97:40:b8:50:62:90:2b:8c:b0:1b:2c:
|
||||
aa:a5:63:0c:bb:7d:d5:7d:3f:c1:4a:00:6b:cb:74:
|
||||
fa:23:35:26:1e:26:1a:30:b2:96:bc:1b:16:2a:62:
|
||||
96:1f:51:20:72:95:36:1a:87:20:26:9f:76:d6:84:
|
||||
1b:67:2a:32:68:b7:e0:c7:80:75:a3:fa:b7:da:a3:
|
||||
03:71:c1
|
||||
Exponent: 65537 (0x10001)
|
||||
X509v3 extensions:
|
||||
X509v3 Subject Key Identifier:
|
||||
4C:57:E2:72:97:CF:DC:C4:B8:4E:DB:D4:C1:C6:3D:AE:EF:D7:0A:19
|
||||
X509v3 Authority Key Identifier:
|
||||
50:76:C3:5C:6B:12:E1:92:0F:28:F8:F2:43:A6:80:C2:9C:E8:56:D6
|
||||
X509v3 Basic Constraints:
|
||||
CA:FALSE
|
||||
X509v3 Key Usage:
|
||||
Digital Signature, Key Encipherment
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:localhost, IP Address:0.0.0.0
|
||||
Signature Algorithm: sha256WithRSAEncryption
|
||||
Signature Value:
|
||||
07:74:84:18:37:74:23:9c:c2:f1:e8:d2:44:49:57:f8:51:fa:
|
||||
cb:db:0e:42:04:6b:61:5b:60:f0:82:7a:df:1b:af:69:75:a8:
|
||||
17:62:89:18:b7:71:3e:8c:40:10:5d:2b:88:35:6a:97:9c:44:
|
||||
9f:93:24:f3:b8:d2:56:dd:2f:aa:27:55:96:67:07:fa:b1:8d:
|
||||
20:df:ea:f7:96:51:9e:46:e5:35:9a:34:53:d0:e7:60:da:a7:
|
||||
02:76:68:c2:12:6d:aa:bc:b6:81:e0:c9:96:67:b6:9e:fa:6d:
|
||||
43:63:80:19:70:49:9b:38:78:68:3d:aa:f2:5d:ec:af:45:65:
|
||||
4c:75:3c:d6:0b:92:8e:d7:7c:c9:76:55:51:ef:c6:d6:33:68:
|
||||
66:58:17:47:21:d7:14:4f:69:d1:59:1e:b2:78:bb:45:f4:24:
|
||||
8b:6b:ba:c4:83:6d:e8:11:c1:56:d8:df:84:3c:56:d2:e7:00:
|
||||
6c:b6:5c:f5:b8:33:e4:11:27:76:88:16:bd:d3:3d:ba:7b:d9:
|
||||
25:68:17:9c:0a:02:2f:d5:d0:57:b4:c9:f3:b1:9d:8e:6b:c9:
|
||||
f1:6f:8f:39:8a:ad:0b:38:07:29:9b:cb:9a:3b:06:b5:03:1a:
|
||||
83:f4:ef:1e:91:a1:4b:eb:cf:fa:89:6f:91:47:5e:f2:bc:cb:
|
||||
c2:8a:dd:7b:19:54:f4:9f:c7:54:7f:d2:e8:ea:a8:d9:c8:c1:
|
||||
6d:17:63:a3:47:30:05:5b:80:90:47:54:81:1f:0a:9b:11:48:
|
||||
c6:ee:52:80:c3:b9:75:9d:d2:ee:1b:83:43:b2:de:05:aa:52:
|
||||
d9:01:a3:f1:71:d3:23:90:28:35:25:0a:71:80:1d:ae:1a:6a:
|
||||
72:c1:2b:ee:a7:a2:72:54:f0:0e:19:87:97:a4:62:79:1a:ea:
|
||||
ec:e2:73:b1:79:d5:c7:25:4f:c7:e6:a4:55:ad:be:3d:d7:59:
|
||||
8c:fb:ee:c3:2e:75:6d:1f:65:4a:be:46:c9:4e:54:bd:2e:49:
|
||||
3e:2f:70:b6:97:eb:8a:41:f4:bb:75:64:84:f4:71:29:e3:f2:
|
||||
b2:30:75:41:5a:04:ac:a6:d1:d0:9c:4d:52:19:76:7f:0d:c7:
|
||||
08:f4:6e:cf:20:c7:3c:a6:d9:6f:72:88:46:16:0c:43:12:28:
|
||||
24:a1:d2:63:d3:04:4c:cd:12:67:1c:8f:00:e6:7b:47:0a:03:
|
||||
87:18:02:d6:bc:01:59:da:90:c4:c6:b1:72:b1:e6:a4:bc:23:
|
||||
fd:5c:cf:32:0c:d9:e0:24:83:5b:55:7a:d0:db:3c:d6:b2:9f:
|
||||
22:a1:a0:f4:48:96:fb:d6:73:a1:43:f7:46:e0:ef:dd:b1:9a:
|
||||
0e:ef:6f:1d:1a:b4:b2:d4
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGIjCCBAqgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBoTELMAkGA1UEBhMCVVMx
|
||||
EDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExEDAOBgNVBAoMB1Rl
|
||||
c3QgQ0ExHDAaBgNVBAsME1Rlc3QgQ0EgT3JnYW56YXRpb24xHTAbBgNVBAMMFFRl
|
||||
c3QgQ0EgT3JnYW5pemF0aW9uMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUu
|
||||
Y29tMCAXDTI0MDgwODE4MDYyMVoYDzIwNTExMjI1MTgwNjIxWjCBjDELMAkGA1UE
|
||||
BhMCVVMxEDAOBgNVBAgMB0dlb3JnaWExEDAOBgNVBAcMB0F0bGFudGExFDASBgNV
|
||||
BAoMC1Rlc3Qgc2VydmVyMSAwHgYDVQQLDBdUZXN0IHNlcnZlciBPcmdhbnphdGlv
|
||||
bjEhMB8GA1UEAwwYVGVzdCBzZXJ2ZXIgT3JnYW5pemF0aW9uMIICIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAg8AMIICCgKCAgEAvUfft9U4kq+0aedIO6B7nmuDDnaRBgaUo4Cj
|
||||
c49Q5UOA+Pf7ZXvwo5TMjqZ+/llDzoBobVVnjjOqkHkhrN5u8AMnHm9QMc/SPsOO
|
||||
mPW7+elEPz9ZrnyjuKeulP9ocND7e8vMNX0EgfUrEni/bhujzdF0QUGf7gIfs0L9
|
||||
yQG1KEPuMQM6XWDT349pHnNKxIM1lQCTg27WsNILMDF/levOyXODuXbrRfEgi3Xe
|
||||
gaMysPcPIWSnHcw7AILISHTJOgv5y26Mq/ywlCC9YAbr0BIVVUjX0zDvWWeY3/Yx
|
||||
km1jHEqTfJeomfZh5XgSNqIkVjdLOM5jAKImszEFkyM8we2x+yV9/FQEOrk69xek
|
||||
WBBP6G2QaUm2HxuB+/XHbKqz4EqxOEB3g6KqjOJ8kak+zUO+kMPnsSOUR/lo2+Qs
|
||||
32XniLZk3GLQhjObE2SUN6oOVp+jQhlnMKHpO1tK5uGBUoEhKniswXd3UvxKlbk/
|
||||
9+YynllbRkypihLTLPwzczooJigiTBypsVmWq6X26edVMqgrojPeoOJfd9jN0aof
|
||||
T8ZpEGZOnap3g4J4lloHIRLbTJdRzbrqAM2Ul0C4UGKQK4ywGyyqpWMMu33VfT/B
|
||||
SgBry3T6IzUmHiYaMLKWvBsWKmKWH1EgcpU2GocgJp921oQbZyoyaLfgx4B1o/q3
|
||||
2qMDccECAwEAAaN2MHQwHQYDVR0OBBYEFExX4nKXz9zEuE7b1MHGPa7v1woZMB8G
|
||||
A1UdIwQYMBaAFFB2w1xrEuGSDyj48kOmgMKc6FbWMAkGA1UdEwQCMAAwCwYDVR0P
|
||||
BAQDAgWgMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEAAAAADANBgkqhkiG9w0BAQsF
|
||||
AAOCAgEAB3SEGDd0I5zC8ejSRElX+FH6y9sOQgRrYVtg8IJ63xuvaXWoF2KJGLdx
|
||||
PoxAEF0riDVql5xEn5Mk87jSVt0vqidVlmcH+rGNIN/q95ZRnkblNZo0U9DnYNqn
|
||||
AnZowhJtqry2geDJlme2nvptQ2OAGXBJmzh4aD2q8l3sr0VlTHU81guSjtd8yXZV
|
||||
Ue/G1jNoZlgXRyHXFE9p0Vkesni7RfQki2u6xINt6BHBVtjfhDxW0ucAbLZc9bgz
|
||||
5BEndogWvdM9unvZJWgXnAoCL9XQV7TJ87GdjmvJ8W+POYqtCzgHKZvLmjsGtQMa
|
||||
g/TvHpGhS+vP+olvkUde8rzLwordexlU9J/HVH/S6Oqo2cjBbRdjo0cwBVuAkEdU
|
||||
gR8KmxFIxu5SgMO5dZ3S7huDQ7LeBapS2QGj8XHTI5AoNSUKcYAdrhpqcsEr7qei
|
||||
clTwDhmHl6RieRrq7OJzsXnVxyVPx+akVa2+PddZjPvuwy51bR9lSr5GyU5UvS5J
|
||||
Pi9wtpfrikH0u3VkhPRxKePysjB1QVoErKbR0JxNUhl2fw3HCPRuzyDHPKbZb3KI
|
||||
RhYMQxIoJKHSY9METM0SZxyPAOZ7RwoDhxgC1rwBWdqQxMaxcrHmpLwj/VzPMgzZ
|
||||
4CSDW1V60Ns81rKfIqGg9EiW+9ZzoUP3RuDv3bGaDu9vHRq0stQ=
|
||||
-----END CERTIFICATE-----
|
||||
52
examples/features/advancedtls/creds/server_key.pem
Normal file
52
examples/features/advancedtls/creds/server_key.pem
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC9R9+31TiSr7Rp
|
||||
50g7oHuea4MOdpEGBpSjgKNzj1DlQ4D49/tle/CjlMyOpn7+WUPOgGhtVWeOM6qQ
|
||||
eSGs3m7wAyceb1Axz9I+w46Y9bv56UQ/P1mufKO4p66U/2hw0Pt7y8w1fQSB9SsS
|
||||
eL9uG6PN0XRBQZ/uAh+zQv3JAbUoQ+4xAzpdYNPfj2kec0rEgzWVAJODbtaw0gsw
|
||||
MX+V687Jc4O5dutF8SCLdd6BozKw9w8hZKcdzDsAgshIdMk6C/nLboyr/LCUIL1g
|
||||
BuvQEhVVSNfTMO9ZZ5jf9jGSbWMcSpN8l6iZ9mHleBI2oiRWN0s4zmMAoiazMQWT
|
||||
IzzB7bH7JX38VAQ6uTr3F6RYEE/obZBpSbYfG4H79cdsqrPgSrE4QHeDoqqM4nyR
|
||||
qT7NQ76Qw+exI5RH+Wjb5CzfZeeItmTcYtCGM5sTZJQ3qg5Wn6NCGWcwoek7W0rm
|
||||
4YFSgSEqeKzBd3dS/EqVuT/35jKeWVtGTKmKEtMs/DNzOigmKCJMHKmxWZarpfbp
|
||||
51UyqCuiM96g4l932M3Rqh9PxmkQZk6dqneDgniWWgchEttMl1HNuuoAzZSXQLhQ
|
||||
YpArjLAbLKqlYwy7fdV9P8FKAGvLdPojNSYeJhowspa8GxYqYpYfUSBylTYahyAm
|
||||
n3bWhBtnKjJot+DHgHWj+rfaowNxwQIDAQABAoICAAg2RBMvtwKg3jecsdo/E4iY
|
||||
qtjydUbzpiM/Li2R/DUrgT72qKY12FzgfyIj6xfnO4qMBlEoBr7OqF8YQkkZEBXD
|
||||
ApbOxttCZEwWI+uoTagsDKqfJFRDqBQXglAzPI7DIlteniomlbl7BOFfnRPj3cQi
|
||||
NY8B5TRoTIPJ1kTlmWcj0K5jsMvjADjkz0S478zS0dJNx23zsxt8zBYihPc2LISP
|
||||
nxlperpQGdSzH8eJaGZGccDweFJR+AVaUeItiZrGcOdN5ostArmKf2zZIAX+TYVO
|
||||
Yb68ksXS5CO4r1yQ+QnTL87qAttF1egkwMrfV1WTlI85tRCOoxXcfJzDnJvf+ia9
|
||||
+laYPj5av7ZoXwZQ4tIh3RqpIFHXZMQU1jRCYOibYNhdOO/H9Z6eH+8HyyFQ9ihe
|
||||
7keGKouLSo/E6dSIJ9D1g+Tr8xELj816a4KL8ShYLTXP+ga43yFVdp94Yd0vFHWK
|
||||
qjyu/x2wLqZAPpVbYg9PkO6Dr4tmyolhL1ZjruM5IqAI+hALzUZDccYQVXu2swpC
|
||||
6p6evB24MI5LQ87+28U4rCcbo/xfdQAEY4LSP9XfvSQ32zUyDHYX1gLvF99fZD5Y
|
||||
1IEX11bGbGFCT0EIwFPXJUuxJlpvMF1bZ+Z+eXVL43yTNdeu+7E7dhpPjz5W3cRa
|
||||
6SKxxcUAbdxpQP2DtIP5AoIBAQD+AuqckaGK/sZotPWstgcRlG31ptW6d2ZNk5X3
|
||||
i99mqaTPDOx+UavkpsBfcUPYmYjo6fEC4yMhkx97XC9tUxi97LBXJrBfwLDnCqQL
|
||||
vhOVcWs9mDcJTJDVT1P2StBm7a6DmCmiqUcxiG57UY9dgSanLwxg+6K4PgOin11Y
|
||||
ChECWFNvdwdOmyQhWm+O0y0R2s7iGINKF6s1t2RweCfXw1W2N8iaTVkNei71bblW
|
||||
sTjPGdEm2CFwlBf031Hkr5S2MIo23RX53tkalMzvyCGfREn+wrWcMfagygHjEjn/
|
||||
C4ZcQIOXC5u9tIc4tps1mr1C8nMScxfSrIHRManE1haqXcBLAoIBAQC+wznR5wzR
|
||||
Az5r0ek1s2sr6gwsmJfbA6tNHwgqrYblsxYMR6BdbGUBkSqwEYDjDw/rreDcQk6D
|
||||
nMxc6BYh2Dk6t/AJQ3c76tuJohlmZfbuFXL+KAwApVnbwpZuAQGgkNMhVm/8FAgi
|
||||
PgCvuHuvOISTHmniQBU91kWGpsqFoF826Mcxa7bmbe2c+jgCmFikp0rzN7jA6Ps/
|
||||
bIRKVIpPhCtAgH5JsFEpim0HubV8qRNmeBh1oSyAFicntEAeL/VSLmDI44kjJyqO
|
||||
qmspz+uyANt7/xxYfAZed1Q503K1tLUws1K8Ux+EEdo1zXGxoL6b90OfcjivJC0d
|
||||
/bv8X5DEyMajAoIBAQCGYSabJBQxQ23V0P4zm60LqNmvXs6tMiOGIPDyoCXU2ySc
|
||||
gPrQLQbiFTGqjHJXMYqTpcfiPiXEyl+aVH+mt5JcT85OnOIsFfXAlQmKSMl1gyY3
|
||||
1MIxAjeREcGah6PPACkV5zcHncRTORkx1kkhL4UyZxqGaDmCfRRRQTwRqmmrMu0Z
|
||||
CABunnazynNAPQoX6wkN5ef3F6R064uQUJDLfcRnfQV8VDUrgxs6rgyiB2nFbqQO
|
||||
h8LRGxe9bTOW5yimZfGI6teIdFOo01XD+L2I04jN5VZMxsXx9EyhQ3A5NHCld1/m
|
||||
VbbT2qC66SgdaLp9o2QrO4Y75xVahYqJ3rTo9mYXAoIBAF6qrWfwPFkBPhntqsj+
|
||||
h+HcHTyIYVvL31e/XaMoSDh3fiqL5RZXs2xqqP+FQCvuDp2LxXoo4aPIzVYRyuHy
|
||||
1rvACjveoi4258nOisJZOYh/VniwUPyFEinP0C05DKCtHkl+BsbW/g5YLKkHaUHU
|
||||
T15fCnbADIqKaihfX0OfCYFLVYa+CJ8j0HZFakRHbD4R000Nyv7Y385iwOfOOnEp
|
||||
ivlQittwx2ZRDrh1vY3mrfz8/k5ptJa/56B5gBQ7AohNAbTPzf+G8USpZ9LxHutQ
|
||||
J5vKRzvWGKcKmt6zg0qPKhfH9fgFXC+DWIG4uYJH3i+yLnnTCjRIRKeMgpzEpCgz
|
||||
5vcCggEBAIi6qKDHfoh4KaMFgW+/EvjssXslDtozMXlPYAswjST+5fegIKVxi8Nz
|
||||
c19KRiWNpsIfACRC1VuI2p2tcJpamiHV+C3nd3e/CMGWKTcA6u6zynhO8pDjfg/A
|
||||
k8Vg85S8bxGkiaVqL9DdmgRJohUbULV365gG2LvncNxps8Jr4VQaUHB4VJYNH+6B
|
||||
DXwDb3N4iNs5wfmM2GB7MlPpu0pS6qoYSxZvXQexPFQ7sQzZc3mP04/BvHm11kSR
|
||||
2DOV28IdXE/ewJfL9cr/ywXuoyz+0tD6FmTGUpzDSxhlvq4TcVnsWRbfqandbg8g
|
||||
znviHVPPYZhKHQW5wGuGa6eMMWa6aog=
|
||||
-----END PRIVATE KEY-----
|
||||
19
examples/features/advancedtls/creds/server_revoked.crl
Normal file
19
examples/features/advancedtls/creds/server_revoked.crl
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN X509 CRL-----
|
||||
MIIDJzCCAQ8CAQEwDQYJKoZIhvcNAQELBQAwgaExCzAJBgNVBAYTAlVTMRAwDgYD
|
||||
VQQIDAdHZW9yZ2lhMRAwDgYDVQQHDAdBdGxhbnRhMRAwDgYDVQQKDAdUZXN0IENB
|
||||
MRwwGgYDVQQLDBNUZXN0IENBIE9yZ2FuemF0aW9uMR0wGwYDVQQDDBRUZXN0IENB
|
||||
IE9yZ2FuaXphdGlvbjEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbRcN
|
||||
MjQwODA4MTgwNjIxWhcNMjQwOTA3MTgwNjIxWjAUMBICAQIXDTI0MDgwODE4MDYy
|
||||
MVqgIzAhMB8GA1UdIwQYMBaAFFB2w1xrEuGSDyj48kOmgMKc6FbWMA0GCSqGSIb3
|
||||
DQEBCwUAA4ICAQAvc4fK9H6OnUrtIzBls7ctVnmg4Up8NLWS9BWM9Ncw4MxLEXwM
|
||||
4iD2Tgg5Yq51iE5pDNet5shae3l/kDNWigPixhpfiT8xwxnrOA3BUtD3affc0Nem
|
||||
kS9Heq99jqvRdDF2nlEoiJesElxlSrwaljpev2SDzX/qnP5iRsBEuRS7Dr+83rcf
|
||||
ysYt0Hd84MCaSJ2iITF7Kg7Zg7R+HV0O++k+ZXCuJkDDo8TGa+Kr87WtFsER1+SX
|
||||
rtIgZEmikF/rEiKOHYV7QSujn8bzSsKzW2S/8/xygQnZ39vk5OPKKokPMUqesMPE
|
||||
8hUwGjCnDDijj6WGyP54FHKcH61P6R5a1EsjNfTUli9J7BNSHjb7AqPFRMCz6Ihj
|
||||
GUidGqCz4mpxkUpOwGJZWyefeWlKCdoBqDHlRtf8EjdE9BClOacJeVx2dmXd7k5r
|
||||
y6grIFfNjHYJmVa8+o2YCV80wY9XFtRUsGpEwHFTrtAjec+y2gtILKpsv5aqyvOa
|
||||
+nffFdMAR05Dx4MeFJSKYQGk64hPmgrRK+MGc224JPmQDi9uMwDKo+jB1TkCgwvR
|
||||
ZTF0VpPWBfhkB6x2haYyq2whf6zHfR+jMA0npUA8vHSUIiQrgWbvwsCc3nFGt4nz
|
||||
WjEZ7Q8Sw9CBfcvXrSV+WQdJF6kHgwaiI56x7DvdEAoDxuDLbmb+D/5gIg==
|
||||
-----END X509 CRL-----
|
||||
91
examples/features/advancedtls/generate.sh
Executable file
91
examples/features/advancedtls/generate.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
if [ -d "creds" ]; then
|
||||
echo "creds directory already exists. Remove it and re-run this script."
|
||||
exit 1
|
||||
fi
|
||||
mkdir creds
|
||||
pushd creds
|
||||
touch index.txt
|
||||
echo "01" > serial.txt
|
||||
cp "../localhost-openssl.cnf" .
|
||||
cp "../openssl-ca.cnf" .
|
||||
|
||||
# Create the CA private key and certificate
|
||||
openssl req -x509 -newkey rsa:4096 -keyout ca_key.pem -out ca_cert.pem -nodes -days 3650 -subj "/C=US/ST=Georgia/L=Atlanta/O=Test CA/OU=Test CA Organzation/CN=Test CA Organization/emailAddress=test@example.com"
|
||||
|
||||
#################### Server cert
|
||||
|
||||
# Generate Server private key
|
||||
openssl genrsa -out server_key.pem 4096
|
||||
|
||||
# Generate Server Certificate Signing Request (CSR)
|
||||
openssl req -config "localhost-openssl.cnf" -new -key server_key.pem -out server_csr.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Test Server/OU=Test Server Organzation/CN=Test Server Organization/emailAddress=testserver@example.com"
|
||||
|
||||
# Use the CA to sign the Server CSR
|
||||
openssl ca -config "openssl-ca.cnf" -policy signing_policy -extensions signing_req -out server_cert.pem -in server_csr.pem -keyfile ca_key.pem -cert ca_cert.pem -batch
|
||||
|
||||
# Verify the server cert works
|
||||
openssl verify -verbose -CAfile ca_cert.pem server_cert.pem
|
||||
|
||||
## Generate another server cert to be revoked
|
||||
openssl req -config "localhost-openssl.cnf" -new -key server_key.pem -out server_csr_revoked.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Test server/OU=Test server Organzation/CN=Test server Organization/emailAddress=testserver@example.com"
|
||||
|
||||
# Use the CA to sign the server CSR
|
||||
openssl ca -config "openssl-ca.cnf" -policy signing_policy -extensions signing_req -out server_cert_revoked.pem -in server_csr_revoked.pem -keyfile ca_key.pem -cert ca_cert.pem -batch
|
||||
|
||||
# Verify the server cert works
|
||||
openssl verify -verbose -CAfile ca_cert.pem server_cert_revoked.pem
|
||||
|
||||
# Revoke the cert
|
||||
openssl ca -config "openssl-ca.cnf" -revoke server_cert_revoked.pem
|
||||
|
||||
# Generate the CRL
|
||||
openssl ca -config "openssl-ca.cnf" -gencrl -out server_revoked.crl
|
||||
|
||||
# Make sure the cert is actually revoked
|
||||
openssl verify -verbose -CAfile ca_cert.pem -CRLfile server_revoked.crl -crl_check_all server_cert_revoked.pem
|
||||
|
||||
#################### Client cert
|
||||
# Generate client private key
|
||||
openssl genrsa -out client_key.pem 4096
|
||||
|
||||
# Generate client Certificate Signing Request (CSR)
|
||||
openssl req -config "localhost-openssl.cnf" -new -key client_key.pem -out client_csr.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Test client/OU=Test client Organzation/CN=Test client Organization/emailAddress=testclient@example.com"
|
||||
|
||||
# Use the CA to sign the client CSR
|
||||
openssl ca -config "openssl-ca.cnf" -policy signing_policy -extensions signing_req -out client_cert.pem -in client_csr.pem -keyfile ca_key.pem -cert ca_cert.pem -batch
|
||||
|
||||
# Verify the client cert works
|
||||
openssl verify -verbose -CAfile ca_cert.pem client_cert.pem
|
||||
|
||||
## Generate another client cert to be revoked
|
||||
openssl req -config "localhost-openssl.cnf" -new -key client_key.pem -out client_csr_revoked.pem -subj "/C=US/ST=Georgia/L=Atlanta/O=Test client/OU=Test client Organzation/CN=Test client Organization/emailAddress=testclient@example.com"
|
||||
|
||||
# Use the CA to sign the client CSR
|
||||
openssl ca -config "openssl-ca.cnf" -policy signing_policy -extensions signing_req -out client_cert_revoked.pem -in client_csr_revoked.pem -keyfile ca_key.pem -cert ca_cert.pem -batch
|
||||
|
||||
# Verify the client cert works
|
||||
openssl verify -verbose -CAfile ca_cert.pem client_cert_revoked.pem
|
||||
|
||||
# Revoke the cert
|
||||
openssl ca -config "openssl-ca.cnf" -revoke client_cert_revoked.pem
|
||||
|
||||
# Generate the CRL
|
||||
openssl ca -config "openssl-ca.cnf" -gencrl -out client_revoked.crl
|
||||
|
||||
# Make sure the cert is actually revoked
|
||||
openssl verify -verbose -CAfile ca_cert.pem -CRLfile client_revoked.crl -crl_check_all client_cert_revoked.pem
|
||||
|
||||
mkdir crl
|
||||
mv client_revoked.crl crl/
|
||||
|
||||
rm 01.pem
|
||||
rm 02.pem
|
||||
rm 03.pem
|
||||
rm 04.pem
|
||||
rm *csr*
|
||||
rm *.txt*
|
||||
|
||||
|
||||
|
||||
popd
|
||||
24
examples/features/advancedtls/localhost-openssl.cnf
Normal file
24
examples/features/advancedtls/localhost-openssl.cnf
Normal file
@@ -0,0 +1,24 @@
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
|
||||
[req_distinguished_name]
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_default = US
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
stateOrProvinceName_default = Georgia
|
||||
localityName = Locality Name (eg, city)
|
||||
localityName_default = Atlanta
|
||||
organizationName = Organization Name (eg, company)
|
||||
organizationName_default = Test Department
|
||||
commonName = Common Name (eg, YOUR name)
|
||||
commonName_max = 64
|
||||
|
||||
[v3_req]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
IP.1 = 0.0.0.0
|
||||
94
examples/features/advancedtls/openssl-ca.cnf
Normal file
94
examples/features/advancedtls/openssl-ca.cnf
Normal file
@@ -0,0 +1,94 @@
|
||||
base_dir = .
|
||||
certificate = $base_dir/ca_cert.pem # The CA certificate
|
||||
private_key = $base_dir/ca_key.pem # The CA private key
|
||||
new_certs_dir = $base_dir # Location for new certs after signing
|
||||
database = $base_dir/index.txt # Database index file
|
||||
serial = $base_dir/serial.txt # The current serial number
|
||||
|
||||
unique_subject = no # Set to 'no' to allow creation of
|
||||
# several certificates with same subject.
|
||||
|
||||
HOME = .
|
||||
RANDFILE = $ENV::HOME/.rnd
|
||||
|
||||
####################################################################
|
||||
[ ca ]
|
||||
default_ca = CA_default # The default ca section
|
||||
|
||||
[ CA_default ]
|
||||
|
||||
default_days = 10000 # How long to certify for
|
||||
default_crl_days = 30 # How long before next CRL
|
||||
default_md = sha256 # Use public key default MD
|
||||
preserve = no # Keep passed DN ordering
|
||||
|
||||
x509_extensions = ca_extensions # The extensions to add to the cert
|
||||
crl_extensions = crl_ext
|
||||
|
||||
email_in_dn = no # Don't concat the email in the DN
|
||||
copy_extensions = copy # Required to copy SANs from CSR to cert
|
||||
|
||||
####################################################################
|
||||
[ req ]
|
||||
default_bits = 4096
|
||||
default_keyfile = ca_key.pem
|
||||
distinguished_name = ca_distinguished_name
|
||||
x509_extensions = ca_extensions
|
||||
string_mask = utf8only
|
||||
|
||||
####################################################################
|
||||
[ ca_distinguished_name ]
|
||||
countryName = Country Name (2 letter code)
|
||||
countryName_default = US
|
||||
|
||||
stateOrProvinceName = State or Province Name (full name)
|
||||
stateOrProvinceName_default = Georgia
|
||||
|
||||
localityName = Locality Name (eg, city)
|
||||
localityName_default = Atlanta
|
||||
|
||||
organizationName = Organization Name (eg, company)
|
||||
organizationName_default = Test CA
|
||||
|
||||
organizationalUnitName = Organizational Unit (eg, division)
|
||||
organizationalUnitName_default = Test CA Organization
|
||||
|
||||
commonName = Common Name (e.g. server FQDN or YOUR name)
|
||||
commonName_default = Test CA Organization
|
||||
|
||||
emailAddress = Email Address
|
||||
emailAddress_default = test@example.com
|
||||
|
||||
####################################################################
|
||||
[ ca_extensions ]
|
||||
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid:always, issuer
|
||||
basicConstraints = critical, CA:true
|
||||
keyUsage = keyCertSign, cRLSign
|
||||
|
||||
|
||||
|
||||
|
||||
####################################################################
|
||||
[ signing_policy ]
|
||||
countryName = optional
|
||||
stateOrProvinceName = optional
|
||||
localityName = optional
|
||||
organizationName = optional
|
||||
organizationalUnitName = optional
|
||||
commonName = supplied
|
||||
emailAddress = optional
|
||||
|
||||
####################################################################
|
||||
[ signing_req ]
|
||||
subjectKeyIdentifier = hash
|
||||
authorityKeyIdentifier = keyid,issuer
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = digitalSignature, keyEncipherment
|
||||
|
||||
[ crl_ext ]
|
||||
# CRL extensions.
|
||||
# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
|
||||
#issuerAltName=issuer:copy
|
||||
authorityKeyIdentifier=keyid:always
|
||||
177
examples/features/advancedtls/server/main.go
Normal file
177
examples/features/advancedtls/server/main.go
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server is an example client demonstrating how to set up a secure gRPC
|
||||
// server using advancedtls.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/credentials/tls/certprovider"
|
||||
"google.golang.org/grpc/credentials/tls/certprovider/pemfile"
|
||||
"google.golang.org/grpc/security/advancedtls"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
name string
|
||||
}
|
||||
|
||||
const credRefreshInterval = 1 * time.Minute
|
||||
const goodServerWithCRLPort int = 50051
|
||||
const revokedServerWithCRLPort int = 50053
|
||||
const insecurePort int = 50054
|
||||
|
||||
func (s *server) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func insecureServer() {
|
||||
createAndRunInsecureServer(insecurePort)
|
||||
}
|
||||
|
||||
func createAndRunInsecureServer(port int) {
|
||||
creds := insecure.NewCredentials()
|
||||
s := grpc.NewServer(grpc.Creds(creds))
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to listen: %v\n", err)
|
||||
}
|
||||
pb.RegisterEchoServer(s, &server{name: "Insecure Server"})
|
||||
if err := s.Serve(lis); err != nil {
|
||||
fmt.Printf("Failed to serve: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func createAndRunTLSServer(credsDirectory string, useRevokedCert bool, port int) {
|
||||
identityProvider := makeIdentityProvider(useRevokedCert, credsDirectory)
|
||||
defer identityProvider.Close()
|
||||
|
||||
rootProvider := makeRootProvider(credsDirectory)
|
||||
defer rootProvider.Close()
|
||||
|
||||
crlProvider := makeCRLProvider(filepath.Join(credsDirectory, "crl"))
|
||||
defer crlProvider.Close()
|
||||
|
||||
options := &advancedtls.Options{
|
||||
IdentityOptions: advancedtls.IdentityCertificateOptions{
|
||||
IdentityProvider: identityProvider,
|
||||
},
|
||||
RootOptions: advancedtls.RootCertificateOptions{
|
||||
RootProvider: rootProvider,
|
||||
},
|
||||
RequireClientCert: true,
|
||||
VerificationType: advancedtls.CertVerification,
|
||||
}
|
||||
|
||||
options.RevocationOptions = &advancedtls.RevocationOptions{
|
||||
CRLProvider: crlProvider,
|
||||
}
|
||||
|
||||
serverTLSCreds, err := advancedtls.NewServerCreds(options)
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
s := grpc.NewServer(grpc.Creds(serverTLSCreds))
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to listen: %v\n", err)
|
||||
}
|
||||
name := "Good TLS Server"
|
||||
if useRevokedCert {
|
||||
name = "Revoked TLS Server"
|
||||
}
|
||||
pb.RegisterEchoServer(s, &server{name: name})
|
||||
if err := s.Serve(lis); err != nil {
|
||||
fmt.Printf("Failed to serve: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func makeRootProvider(credsDirectory string) certprovider.Provider {
|
||||
rootOptions := pemfile.Options{
|
||||
RootFile: filepath.Join(credsDirectory, "/ca_cert.pem"),
|
||||
RefreshDuration: credRefreshInterval,
|
||||
}
|
||||
|
||||
rootProvider, err := pemfile.NewProvider(rootOptions)
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return rootProvider
|
||||
}
|
||||
|
||||
func makeIdentityProvider(useRevokedCert bool, credsDirectory string) certprovider.Provider {
|
||||
certFilePath := ""
|
||||
if useRevokedCert {
|
||||
certFilePath = filepath.Join(credsDirectory, "server_cert_revoked.pem")
|
||||
} else {
|
||||
certFilePath = filepath.Join(credsDirectory, "server_cert.pem")
|
||||
}
|
||||
identityOptions := pemfile.Options{
|
||||
CertFile: certFilePath,
|
||||
KeyFile: filepath.Join(credsDirectory, "server_key.pem"),
|
||||
RefreshDuration: credRefreshInterval,
|
||||
}
|
||||
identityProvider, err := pemfile.NewProvider(identityOptions)
|
||||
if err != nil {
|
||||
fmt.Printf("Error %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return identityProvider
|
||||
}
|
||||
|
||||
func makeCRLProvider(crlDirectory string) *advancedtls.FileWatcherCRLProvider {
|
||||
options := advancedtls.FileWatcherOptions{
|
||||
CRLDirectory: crlDirectory,
|
||||
}
|
||||
provider, err := advancedtls.NewFileWatcherCRLProvider(options)
|
||||
if err != nil {
|
||||
fmt.Printf("Error making CRL Provider: %v\nExiting...", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return provider
|
||||
}
|
||||
|
||||
func main() {
|
||||
credentialsDirectory := flag.String("credentials_directory", "", "Path to the creds directory of this repo")
|
||||
flag.Parse()
|
||||
if *credentialsDirectory == "" {
|
||||
fmt.Println("Must set credentials_directory argument")
|
||||
os.Exit(1)
|
||||
}
|
||||
go createAndRunTLSServer(*credentialsDirectory, false, goodServerWithCRLPort)
|
||||
go createAndRunTLSServer(*credentialsDirectory, true, revokedServerWithCRLPort)
|
||||
insecureServer()
|
||||
}
|
||||
62
examples/features/authentication/README.md
Normal file
62
examples/features/authentication/README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Authentication
|
||||
|
||||
In grpc, authentication is abstracted as
|
||||
[`credentials.PerRPCCredentials`](https://godoc.org/google.golang.org/grpc/credentials#PerRPCCredentials).
|
||||
It usually also encompasses authorization. Users can configure it on a
|
||||
per-connection basis or a per-call basis.
|
||||
|
||||
The example for authentication currently includes an example for using oauth2
|
||||
with grpc.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
### OAuth2
|
||||
|
||||
OAuth 2.0 Protocol is a widely used authentication and authorization mechanism
|
||||
nowadays. And grpc provides convenient APIs to configure OAuth to use with grpc.
|
||||
Please refer to the godoc:
|
||||
https://godoc.org/google.golang.org/grpc/credentials/oauth for details.
|
||||
|
||||
#### Client
|
||||
|
||||
On client side, users should first get a valid oauth token, and then initialize a
|
||||
[`oauth.TokenSource`](https://godoc.org/google.golang.org/grpc/credentials/oauth#TokenSource)
|
||||
which implements `credentials.PerRPCCredentials`. Next, if user wants to
|
||||
apply a single OAuth token for all RPC calls on the same connection, then
|
||||
configure grpc `Dial` with `DialOption`
|
||||
[`WithPerRPCCredentials`](https://godoc.org/google.golang.org/grpc#WithPerRPCCredentials).
|
||||
Or, if user wants to apply OAuth token per call, then configure the grpc RPC
|
||||
call with `CallOption`
|
||||
[`PerRPCCredentials`](https://godoc.org/google.golang.org/grpc#PerRPCCredentials).
|
||||
|
||||
Note that OAuth requires the underlying transport to be secure (e.g. TLS, etc.)
|
||||
|
||||
Inside grpc, the provided token is prefixed with the token type and a space, and
|
||||
is then attached to the metadata with the key "authorization".
|
||||
|
||||
### Server
|
||||
|
||||
On server side, users usually get the token and verify it inside an interceptor.
|
||||
To get the token, call
|
||||
[`metadata.FromIncomingContext`](https://godoc.org/google.golang.org/grpc/metadata#FromIncomingContext)
|
||||
on the given context. It returns the metadata map. Next, use the key
|
||||
"authorization" to get corresponding value, which is a slice of strings. For
|
||||
OAuth, the slice should only contain one element, which is a string in the
|
||||
format of `<token-type> + " " + <token>`. Users can easily get the token by
|
||||
parsing the string, and then verify the validity of it.
|
||||
|
||||
If the token is not valid, returns an error with error code
|
||||
`codes.Unauthenticated`.
|
||||
|
||||
If the token is valid, then invoke the method handler to start processing the
|
||||
RPC.
|
||||
86
examples/features/authentication/client/main.go
Normal file
86
examples/features/authentication/client/main.go
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// The client demonstrates how to supply an OAuth2 token for every RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/oauth"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func callUnaryEcho(client ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Set up the credentials for the connection.
|
||||
perRPC := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(fetchToken())}
|
||||
creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load credentials: %v", err)
|
||||
}
|
||||
opts := []grpc.DialOption{
|
||||
// In addition to the following grpc.DialOption, callers may also use
|
||||
// the grpc.CallOption grpc.PerRPCCredentials with the RPC invocation
|
||||
// itself.
|
||||
// See: https://godoc.org/google.golang.org/grpc#PerRPCCredentials
|
||||
grpc.WithPerRPCCredentials(perRPC),
|
||||
// oauth.TokenSource requires the configuration of transport
|
||||
// credentials.
|
||||
grpc.WithTransportCredentials(creds),
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(*addr, opts...)
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
rgc := ecpb.NewEchoClient(conn)
|
||||
|
||||
callUnaryEcho(rgc, "hello world")
|
||||
}
|
||||
|
||||
// fetchToken simulates a token lookup and omits the details of proper token
|
||||
// acquisition. For examples of how to acquire an OAuth2 token, see:
|
||||
// https://godoc.org/golang.org/x/oauth2
|
||||
func fetchToken() *oauth2.Token {
|
||||
return &oauth2.Token{
|
||||
AccessToken: "some-secret-token",
|
||||
}
|
||||
}
|
||||
112
examples/features/authentication/server/main.go
Normal file
112
examples/features/authentication/server/main.go
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// The server demonstrates how to consume and validate OAuth2 tokens provided by
|
||||
// clients for each RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var (
|
||||
errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
|
||||
errInvalidToken = status.Errorf(codes.Unauthenticated, "invalid token")
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
fmt.Printf("server starting on port %d...\n", *port)
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load key pair: %s", err)
|
||||
}
|
||||
opts := []grpc.ServerOption{
|
||||
// The following grpc.ServerOption adds an interceptor for all unary
|
||||
// RPCs. To configure an interceptor for streaming RPCs, see:
|
||||
// https://godoc.org/google.golang.org/grpc#StreamInterceptor
|
||||
grpc.UnaryInterceptor(ensureValidToken),
|
||||
// Enable TLS for all incoming connections.
|
||||
grpc.Creds(credentials.NewServerTLSFromCert(&cert)),
|
||||
}
|
||||
s := grpc.NewServer(opts...)
|
||||
pb.RegisterEchoServer(s, &ecServer{})
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type ecServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
// valid validates the authorization.
|
||||
func valid(authorization []string) bool {
|
||||
if len(authorization) < 1 {
|
||||
return false
|
||||
}
|
||||
token := strings.TrimPrefix(authorization[0], "Bearer ")
|
||||
// Perform the token validation here. For the sake of this example, the code
|
||||
// here forgoes any of the usual OAuth2 token validation and instead checks
|
||||
// for a token matching an arbitrary string.
|
||||
return token == "some-secret-token"
|
||||
}
|
||||
|
||||
// ensureValidToken ensures a valid token exists within a request's metadata. If
|
||||
// the token is missing or invalid, the interceptor blocks execution of the
|
||||
// handler and returns an error. Otherwise, the interceptor invokes the unary
|
||||
// handler.
|
||||
func ensureValidToken(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, errMissingMetadata
|
||||
}
|
||||
// The keys within metadata.MD are normalized to lowercase.
|
||||
// See: https://godoc.org/google.golang.org/grpc/metadata#New
|
||||
if !valid(md["authorization"]) {
|
||||
return nil, errInvalidToken
|
||||
}
|
||||
// Continue execution of handler after ensuring a valid token.
|
||||
return handler(ctx, req)
|
||||
}
|
||||
75
examples/features/authz/README.md
Normal file
75
examples/features/authz/README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# RBAC authorization
|
||||
|
||||
This example uses the `StaticInterceptor` and `FileWatcherInterceptor` from the
|
||||
`google.golang.org/grpc/authz` package. It uses a header based RBAC policy to
|
||||
match each gRPC method to a required role. For simplicity, the context is
|
||||
injected with mock metadata which includes the required roles, but this should
|
||||
be fetched from an appropriate service based on the authenticated context.
|
||||
|
||||
## Try it
|
||||
|
||||
Server is expected to require the following roles on an authenticated user to
|
||||
authorize usage of these methods:
|
||||
|
||||
- `UnaryEcho` requires the role `UNARY_ECHO:W`
|
||||
- `BidirectionalStreamingEcho` requires the role `STREAM_ECHO:RW`
|
||||
|
||||
Upon receiving a request, the server first checks that a token was supplied,
|
||||
decodes it and checks that a secret is correctly set (hardcoded to
|
||||
`super-secret` for simplicity, this should use a proper ID provider in
|
||||
production).
|
||||
|
||||
If the above is successful, it uses the username in the token to set appropriate
|
||||
roles (hardcoded to the 2 required roles above if the username matches
|
||||
`super-user` for simplicity, these roles should be supplied externally as well).
|
||||
|
||||
### Authorization with static policy
|
||||
|
||||
Start the server with:
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
The client implementation shows how using a valid token (setting username and
|
||||
secret) with each of the endpoints will return successfully. It also exemplifies
|
||||
how using a bad token will result in `codes.PermissionDenied` being returned
|
||||
from the service.
|
||||
|
||||
Start the client with:
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
### Authorization by watching a policy file
|
||||
|
||||
The server accepts an optional `--authz-option filewatcher` flag to set up
|
||||
authorization policy by reading a [policy
|
||||
file](/examples/data/rbac/policy.json), and to look for update on the policy
|
||||
file every 100 millisecond. Having `GRPC_GO_LOG_SEVERITY_LEVEL` environment
|
||||
variable set to `info` will log out the reload activity of the policy every time
|
||||
a file update is detected.
|
||||
|
||||
Start the server with:
|
||||
|
||||
```
|
||||
GRPC_GO_LOG_SEVERITY_LEVEL=info go run server/main.go --authz-option filewatcher
|
||||
```
|
||||
|
||||
Start the client with:
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
The client will first hit `codes.PermissionDenied` error when invoking
|
||||
`UnaryEcho` although a legitimate username (`super-user`) is associated with the
|
||||
RPC. This is because the policy file has an intentional glitch (falsely asks for
|
||||
role `UNARY_ECHO:RW`).
|
||||
|
||||
While the server is still running, edit and save the policy file to replace
|
||||
`UNARY_ECHO:RW` with the correct role `UNARY_ECHO:W` (policy reload activity
|
||||
should now be found in server logs). This time when the client is started again
|
||||
with the command above, it will be able to get responses just as in the
|
||||
static-policy example.
|
||||
141
examples/features/authz/client/main.go
Normal file
141
examples/features/authz/client/main.go
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to include authorization credentials in the
|
||||
// form of metadata in every RPC for server side validation.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/oauth"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
"google.golang.org/grpc/examples/features/authz/token"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func callUnaryEcho(ctx context.Context, client ecpb.EchoClient, message string, opts ...grpc.CallOption) error {
|
||||
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message}, opts...)
|
||||
if err != nil {
|
||||
return status.Errorf(status.Code(err), "UnaryEcho RPC failed: %v", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func callBidiStreamingEcho(ctx context.Context, client ecpb.EchoClient, opts ...grpc.CallOption) error {
|
||||
c, err := client.BidirectionalStreamingEcho(ctx, opts...)
|
||||
if err != nil {
|
||||
return status.Errorf(status.Code(err), "BidirectionalStreamingEcho RPC failed: %v", err)
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
err := c.Send(&ecpb.EchoRequest{Message: fmt.Sprintf("Request %d", i+1)})
|
||||
if err == io.EOF {
|
||||
// Bidi streaming RPC errors happen and make Send return io.EOF,
|
||||
// not the RPC error itself. Call Recv to determine the error.
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// Some local errors are reported this way, e.g. errors serializing
|
||||
// the request message.
|
||||
return status.Errorf(status.Code(err), "sending StreamingEcho message: %v", err)
|
||||
}
|
||||
}
|
||||
c.CloseSend()
|
||||
for {
|
||||
resp, err := c.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return status.Errorf(status.Code(err), "receiving StreamingEcho message: %v", err)
|
||||
}
|
||||
fmt.Println("BidiStreaming Echo: ", resp.Message)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCredentialsCallOption(t token.Token) grpc.CallOption {
|
||||
tokenBase64, err := t.Encode()
|
||||
if err != nil {
|
||||
log.Fatalf("encoding token: %v", err)
|
||||
}
|
||||
oath2Token := oauth2.Token{AccessToken: tokenBase64}
|
||||
return grpc.PerRPCCredentials(oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oath2Token)})
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Create tls based credential.
|
||||
creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load credentials: %v", err)
|
||||
}
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
log.Fatalf("grpc.NewClient(%q): %v", *addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Make an echo client and send RPCs.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
client := ecpb.NewEchoClient(conn)
|
||||
|
||||
// Make RPCs as an authorized user and expect them to succeed.
|
||||
authorisedUserTokenCallOption := newCredentialsCallOption(token.Token{Username: "super-user", Secret: "super-secret"})
|
||||
if err := callUnaryEcho(ctx, client, "hello world", authorisedUserTokenCallOption); err != nil {
|
||||
log.Fatalf("Unary RPC by authorized user failed: %v", err)
|
||||
}
|
||||
if err := callBidiStreamingEcho(ctx, client, authorisedUserTokenCallOption); err != nil {
|
||||
log.Fatalf("Bidirectional RPC by authorized user failed: %v", err)
|
||||
}
|
||||
|
||||
// Make RPCs as an unauthorized user and expect them to fail with status code PermissionDenied.
|
||||
unauthorisedUserTokenCallOption := newCredentialsCallOption(token.Token{Username: "bad-actor", Secret: "super-secret"})
|
||||
if err := callUnaryEcho(ctx, client, "hello world", unauthorisedUserTokenCallOption); err != nil {
|
||||
switch c := status.Code(err); c {
|
||||
case codes.PermissionDenied:
|
||||
log.Printf("Unary RPC by unauthorized user failed as expected: %v", err)
|
||||
default:
|
||||
log.Fatalf("Unary RPC by unauthorized user failed unexpectedly: %v, %v", c, err)
|
||||
}
|
||||
}
|
||||
if err := callBidiStreamingEcho(ctx, client, unauthorisedUserTokenCallOption); err != nil {
|
||||
switch c := status.Code(err); c {
|
||||
case codes.PermissionDenied:
|
||||
log.Printf("Bidirectional RPC by unauthorized user failed as expected: %v", err)
|
||||
default:
|
||||
log.Fatalf("Bidirectional RPC by unauthorized user failed unexpectedly: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
238
examples/features/authz/server/main.go
Normal file
238
examples/features/authz/server/main.go
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to validate authorization credential metadata
|
||||
// for incoming RPCs.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/authz"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"google.golang.org/grpc/examples/features/authz/token"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
const (
|
||||
unaryEchoWriterRole = "UNARY_ECHO:W"
|
||||
streamEchoReadWriterRole = "STREAM_ECHO:RW"
|
||||
authzPolicy = `
|
||||
{
|
||||
"name": "authz",
|
||||
"allow_rules": [
|
||||
{
|
||||
"name": "allow_UnaryEcho",
|
||||
"request": {
|
||||
"paths": ["/grpc.examples.echo.Echo/UnaryEcho"],
|
||||
"headers": [
|
||||
{
|
||||
"key": "UNARY_ECHO:W",
|
||||
"values": ["true"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "allow_BidirectionalStreamingEcho",
|
||||
"request": {
|
||||
"paths": ["/grpc.examples.echo.Echo/BidirectionalStreamingEcho"],
|
||||
"headers": [
|
||||
{
|
||||
"key": "STREAM_ECHO:RW",
|
||||
"values": ["true"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"deny_rules": []
|
||||
}
|
||||
`
|
||||
authzOptStatic = "static"
|
||||
authzOptFileWatcher = "filewatcher"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 50051, "the port to serve on")
|
||||
authzOpt = flag.String("authz-option", authzOptStatic, "the authz option (static or filewatcher)")
|
||||
|
||||
errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
|
||||
)
|
||||
|
||||
func newContextWithRoles(ctx context.Context, username string) context.Context {
|
||||
md := metadata.MD{}
|
||||
if username == "super-user" {
|
||||
md.Set(unaryEchoWriterRole, "true")
|
||||
md.Set(streamEchoReadWriterRole, "true")
|
||||
}
|
||||
return metadata.NewIncomingContext(ctx, md)
|
||||
}
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *server) UnaryEcho(_ context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
fmt.Printf("unary echoing message %q\n", in.Message)
|
||||
return &pb.EchoResponse{Message: in.Message}, nil
|
||||
}
|
||||
|
||||
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Receiving message from stream: %v\n", err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("bidi echoing message %q\n", in.Message)
|
||||
stream.Send(&pb.EchoResponse{Message: in.Message})
|
||||
}
|
||||
}
|
||||
|
||||
// isAuthenticated validates the authorization.
|
||||
func isAuthenticated(authorization []string) (username string, err error) {
|
||||
if len(authorization) < 1 {
|
||||
return "", errors.New("received empty authorization token from client")
|
||||
}
|
||||
tokenBase64 := strings.TrimPrefix(authorization[0], "Bearer ")
|
||||
// Perform the token validation here. For the sake of this example, the code
|
||||
// here forgoes any of the usual OAuth2 token validation and instead checks
|
||||
// for a token matching an arbitrary string.
|
||||
var token token.Token
|
||||
err = token.Decode(tokenBase64)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("base64 decoding of received token %q: %v", tokenBase64, err)
|
||||
}
|
||||
if token.Secret != "super-secret" {
|
||||
return "", fmt.Errorf("received token %q does not match expected %q", token.Secret, "super-secret")
|
||||
}
|
||||
return token.Username, nil
|
||||
}
|
||||
|
||||
// authUnaryInterceptor looks up the authorization header from the incoming RPC context,
|
||||
// retrieves the username from it and creates a new context with the username before invoking
|
||||
// the provided handler.
|
||||
func authUnaryInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, errMissingMetadata
|
||||
}
|
||||
username, err := isAuthenticated(md["authorization"])
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Unauthenticated, err.Error())
|
||||
}
|
||||
return handler(newContextWithRoles(ctx, username), req)
|
||||
}
|
||||
|
||||
// wrappedStream wraps a grpc.ServerStream associated with an incoming RPC, and
|
||||
// a custom context containing the username derived from the authorization header
|
||||
// specified in the incoming RPC metadata
|
||||
type wrappedStream struct {
|
||||
grpc.ServerStream
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (w *wrappedStream) Context() context.Context {
|
||||
return w.ctx
|
||||
}
|
||||
|
||||
func newWrappedStream(ctx context.Context, s grpc.ServerStream) grpc.ServerStream {
|
||||
return &wrappedStream{s, ctx}
|
||||
}
|
||||
|
||||
// authStreamInterceptor looks up the authorization header from the incoming RPC context,
|
||||
// retrieves the username from it and creates a new context with the username before invoking
|
||||
// the provided handler.
|
||||
func authStreamInterceptor(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
md, ok := metadata.FromIncomingContext(ss.Context())
|
||||
if !ok {
|
||||
return errMissingMetadata
|
||||
}
|
||||
username, err := isAuthenticated(md["authorization"])
|
||||
if err != nil {
|
||||
return status.Error(codes.Unauthenticated, err.Error())
|
||||
}
|
||||
return handler(srv, newWrappedStream(newContextWithRoles(ss.Context(), username), ss))
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *authzOpt != authzOptStatic && *authzOpt != authzOptFileWatcher {
|
||||
log.Fatalf("Invalid authz option: %s", *authzOpt)
|
||||
}
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("Listening on local port %q: %v", *port, err)
|
||||
}
|
||||
|
||||
// Create tls based credential.
|
||||
creds, err := credentials.NewServerTLSFromFile(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
|
||||
if err != nil {
|
||||
log.Fatalf("Loading credentials: %v", err)
|
||||
}
|
||||
|
||||
// Create authorization interceptors according to the authz-option command-line flag.
|
||||
var unaryAuthzInterceptor grpc.UnaryServerInterceptor
|
||||
var streamAuthzInterceptor grpc.StreamServerInterceptor
|
||||
if *authzOpt == authzOptStatic {
|
||||
// Create an authorization interceptor using a static policy.
|
||||
staticInterceptor, err := authz.NewStatic(authzPolicy)
|
||||
if err != nil {
|
||||
log.Fatalf("Creating a static authz interceptor: %v", err)
|
||||
}
|
||||
unaryAuthzInterceptor, streamAuthzInterceptor = staticInterceptor.UnaryInterceptor, staticInterceptor.StreamInterceptor
|
||||
} else if *authzOpt == authzOptFileWatcher {
|
||||
// Create an authorization interceptor by watching a policy file.
|
||||
fileWatcherInterceptor, err := authz.NewFileWatcher(data.Path("rbac/policy.json"), 100*time.Millisecond)
|
||||
if err != nil {
|
||||
log.Fatalf("Creating a file watcher authz interceptor: %v", err)
|
||||
}
|
||||
unaryAuthzInterceptor, streamAuthzInterceptor = fileWatcherInterceptor.UnaryInterceptor, fileWatcherInterceptor.StreamInterceptor
|
||||
}
|
||||
|
||||
unaryInterceptors := grpc.ChainUnaryInterceptor(authUnaryInterceptor, unaryAuthzInterceptor)
|
||||
streamInterceptors := grpc.ChainStreamInterceptor(authStreamInterceptor, streamAuthzInterceptor)
|
||||
s := grpc.NewServer(grpc.Creds(creds), unaryInterceptors, streamInterceptors)
|
||||
|
||||
// Register EchoServer on the server.
|
||||
pb.RegisterEchoServer(s, &server{})
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("Serving Echo service on local port: %v", err)
|
||||
}
|
||||
}
|
||||
55
examples/features/authz/token/token.go
Normal file
55
examples/features/authz/token/token.go
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package token implements an example of authorization token encoding/decoding
|
||||
// that can be used in RPC headers.
|
||||
package token
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Token is a mock authorization token sent by the client as part of the RPC headers,
|
||||
// and used by the server for authorization against a predefined policy.
|
||||
type Token struct {
|
||||
// Secret is used by the server to authenticate the user
|
||||
Secret string `json:"secret"`
|
||||
// Username is used by the server to assign roles in the metadata for authorization
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// Encode returns a base64 encoded version of the JSON representation of token.
|
||||
func (t *Token) Encode() (string, error) {
|
||||
barr, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s := base64.StdEncoding.EncodeToString(barr)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Decode updates the internals of Token using the passed in base64
|
||||
// encoded version of the JSON representation of token.
|
||||
func (t *Token) Decode(s string) error {
|
||||
barr, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal(barr, t)
|
||||
}
|
||||
13
examples/features/cancellation/README.md
Normal file
13
examples/features/cancellation/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Cancellation
|
||||
|
||||
This example shows how clients can cancel in-flight RPCs by canceling the
|
||||
context passed to the RPC call. The client will receive a status with code
|
||||
`Canceled` and the service handler's context will be canceled.
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
96
examples/features/cancellation/client/main.go
Normal file
96
examples/features/cancellation/client/main.go
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to cancel in-flight RPCs by canceling the
|
||||
// context passed to the RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func sendMessage(stream pb.Echo_BidirectionalStreamingEchoClient, msg string) error {
|
||||
fmt.Printf("sending message %q\n", msg)
|
||||
return stream.Send(&pb.EchoRequest{Message: msg})
|
||||
}
|
||||
|
||||
func recvMessage(stream pb.Echo_BidirectionalStreamingEchoClient, wantErrCode codes.Code) {
|
||||
res, err := stream.Recv()
|
||||
if status.Code(err) != wantErrCode {
|
||||
log.Fatalf("stream.Recv() = %v, %v; want _, status.Code(err)=%v", res, err, wantErrCode)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Printf("stream.Recv() returned expected error %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("received message %q\n", res.GetMessage())
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := pb.NewEchoClient(conn)
|
||||
|
||||
// Initiate the stream with a context that supports cancellation.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
stream, err := c.BidirectionalStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("error creating stream: %v", err)
|
||||
}
|
||||
|
||||
// Send some test messages.
|
||||
if err := sendMessage(stream, "hello"); err != nil {
|
||||
log.Fatalf("error sending on stream: %v", err)
|
||||
}
|
||||
if err := sendMessage(stream, "world"); err != nil {
|
||||
log.Fatalf("error sending on stream: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the RPC is working.
|
||||
recvMessage(stream, codes.OK)
|
||||
recvMessage(stream, codes.OK)
|
||||
|
||||
fmt.Println("cancelling context")
|
||||
cancel()
|
||||
|
||||
// This Send may or may not return an error, depending on whether the
|
||||
// monitored context detects cancellation before the call is made.
|
||||
sendMessage(stream, "closed")
|
||||
|
||||
// This Recv should never succeed.
|
||||
recvMessage(stream, codes.Canceled)
|
||||
}
|
||||
67
examples/features/cancellation/server/main.go
Normal file
67
examples/features/cancellation/server/main.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to handle canceled contexts when a client
|
||||
// cancels an in-flight RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err != nil {
|
||||
fmt.Printf("server: error receiving from stream: %v\n", err)
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Printf("echoing message %q\n", in.Message)
|
||||
stream.Send(&pb.EchoResponse{Message: in.Message})
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
fmt.Printf("server listening at port %v\n", lis.Addr())
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterEchoServer(s, &server{})
|
||||
s.Serve(lis)
|
||||
}
|
||||
14
examples/features/compression/README.md
Normal file
14
examples/features/compression/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Compression
|
||||
|
||||
This example shows how clients can specify compression options when performing
|
||||
RPCs, and how to install support for compressors on the server. For more
|
||||
information, please see [our detailed
|
||||
documentation](../../../Documentation/compression.md).
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
62
examples/features/compression/client/main.go
Normal file
62
examples/features/compression/client/main.go
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to specify compression options when performing
|
||||
// RPCs.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip" // Install the gzip compressor
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := pb.NewEchoClient(conn)
|
||||
|
||||
// Send the RPC compressed. If all RPCs on a client should be sent this
|
||||
// way, use the DialOption:
|
||||
// grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))
|
||||
const msg = "compress"
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
res, err := c.UnaryEcho(ctx, &pb.EchoRequest{Message: msg}, grpc.UseCompressor(gzip.Name))
|
||||
fmt.Printf("UnaryEcho call returned %q, %v\n", res.GetMessage(), err)
|
||||
if err != nil || res.GetMessage() != msg {
|
||||
log.Fatalf("Message=%q, err=%v; want Message=%q, err=<nil>", res.GetMessage(), err, msg)
|
||||
}
|
||||
|
||||
}
|
||||
61
examples/features/compression/server/main.go
Normal file
61
examples/features/compression/server/main.go
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to install and support compressors for
|
||||
// incoming RPCs.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
// Installing the gzip encoding registers it as an available compressor.
|
||||
// gRPC will automatically negotiate and use gzip if the client supports it.
|
||||
_ "google.golang.org/grpc/encoding/gzip"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *server) UnaryEcho(_ context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
fmt.Printf("UnaryEcho called with message %q\n", in.GetMessage())
|
||||
return &pb.EchoResponse{Message: in.Message}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
fmt.Printf("server listening at %v\n", lis.Addr())
|
||||
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterEchoServer(s, &server{})
|
||||
s.Serve(lis)
|
||||
}
|
||||
38
examples/features/csm_observability/README.md
Normal file
38
examples/features/csm_observability/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# CSM Observability
|
||||
|
||||
This examples shows how to configure CSM Observability for gRPC client and
|
||||
server applications (configured once per binary), and shows what type of
|
||||
telemetry data it can produce for certain RPCs with additional CSM Labels. The
|
||||
gRPC Client accepts configuration from an xDS Control plane as the default
|
||||
address that it connects to is "xds:///helloworld:50051", but this can be
|
||||
overridden with the command line flag --server_addr. This can be plugged into
|
||||
the steps outlined in the CSM Observability User Guide.
|
||||
|
||||
## Try it (locally if overwritten xDS Address)
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
Curl to the port where Prometheus exporter is outputting metrics data:
|
||||
```
|
||||
curl localhost:9464/metrics
|
||||
```
|
||||
|
||||
# Building
|
||||
From the grpc-go directory:
|
||||
|
||||
Client:
|
||||
docker build -t <TAG> -f examples/features/csm_observability/client/Dockerfile .
|
||||
|
||||
Server:
|
||||
docker build -t <TAG> -f examples/features/csm_observability/server/Dockerfile .
|
||||
|
||||
Note that this example will not work by default, as the client uses an xDS
|
||||
Scheme and thus needs xDS Resources to connect to the server. Deploy the built
|
||||
client and server containers within Cloud Service Mesh in order for this example
|
||||
to work, or overwrite target to point to :<server serving port>.
|
||||
35
examples/features/csm_observability/client/Dockerfile
Normal file
35
examples/features/csm_observability/client/Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
# Copyright 2024 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.
|
||||
|
||||
# Dockerfile for building the example client. To build the image, run the
|
||||
# following command from grpc-go directory:
|
||||
# docker build -t <TAG> -f examples/features/csm_observability/client/Dockerfile .
|
||||
FROM golang:1.25-alpine as build
|
||||
|
||||
RUN apk --no-cache add curl
|
||||
|
||||
# Make a grpc-go directory and copy the repo into it.
|
||||
WORKDIR /go/src/grpc-go
|
||||
COPY . .
|
||||
|
||||
# Build a static binary without cgo so that we can copy just the binary in the
|
||||
# final image, and can get rid of the Go compiler and gRPC-Go dependencies.
|
||||
RUN cd examples/features/csm_observability/client && go build -tags osusergo,netgo .
|
||||
|
||||
FROM alpine
|
||||
RUN apk --no-cache add curl
|
||||
COPY --from=build /go/src/grpc-go/examples/features/csm_observability/client/client .
|
||||
ENV GRPC_GO_LOG_VERBOSITY_LEVEL=99
|
||||
ENV GRPC_GO_LOG_SEVERITY_LEVEL="info"
|
||||
ENTRYPOINT ["./client"]
|
||||
88
examples/features/csm_observability/client/main.go
Normal file
88
examples/features/csm_observability/client/main.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client is a client for the CSM Observability example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
xdscreds "google.golang.org/grpc/credentials/xds"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
"google.golang.org/grpc/stats/opentelemetry"
|
||||
"google.golang.org/grpc/stats/opentelemetry/csm"
|
||||
_ "google.golang.org/grpc/xds" // To install the xds resolvers and balancers.
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
)
|
||||
|
||||
const defaultName = "world"
|
||||
|
||||
var (
|
||||
target = flag.String("target", "xds:///helloworld:50051", "the server address to connect to")
|
||||
prometheusEndpoint = flag.String("prometheus_endpoint", ":9464", "the Prometheus exporter endpoint")
|
||||
name = flag.String("name", defaultName, "Name to greet")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
exporter, err := prometheus.New()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start prometheus exporter: %v", err)
|
||||
}
|
||||
provider := metric.NewMeterProvider(metric.WithReader(exporter))
|
||||
go http.ListenAndServe(*prometheusEndpoint, promhttp.Handler())
|
||||
|
||||
cleanup := csm.EnableObservability(context.Background(), opentelemetry.Options{MetricsOptions: opentelemetry.MetricsOptions{MeterProvider: provider}})
|
||||
defer cleanup()
|
||||
|
||||
// Set up xds credentials that fall back to insecure as described in:
|
||||
// https://cloud.google.com/service-mesh/docs/service-routing/security-proxyless-setup#workloads_are_unable_to_communicate_in_the_security_setup.
|
||||
creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create xDS credentials: %v", err)
|
||||
}
|
||||
cc, err := grpc.NewClient(*target, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start NewClient: %v", err)
|
||||
}
|
||||
defer cc.Close()
|
||||
c := pb.NewGreeterClient(cc)
|
||||
|
||||
// Make an RPC every second. This should trigger telemetry to be emitted from
|
||||
// the client and the server.
|
||||
for {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not greet: %v", err)
|
||||
}
|
||||
fmt.Println(r)
|
||||
time.Sleep(time.Second)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
34
examples/features/csm_observability/server/Dockerfile
Normal file
34
examples/features/csm_observability/server/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright 2024 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.
|
||||
|
||||
# Dockerfile for building the example server. To build the image, run the
|
||||
# following command from grpc-go directory:
|
||||
# docker build -t <TAG> -f examples/features/csm_observability/server/Dockerfile .
|
||||
|
||||
FROM golang:1.25-alpine as build
|
||||
RUN apk --no-cache add curl
|
||||
# Make a grpc-go directory and copy the repo into it.
|
||||
WORKDIR /go/src/grpc-go
|
||||
COPY . .
|
||||
|
||||
# Build a static binary without cgo so that we can copy just the binary in the
|
||||
# final image, and can get rid of the Go compiler and gRPC-Go dependencies.
|
||||
RUN cd examples/features/csm_observability/server && go build -tags osusergo,netgo .
|
||||
|
||||
FROM alpine
|
||||
RUN apk --no-cache add curl
|
||||
COPY --from=build /go/src/grpc-go/examples/features/csm_observability/server/server .
|
||||
ENV GRPC_GO_LOG_VERBOSITY_LEVEL=99
|
||||
ENV GRPC_GO_LOG_SEVERITY_LEVEL="info"
|
||||
ENTRYPOINT ["./server"]
|
||||
91
examples/features/csm_observability/server/main.go
Normal file
91
examples/features/csm_observability/server/main.go
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server is a server for the CSM Observability example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
xdscreds "google.golang.org/grpc/credentials/xds"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
"google.golang.org/grpc/stats/opentelemetry"
|
||||
"google.golang.org/grpc/stats/opentelemetry/csm"
|
||||
"google.golang.org/grpc/xds"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||
"go.opentelemetry.io/otel/sdk/metric"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.String("port", "50051", "the server address to connect to")
|
||||
prometheusEndpoint = flag.String("prometheus_endpoint", ":9464", "the Prometheus exporter endpoint")
|
||||
)
|
||||
|
||||
// server is used to implement helloworld.GreeterServer.
|
||||
type server struct {
|
||||
pb.UnimplementedGreeterServer
|
||||
addr string
|
||||
}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer
|
||||
func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
exporter, err := prometheus.New()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start prometheus exporter: %v", err)
|
||||
}
|
||||
provider := metric.NewMeterProvider(metric.WithReader(exporter))
|
||||
go http.ListenAndServe(*prometheusEndpoint, promhttp.Handler())
|
||||
|
||||
cleanup := csm.EnableObservability(context.Background(), opentelemetry.Options{MetricsOptions: opentelemetry.MetricsOptions{MeterProvider: provider}})
|
||||
defer cleanup()
|
||||
|
||||
lis, err := net.Listen("tcp4", "0.0.0.0:"+*port)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
// Set up xds credentials that fall back to insecure as described in:
|
||||
// https://cloud.google.com/service-mesh/docs/service-routing/security-proxyless-setup#workloads_are_unable_to_communicate_in_the_security_setup.
|
||||
creds, err := xdscreds.NewServerCredentials(xdscreds.ServerOptions{FallbackCreds: insecure.NewCredentials()})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create xDS credentials: %v", err)
|
||||
}
|
||||
s, err := xds.NewGRPCServer(grpc.Creds(creds))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start xDS Server: %v", err)
|
||||
}
|
||||
pb.RegisterGreeterServer(s, &server{addr: ":" + *port})
|
||||
|
||||
log.Printf("Serving on %s\n", *port)
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
52
examples/features/customloadbalancer/README.md
Normal file
52
examples/features/customloadbalancer/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Custom Load Balancer
|
||||
|
||||
This example shows how to deploy a custom load balancer in a `ClientConn`.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
Two echo servers are serving on "localhost:20000" and "localhost:20001". They
|
||||
will include their serving address in the response. So the server on
|
||||
"localhost:20001" will reply to the RPC with `this is
|
||||
examples/customloadbalancing (from localhost:20001)`.
|
||||
|
||||
A client is created, to connect to both of these servers (they get both server
|
||||
addresses from the name resolver in two separate endpoints). The client is
|
||||
configured with the load balancer specified in the service config, which in this
|
||||
case is custom_round_robin.
|
||||
|
||||
### custom_round_robin
|
||||
|
||||
The client is configured to use `custom_round_robin`. `custom_round_robin`
|
||||
creates a pick first child for every endpoint it receives. It waits until both
|
||||
pick first children become ready, then defers to the first pick first child's
|
||||
picker, choosing the connection to localhost:20000, except every chooseSecond
|
||||
times, where it defers to second pick first child's picker, choosing the
|
||||
connection to localhost:20001 (or vice versa).
|
||||
|
||||
`custom_round_robin` is written as a delegating policy wrapping `pick_first`
|
||||
load balancers, one for every endpoint received. This is the intended way a user
|
||||
written custom lb should be specified, as pick first will contain a lot of
|
||||
useful functionality, such as Sticky Transient Failure, Happy Eyeballs, and
|
||||
Health Checking.
|
||||
|
||||
```
|
||||
this is examples/customloadbalancing (from localhost:50050)
|
||||
this is examples/customloadbalancing (from localhost:50050)
|
||||
this is examples/customloadbalancing (from localhost:50051)
|
||||
this is examples/customloadbalancing (from localhost:50050)
|
||||
this is examples/customloadbalancing (from localhost:50050)
|
||||
this is examples/customloadbalancing (from localhost:50051)
|
||||
this is examples/customloadbalancing (from localhost:50050)
|
||||
this is examples/customloadbalancing (from localhost:50050)
|
||||
this is examples/customloadbalancing (from localhost:50051)
|
||||
```
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Package customroundrobin provides an example for the custom roundrobin balancer.
|
||||
package customroundrobin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
_ "google.golang.org/grpc" // to register pick_first
|
||||
"google.golang.org/grpc/balancer"
|
||||
"google.golang.org/grpc/balancer/endpointsharding"
|
||||
"google.golang.org/grpc/balancer/pickfirst"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
func init() {
|
||||
balancer.Register(customRoundRobinBuilder{})
|
||||
}
|
||||
|
||||
const customRRName = "custom_round_robin"
|
||||
|
||||
type customRRConfig struct {
|
||||
serviceconfig.LoadBalancingConfig `json:"-"`
|
||||
|
||||
// ChooseSecond represents how often pick iterations choose the second
|
||||
// SubConn in the list. Defaults to 3. If 0 never choose the second SubConn.
|
||||
ChooseSecond uint32 `json:"chooseSecond,omitempty"`
|
||||
}
|
||||
|
||||
type customRoundRobinBuilder struct{}
|
||||
|
||||
func (customRoundRobinBuilder) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
|
||||
lbConfig := &customRRConfig{
|
||||
ChooseSecond: 3,
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(s, lbConfig); err != nil {
|
||||
return nil, fmt.Errorf("custom-round-robin: unable to unmarshal customRRConfig: %v", err)
|
||||
}
|
||||
return lbConfig, nil
|
||||
}
|
||||
|
||||
func (customRoundRobinBuilder) Name() string {
|
||||
return customRRName
|
||||
}
|
||||
|
||||
func (customRoundRobinBuilder) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
|
||||
crr := &customRoundRobin{
|
||||
ClientConn: cc,
|
||||
bOpts: bOpts,
|
||||
}
|
||||
crr.Balancer = endpointsharding.NewBalancer(crr, bOpts, balancer.Get(pickfirst.Name).Build, endpointsharding.Options{})
|
||||
return crr
|
||||
}
|
||||
|
||||
type customRoundRobin struct {
|
||||
// All state and operations on this balancer are either initialized at build
|
||||
// time and read only after, or are only accessed as part of its
|
||||
// balancer.Balancer API (UpdateState from children only comes in from
|
||||
// balancer.Balancer calls as well, and children are called one at a time),
|
||||
// in which calls are guaranteed to come synchronously. Thus, no extra
|
||||
// synchronization is required in this balancer.
|
||||
balancer.Balancer
|
||||
balancer.ClientConn
|
||||
bOpts balancer.BuildOptions
|
||||
|
||||
cfg atomic.Pointer[customRRConfig]
|
||||
}
|
||||
|
||||
func (crr *customRoundRobin) UpdateClientConnState(state balancer.ClientConnState) error {
|
||||
crrCfg, ok := state.BalancerConfig.(*customRRConfig)
|
||||
if !ok {
|
||||
return balancer.ErrBadResolverState
|
||||
}
|
||||
if el := state.ResolverState.Endpoints; len(el) != 2 {
|
||||
return fmt.Errorf("UpdateClientConnState wants two endpoints, got: %v", el)
|
||||
}
|
||||
crr.cfg.Store(crrCfg)
|
||||
// A call to UpdateClientConnState should always produce a new Picker. That
|
||||
// is guaranteed to happen since the aggregator will always call
|
||||
// UpdateChildState in its UpdateClientConnState.
|
||||
return crr.Balancer.UpdateClientConnState(balancer.ClientConnState{
|
||||
ResolverState: state.ResolverState,
|
||||
})
|
||||
}
|
||||
|
||||
func (crr *customRoundRobin) UpdateState(state balancer.State) {
|
||||
if state.ConnectivityState == connectivity.Ready {
|
||||
childStates := endpointsharding.ChildStatesFromPicker(state.Picker)
|
||||
var readyPickers []balancer.Picker
|
||||
for _, childState := range childStates {
|
||||
if childState.State.ConnectivityState == connectivity.Ready {
|
||||
readyPickers = append(readyPickers, childState.State.Picker)
|
||||
}
|
||||
}
|
||||
// If both children are ready, pick using the custom round robin
|
||||
// algorithm.
|
||||
if len(readyPickers) == 2 {
|
||||
picker := &customRoundRobinPicker{
|
||||
pickers: readyPickers,
|
||||
chooseSecond: crr.cfg.Load().ChooseSecond,
|
||||
next: 0,
|
||||
}
|
||||
crr.ClientConn.UpdateState(balancer.State{
|
||||
ConnectivityState: connectivity.Ready,
|
||||
Picker: picker,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
// Delegate to default behavior/picker from below.
|
||||
crr.ClientConn.UpdateState(state)
|
||||
}
|
||||
|
||||
type customRoundRobinPicker struct {
|
||||
pickers []balancer.Picker
|
||||
chooseSecond uint32
|
||||
next uint32
|
||||
}
|
||||
|
||||
func (crrp *customRoundRobinPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) {
|
||||
next := atomic.AddUint32(&crrp.next, 1)
|
||||
index := 0
|
||||
if next != 0 && next%crrp.chooseSecond == 0 {
|
||||
index = 1
|
||||
}
|
||||
childPicker := crrp.pickers[index%len(crrp.pickers)]
|
||||
return childPicker.Pick(info)
|
||||
}
|
||||
155
examples/features/customloadbalancer/client/main.go
Normal file
155
examples/features/customloadbalancer/client/main.go
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client is a client for the custom load balancer example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
_ "google.golang.org/grpc/examples/features/customloadbalancer/client/customroundrobin" // To register custom_round_robin.
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/internal"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/resolver/manual"
|
||||
"google.golang.org/grpc/serviceconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
addr1 = "localhost:50050"
|
||||
addr2 = "localhost:50051"
|
||||
)
|
||||
|
||||
func main() {
|
||||
mr := manual.NewBuilderWithScheme("example")
|
||||
defer mr.Close()
|
||||
|
||||
// You can also plug in your own custom lb policy, which needs to be
|
||||
// configurable. This n is configurable. Try changing n and see how the
|
||||
// behavior changes.
|
||||
json := `{"loadBalancingConfig": [{"custom_round_robin":{"chooseSecond": 3}}]}`
|
||||
sc := internal.ParseServiceConfig.(func(string) *serviceconfig.ParseResult)(json)
|
||||
mr.InitialState(resolver.State{
|
||||
Endpoints: []resolver.Endpoint{
|
||||
{Addresses: []resolver.Address{{Addr: addr1}}},
|
||||
{Addresses: []resolver.Address{{Addr: addr2}}},
|
||||
},
|
||||
ServiceConfig: sc,
|
||||
})
|
||||
|
||||
cc, err := grpc.NewClient(mr.Scheme()+":///", grpc.WithResolvers(mr), grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("grpc.NewClient() failed: %v", err)
|
||||
}
|
||||
defer cc.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
ec := pb.NewEchoClient(cc)
|
||||
if err := waitForDistribution(ctx, ec); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Successful multiple iterations of 1:2 ratio")
|
||||
}
|
||||
|
||||
// waitForDistribution makes RPC's on the echo client until 3 RPC's follow the
|
||||
// same 1:2 address ratio for the peer. Returns an error if fails to do so
|
||||
// before context timeout.
|
||||
func waitForDistribution(ctx context.Context, ec pb.EchoClient) error {
|
||||
for {
|
||||
results := make(map[string]uint32)
|
||||
InnerLoop:
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("timeout waiting for 1:2 distribution between addresses %v and %v", addr1, addr2)
|
||||
}
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
res := make(map[string]uint32)
|
||||
for j := 0; j < 3; j++ {
|
||||
var peer peer.Peer
|
||||
r, err := ec.UnaryEcho(ctx, &pb.EchoRequest{Message: "this is examples/customloadbalancing"}, grpc.Peer(&peer))
|
||||
if err != nil {
|
||||
return fmt.Errorf("UnaryEcho failed: %v", err)
|
||||
}
|
||||
fmt.Println(r)
|
||||
peerAddr := peer.Addr.String()
|
||||
if !strings.HasSuffix(peerAddr, "50050") && !strings.HasSuffix(peerAddr, "50051") {
|
||||
return fmt.Errorf("peer address was not one of %v or %v, got: %v", addr1, addr2, peerAddr)
|
||||
}
|
||||
res[peerAddr]++
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
// Make sure the addresses come in a 1:2 ratio for this
|
||||
// iteration.
|
||||
var seen1, seen2 bool
|
||||
for addr, count := range res {
|
||||
if count != 1 && count != 2 {
|
||||
break InnerLoop
|
||||
}
|
||||
if count == 1 {
|
||||
if seen1 {
|
||||
break InnerLoop
|
||||
}
|
||||
seen1 = true
|
||||
}
|
||||
if count == 2 {
|
||||
if seen2 {
|
||||
break InnerLoop
|
||||
}
|
||||
seen2 = true
|
||||
}
|
||||
results[addr] = results[addr] + count
|
||||
}
|
||||
if !seen1 || !seen2 {
|
||||
break InnerLoop
|
||||
}
|
||||
}
|
||||
// Make sure iteration is 3 and 6 for addresses seen. This makes
|
||||
// sure the distribution is the same 1:2 ratio for each iteration.
|
||||
var seen3, seen6 bool
|
||||
for _, count := range results {
|
||||
if count != 3 && count != 6 {
|
||||
break InnerLoop
|
||||
}
|
||||
if count == 3 {
|
||||
if seen3 {
|
||||
break InnerLoop
|
||||
}
|
||||
seen3 = true
|
||||
}
|
||||
if count == 6 {
|
||||
if seen6 {
|
||||
break InnerLoop
|
||||
}
|
||||
seen6 = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !seen3 || !seen6 {
|
||||
break InnerLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
examples/features/customloadbalancer/server/main.go
Normal file
67
examples/features/customloadbalancer/server/main.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server is a server for the custom load balancer example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var (
|
||||
addrs = []string{"localhost:50050", "localhost:50051"}
|
||||
)
|
||||
|
||||
type echoServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
addr string
|
||||
}
|
||||
|
||||
func (s *echoServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: fmt.Sprintf("%s (from %s)", req.Message, s.addr)}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
for _, addr := range addrs {
|
||||
lis, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterEchoServer(s, &echoServer{
|
||||
addr: addr,
|
||||
})
|
||||
log.Printf("serving on %s\n", addr)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
97
examples/features/deadline/client/main.go
Normal file
97
examples/features/deadline/client/main.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to set deadlines for RPCs and how to handle
|
||||
// deadline-exceeded errors.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50052", "the address to connect to")
|
||||
|
||||
func unaryCall(c pb.EchoClient, requestID int, message string, want codes.Code) {
|
||||
// Creates a context with a one second deadline for the RPC.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := &pb.EchoRequest{Message: message}
|
||||
|
||||
_, err := c.UnaryEcho(ctx, req)
|
||||
got := status.Code(err)
|
||||
fmt.Printf("[%v] wanted = %v, got = %v\n", requestID, want, got)
|
||||
}
|
||||
|
||||
func streamingCall(c pb.EchoClient, requestID int, message string, want codes.Code) {
|
||||
// Creates a context with a one second deadline for the RPC.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
stream, err := c.BidirectionalStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
log.Printf("Stream err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = stream.Send(&pb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Printf("Send error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = stream.Recv()
|
||||
|
||||
got := status.Code(err)
|
||||
fmt.Printf("[%v] wanted = %v, got = %v\n", requestID, want, got)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := pb.NewEchoClient(conn)
|
||||
|
||||
// A successful request
|
||||
unaryCall(c, 1, "world", codes.OK)
|
||||
// Exceeds deadline
|
||||
unaryCall(c, 2, "delay", codes.DeadlineExceeded)
|
||||
// A successful request with propagated deadline
|
||||
unaryCall(c, 3, "[propagate me]world", codes.OK)
|
||||
// Exceeds propagated deadline
|
||||
unaryCall(c, 4, "[propagate me][propagate me]world", codes.DeadlineExceeded)
|
||||
// Receives a response from the stream successfully.
|
||||
streamingCall(c, 5, "[propagate me]world", codes.OK)
|
||||
// Exceeds propagated deadline before receiving a response
|
||||
streamingCall(c, 6, "[propagate me][propagate me]world", codes.DeadlineExceeded)
|
||||
}
|
||||
124
examples/features/deadline/server/main.go
Normal file
124
examples/features/deadline/server/main.go
Normal file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to handle RPCs with deadlines and how to
|
||||
// propagate deadlines in requests.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50052, "port number")
|
||||
|
||||
// server is used to implement EchoServer.
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
client pb.EchoClient
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func (s *server) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
message := req.Message
|
||||
if strings.HasPrefix(message, "[propagate me]") {
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
message = strings.TrimPrefix(message, "[propagate me]")
|
||||
return s.client.UnaryEcho(ctx, &pb.EchoRequest{Message: message})
|
||||
}
|
||||
|
||||
if message == "delay" {
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
}
|
||||
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return status.Error(codes.InvalidArgument, "request message not received")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := req.Message
|
||||
if strings.HasPrefix(message, "[propagate me]") {
|
||||
time.Sleep(800 * time.Millisecond)
|
||||
message = strings.TrimPrefix(message, "[propagate me]")
|
||||
res, err := s.client.UnaryEcho(stream.Context(), &pb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stream.Send(res)
|
||||
}
|
||||
|
||||
if message == "delay" {
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
}
|
||||
stream.Send(&pb.EchoResponse{Message: message})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) Close() {
|
||||
s.cc.Close()
|
||||
}
|
||||
|
||||
func newEchoServer() *server {
|
||||
target := fmt.Sprintf("localhost:%v", *port)
|
||||
cc, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
return &server{client: pb.NewEchoClient(cc), cc: cc}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
address := fmt.Sprintf(":%v", *port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
echoServer := newEchoServer()
|
||||
defer echoServer.Close()
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterEchoServer(grpcServer, echoServer)
|
||||
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
29
examples/features/debugging/README.md
Normal file
29
examples/features/debugging/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Debugging
|
||||
|
||||
Currently, grpc provides two major tools to help user debug issues, which are logging and channelz.
|
||||
|
||||
## Logs
|
||||
gRPC has put substantial logging instruments on critical paths of gRPC to help users debug issues.
|
||||
The [Log Levels](https://github.com/grpc/grpc-go/blob/master/Documentation/log_levels.md) doc describes
|
||||
what each log level means in the gRPC context.
|
||||
|
||||
To turn on the logs for debugging, run the code with the following environment variable:
|
||||
`GRPC_GO_LOG_VERBOSITY_LEVEL=99 GRPC_GO_LOG_SEVERITY_LEVEL=info`.
|
||||
|
||||
## Channelz
|
||||
We also provide a runtime debugging tool, Channelz, to help users with live debugging.
|
||||
|
||||
See the channelz blog post here ([link](https://grpc.io/blog/a-short-introduction-to-channelz/)) for
|
||||
details about how to use channelz service to debug live program.
|
||||
|
||||
## Try it
|
||||
The example is able to showcase how logging and channelz can help with debugging. See the channelz
|
||||
blog post linked above for full explanation.
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
93
examples/features/debugging/client/main.go
Normal file
93
examples/features/debugging/client/main.go
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to use logging and Channelz for debugging
|
||||
// gRPC operations.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/channelz/service"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/resolver/manual"
|
||||
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultName = "world"
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
name = flag.String("name", defaultName, "Name to greet")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
/***** Set up the server serving channelz service. *****/
|
||||
lis, err := net.Listen("tcp", *addr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
defer lis.Close()
|
||||
s := grpc.NewServer()
|
||||
service.RegisterChannelzServiceToServer(s)
|
||||
go s.Serve(lis)
|
||||
defer s.Stop()
|
||||
|
||||
/***** Initialize manual resolver and Dial *****/
|
||||
r := manual.NewBuilderWithScheme("whatever")
|
||||
r.InitialState(resolver.State{Addresses: []resolver.Address{{Addr: ":10001"}, {Addr: ":10002"}, {Addr: ":10003"}}})
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(r.Scheme()+":///test.server", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithResolvers(r), grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
// Manually provide resolved addresses for the target.
|
||||
|
||||
c := pb.NewGreeterClient(conn)
|
||||
|
||||
// Contact the server and print out its response.
|
||||
|
||||
/***** Make 100 SayHello RPCs *****/
|
||||
for i := 0; i < 100; i++ {
|
||||
// Setting a 150ms timeout on the RPC.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 150*time.Millisecond)
|
||||
defer cancel()
|
||||
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
|
||||
if err != nil {
|
||||
log.Printf("could not greet: %v", err)
|
||||
} else {
|
||||
log.Printf("Greeting: %s", r.Message)
|
||||
}
|
||||
}
|
||||
|
||||
/***** Wait for user exiting the program *****/
|
||||
// Unless you exit the program (e.g. CTRL+C), channelz data will be available for querying.
|
||||
// Users can take time to examine and learn about the info provided by channelz.
|
||||
select {}
|
||||
}
|
||||
92
examples/features/debugging/server/main.go
Normal file
92
examples/features/debugging/server/main.go
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to enable logging and Channelz for debugging
|
||||
// gRPC services.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
rand "math/rand/v2"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/channelz/service"
|
||||
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
var (
|
||||
ports = []string{":10001", ":10002", ":10003"}
|
||||
)
|
||||
|
||||
// server is used to implement helloworld.GreeterServer.
|
||||
type server struct {
|
||||
pb.UnimplementedGreeterServer
|
||||
}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer
|
||||
func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
|
||||
}
|
||||
|
||||
// slow server is used to simulate a server that has a variable delay in its response.
|
||||
type slowServer struct {
|
||||
pb.UnimplementedGreeterServer
|
||||
}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer
|
||||
func (s *slowServer) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
// Delay 100ms ~ 200ms before replying
|
||||
time.Sleep(time.Duration(100+rand.IntN(100)) * time.Millisecond)
|
||||
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
/***** Set up the server serving channelz service. *****/
|
||||
lis, err := net.Listen("tcp", ":50052")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
defer lis.Close()
|
||||
s := grpc.NewServer()
|
||||
service.RegisterChannelzServiceToServer(s)
|
||||
go s.Serve(lis)
|
||||
defer s.Stop()
|
||||
|
||||
/***** Start three GreeterServers(with one of them to be the slowServer). *****/
|
||||
for i := 0; i < 3; i++ {
|
||||
lis, err := net.Listen("tcp", ports[i])
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
defer lis.Close()
|
||||
s := grpc.NewServer()
|
||||
if i == 2 {
|
||||
pb.RegisterGreeterServer(s, &slowServer{})
|
||||
} else {
|
||||
pb.RegisterGreeterServer(s, &server{})
|
||||
}
|
||||
go s.Serve(lis)
|
||||
}
|
||||
|
||||
/***** Wait for user exiting the program *****/
|
||||
select {}
|
||||
}
|
||||
29
examples/features/dualstack/README.md
Normal file
29
examples/features/dualstack/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Dualstack
|
||||
|
||||
The dualstack example uses a custom name resolver that provides both IPv4 and
|
||||
IPv6 localhost endpoints for each of 3 server instances. The client will first
|
||||
use the default name resolver and load balancers which will only connect to the
|
||||
first server. It will then use the custom name resolver with round robin to
|
||||
connect to each of the servers in turn. The 3 instances of the server will bind
|
||||
respectively to: both IPv4 and IPv6, IPv4 only, and IPv6 only.
|
||||
|
||||
Three servers are serving on the following loopback addresses:
|
||||
|
||||
1. `[::]:50052`: Listening on both IPv4 and IPv6 loopback addresses.
|
||||
1. `127.0.0.1:50050`: Listening only on the IPv4 loopback address.
|
||||
1. `[::1]:50051`: Listening only on the IPv6 loopback address.
|
||||
|
||||
The server response will include its serving port and address type (IPv4, IPv6
|
||||
or both). So the server on "127.0.0.1:50050" will reply to the RPC with the
|
||||
following message: `Greeting:Hello request:1 from server<50052> type: IPv4
|
||||
only)`.
|
||||
|
||||
## Try it
|
||||
|
||||
```sh
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```sh
|
||||
go run client/main.go
|
||||
```
|
||||
189
examples/features/dualstack/client/main.go
Normal file
189
examples/features/dualstack/client/main.go
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2025 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client is a client for the dualstack example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
hwpb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
port1 = 50051
|
||||
port2 = 50052
|
||||
port3 = 50053
|
||||
)
|
||||
|
||||
func init() {
|
||||
resolver.Register(&exampleResolver{})
|
||||
}
|
||||
|
||||
// exampleResolver implements both, a fake `resolver.Resolver` and
|
||||
// `resolver.Builder`. This resolver sends a hard-coded list of 3 endpoints each
|
||||
// with 2 addresses (one IPv4 and one IPv6) to the channel.
|
||||
type exampleResolver struct{}
|
||||
|
||||
func (*exampleResolver) Close() {}
|
||||
|
||||
func (*exampleResolver) ResolveNow(resolver.ResolveNowOptions) {}
|
||||
|
||||
func (*exampleResolver) Build(_ resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
|
||||
go func() {
|
||||
err := cc.UpdateState(resolver.State{
|
||||
Endpoints: []resolver.Endpoint{
|
||||
{Addresses: []resolver.Address{
|
||||
{Addr: fmt.Sprintf("[::1]:%d", port1)},
|
||||
{Addr: fmt.Sprintf("127.0.0.1:%d", port1)},
|
||||
}},
|
||||
{Addresses: []resolver.Address{
|
||||
{Addr: fmt.Sprintf("[::1]:%d", port2)},
|
||||
{Addr: fmt.Sprintf("127.0.0.1:%d", port2)},
|
||||
}},
|
||||
{Addresses: []resolver.Address{
|
||||
{Addr: fmt.Sprintf("[::1]:%d", port3)},
|
||||
{Addr: fmt.Sprintf("127.0.0.1:%d", port3)},
|
||||
}},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to update resolver state", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return &exampleResolver{}, nil
|
||||
}
|
||||
|
||||
func (*exampleResolver) Scheme() string {
|
||||
return "example"
|
||||
}
|
||||
|
||||
func main() {
|
||||
// First send 5 requests using the default DNS and pickfirst load balancer.
|
||||
log.Print("**** Use default DNS resolver ****")
|
||||
target := fmt.Sprintf("localhost:%d", port1)
|
||||
cc, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
defer cc.Close()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
client := hwpb.NewGreeterClient(cc)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
resp, err := client.SayHello(ctx, &hwpb.HelloRequest{
|
||||
Name: fmt.Sprintf("request:%d", i),
|
||||
})
|
||||
if err != nil {
|
||||
log.Panicf("RPC failed: %v", err)
|
||||
}
|
||||
log.Print("Greeting:", resp.GetMessage())
|
||||
}
|
||||
cc.Close()
|
||||
|
||||
log.Print("**** Change to use example name resolver ****")
|
||||
dOpts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`),
|
||||
}
|
||||
cc, err = grpc.NewClient("example:///ignored", dOpts...)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create client: %v", err)
|
||||
}
|
||||
client = hwpb.NewGreeterClient(cc)
|
||||
|
||||
// Send 10 requests using the example nameresolver and round robin load
|
||||
// balancer. These requests are evenly distributed among the 3 servers
|
||||
// rather than favoring the server listening on both addresses because the
|
||||
// resolver groups the 3 servers as 3 endpoints each with 2 addresses.
|
||||
if err := waitForDistribution(ctx, client); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
log.Print("Successful multiple iterations of 1:1:1 ratio")
|
||||
}
|
||||
|
||||
// waitForDistribution makes RPC's on the greeter client until 3 RPC's follow
|
||||
// the same 1:1:1 address ratio for the peer. Returns an error if fails to do so
|
||||
// before context timeout.
|
||||
func waitForDistribution(ctx context.Context, client hwpb.GreeterClient) error {
|
||||
wantPeers := []string{
|
||||
// Server 1 is listening on both IPv4 and IPv6 loopback addresses.
|
||||
// Since the IPv6 address comes first in the resolver list, it will be
|
||||
// given higher priority.
|
||||
fmt.Sprintf("[::1]:%d", port1),
|
||||
// Server 2 is listening only on the IPv4 loopback address.
|
||||
fmt.Sprintf("127.0.0.1:%d", port2),
|
||||
// Server 3 is listening only on the IPv6 loopback address.
|
||||
fmt.Sprintf("[::1]:%d", port3),
|
||||
}
|
||||
const iterationsToVerify = 3
|
||||
const backendCount = 3
|
||||
requestCounter := 0
|
||||
|
||||
for ctx.Err() == nil {
|
||||
result := make(map[string]int)
|
||||
badRatioSeen := false
|
||||
for i := 1; i <= iterationsToVerify && !badRatioSeen; i++ {
|
||||
for j := 0; j < backendCount; j++ {
|
||||
var peer peer.Peer
|
||||
resp, err := client.SayHello(ctx, &hwpb.HelloRequest{
|
||||
Name: fmt.Sprintf("request:%d", requestCounter),
|
||||
}, grpc.Peer(&peer))
|
||||
requestCounter++
|
||||
if err != nil {
|
||||
return fmt.Errorf("RPC failed: %v", err)
|
||||
}
|
||||
log.Print("Greeting:", resp.GetMessage())
|
||||
|
||||
peerAddr := peer.Addr.String()
|
||||
if !slices.Contains(wantPeers, peerAddr) {
|
||||
return fmt.Errorf("peer address was not one of %q, got: %v", strings.Join(wantPeers, ", "), peerAddr)
|
||||
}
|
||||
result[peerAddr]++
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// Verify the results of this iteration.
|
||||
for _, count := range result {
|
||||
if count == i {
|
||||
continue
|
||||
}
|
||||
badRatioSeen = true
|
||||
break
|
||||
}
|
||||
if !badRatioSeen {
|
||||
log.Print("Got iteration with 1:1:1 distribution between addresses.")
|
||||
}
|
||||
}
|
||||
if !badRatioSeen {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("timeout waiting for 1:1:1 distribution between addresses %v", wantPeers)
|
||||
}
|
||||
84
examples/features/dualstack/server/main.go
Normal file
84
examples/features/dualstack/server/main.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2025 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server is a server for the dualstack example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
hwpb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
type greeterServer struct {
|
||||
hwpb.UnimplementedGreeterServer
|
||||
addressType string
|
||||
address string
|
||||
port uint32
|
||||
}
|
||||
|
||||
func (s *greeterServer) SayHello(_ context.Context, req *hwpb.HelloRequest) (*hwpb.HelloReply, error) {
|
||||
return &hwpb.HelloReply{
|
||||
Message: fmt.Sprintf("Hello %s from server<%d> type: %s)", req.GetName(), s.port, s.addressType),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
servers := []*greeterServer{
|
||||
{
|
||||
addressType: "both IPv4 and IPv6",
|
||||
address: "[::]",
|
||||
port: 50051,
|
||||
},
|
||||
{
|
||||
addressType: "IPv4 only",
|
||||
address: "127.0.0.1",
|
||||
port: 50052,
|
||||
},
|
||||
{
|
||||
addressType: "IPv6 only",
|
||||
address: "[::1]",
|
||||
port: 50053,
|
||||
},
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, server := range servers {
|
||||
bindAddr := fmt.Sprintf("%s:%d", server.address, server.port)
|
||||
lis, err := net.Listen("tcp", bindAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
hwpb.RegisterGreeterServer(s, server)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Panicf("failed to serve: %v", err)
|
||||
}
|
||||
}()
|
||||
log.Printf("serving on %s\n", bindAddr)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
63
examples/features/encryption/ALTS/client/main.go
Normal file
63
examples/features/encryption/ALTS/client/main.go
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to use ALTS credentials for secure
|
||||
// gRPC communication.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/alts"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func callUnaryEcho(client ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Create alts based credential.
|
||||
altsTC := alts.NewClientCreds(alts.DefaultClientOptions())
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(altsTC))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Make an echo client and send an RPC.
|
||||
rgc := ecpb.NewEchoClient(conn)
|
||||
callUnaryEcho(rgc, "hello world")
|
||||
}
|
||||
64
examples/features/encryption/ALTS/server/main.go
Normal file
64
examples/features/encryption/ALTS/server/main.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to use ALTS credentials to secure gRPC
|
||||
// services.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/alts"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
type ecServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
// Create alts based credential.
|
||||
altsTC := alts.NewServerCreds(alts.DefaultServerOptions())
|
||||
|
||||
s := grpc.NewServer(grpc.Creds(altsTC))
|
||||
|
||||
// Register EchoServer on the server.
|
||||
pb.RegisterEchoServer(s, &ecServer{})
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
119
examples/features/encryption/README.md
Normal file
119
examples/features/encryption/README.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Encryption
|
||||
|
||||
The example for encryption includes three individual examples for TLS, ALTS
|
||||
and mTLS encryption mechanism respectively.
|
||||
|
||||
## Try it
|
||||
|
||||
In each example's subdirectory:
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
### TLS
|
||||
|
||||
TLS is a commonly used cryptographic protocol to provide end-to-end
|
||||
communication security. In the example, we show how to set up a server
|
||||
authenticated TLS connection to transmit RPC.
|
||||
|
||||
In our `grpc/credentials` package, we provide several convenience methods to
|
||||
create grpc
|
||||
[`credentials.TransportCredentials`](https://godoc.org/google.golang.org/grpc/credentials#TransportCredentials)
|
||||
base on TLS. Refer to the
|
||||
[godoc](https://godoc.org/google.golang.org/grpc/credentials) for details.
|
||||
|
||||
In our example, we use the public/private keys created ahead:
|
||||
* "server_cert.pem" contains the server certificate (public key).
|
||||
* "server_key.pem" contains the server private key.
|
||||
* "ca_cert.pem" contains the certificate (certificate authority)
|
||||
that can verify the server's certificate.
|
||||
|
||||
On server side, we provide the paths to "server_cert.pem" and "server_key.pem" to
|
||||
configure TLS and create the server credential using
|
||||
[`credentials.NewServerTLSFromFile`](https://godoc.org/google.golang.org/grpc/credentials#NewServerTLSFromFile).
|
||||
|
||||
On client side, we provide the path to the "ca_cert.pem" to configure TLS and create
|
||||
the client credential using
|
||||
[`credentials.NewClientTLSFromFile`](https://godoc.org/google.golang.org/grpc/credentials#NewClientTLSFromFile).
|
||||
Note that we override the server name with "x.test.example.com", as the server
|
||||
certificate is valid for *.test.example.com but not localhost. It is solely for
|
||||
the convenience of making an example.
|
||||
|
||||
Once the credentials have been created at both sides, we can start the server
|
||||
with the just created server credential (by calling
|
||||
[`grpc.Creds`](https://godoc.org/google.golang.org/grpc#Creds)) and let client dial
|
||||
to the server with the created client credential (by calling
|
||||
[`grpc.WithTransportCredentials`](https://godoc.org/google.golang.org/grpc#WithTransportCredentials))
|
||||
|
||||
And finally we make an RPC call over the created `grpc.ClientConn` to test the secure
|
||||
connection based upon TLS is successfully up.
|
||||
|
||||
### ALTS
|
||||
NOTE: ALTS currently needs special early access permission on GCP. You can ask
|
||||
about the detailed process in https://groups.google.com/forum/#!forum/grpc-io.
|
||||
|
||||
ALTS is the Google's Application Layer Transport Security, which supports mutual
|
||||
authentication and transport encryption. Note that ALTS is currently only
|
||||
supported on Google Cloud Platform, and therefore you can only run the example
|
||||
successfully in a GCP environment. In our example, we show how to initiate a
|
||||
secure connection that is based on ALTS.
|
||||
|
||||
Unlike TLS, ALTS makes certificate/key management transparent to user. So it is
|
||||
easier to set up.
|
||||
|
||||
On server side, first call
|
||||
[`alts.DefaultServerOptions`](https://godoc.org/google.golang.org/grpc/credentials/alts#DefaultServerOptions)
|
||||
to get the configuration for alts and then provide the configuration to
|
||||
[`alts.NewServerCreds`](https://godoc.org/google.golang.org/grpc/credentials/alts#NewServerCreds)
|
||||
to create the server credential based upon alts.
|
||||
|
||||
On client side, first call
|
||||
[`alts.DefaultClientOptions`](https://godoc.org/google.golang.org/grpc/credentials/alts#DefaultClientOptions)
|
||||
to get the configuration for alts and then provide the configuration to
|
||||
[`alts.NewClientCreds`](https://godoc.org/google.golang.org/grpc/credentials/alts#NewClientCreds)
|
||||
to create the client credential based upon alts.
|
||||
|
||||
Next, same as TLS, start the server with the server credential and let client
|
||||
dial to server with the client credential.
|
||||
|
||||
Finally, make an RPC to test the secure connection based upon ALTS is
|
||||
successfully up.
|
||||
|
||||
### mTLS
|
||||
|
||||
In mutual TLS (mTLS), the client and the server authenticate each other. gRPC
|
||||
allows users to configure mutual TLS at the connection level.
|
||||
|
||||
In this example, we use the following public/private keys created ahead of time:
|
||||
|
||||
* "server_cert.pem" contains the server's certificate (public key).
|
||||
* "server_key.pem" contains the server's private key.
|
||||
* "ca_cert.pem" contains the certificate of the certificate authority that can
|
||||
verify the server's certificate.
|
||||
* "client_cert.pem" contains the client's certificate (public key).
|
||||
* "client_key.pem" contains the client's private key.
|
||||
* "client_ca_cert.pem" contains the certificate of the certificate authority
|
||||
that can verify the client's certificate.
|
||||
|
||||
In normal TLS, the server is only concerned with presenting the server
|
||||
certificate for clients to verify. In mutual TLS, the server also loads in a
|
||||
list of trusted CA files for verifying the client's presented certificates.
|
||||
This is done by setting
|
||||
[`tls.Config.ClientCAs`](https://pkg.go.dev/crypto/tls#Config.ClientCAs)
|
||||
to the list of trusted CA files,
|
||||
and setting
|
||||
[`tls.config.ClientAuth`](https://pkg.go.dev/crypto/tls#Config.ClientAuth)
|
||||
to
|
||||
[`tls.RequireAndVerifyClientCert`](https://pkg.go.dev/crypto/tls#RequireAndVerifyClientCert).
|
||||
|
||||
In normal TLS, the client is only concerned with authenticating the server by
|
||||
using one or more trusted CA file. In mutual TLS, the client also presents its
|
||||
client certificate to the server for authentication. This is done by setting
|
||||
[`tls.Config.Certificates`](https://pkg.go.dev/crypto/tls#Config.Certificates).
|
||||
67
examples/features/encryption/TLS/client/main.go
Normal file
67
examples/features/encryption/TLS/client/main.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to use TLS credentials for secure
|
||||
// gRPC communication.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func callUnaryEcho(client ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Create tls based credential.
|
||||
creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load credentials: %v", err)
|
||||
}
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(creds))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Make an echo client and send an RPC.
|
||||
rgc := ecpb.NewEchoClient(conn)
|
||||
callUnaryEcho(rgc, "hello world")
|
||||
}
|
||||
68
examples/features/encryption/TLS/server/main.go
Normal file
68
examples/features/encryption/TLS/server/main.go
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to use TLS credentials to secure gRPC services.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
type ecServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
// Create tls based credential.
|
||||
creds, err := credentials.NewServerTLSFromFile(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create credentials: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer(grpc.Creds(creds))
|
||||
|
||||
// Register EchoServer on the server.
|
||||
pb.RegisterEchoServer(s, &ecServer{})
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
81
examples/features/encryption/mTLS/client/main.go
Normal file
81
examples/features/encryption/mTLS/client/main.go
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client is an example client which connects to the server using mTLS.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func callUnaryEcho(client ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(data.Path("x509/client_cert.pem"), data.Path("x509/client_key.pem"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load client cert: %v", err)
|
||||
}
|
||||
|
||||
ca := x509.NewCertPool()
|
||||
caFilePath := data.Path("x509/ca_cert.pem")
|
||||
caBytes, err := os.ReadFile(caFilePath)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read ca cert %q: %v", caFilePath, err)
|
||||
}
|
||||
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
|
||||
log.Fatalf("failed to parse %q", caFilePath)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
ServerName: "x.test.example.com",
|
||||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: ca,
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
callUnaryEcho(ecpb.NewEchoClient(conn), "hello world")
|
||||
}
|
||||
82
examples/features/encryption/mTLS/server/main.go
Normal file
82
examples/features/encryption/mTLS/server/main.go
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server is an example server which authenticates clients using mTLS.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
type ecServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.Printf("server starting on port %d...\n", *port)
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load key pair: %s", err)
|
||||
}
|
||||
|
||||
ca := x509.NewCertPool()
|
||||
caFilePath := data.Path("x509/client_ca_cert.pem")
|
||||
caBytes, err := os.ReadFile(caFilePath)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to read ca cert %q: %v", caFilePath, err)
|
||||
}
|
||||
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
|
||||
log.Fatalf("failed to parse %q", caFilePath)
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
ClientCAs: ca,
|
||||
}
|
||||
|
||||
s := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)))
|
||||
pb.RegisterEchoServer(s, &ecServer{})
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
25
examples/features/error_details/README.md
Normal file
25
examples/features/error_details/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Description
|
||||
|
||||
This example demonstrates the use of status details in grpc errors.
|
||||
|
||||
# Run the sample code
|
||||
|
||||
Run the server:
|
||||
|
||||
```sh
|
||||
$ go run ./server/main.go
|
||||
```
|
||||
Then run the client in another terminal:
|
||||
|
||||
```sh
|
||||
$ go run ./client/main.go
|
||||
```
|
||||
|
||||
It should succeed and print the greeting it received from the server.
|
||||
Then run the client again:
|
||||
|
||||
```sh
|
||||
$ go run ./client/main.go
|
||||
```
|
||||
|
||||
This time, it should fail by printing error status details that it received from the server.
|
||||
69
examples/features/error_details/client/main.go
Normal file
69
examples/features/error_details/client/main.go
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to handle error messages from a gRPC server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
epb "google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50052", "the address to connect to")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if e := conn.Close(); e != nil {
|
||||
log.Printf("failed to close connection: %s", e)
|
||||
}
|
||||
}()
|
||||
c := pb.NewGreeterClient(conn)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "world"})
|
||||
if err != nil {
|
||||
s := status.Convert(err)
|
||||
for _, d := range s.Details() {
|
||||
switch info := d.(type) {
|
||||
case *epb.QuotaFailure:
|
||||
log.Printf("Quota failure: %s", info)
|
||||
default:
|
||||
log.Printf("Unexpected type: %s", info)
|
||||
}
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Printf("Greeting: %s", r.Message)
|
||||
}
|
||||
85
examples/features/error_details/server/main.go
Normal file
85
examples/features/error_details/server/main.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to provide error messages in gRPC responses.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
epb "google.golang.org/genproto/googleapis/rpc/errdetails"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50052, "port number")
|
||||
|
||||
// server is used to implement helloworld.GreeterServer.
|
||||
type server struct {
|
||||
pb.UnimplementedGreeterServer
|
||||
mu sync.Mutex
|
||||
count map[string]int
|
||||
}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer
|
||||
func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
// Track the number of times the user has been greeted.
|
||||
s.count[in.Name]++
|
||||
if s.count[in.Name] > 1 {
|
||||
st := status.New(codes.ResourceExhausted, "Request limit exceeded.")
|
||||
ds, err := st.WithDetails(
|
||||
&epb.QuotaFailure{
|
||||
Violations: []*epb.QuotaFailure_Violation{{
|
||||
Subject: fmt.Sprintf("name:%s", in.Name),
|
||||
Description: "Limit one greeting per person",
|
||||
}},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, st.Err()
|
||||
}
|
||||
return nil, ds.Err()
|
||||
}
|
||||
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
address := fmt.Sprintf(":%v", *port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterGreeterServer(s, &server{count: make(map[string]int)})
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
22
examples/features/error_handling/README.md
Normal file
22
examples/features/error_handling/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Description
|
||||
|
||||
This example demonstrates basic RPC error handling in gRPC.
|
||||
|
||||
# Run the sample code
|
||||
|
||||
Run the server, which returns an error if the RPC request's `Name` field is
|
||||
empty.
|
||||
|
||||
```sh
|
||||
$ go run ./server/main.go
|
||||
```
|
||||
|
||||
Then run the client in another terminal, which does two requests: one with an
|
||||
empty Name field and one with it populated with the current username provided by
|
||||
os/user.
|
||||
|
||||
```sh
|
||||
$ go run ./client/main.go
|
||||
```
|
||||
|
||||
It should print the status codes it received from the server.
|
||||
70
examples/features/error_handling/client/main.go
Normal file
70
examples/features/error_handling/client/main.go
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to handle errors returned by a gRPC server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"os/user"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50052", "the address to connect to")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
name := "unknown"
|
||||
if u, err := user.Current(); err == nil && u.Username != "" {
|
||||
name = u.Username
|
||||
}
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
c := pb.NewGreeterClient(conn)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
||||
for _, reqName := range []string{"", name} {
|
||||
log.Printf("Calling SayHello with Name:%q", reqName)
|
||||
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: reqName})
|
||||
if err != nil {
|
||||
if status.Code(err) != codes.InvalidArgument {
|
||||
log.Printf("Received unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("Received error: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Printf("Received response: %s", r.Message)
|
||||
}
|
||||
}
|
||||
66
examples/features/error_handling/server/main.go
Normal file
66
examples/features/error_handling/server/main.go
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to return specific error codes in gRPC
|
||||
// responses.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50052, "port number")
|
||||
|
||||
// server is used to implement helloworld.GreeterServer.
|
||||
type server struct {
|
||||
pb.UnimplementedGreeterServer
|
||||
}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer.
|
||||
func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
if in.Name == "" {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "request missing required field: Name")
|
||||
}
|
||||
return &pb.HelloReply{Message: "Hello " + in.Name}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
address := fmt.Sprintf(":%v", *port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterGreeterServer(s, &server{})
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
55
examples/features/flow_control/README.md
Normal file
55
examples/features/flow_control/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Flow Control
|
||||
|
||||
Flow control is a feature in gRPC that prevents senders from writing more data
|
||||
on a stream than a receiver is capable of handling. This feature behaves the
|
||||
same for both clients and servers. Because gRPC-Go uses a blocking-style API
|
||||
for stream operations, flow control pushback is implemented by simply blocking
|
||||
send operations on a stream when that stream's flow control limits have been
|
||||
reached. When the receiver has read enough data from the stream, the send
|
||||
operation will unblock automatically. Flow control is configured automatically
|
||||
based on a connection's Bandwidth Delay Product (BDP) to ensure the buffer is
|
||||
the minimum size necessary to allow for maximum throughput on the stream if the
|
||||
receiver is reading at its maximum speed.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run ./server
|
||||
```
|
||||
|
||||
```
|
||||
go run ./client
|
||||
```
|
||||
|
||||
## Example explanation
|
||||
|
||||
The example client and server are written to demonstrate the blocking by
|
||||
intentionally sending messages while the other side is not receiving. The
|
||||
bidirectional echo stream in the example begins by having the client send
|
||||
messages until it detects it has blocked (utilizing another goroutine). The
|
||||
server sleeps for 2 seconds to allow this to occur. Then the server will read
|
||||
all of these messages, and the roles of the client and server are swapped so the
|
||||
server attempts to send continuously while the client sleeps. After the client
|
||||
sleeps for 2 seconds, it will read again to unblock the server. The server will
|
||||
detect that it has blocked, and end the stream once it has unblocked.
|
||||
|
||||
### Expected Output
|
||||
|
||||
The client output should look like:
|
||||
```
|
||||
2023/09/19 15:49:49 New stream began.
|
||||
2023/09/19 15:49:50 Sending is blocked.
|
||||
2023/09/19 15:49:51 Sent 25 messages.
|
||||
2023/09/19 15:49:53 Read 25 messages.
|
||||
2023/09/19 15:49:53 Stream ended successfully.
|
||||
```
|
||||
|
||||
while the server should output the following logs:
|
||||
|
||||
```
|
||||
2023/09/19 15:49:49 New stream began.
|
||||
2023/09/19 15:49:51 Read 25 messages.
|
||||
2023/09/19 15:49:52 Sending is blocked.
|
||||
2023/09/19 15:49:53 Sent 25 messages.
|
||||
2023/09/19 15:49:53 Stream ended successfully.
|
||||
```
|
||||
103
examples/features/flow_control/client/main.go
Normal file
103
examples/features/flow_control/client/main.go
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how the gRPC flow control blocks sending when the
|
||||
// receiver is not ready.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/internal/grpcsync"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50052", "the address to connect to")
|
||||
|
||||
var payload = string(make([]byte, 8*1024)) // 8KB
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := pb.NewEchoClient(conn)
|
||||
|
||||
stream, err := c.BidirectionalStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating stream: %v", err)
|
||||
}
|
||||
log.Printf("New stream began.")
|
||||
|
||||
// First we will send data on the stream until we cannot send any more. We
|
||||
// detect this by not seeing a message sent 1s after the last sent message.
|
||||
stopSending := grpcsync.NewEvent()
|
||||
sentOne := make(chan struct{})
|
||||
go func() {
|
||||
i := 0
|
||||
for !stopSending.HasFired() {
|
||||
i++
|
||||
if err := stream.Send(&pb.EchoRequest{Message: payload}); err != nil {
|
||||
log.Fatalf("Error sending data: %v", err)
|
||||
}
|
||||
sentOne <- struct{}{}
|
||||
}
|
||||
log.Printf("Sent %v messages.", i)
|
||||
stream.CloseSend()
|
||||
}()
|
||||
|
||||
for !stopSending.HasFired() {
|
||||
after := time.NewTimer(time.Second)
|
||||
select {
|
||||
case <-sentOne:
|
||||
after.Stop()
|
||||
case <-after.C:
|
||||
log.Printf("Sending is blocked.")
|
||||
stopSending.Fire()
|
||||
<-sentOne
|
||||
}
|
||||
}
|
||||
|
||||
// Next, we wait 2 seconds before reading from the stream, to give the
|
||||
// server an opportunity to block while sending its responses.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Finally, read all the data sent by the server to allow it to unblock.
|
||||
for i := 0; true; i++ {
|
||||
if _, err := stream.Recv(); err != nil {
|
||||
log.Printf("Read %v messages.", i)
|
||||
if err == io.EOF {
|
||||
log.Printf("Stream ended successfully.")
|
||||
return
|
||||
}
|
||||
log.Fatalf("Error receiving data: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
111
examples/features/flow_control/server/main.go
Normal file
111
examples/features/flow_control/server/main.go
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how gRPC flow control block sending when the
|
||||
// receiver is not ready.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/internal/grpcsync"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50052, "port number")
|
||||
|
||||
var payload = string(make([]byte, 8*1024)) // 8KB
|
||||
|
||||
// server is used to implement EchoServer.
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||
log.Printf("New stream began.")
|
||||
// First, we wait 2 seconds before reading from the stream, to give the
|
||||
// client an opportunity to block while sending its requests.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Next, read all the data sent by the client to allow it to unblock.
|
||||
for i := 0; true; i++ {
|
||||
if _, err := stream.Recv(); err != nil {
|
||||
log.Printf("Read %v messages.", i)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Printf("Error receiving data: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, send data until we block, then end the stream after we unblock.
|
||||
stopSending := grpcsync.NewEvent()
|
||||
sentOne := make(chan struct{})
|
||||
go func() {
|
||||
for !stopSending.HasFired() {
|
||||
after := time.NewTimer(time.Second)
|
||||
select {
|
||||
case <-sentOne:
|
||||
after.Stop()
|
||||
case <-after.C:
|
||||
log.Printf("Sending is blocked.")
|
||||
stopSending.Fire()
|
||||
<-sentOne
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
i := 0
|
||||
for !stopSending.HasFired() {
|
||||
i++
|
||||
if err := stream.Send(&pb.EchoResponse{Message: payload}); err != nil {
|
||||
log.Printf("Error sending data: %v", err)
|
||||
return err
|
||||
}
|
||||
sentOne <- struct{}{}
|
||||
}
|
||||
log.Printf("Sent %v messages.", i)
|
||||
|
||||
log.Printf("Stream ended successfully.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
address := fmt.Sprintf(":%v", *port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterEchoServer(grpcServer, &server{})
|
||||
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
45
examples/features/gracefulstop/README.md
Normal file
45
examples/features/gracefulstop/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Graceful Stop
|
||||
|
||||
This example demonstrates how to gracefully stop a gRPC server using
|
||||
`Server.GracefulStop()`. The graceful shutdown process involves two key steps:
|
||||
|
||||
- Initiate `Server.GracefulStop()`. This function blocks until all currently
|
||||
running RPCs have completed. This ensures that in-flight requests are
|
||||
allowed to finish processing.
|
||||
|
||||
- It's crucial to call `Server.Stop()` with a timeout before calling
|
||||
`GracefulStop()`. This acts as a safety net, ensuring that the server
|
||||
eventually shuts down even if some in-flight RPCs don't complete within a
|
||||
reasonable timeframe. This prevents indefinite blocking.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
The server starts with a client streaming and unary request handler. When
|
||||
client streaming is started, client streaming handler signals the server to
|
||||
initiate graceful stop and waits for the stream to be closed or aborted. Until
|
||||
the`Server.GracefulStop()` is initiated, server will continue to accept unary
|
||||
requests. Once `Server.GracefulStop()` is initiated, server will not accept
|
||||
new unary requests.
|
||||
|
||||
Client will start the client stream to the server and starts making unary
|
||||
requests until receiving an error. Error will indicate that the server graceful
|
||||
shutdown is initiated so client will stop making further unary requests and
|
||||
closes the client stream.
|
||||
|
||||
Server and client will keep track of number of unary requests processed on
|
||||
their side. Once the client has successfully closed the stream, server returns
|
||||
the total number of unary requests processed as response. The number from
|
||||
stream response should be equal to the number of unary requests tracked by
|
||||
client. This indicates that server has processed all in-flight requests before
|
||||
shutting down.
|
||||
|
||||
80
examples/features/gracefulstop/client/main.go
Normal file
80
examples/features/gracefulstop/client/main.go
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// Binary client demonstrates sending multiple requests to server and observe
|
||||
// graceful stop.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50052", "the address to connect to")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create new client: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
c := pb.NewEchoClient(conn)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Start a client stream and keep calling the `c.UnaryEcho` until receiving
|
||||
// an error. Error will indicate that server graceful stop is initiated and
|
||||
// it won't accept any new requests.
|
||||
stream, err := c.ClientStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("Error starting stream: %v", err)
|
||||
}
|
||||
|
||||
// Keep track of successful unary requests which can be compared later to
|
||||
// the successful unary requests reported by the server.
|
||||
unaryRequests := 0
|
||||
for {
|
||||
r, err := c.UnaryEcho(ctx, &pb.EchoRequest{Message: "Hello"})
|
||||
if err != nil {
|
||||
log.Printf("Error calling `UnaryEcho`. Server graceful stop initiated: %v", err)
|
||||
break
|
||||
}
|
||||
unaryRequests++
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
log.Print(r.Message)
|
||||
}
|
||||
log.Printf("Successful unary requests made by client: %d", unaryRequests)
|
||||
|
||||
r, err := stream.CloseAndRecv()
|
||||
if err != nil {
|
||||
log.Fatalf("Error closing stream: %v", err)
|
||||
}
|
||||
if fmt.Sprintf("%d", unaryRequests) != r.Message {
|
||||
log.Fatalf("Got %s successful unary requests processed from server, want: %d", r.Message, unaryRequests)
|
||||
}
|
||||
log.Printf("Successful unary requests processed by server and made by client are same.")
|
||||
}
|
||||
117
examples/features/gracefulstop/server/main.go
Normal file
117
examples/features/gracefulstop/server/main.go
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to gracefully stop a gRPC server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 50052, "port number")
|
||||
)
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
|
||||
unaryRequests atomic.Int32 // to track number of unary RPCs processed
|
||||
streamStart chan struct{} // to signal if server streaming started
|
||||
}
|
||||
|
||||
// ClientStreamingEcho implements the EchoService.ClientStreamingEcho method.
|
||||
// It signals the server that streaming has started and waits for the stream to
|
||||
// be done or aborted. If `io.EOF` is received on stream that means client
|
||||
// has successfully closed the stream using `stream.CloseAndRecv()`, so it
|
||||
// returns an `EchoResponse` with the total number of unary RPCs processed
|
||||
// otherwise, it returns the error indicating stream is aborted.
|
||||
func (s *server) ClientStreamingEcho(stream pb.Echo_ClientStreamingEchoServer) error {
|
||||
// Signal streaming start to initiate graceful stop which should wait until
|
||||
// server streaming finishes.
|
||||
s.streamStart <- struct{}{}
|
||||
|
||||
if err := stream.RecvMsg(&pb.EchoResponse{}); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
stream.SendAndClose(&pb.EchoResponse{Message: fmt.Sprintf("%d", s.unaryRequests.Load())})
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnaryEcho implements the EchoService.UnaryEcho method. It increments
|
||||
// `s.unaryRequests` on every call and returns it as part of `EchoResponse`.
|
||||
func (s *server) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
s.unaryRequests.Add(1)
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
address := fmt.Sprintf(":%v", *port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
ss := &server{streamStart: make(chan struct{})}
|
||||
pb.RegisterEchoServer(s, ss)
|
||||
serverStopped := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
<-ss.streamStart // wait until server streaming starts
|
||||
time.Sleep(1 * time.Second)
|
||||
log.Println("Initiating graceful shutdown...")
|
||||
timer := time.AfterFunc(10*time.Second, func() {
|
||||
log.Println("Server couldn't stop gracefully in time. Doing force stop.")
|
||||
s.Stop()
|
||||
// Unblock the main function.
|
||||
select {
|
||||
case serverStopped <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
})
|
||||
defer timer.Stop()
|
||||
s.GracefulStop() // gracefully stop server after in-flight server streaming rpc finishes
|
||||
log.Println("Server stopped gracefully.")
|
||||
// Unblock the main function.
|
||||
select {
|
||||
case serverStopped <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
<-serverStopped
|
||||
}
|
||||
74
examples/features/health/README.md
Normal file
74
examples/features/health/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Health
|
||||
|
||||
gRPC provides a health library to communicate a system's health to their
|
||||
clients. It works by providing a service definition via the
|
||||
[health/v1](https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto)
|
||||
api.
|
||||
|
||||
Clients use the health library to gracefully avoid servers that encounter
|
||||
issues. Most languages provide an out-of-box implementation, which makes it
|
||||
interoperable between systems.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go -port=50051 -sleep=5s
|
||||
go run server/main.go -port=50052 -sleep=10s
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
### Client
|
||||
|
||||
Clients have two ways to monitor a server's health. They use `Check()` to probe
|
||||
a server's health or `Watch()` to observe changes.
|
||||
|
||||
In most cases, clients do not directly check backend servers. Instead, they do
|
||||
this transparently when you specify a `healthCheckConfig` in the [service
|
||||
config](https://github.com/grpc/proposal/blob/master/A17-client-side-health-checking.md#service-config-changes)
|
||||
and import the [health
|
||||
package](https://pkg.go.dev/google.golang.org/grpc/health). The `serviceName` in
|
||||
`healthCheckConfig` is used in the health check when connections are
|
||||
established. An empty string (`""`) typically indicates the overall health of a
|
||||
server.
|
||||
|
||||
```go
|
||||
// import grpc/health to enable transparent client side checking
|
||||
import _ "google.golang.org/grpc/health"
|
||||
|
||||
serviceConfig := grpc.WithDefaultServiceConfig(`{
|
||||
"loadBalancingPolicy": "round_robin",
|
||||
"healthCheckConfig": {
|
||||
"serviceName": ""
|
||||
}
|
||||
}`)
|
||||
|
||||
conn, err := grpc.NewClient(..., serviceConfig)
|
||||
```
|
||||
|
||||
See [A17 - Client-Side Health
|
||||
Checking](https://github.com/grpc/proposal/blob/master/A17-client-side-health-checking.md)
|
||||
for more details.
|
||||
|
||||
### Server
|
||||
|
||||
Servers control their serving status. They do this by inspecting dependent
|
||||
systems, then update their status accordingly. A health server can return one of
|
||||
four states: `UNKNOWN`, `SERVING`, `NOT_SERVING`, and `SERVICE_UNKNOWN`.
|
||||
|
||||
`UNKNOWN` indicates the system does not yet know the current state. Servers
|
||||
often show this state at startup.
|
||||
|
||||
`SERVING` means that the system is healthy and ready to service requests.
|
||||
Conversely, `NOT_SERVING` indicates the system is unable to service requests at
|
||||
the time.
|
||||
|
||||
`SERVICE_UNKNOWN` communicates the `serviceName` requested by the client is not
|
||||
known by the server. This status is only reported by the `Watch()` call.
|
||||
|
||||
A server may toggle its health using
|
||||
`healthServer.SetServingStatus("serviceName", servingStatus)`.
|
||||
88
examples/features/health/client/main.go
Normal file
88
examples/features/health/client/main.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to check and observe gRPC server health using
|
||||
// the health library.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
_ "google.golang.org/grpc/health" // REQUIRED to enable health checks
|
||||
"google.golang.org/grpc/resolver"
|
||||
"google.golang.org/grpc/resolver/manual"
|
||||
)
|
||||
|
||||
const serviceConfig = `{
|
||||
"loadBalancingPolicy": "round_robin",
|
||||
"healthCheckConfig": {
|
||||
"serviceName": ""
|
||||
}
|
||||
}`
|
||||
|
||||
func callUnaryEcho(c pb.EchoClient) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
r, err := c.UnaryEcho(ctx, &pb.EchoRequest{})
|
||||
if err != nil {
|
||||
fmt.Println("UnaryEcho: _, ", err)
|
||||
} else {
|
||||
fmt.Println("UnaryEcho: ", r.GetMessage())
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
r := manual.NewBuilderWithScheme("whatever")
|
||||
r.InitialState(resolver.State{
|
||||
Addresses: []resolver.Address{
|
||||
{Addr: "localhost:50051"},
|
||||
{Addr: "localhost:50052"},
|
||||
},
|
||||
})
|
||||
|
||||
address := fmt.Sprintf("%s:///unused", r.Scheme())
|
||||
|
||||
options := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithResolvers(r),
|
||||
// google.golang.org/grpc/health must also be imported
|
||||
grpc.WithDefaultServiceConfig(serviceConfig),
|
||||
}
|
||||
|
||||
conn, err := grpc.NewClient(address, options...)
|
||||
if err != nil {
|
||||
log.Fatalf("grpc.NewClient(%q): %v", address, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
echoClient := pb.NewEchoClient(conn)
|
||||
|
||||
for {
|
||||
callUnaryEcho(echoClient)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
90
examples/features/health/server/main.go
Normal file
90
examples/features/health/server/main.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2020 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to manage and report its health status using
|
||||
// the gRPC health library.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/health"
|
||||
healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 50051, "the port to serve on")
|
||||
sleep = flag.Duration("sleep", time.Second*5, "duration between changes in health")
|
||||
|
||||
system = "" // empty string represents the health of the system
|
||||
)
|
||||
|
||||
type echoServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (e *echoServer) UnaryEcho(context.Context, *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{
|
||||
Message: fmt.Sprintf("hello from localhost:%d", *port),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ pb.EchoServer = &echoServer{}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer()
|
||||
healthcheck := health.NewServer()
|
||||
healthgrpc.RegisterHealthServer(s, healthcheck)
|
||||
pb.RegisterEchoServer(s, &echoServer{})
|
||||
|
||||
go func() {
|
||||
// asynchronously inspect dependencies and toggle serving status as needed
|
||||
next := healthpb.HealthCheckResponse_SERVING
|
||||
|
||||
for {
|
||||
healthcheck.SetServingStatus(system, next)
|
||||
|
||||
if next == healthpb.HealthCheckResponse_SERVING {
|
||||
next = healthpb.HealthCheckResponse_NOT_SERVING
|
||||
} else {
|
||||
next = healthpb.HealthCheckResponse_SERVING
|
||||
}
|
||||
|
||||
time.Sleep(*sleep)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
130
examples/features/interceptor/README.md
Normal file
130
examples/features/interceptor/README.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Interceptor
|
||||
|
||||
gRPC provides simple APIs to implement and install interceptors on a per
|
||||
ClientConn/Server basis. Interceptors act as a layer between the application and
|
||||
gRPC and can be used to observe or control the behavior of gRPC. Interceptors
|
||||
can be used for logging, authentication/authorization, metrics collection, and
|
||||
other functionality that is shared across RPCs.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
gRPC has separate interceptors for unary RPCs and streaming RPCs. See the
|
||||
[gRPC docs](https://grpc.io/docs/guides/concepts.html#rpc-life-cycle) for an
|
||||
explanation about unary and streaming RPCs. Both the client and the server have
|
||||
their own types of unary and stream interceptors. Thus, there are four different
|
||||
types of interceptors in total.
|
||||
|
||||
### Client-side
|
||||
|
||||
#### Unary Interceptor
|
||||
|
||||
The type for client-side unary interceptors is
|
||||
[`UnaryClientInterceptor`](https://godoc.org/google.golang.org/grpc#UnaryClientInterceptor).
|
||||
It is a function type with signature:
|
||||
|
||||
```golang
|
||||
func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
|
||||
```
|
||||
|
||||
Unary interceptor implementations can usually be divided into three parts:
|
||||
pre-processing, invoking the RPC method, and post-processing.
|
||||
|
||||
For pre-processing, users can get info about the current RPC call by examining
|
||||
the args passed in. The args include the RPC context, method string, request to
|
||||
be sent, and the CallOptions configured. With this info, users can even modify
|
||||
the RPC call. For instance, in the example, we examine the list of CallOptions
|
||||
and check if the call credentials have been configured. If not, the interceptor
|
||||
configures the RPC call to use oauth2 with a token "some-secret-token" as a
|
||||
fallback. In our example, we intentionally omit configuring the per RPC
|
||||
credential to resort to the fallback.
|
||||
|
||||
After pre-processing, users can invoke the RPC call by calling the `invoker`.
|
||||
|
||||
Once the invoker returns, users can post-process the RPC call. This usually
|
||||
involves dealing with the returned reply and error. In the example, we log the
|
||||
RPC timing and error info.
|
||||
|
||||
To install a unary interceptor on a ClientConn, configure `Dial` with the
|
||||
[`WithUnaryInterceptor`](https://godoc.org/google.golang.org/grpc#WithUnaryInterceptor)
|
||||
`DialOption`.
|
||||
|
||||
#### Stream Interceptor
|
||||
|
||||
The type for client-side stream interceptors is
|
||||
[`StreamClientInterceptor`](https://godoc.org/google.golang.org/grpc#StreamClientInterceptor).
|
||||
It is a function type with signature:
|
||||
|
||||
```golang
|
||||
func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)
|
||||
```
|
||||
|
||||
An implementation of a stream interceptor usually includes pre-processing, and
|
||||
stream operation interception.
|
||||
|
||||
The pre-processing is similar to unary interceptors.
|
||||
|
||||
However, rather than invoking the RPC method followed by post-processing, stream
|
||||
interceptors intercept the users' operations on the stream. The interceptor
|
||||
first calls the passed-in `streamer` to get a `ClientStream`, and then wraps the
|
||||
`ClientStream` while overriding its methods with the interception logic.
|
||||
Finally, the interceptor returns the wrapped `ClientStream` to user to operate
|
||||
on.
|
||||
|
||||
In the example, we define a new struct `wrappedStream`, which embeds a
|
||||
`ClientStream`. We then implement (override) the `SendMsg` and `RecvMsg` methods
|
||||
on `wrappedStream` to intercept these two operations on the embedded
|
||||
`ClientStream`. In the example, we log the message type info and time info for
|
||||
interception purpose.
|
||||
|
||||
To install a stream interceptor for a ClientConn, configure `Dial` with the
|
||||
[`WithStreamInterceptor`](https://godoc.org/google.golang.org/grpc#WithStreamInterceptor)
|
||||
`DialOption`.
|
||||
|
||||
### Server-side
|
||||
|
||||
Server side interceptors are similar to client side interceptors, with slightly
|
||||
different information provided as args.
|
||||
|
||||
#### Unary Interceptor
|
||||
|
||||
The type for server-side unary interceptors is
|
||||
[`UnaryServerInterceptor`](https://godoc.org/google.golang.org/grpc#UnaryServerInterceptor).
|
||||
It is a function type with signature:
|
||||
|
||||
```golang
|
||||
func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
|
||||
```
|
||||
|
||||
Refer to the client-side unary interceptor section for a detailed implementation
|
||||
and explanation.
|
||||
|
||||
To install a unary interceptor on a Server, configure `NewServer` with the
|
||||
[`UnaryInterceptor`](https://godoc.org/google.golang.org/grpc#UnaryInterceptor)
|
||||
`ServerOption`.
|
||||
|
||||
#### Stream Interceptor
|
||||
|
||||
The type for server-side stream interceptors is
|
||||
[`StreamServerInterceptor`](https://godoc.org/google.golang.org/grpc#StreamServerInterceptor).
|
||||
It is a function type with the signature:
|
||||
|
||||
```golang
|
||||
func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error
|
||||
```
|
||||
|
||||
Refer to the client-side stream interceptor section for a detailed
|
||||
implementation and explanation.
|
||||
|
||||
To install a stream interceptor on a Server, configure `NewServer` with the
|
||||
[`StreamInterceptor`](https://godoc.org/google.golang.org/grpc#StreamInterceptor)
|
||||
`ServerOption`.
|
||||
167
examples/features/interceptor/client/main.go
Normal file
167
examples/features/interceptor/client/main.go
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to use interceptors to observe or control the
|
||||
// behavior of gRPC including logging, authentication,metrics collection, etc.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/oauth"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
const fallbackToken = "some-secret-token"
|
||||
|
||||
// logger is to mock a sophisticated logging system. To simplify the example, we just print out the content.
|
||||
func logger(format string, a ...any) {
|
||||
fmt.Printf("LOG:\t"+format+"\n", a...)
|
||||
}
|
||||
|
||||
// unaryInterceptor is an example unary interceptor.
|
||||
func unaryInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||
var credsConfigured bool
|
||||
for _, o := range opts {
|
||||
_, ok := o.(grpc.PerRPCCredsCallOption)
|
||||
if ok {
|
||||
credsConfigured = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !credsConfigured {
|
||||
opts = append(opts, grpc.PerRPCCredentials(oauth.TokenSource{
|
||||
TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: fallbackToken}),
|
||||
}))
|
||||
}
|
||||
start := time.Now()
|
||||
err := invoker(ctx, method, req, reply, cc, opts...)
|
||||
end := time.Now()
|
||||
logger("RPC: %s, start time: %s, end time: %s, err: %v", method, start.Format("Basic"), end.Format(time.RFC3339), err)
|
||||
return err
|
||||
}
|
||||
|
||||
// wrappedStream wraps around the embedded grpc.ClientStream, and intercepts the RecvMsg and
|
||||
// SendMsg method call.
|
||||
type wrappedStream struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (w *wrappedStream) RecvMsg(m any) error {
|
||||
logger("Receive a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
|
||||
return w.ClientStream.RecvMsg(m)
|
||||
}
|
||||
|
||||
func (w *wrappedStream) SendMsg(m any) error {
|
||||
logger("Send a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
|
||||
return w.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func newWrappedStream(s grpc.ClientStream) grpc.ClientStream {
|
||||
return &wrappedStream{s}
|
||||
}
|
||||
|
||||
// streamInterceptor is an example stream interceptor.
|
||||
func streamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
|
||||
var credsConfigured bool
|
||||
for _, o := range opts {
|
||||
_, ok := o.(*grpc.PerRPCCredsCallOption)
|
||||
if ok {
|
||||
credsConfigured = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !credsConfigured {
|
||||
opts = append(opts, grpc.PerRPCCredentials(oauth.TokenSource{
|
||||
TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: fallbackToken}),
|
||||
}))
|
||||
}
|
||||
s, err := streamer(ctx, desc, cc, method, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWrappedStream(s), nil
|
||||
}
|
||||
|
||||
func callUnaryEcho(client ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
}
|
||||
|
||||
func callBidiStreamingEcho(client ecpb.EchoClient) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
c, err := client.BidirectionalStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < 5; i++ {
|
||||
if err := c.Send(&ecpb.EchoRequest{Message: fmt.Sprintf("Request %d", i+1)}); err != nil {
|
||||
log.Fatalf("failed to send request due to error: %v", err)
|
||||
}
|
||||
}
|
||||
c.CloseSend()
|
||||
for {
|
||||
resp, err := c.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("failed to receive response due to error: %v", err)
|
||||
}
|
||||
fmt.Println("BidiStreaming Echo: ", resp.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Create tls based credential.
|
||||
creds, err := credentials.NewClientTLSFromFile(data.Path("x509/ca_cert.pem"), "x.test.example.com")
|
||||
if err != nil {
|
||||
log.Fatalf("failed to load credentials: %v", err)
|
||||
}
|
||||
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(creds), grpc.WithUnaryInterceptor(unaryInterceptor), grpc.WithStreamInterceptor(streamInterceptor))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Make an echo client and send RPCs.
|
||||
rgc := ecpb.NewEchoClient(conn)
|
||||
callUnaryEcho(rgc, "hello world")
|
||||
callBidiStreamingEcho(rgc)
|
||||
}
|
||||
166
examples/features/interceptor/server/main.go
Normal file
166
examples/features/interceptor/server/main.go
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to use interceptors to observe or control the
|
||||
// behavior of gRPC including logging, authentication,metrics collection, etc.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/examples/data"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
errMissingMetadata = status.Errorf(codes.InvalidArgument, "missing metadata")
|
||||
errInvalidToken = status.Errorf(codes.Unauthenticated, "invalid token")
|
||||
)
|
||||
|
||||
// logger is to mock a sophisticated logging system. To simplify the example, we just print out the content.
|
||||
func logger(format string, a ...any) {
|
||||
fmt.Printf("LOG:\t"+format+"\n", a...)
|
||||
}
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *server) UnaryEcho(_ context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
fmt.Printf("unary echoing message %q\n", in.Message)
|
||||
return &pb.EchoResponse{Message: in.Message}, nil
|
||||
}
|
||||
|
||||
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("server: error receiving from stream: %v\n", err)
|
||||
return err
|
||||
}
|
||||
fmt.Printf("bidi echoing message %q\n", in.Message)
|
||||
stream.Send(&pb.EchoResponse{Message: in.Message})
|
||||
}
|
||||
}
|
||||
|
||||
// valid validates the authorization.
|
||||
func valid(authorization []string) bool {
|
||||
if len(authorization) < 1 {
|
||||
return false
|
||||
}
|
||||
token := strings.TrimPrefix(authorization[0], "Bearer ")
|
||||
// Perform the token validation here. For the sake of this example, the code
|
||||
// here forgoes any of the usual OAuth2 token validation and instead checks
|
||||
// for a token matching an arbitrary string.
|
||||
return token == "some-secret-token"
|
||||
}
|
||||
|
||||
func unaryInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
// authentication (token verification)
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, errMissingMetadata
|
||||
}
|
||||
if !valid(md["authorization"]) {
|
||||
return nil, errInvalidToken
|
||||
}
|
||||
m, err := handler(ctx, req)
|
||||
if err != nil {
|
||||
logger("RPC failed with error: %v", err)
|
||||
}
|
||||
return m, err
|
||||
}
|
||||
|
||||
// wrappedStream wraps around the embedded grpc.ServerStream, and intercepts the RecvMsg and
|
||||
// SendMsg method call.
|
||||
type wrappedStream struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (w *wrappedStream) RecvMsg(m any) error {
|
||||
logger("Receive a message (Type: %T) at %s", m, time.Now().Format(time.RFC3339))
|
||||
return w.ServerStream.RecvMsg(m)
|
||||
}
|
||||
|
||||
func (w *wrappedStream) SendMsg(m any) error {
|
||||
logger("Send a message (Type: %T) at %v", m, time.Now().Format(time.RFC3339))
|
||||
return w.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func newWrappedStream(s grpc.ServerStream) grpc.ServerStream {
|
||||
return &wrappedStream{s}
|
||||
}
|
||||
|
||||
func streamInterceptor(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
// authentication (token verification)
|
||||
md, ok := metadata.FromIncomingContext(ss.Context())
|
||||
if !ok {
|
||||
return errMissingMetadata
|
||||
}
|
||||
if !valid(md["authorization"]) {
|
||||
return errInvalidToken
|
||||
}
|
||||
|
||||
err := handler(srv, newWrappedStream(ss))
|
||||
if err != nil {
|
||||
logger("RPC failed with error: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
// Create tls based credential.
|
||||
creds, err := credentials.NewServerTLSFromFile(data.Path("x509/server_cert.pem"), data.Path("x509/server_key.pem"))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create credentials: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer(grpc.Creds(creds), grpc.UnaryInterceptor(unaryInterceptor), grpc.StreamInterceptor(streamInterceptor))
|
||||
|
||||
// Register EchoServer on the server.
|
||||
pb.RegisterEchoServer(s, &server{})
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
15
examples/features/keepalive/README.md
Normal file
15
examples/features/keepalive/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Keepalive
|
||||
|
||||
This example illustrates how to set up client-side keepalive pings and
|
||||
server-side keepalive ping enforcement and connection idleness settings. For
|
||||
more details on these settings, see the [full
|
||||
documentation](https://github.com/grpc/grpc-go/tree/master/Documentation/keepalive.md).
|
||||
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
GODEBUG=http2debug=2 go run client/main.go
|
||||
```
|
||||
64
examples/features/keepalive/client/main.go
Normal file
64
examples/features/keepalive/client/main.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to configure keepalive pings to maintain
|
||||
// connectivity and detect stale connections.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50052", "the address to connect to")
|
||||
|
||||
var kacp = keepalive.ClientParameters{
|
||||
Time: 10 * time.Second, // send pings every 10 seconds if there is no activity
|
||||
Timeout: time.Second, // wait 1 second for ping ack before considering the connection dead
|
||||
PermitWithoutStream: true, // send pings even without active streams
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithKeepaliveParams(kacp))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := pb.NewEchoClient(conn)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
defer cancel()
|
||||
fmt.Println("Performing unary request")
|
||||
res, err := c.UnaryEcho(ctx, &pb.EchoRequest{Message: "keepalive demo"})
|
||||
if err != nil {
|
||||
log.Fatalf("unexpected error from UnaryEcho: %v", err)
|
||||
}
|
||||
fmt.Println("RPC response:", res)
|
||||
select {} // Block forever; run with GODEBUG=http2debug=2 to observe ping frames and GOAWAYs due to idleness.
|
||||
}
|
||||
76
examples/features/keepalive/server/main.go
Normal file
76
examples/features/keepalive/server/main.go
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to enforce keepalive settings and manage idle
|
||||
// connections to maintain active client connections.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50052, "port number")
|
||||
|
||||
var kaep = keepalive.EnforcementPolicy{
|
||||
MinTime: 5 * time.Second, // If a client pings more than once every 5 seconds, terminate the connection
|
||||
PermitWithoutStream: true, // Allow pings even when there are no active streams
|
||||
}
|
||||
|
||||
var kasp = keepalive.ServerParameters{
|
||||
MaxConnectionIdle: 15 * time.Second, // If a client is idle for 15 seconds, send a GOAWAY
|
||||
MaxConnectionAge: 30 * time.Second, // If any connection is alive for more than 30 seconds, send a GOAWAY
|
||||
MaxConnectionAgeGrace: 5 * time.Second, // Allow 5 seconds for pending RPCs to complete before forcibly closing connections
|
||||
Time: 5 * time.Second, // Ping the client if it is idle for 5 seconds to ensure the connection is still active
|
||||
Timeout: 1 * time.Second, // Wait 1 second for the ping ack before assuming the connection is dead
|
||||
}
|
||||
|
||||
// server implements EchoServer.
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *server) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
address := fmt.Sprintf(":%v", *port)
|
||||
lis, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
|
||||
s := grpc.NewServer(grpc.KeepaliveEnforcementPolicy(kaep), grpc.KeepaliveParams(kasp))
|
||||
pb.RegisterEchoServer(s, &server{})
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
83
examples/features/load_balancing/README.md
Normal file
83
examples/features/load_balancing/README.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Load balancing
|
||||
|
||||
This examples shows how `ClientConn` can pick different load balancing policies.
|
||||
|
||||
Note: to show the effect of load balancers, an example resolver is installed in
|
||||
this example to get the backend addresses. It's suggested to read the name
|
||||
resolver example before this example.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
Two echo servers are serving on ":50051" and ":50052". They will include their
|
||||
serving address in the response. So the server on ":50051" will reply to the RPC
|
||||
with `this is examples/load_balancing (from :50051)`.
|
||||
|
||||
Two clients are created, to connect to both of these servers (they get both
|
||||
server addresses from the name resolver).
|
||||
|
||||
Each client picks a different load balancer (using
|
||||
`grpc.WithDefaultServiceConfig`): `pick_first` or `round_robin`. (These two
|
||||
policies are supported in gRPC by default. To add a custom balancing policy,
|
||||
implement the interfaces defined in
|
||||
https://godoc.org/google.golang.org/grpc/balancer).
|
||||
|
||||
Note that balancers can also be switched using service config, which allows
|
||||
service owners (instead of client owners) to pick the balancer to use. Service
|
||||
config doc is available at
|
||||
https://github.com/grpc/grpc/blob/master/doc/service_config.md.
|
||||
|
||||
### pick_first
|
||||
|
||||
The first client is configured to use `pick_first`. `pick_first` tries to
|
||||
connect to the first address, uses it for all RPCs if it connects, or try the
|
||||
next address if it fails (and keep doing that until one connection is
|
||||
successful). Because of this, all the RPCs will be sent to the same backend. The
|
||||
responses received all show the same backend address.
|
||||
|
||||
```
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
```
|
||||
|
||||
### round_robin
|
||||
|
||||
The second client is configured to use `round_robin`. `round_robin` connects to
|
||||
all the addresses it sees, and sends an RPC to each backend one at a time in
|
||||
order. E.g. the first RPC will be sent to backend-1, the second RPC will be
|
||||
sent to backend-2, and the third RPC will be sent to backend-1 again.
|
||||
|
||||
```
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50052)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50052)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50052)
|
||||
this is examples/load_balancing (from :50051)
|
||||
this is examples/load_balancing (from :50052)
|
||||
this is examples/load_balancing (from :50051)
|
||||
```
|
||||
|
||||
Note that it's possible to see two continues RPC sent to the same backend.
|
||||
That's because `round_robin` only picks the connections ready for RPCs. So if
|
||||
one of the two connections is not ready for some reason, all RPCs will be sent
|
||||
to the ready connection.
|
||||
127
examples/features/load_balancing/client/main.go
Normal file
127
examples/features/load_balancing/client/main.go
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to configure load balancing policies to
|
||||
// distribute RPCs across backend servers.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
exampleScheme = "example"
|
||||
exampleServiceName = "lb.example.grpc.io"
|
||||
)
|
||||
|
||||
var addrs = []string{"localhost:50051", "localhost:50052"}
|
||||
|
||||
func callUnaryEcho(c ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
r, err := c.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("could not greet: %v", err)
|
||||
}
|
||||
fmt.Println(r.Message)
|
||||
}
|
||||
|
||||
func makeRPCs(cc *grpc.ClientConn, n int) {
|
||||
hwc := ecpb.NewEchoClient(cc)
|
||||
for i := 0; i < n; i++ {
|
||||
callUnaryEcho(hwc, "this is examples/load_balancing")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
// "pick_first" is the default, so there's no need to set the load balancing policy.
|
||||
pickfirstConn, err := grpc.NewClient(
|
||||
fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName),
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer pickfirstConn.Close()
|
||||
|
||||
fmt.Println("--- calling helloworld.Greeter/SayHello with pick_first ---")
|
||||
makeRPCs(pickfirstConn, 10)
|
||||
|
||||
fmt.Println()
|
||||
|
||||
// Make another ClientConn with round_robin policy.
|
||||
roundrobinConn, err := grpc.NewClient(
|
||||
fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName),
|
||||
grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`), // This sets the initial balancing policy.
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer roundrobinConn.Close()
|
||||
|
||||
fmt.Println("--- calling helloworld.Greeter/SayHello with round_robin ---")
|
||||
makeRPCs(roundrobinConn, 10)
|
||||
}
|
||||
|
||||
// Following is an example name resolver implementation. Read the name
|
||||
// resolution example to learn more about it.
|
||||
|
||||
type exampleResolverBuilder struct{}
|
||||
|
||||
func (*exampleResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
|
||||
r := &exampleResolver{
|
||||
target: target,
|
||||
cc: cc,
|
||||
addrsStore: map[string][]string{
|
||||
exampleServiceName: addrs,
|
||||
},
|
||||
}
|
||||
r.start()
|
||||
return r, nil
|
||||
}
|
||||
func (*exampleResolverBuilder) Scheme() string { return exampleScheme }
|
||||
|
||||
type exampleResolver struct {
|
||||
target resolver.Target
|
||||
cc resolver.ClientConn
|
||||
addrsStore map[string][]string
|
||||
}
|
||||
|
||||
func (r *exampleResolver) start() {
|
||||
addrStrs := r.addrsStore[r.target.Endpoint()]
|
||||
addrs := make([]resolver.Address, len(addrStrs))
|
||||
for i, s := range addrStrs {
|
||||
addrs[i] = resolver.Address{Addr: s}
|
||||
}
|
||||
r.cc.UpdateState(resolver.State{Addresses: addrs})
|
||||
}
|
||||
func (*exampleResolver) ResolveNow(resolver.ResolveNowOptions) {}
|
||||
func (*exampleResolver) Close() {}
|
||||
|
||||
func init() {
|
||||
resolver.Register(&exampleResolverBuilder{})
|
||||
}
|
||||
71
examples/features/load_balancing/server/main.go
Normal file
71
examples/features/load_balancing/server/main.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to spin up multiple server backends
|
||||
// to enable client-side load balancing.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var (
|
||||
addrs = []string{":50051", ":50052"}
|
||||
)
|
||||
|
||||
type ecServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
addr string
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: fmt.Sprintf("%s (from %s)", req.Message, s.addr)}, nil
|
||||
}
|
||||
|
||||
func startServer(addr string) {
|
||||
lis, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterEchoServer(s, &ecServer{addr: addr})
|
||||
log.Printf("serving on %s\n", addr)
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
var wg sync.WaitGroup
|
||||
for _, addr := range addrs {
|
||||
wg.Add(1)
|
||||
go func(addr string) {
|
||||
defer wg.Done()
|
||||
startServer(addr)
|
||||
}(addr)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
18
examples/features/metadata/README.md
Normal file
18
examples/features/metadata/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Metadata example
|
||||
|
||||
This example shows how to set and read metadata in RPC headers and trailers.
|
||||
Please see
|
||||
[grpc-metadata.md](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md)
|
||||
for more information.
|
||||
|
||||
## Start the server
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
## Run the client
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
308
examples/features/metadata/client/main.go
Normal file
308
examples/features/metadata/client/main.go
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to send and receive metadata to and from an RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
const (
|
||||
timestampFormat = time.StampNano // "Jan _2 15:04:05.000"
|
||||
streamingCount = 10
|
||||
)
|
||||
|
||||
func unaryCallWithMetadata(c pb.EchoClient, message string) {
|
||||
fmt.Printf("--- unary ---\n")
|
||||
// Create metadata and context.
|
||||
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
ctx := metadata.NewOutgoingContext(context.Background(), md)
|
||||
|
||||
// Make RPC using the context with the metadata.
|
||||
var header, trailer metadata.MD
|
||||
r, err := c.UnaryEcho(ctx, &pb.EchoRequest{Message: message}, grpc.Header(&header), grpc.Trailer(&trailer))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to call UnaryEcho: %v", err)
|
||||
}
|
||||
|
||||
if t, ok := header["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from header:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in header")
|
||||
}
|
||||
if l, ok := header["location"]; ok {
|
||||
fmt.Printf("location from header:\n")
|
||||
for i, e := range l {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("location expected but doesn't exist in header")
|
||||
}
|
||||
fmt.Printf("response:\n")
|
||||
fmt.Printf(" - %s\n", r.Message)
|
||||
|
||||
if t, ok := trailer["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from trailer:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in trailer")
|
||||
}
|
||||
}
|
||||
|
||||
func serverStreamingWithMetadata(c pb.EchoClient, message string) {
|
||||
fmt.Printf("--- server streaming ---\n")
|
||||
// Create metadata and context.
|
||||
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
ctx := metadata.NewOutgoingContext(context.Background(), md)
|
||||
|
||||
// Make RPC using the context with the metadata.
|
||||
stream, err := c.ServerStreamingEcho(ctx, &pb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("failed to call ServerStreamingEcho: %v", err)
|
||||
}
|
||||
|
||||
// Read the header when the header arrives.
|
||||
header, err := stream.Header()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get header from stream: %v", err)
|
||||
}
|
||||
// Read metadata from server's header.
|
||||
if t, ok := header["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from header:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in header")
|
||||
}
|
||||
if l, ok := header["location"]; ok {
|
||||
fmt.Printf("location from header:\n")
|
||||
for i, e := range l {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("location expected but doesn't exist in header")
|
||||
}
|
||||
|
||||
// Read all the responses.
|
||||
var rpcStatus error
|
||||
fmt.Printf("response:\n")
|
||||
for {
|
||||
r, err := stream.Recv()
|
||||
if err != nil {
|
||||
rpcStatus = err
|
||||
break
|
||||
}
|
||||
fmt.Printf(" - %s\n", r.Message)
|
||||
}
|
||||
if rpcStatus != io.EOF {
|
||||
log.Fatalf("failed to finish server streaming: %v", rpcStatus)
|
||||
}
|
||||
|
||||
// Read the trailer after the RPC is finished.
|
||||
trailer := stream.Trailer()
|
||||
// Read metadata from server's trailer.
|
||||
if t, ok := trailer["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from trailer:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in trailer")
|
||||
}
|
||||
}
|
||||
|
||||
func clientStreamWithMetadata(c pb.EchoClient, message string) {
|
||||
fmt.Printf("--- client streaming ---\n")
|
||||
// Create metadata and context.
|
||||
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
ctx := metadata.NewOutgoingContext(context.Background(), md)
|
||||
|
||||
// Make RPC using the context with the metadata.
|
||||
stream, err := c.ClientStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to call ClientStreamingEcho: %v\n", err)
|
||||
}
|
||||
|
||||
// Read the header when the header arrives.
|
||||
header, err := stream.Header()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get header from stream: %v", err)
|
||||
}
|
||||
// Read metadata from server's header.
|
||||
if t, ok := header["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from header:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in header")
|
||||
}
|
||||
if l, ok := header["location"]; ok {
|
||||
fmt.Printf("location from header:\n")
|
||||
for i, e := range l {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("location expected but doesn't exist in header")
|
||||
}
|
||||
|
||||
// Send all requests to the server.
|
||||
for i := 0; i < streamingCount; i++ {
|
||||
if err := stream.Send(&pb.EchoRequest{Message: message}); err != nil {
|
||||
log.Fatalf("failed to send streaming: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Read the response.
|
||||
r, err := stream.CloseAndRecv()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to CloseAndRecv: %v\n", err)
|
||||
}
|
||||
fmt.Printf("response:\n")
|
||||
fmt.Printf(" - %s\n\n", r.Message)
|
||||
|
||||
// Read the trailer after the RPC is finished.
|
||||
trailer := stream.Trailer()
|
||||
// Read metadata from server's trailer.
|
||||
if t, ok := trailer["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from trailer:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in trailer")
|
||||
}
|
||||
}
|
||||
|
||||
func bidirectionalWithMetadata(c pb.EchoClient, message string) {
|
||||
fmt.Printf("--- bidirectional ---\n")
|
||||
// Create metadata and context.
|
||||
md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
ctx := metadata.NewOutgoingContext(context.Background(), md)
|
||||
|
||||
// Make RPC using the context with the metadata.
|
||||
stream, err := c.BidirectionalStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to call BidirectionalStreamingEcho: %v\n", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
// Read the header when the header arrives.
|
||||
header, err := stream.Header()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to get header from stream: %v", err)
|
||||
}
|
||||
// Read metadata from server's header.
|
||||
if t, ok := header["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from header:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in header")
|
||||
}
|
||||
if l, ok := header["location"]; ok {
|
||||
fmt.Printf("location from header:\n")
|
||||
for i, e := range l {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("location expected but doesn't exist in header")
|
||||
}
|
||||
|
||||
// Send all requests to the server.
|
||||
for i := 0; i < streamingCount; i++ {
|
||||
if err := stream.Send(&pb.EchoRequest{Message: message}); err != nil {
|
||||
log.Fatalf("failed to send streaming: %v\n", err)
|
||||
}
|
||||
}
|
||||
stream.CloseSend()
|
||||
}()
|
||||
|
||||
// Read all the responses.
|
||||
var rpcStatus error
|
||||
fmt.Printf("response:\n")
|
||||
for {
|
||||
r, err := stream.Recv()
|
||||
if err != nil {
|
||||
rpcStatus = err
|
||||
break
|
||||
}
|
||||
fmt.Printf(" - %s\n", r.Message)
|
||||
}
|
||||
if rpcStatus != io.EOF {
|
||||
log.Fatalf("failed to finish server streaming: %v", rpcStatus)
|
||||
}
|
||||
|
||||
// Read the trailer after the RPC is finished.
|
||||
trailer := stream.Trailer()
|
||||
// Read metadata from server's trailer.
|
||||
if t, ok := trailer["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from trailer:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
} else {
|
||||
log.Fatal("timestamp expected but doesn't exist in trailer")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const message = "this is examples/metadata"
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c := pb.NewEchoClient(conn)
|
||||
|
||||
unaryCallWithMetadata(c, message)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
serverStreamingWithMetadata(c, message)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
clientStreamWithMetadata(c, message)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
bidirectionalWithMetadata(c, message)
|
||||
}
|
||||
208
examples/features/metadata/server/main.go
Normal file
208
examples/features/metadata/server/main.go
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to send and read metadata to and from RPC.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
const (
|
||||
timestampFormat = time.StampNano
|
||||
streamingCount = 10
|
||||
)
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *server) UnaryEcho(ctx context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
fmt.Printf("--- UnaryEcho ---\n")
|
||||
// Create trailer in defer to record function return time.
|
||||
defer func() {
|
||||
trailer := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
grpc.SetTrailer(ctx, trailer)
|
||||
}()
|
||||
|
||||
// Read metadata from client.
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.DataLoss, "UnaryEcho: failed to get metadata")
|
||||
}
|
||||
if t, ok := md["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from metadata:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Create and send header.
|
||||
header := metadata.New(map[string]string{"location": "MTV", "timestamp": time.Now().Format(timestampFormat)})
|
||||
grpc.SendHeader(ctx, header)
|
||||
|
||||
fmt.Printf("request received: %v, sending echo\n", in)
|
||||
|
||||
return &pb.EchoResponse{Message: in.Message}, nil
|
||||
}
|
||||
|
||||
func (s *server) ServerStreamingEcho(in *pb.EchoRequest, stream pb.Echo_ServerStreamingEchoServer) error {
|
||||
fmt.Printf("--- ServerStreamingEcho ---\n")
|
||||
// Create trailer in defer to record function return time.
|
||||
defer func() {
|
||||
trailer := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
stream.SetTrailer(trailer)
|
||||
}()
|
||||
|
||||
// Read metadata from client.
|
||||
md, ok := metadata.FromIncomingContext(stream.Context())
|
||||
if !ok {
|
||||
return status.Errorf(codes.DataLoss, "ServerStreamingEcho: failed to get metadata")
|
||||
}
|
||||
if t, ok := md["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from metadata:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Create and send header.
|
||||
header := metadata.New(map[string]string{"location": "MTV", "timestamp": time.Now().Format(timestampFormat)})
|
||||
stream.SendHeader(header)
|
||||
|
||||
fmt.Printf("request received: %v\n", in)
|
||||
|
||||
// Read requests and send responses.
|
||||
for i := 0; i < streamingCount; i++ {
|
||||
fmt.Printf("echo message %v\n", in.Message)
|
||||
err := stream.Send(&pb.EchoResponse{Message: in.Message})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) ClientStreamingEcho(stream pb.Echo_ClientStreamingEchoServer) error {
|
||||
fmt.Printf("--- ClientStreamingEcho ---\n")
|
||||
// Create trailer in defer to record function return time.
|
||||
defer func() {
|
||||
trailer := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
stream.SetTrailer(trailer)
|
||||
}()
|
||||
|
||||
// Read metadata from client.
|
||||
md, ok := metadata.FromIncomingContext(stream.Context())
|
||||
if !ok {
|
||||
return status.Errorf(codes.DataLoss, "ClientStreamingEcho: failed to get metadata")
|
||||
}
|
||||
if t, ok := md["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from metadata:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Create and send header.
|
||||
header := metadata.New(map[string]string{"location": "MTV", "timestamp": time.Now().Format(timestampFormat)})
|
||||
stream.SendHeader(header)
|
||||
|
||||
// Read requests and send responses.
|
||||
var message string
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
fmt.Printf("echo last received message\n")
|
||||
return stream.SendAndClose(&pb.EchoResponse{Message: message})
|
||||
}
|
||||
message = in.Message
|
||||
fmt.Printf("request received: %v, building echo\n", in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||
fmt.Printf("--- BidirectionalStreamingEcho ---\n")
|
||||
// Create trailer in defer to record function return time.
|
||||
defer func() {
|
||||
trailer := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
|
||||
stream.SetTrailer(trailer)
|
||||
}()
|
||||
|
||||
// Read metadata from client.
|
||||
md, ok := metadata.FromIncomingContext(stream.Context())
|
||||
if !ok {
|
||||
return status.Errorf(codes.DataLoss, "BidirectionalStreamingEcho: failed to get metadata")
|
||||
}
|
||||
|
||||
if t, ok := md["timestamp"]; ok {
|
||||
fmt.Printf("timestamp from metadata:\n")
|
||||
for i, e := range t {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Create and send header.
|
||||
header := metadata.New(map[string]string{"location": "MTV", "timestamp": time.Now().Format(timestampFormat)})
|
||||
stream.SendHeader(header)
|
||||
|
||||
// Read requests and send responses.
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("request received %v, sending echo\n", in)
|
||||
if err := stream.Send(&pb.EchoResponse{Message: in.Message}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
fmt.Printf("server listening at %v\n", lis.Addr())
|
||||
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterEchoServer(s, &server{})
|
||||
s.Serve(lis)
|
||||
}
|
||||
70
examples/features/metadata_interceptor/README.md
Normal file
70
examples/features/metadata_interceptor/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Metadata interceptor example
|
||||
|
||||
This example shows how to update metadata from unary and streaming interceptors on the server.
|
||||
Please see
|
||||
[grpc-metadata.md](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md)
|
||||
for more information.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
#### Unary interceptor
|
||||
|
||||
The interceptor can read existing metadata from the RPC context passed to it.
|
||||
Since Go contexts are immutable, the interceptor will have to create a new context
|
||||
with updated metadata and pass it to the provided handler.
|
||||
|
||||
```go
|
||||
func SomeInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
// Get the incoming metadata from the RPC context, and add a new
|
||||
// key-value pair to it.
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
md.Append("key1", "value1")
|
||||
|
||||
// Create a context with the new metadata and pass it to handler.
|
||||
ctx = metadata.NewIncomingContext(ctx, md)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
```
|
||||
|
||||
#### Streaming interceptor
|
||||
|
||||
`grpc.ServerStream` does not provide a way to modify its RPC context. The streaming
|
||||
interceptor therefore needs to implement the `grpc.ServerStream` interface and return
|
||||
a context with updated metadata.
|
||||
|
||||
The easiest way to do this would be to create a type which embeds the `grpc.ServerStream`
|
||||
interface and overrides only the `Context()` method to return a context with updated
|
||||
metadata. The streaming interceptor would then pass this wrapped stream to the provided handler.
|
||||
|
||||
```go
|
||||
type wrappedStream struct {
|
||||
grpc.ServerStream
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (s *wrappedStream) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
func SomeStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
// Get the incoming metadata from the RPC context, and add a new
|
||||
// key-value pair to it.
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
md.Append("key1", "value1")
|
||||
|
||||
// Create a context with the new metadata and pass it to handler.
|
||||
ctx = metadata.NewIncomingContext(ctx, md)
|
||||
|
||||
return handler(srv, &wrappedStream{ss, ctx})
|
||||
}
|
||||
```
|
||||
114
examples/features/metadata_interceptor/client/main.go
Normal file
114
examples/features/metadata_interceptor/client/main.go
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to receive metadata in RPC headers
|
||||
// and trailers.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
func callUnaryEcho(ctx context.Context, client pb.EchoClient) {
|
||||
var header, trailer metadata.MD
|
||||
resp, err := client.UnaryEcho(ctx, &pb.EchoRequest{Message: "hello world"}, grpc.Header(&header), grpc.Trailer(&trailer))
|
||||
if err != nil {
|
||||
log.Fatalf("UnaryEcho: %v", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
|
||||
fmt.Println("Received headers:")
|
||||
for k, v := range header {
|
||||
fmt.Printf("%s: %v\n", k, v)
|
||||
}
|
||||
|
||||
fmt.Println("Received trailers:")
|
||||
for k, v := range trailer {
|
||||
fmt.Printf("%s: %v\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func callBidiStreamingEcho(ctx context.Context, client pb.EchoClient) {
|
||||
c, err := client.BidirectionalStreamingEcho(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("BidiStreamingEcho: %v", err)
|
||||
}
|
||||
|
||||
if err := c.Send(&pb.EchoRequest{Message: "hello world"}); err != nil {
|
||||
log.Fatalf("Sending echo request: %v", err)
|
||||
}
|
||||
c.CloseSend()
|
||||
|
||||
for {
|
||||
resp, err := c.Recv()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Receiving echo response: %v", err)
|
||||
}
|
||||
fmt.Println("BidiStreaming Echo: ", resp.Message)
|
||||
}
|
||||
|
||||
header, err := c.Header()
|
||||
if err != nil {
|
||||
log.Fatalf("Receiving headers: %v", err)
|
||||
}
|
||||
fmt.Println("Received headers:")
|
||||
for k, v := range header {
|
||||
fmt.Printf("%s: %v\n", k, v)
|
||||
}
|
||||
|
||||
trailer := c.Trailer()
|
||||
fmt.Println("Received tailers:")
|
||||
for k, v := range trailer {
|
||||
fmt.Printf("%s: %v\n", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("grpc.NewClient(%q): %v", *addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
ec := pb.NewEchoClient(conn)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
callUnaryEcho(ctx, ec)
|
||||
|
||||
callBidiStreamingEcho(ctx, ec)
|
||||
}
|
||||
164
examples/features/metadata_interceptor/server/main.go
Normal file
164
examples/features/metadata_interceptor/server/main.go
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2022 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to update metadata from interceptors on server.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
var errMissingMetadata = status.Errorf(codes.InvalidArgument, "no incoming metadata in rpc context")
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func unaryInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, errMissingMetadata
|
||||
}
|
||||
|
||||
// Create and set metadata from interceptor to server.
|
||||
md.Append("key1", "value1")
|
||||
ctx = metadata.NewIncomingContext(ctx, md)
|
||||
|
||||
// Call the handler to complete the normal execution of the RPC.
|
||||
resp, err := handler(ctx, req)
|
||||
|
||||
// Create and set header metadata from interceptor to client.
|
||||
header := metadata.Pairs("header-key", "val")
|
||||
grpc.SetHeader(ctx, header)
|
||||
|
||||
// Create and set trailer metadata from interceptor to client.
|
||||
trailer := metadata.Pairs("trailer-key", "val")
|
||||
grpc.SetTrailer(ctx, trailer)
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *server) UnaryEcho(ctx context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
fmt.Printf("--- UnaryEcho ---\n")
|
||||
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.Internal, "UnaryEcho: missing incoming metadata in rpc context")
|
||||
}
|
||||
|
||||
// Read and print metadata added by the interceptor.
|
||||
if v, ok := md["key1"]; ok {
|
||||
fmt.Printf("key1 from metadata: \n")
|
||||
for i, e := range v {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.EchoResponse{Message: in.Message}, nil
|
||||
}
|
||||
|
||||
type wrappedStream struct {
|
||||
grpc.ServerStream
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (s *wrappedStream) Context() context.Context {
|
||||
return s.ctx
|
||||
}
|
||||
|
||||
func streamInterceptor(srv any, ss grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||
md, ok := metadata.FromIncomingContext(ss.Context())
|
||||
if !ok {
|
||||
return errMissingMetadata
|
||||
}
|
||||
|
||||
// Create and set metadata from interceptor to server.
|
||||
md.Append("key1", "value1")
|
||||
ctx := metadata.NewIncomingContext(ss.Context(), md)
|
||||
|
||||
// Call the handler to complete the normal execution of the RPC.
|
||||
err := handler(srv, &wrappedStream{ss, ctx})
|
||||
|
||||
// Create and set header metadata from interceptor to client.
|
||||
header := metadata.Pairs("header-key", "val")
|
||||
ss.SetHeader(header)
|
||||
|
||||
// Create and set trailer metadata from interceptor to client.
|
||||
trailer := metadata.Pairs("trailer-key", "val")
|
||||
ss.SetTrailer(trailer)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||
fmt.Printf("--- BidirectionalStreamingEcho ---\n")
|
||||
|
||||
md, ok := metadata.FromIncomingContext(stream.Context())
|
||||
if !ok {
|
||||
return status.Errorf(codes.Internal, "BidirectionalStreamingEcho: missing incoming metadata in rpc context")
|
||||
}
|
||||
|
||||
// Read and print metadata added by the interceptor.
|
||||
if v, ok := md["key1"]; ok {
|
||||
fmt.Printf("key1 from metadata: \n")
|
||||
for i, e := range v {
|
||||
fmt.Printf(" %d. %s\n", i, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Read requests and send responses.
|
||||
for {
|
||||
in, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = stream.Send(&pb.EchoResponse{Message: in.Message}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("net.Listen() failed: %v", err)
|
||||
}
|
||||
fmt.Printf("Server listening at %v\n", lis.Addr())
|
||||
|
||||
s := grpc.NewServer(grpc.UnaryInterceptor(unaryInterceptor), grpc.StreamInterceptor(streamInterceptor))
|
||||
pb.RegisterEchoServer(s, &server{})
|
||||
s.Serve(lis)
|
||||
}
|
||||
12
examples/features/multiplex/README.md
Normal file
12
examples/features/multiplex/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Multiplex
|
||||
|
||||
A `grpc.ClientConn` can be shared by two stubs and two services can share a
|
||||
`grpc.Server`. This example illustrates how to perform both types of sharing.
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
79
examples/features/multiplex/client/main.go
Normal file
79
examples/features/multiplex/client/main.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to use a single grpc.ClientConn for multiple
|
||||
// service stubs.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
hwpb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
|
||||
// callSayHello calls SayHello on c with the given name, and prints the
|
||||
// response.
|
||||
func callSayHello(c hwpb.GreeterClient, name string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
r, err := c.SayHello(ctx, &hwpb.HelloRequest{Name: name})
|
||||
if err != nil {
|
||||
log.Fatalf("client.SayHello(_) = _, %v", err)
|
||||
}
|
||||
fmt.Println("Greeting: ", r.Message)
|
||||
}
|
||||
|
||||
func callUnaryEcho(client ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
|
||||
}
|
||||
fmt.Println("UnaryEcho: ", resp.Message)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
fmt.Println("--- calling helloworld.Greeter/SayHello ---")
|
||||
// Make a greeter client and send an RPC.
|
||||
hwc := hwpb.NewGreeterClient(conn)
|
||||
callSayHello(hwc, "multiplex")
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("--- calling routeguide.RouteGuide/GetFeature ---")
|
||||
// Make a routeguide client with the same ClientConn.
|
||||
rgc := ecpb.NewEchoClient(conn)
|
||||
callUnaryEcho(rgc, "this is examples/multiplex")
|
||||
}
|
||||
75
examples/features/multiplex/server/main.go
Normal file
75
examples/features/multiplex/server/main.go
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to use a single grpc.Server instance to
|
||||
// register and serve multiple services.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
hwpb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 50051, "the port to serve on")
|
||||
|
||||
// hwServer is used to implement helloworld.GreeterServer.
|
||||
type hwServer struct {
|
||||
hwpb.UnimplementedGreeterServer
|
||||
}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer
|
||||
func (s *hwServer) SayHello(_ context.Context, in *hwpb.HelloRequest) (*hwpb.HelloReply, error) {
|
||||
return &hwpb.HelloReply{Message: "Hello " + in.Name}, nil
|
||||
}
|
||||
|
||||
type ecServer struct {
|
||||
ecpb.UnimplementedEchoServer
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *ecpb.EchoRequest) (*ecpb.EchoResponse, error) {
|
||||
return &ecpb.EchoResponse{Message: req.Message}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
fmt.Printf("server listening at %v\n", lis.Addr())
|
||||
|
||||
s := grpc.NewServer()
|
||||
|
||||
// Register Greeter on the server.
|
||||
hwpb.RegisterGreeterServer(s, &hwServer{})
|
||||
|
||||
// Register RouteGuide on the same server.
|
||||
ecpb.RegisterEchoServer(s, &ecServer{})
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
41
examples/features/name_resolving/README.md
Normal file
41
examples/features/name_resolving/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Name resolving
|
||||
|
||||
This examples shows how `ClientConn` can pick different name resolvers.
|
||||
|
||||
## What is a name resolver
|
||||
|
||||
A name resolver can be seen as a `map[service-name][]backend-ip`. It takes a
|
||||
service name, and returns a list of IPs of the backends. A common used name
|
||||
resolver is DNS.
|
||||
|
||||
In this example, a resolver is created to resolve `resolver.example.grpc.io` to
|
||||
`localhost:50051`.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
The echo server is serving on ":50051". Two clients are created, one is dialing
|
||||
to `passthrough:///localhost:50051`, while the other is dialing to
|
||||
`example:///resolver.example.grpc.io`. Both of them can connect the server.
|
||||
|
||||
Name resolver is picked based on the `scheme` in the target string. See
|
||||
https://github.com/grpc/grpc/blob/master/doc/naming.md for the target syntax.
|
||||
|
||||
The first client picks the `passthrough` resolver, which takes the input, and
|
||||
use it as the backend addresses.
|
||||
|
||||
The second is connecting to service name `resolver.example.grpc.io`. Without a
|
||||
proper name resolver, this would fail. In the example it picks the `example`
|
||||
resolver that we installed. The `example` resolver can handle
|
||||
`resolver.example.grpc.io` correctly by returning the backend address. So even
|
||||
though the backend IP is not set when ClientConn is created, the connection will
|
||||
be created to the correct backend.
|
||||
137
examples/features/name_resolving/client/main.go
Normal file
137
examples/features/name_resolving/client/main.go
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to use custom name resolvers to resolve
|
||||
// server backend addresses.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
ecpb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
"google.golang.org/grpc/resolver"
|
||||
)
|
||||
|
||||
const (
|
||||
exampleScheme = "example"
|
||||
exampleServiceName = "resolver.example.grpc.io"
|
||||
|
||||
backendAddr = "localhost:50051"
|
||||
)
|
||||
|
||||
func callUnaryEcho(c ecpb.EchoClient, message string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
r, err := c.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
|
||||
if err != nil {
|
||||
log.Fatalf("could not greet: %v", err)
|
||||
}
|
||||
fmt.Println(r.Message)
|
||||
}
|
||||
|
||||
func makeRPCs(cc *grpc.ClientConn, n int) {
|
||||
hwc := ecpb.NewEchoClient(cc)
|
||||
for i := 0; i < n; i++ {
|
||||
callUnaryEcho(hwc, "this is examples/name_resolving")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
passthroughConn, err := grpc.NewClient(
|
||||
fmt.Sprintf("passthrough:///%s", backendAddr), // Dial to "passthrough:///localhost:50051"
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer passthroughConn.Close()
|
||||
|
||||
fmt.Printf("--- calling helloworld.Greeter/SayHello to \"passthrough:///%s\"\n", backendAddr)
|
||||
makeRPCs(passthroughConn, 10)
|
||||
|
||||
fmt.Println()
|
||||
|
||||
exampleConn, err := grpc.NewClient(
|
||||
fmt.Sprintf("%s:///%s", exampleScheme, exampleServiceName), // Dial to "example:///resolver.example.grpc.io"
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer exampleConn.Close()
|
||||
|
||||
fmt.Printf("--- calling helloworld.Greeter/SayHello to \"%s:///%s\"\n", exampleScheme, exampleServiceName)
|
||||
makeRPCs(exampleConn, 10)
|
||||
}
|
||||
|
||||
// Following is an example name resolver. It includes a
|
||||
// ResolverBuilder(https://godoc.org/google.golang.org/grpc/resolver#Builder)
|
||||
// and a Resolver(https://godoc.org/google.golang.org/grpc/resolver#Resolver).
|
||||
//
|
||||
// A ResolverBuilder is registered for a scheme (in this example, "example" is
|
||||
// the scheme). When a ClientConn is created for this scheme, the
|
||||
// ResolverBuilder will be picked to build a Resolver. Note that a new Resolver
|
||||
// is built for each ClientConn. The Resolver will watch the updates for the
|
||||
// target, and send updates to the ClientConn.
|
||||
|
||||
// exampleResolverBuilder is a
|
||||
// ResolverBuilder(https://godoc.org/google.golang.org/grpc/resolver#Builder).
|
||||
type exampleResolverBuilder struct{}
|
||||
|
||||
func (*exampleResolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (resolver.Resolver, error) {
|
||||
r := &exampleResolver{
|
||||
target: target,
|
||||
cc: cc,
|
||||
addrsStore: map[string][]string{
|
||||
exampleServiceName: {backendAddr},
|
||||
},
|
||||
}
|
||||
r.start()
|
||||
return r, nil
|
||||
}
|
||||
func (*exampleResolverBuilder) Scheme() string { return exampleScheme }
|
||||
|
||||
// exampleResolver is a
|
||||
// Resolver(https://godoc.org/google.golang.org/grpc/resolver#Resolver).
|
||||
type exampleResolver struct {
|
||||
target resolver.Target
|
||||
cc resolver.ClientConn
|
||||
addrsStore map[string][]string
|
||||
}
|
||||
|
||||
func (r *exampleResolver) start() {
|
||||
addrStrs := r.addrsStore[r.target.Endpoint()]
|
||||
addrs := make([]resolver.Address, len(addrStrs))
|
||||
for i, s := range addrStrs {
|
||||
addrs[i] = resolver.Address{Addr: s}
|
||||
}
|
||||
r.cc.UpdateState(resolver.State{Addresses: addrs})
|
||||
}
|
||||
func (*exampleResolver) ResolveNow(resolver.ResolveNowOptions) {}
|
||||
func (*exampleResolver) Close() {}
|
||||
|
||||
func init() {
|
||||
// Register the example ResolverBuilder. This is usually done in a package's
|
||||
// init() function.
|
||||
resolver.Register(&exampleResolverBuilder{})
|
||||
}
|
||||
56
examples/features/name_resolving/server/main.go
Normal file
56
examples/features/name_resolving/server/main.go
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to set up a gRPC server that listens on a
|
||||
// specified port for name resolution examples.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
)
|
||||
|
||||
const addr = "localhost:50051"
|
||||
|
||||
type ecServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
addr string
|
||||
}
|
||||
|
||||
func (s *ecServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: fmt.Sprintf("%s (from %s)", req.Message, s.addr)}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
lis, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterEchoServer(s, &ecServer{addr: addr})
|
||||
log.Printf("serving on %s\n", addr)
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
3
examples/features/observability/README.md
Normal file
3
examples/features/observability/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
This example is the Hello World example instrumented for logs, metrics, and tracing.
|
||||
|
||||
Please refer to Microservices Observability user guide for setup.
|
||||
17
examples/features/observability/client/clientConfig.json
Normal file
17
examples/features/observability/client/clientConfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"cloud_monitoring": {},
|
||||
"cloud_trace": {
|
||||
"sampling_rate": 1.0
|
||||
},
|
||||
"cloud_logging": {
|
||||
"client_rpc_events": [{
|
||||
"methods": ["*"]
|
||||
}],
|
||||
"server_rpc_events": [{
|
||||
"methods": ["*"]
|
||||
}]
|
||||
},
|
||||
"labels": {
|
||||
"environment" : "example-client"
|
||||
}
|
||||
}
|
||||
71
examples/features/observability/client/main.go
Normal file
71
examples/features/observability/client/main.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client demonstrates how to instrument RPCs with logging, metrics,
|
||||
// and tracing.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
"google.golang.org/grpc/gcp/observability"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultName = "world"
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
name = flag.String("name", defaultName, "Name to greet")
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Turn on global telemetry for the whole binary. If a configuration is
|
||||
// specified, any created gRPC Client Conn's or Servers will emit telemetry
|
||||
// data according the configuration.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
err := observability.Start(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("observability.Start() failed: %v", err)
|
||||
}
|
||||
defer observability.End()
|
||||
|
||||
flag.Parse()
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
c := pb.NewGreeterClient(conn)
|
||||
|
||||
// Contact the server and print out its response.
|
||||
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
|
||||
if err != nil {
|
||||
log.Fatalf("could not greet: %v", err)
|
||||
}
|
||||
log.Printf("Greeting: %s", r.GetMessage())
|
||||
}
|
||||
90
examples/features/observability/server/main.go
Normal file
90
examples/features/observability/server/main.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2023 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server demonstrates how to instrument RPCs for logging, metrics,
|
||||
// and tracing.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
pb "google.golang.org/grpc/examples/helloworld/helloworld"
|
||||
"google.golang.org/grpc/gcp/observability"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 50051, "The server port")
|
||||
)
|
||||
|
||||
// server is used to implement helloworld.GreeterServer.
|
||||
type server struct {
|
||||
pb.UnimplementedGreeterServer
|
||||
}
|
||||
|
||||
// SayHello implements helloworld.GreeterServer
|
||||
func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
|
||||
log.Printf("Received: %v", in.GetName())
|
||||
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Turn on global telemetry for the whole binary. If a configuration is
|
||||
// specified, any created gRPC Client Conn's or Servers will emit telemetry
|
||||
// data according the configuration.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
err := observability.Start(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("observability.Start() failed: %v", err)
|
||||
}
|
||||
defer observability.End()
|
||||
|
||||
flag.Parse()
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterGreeterServer(s, &server{})
|
||||
log.Printf("server listening at %v", lis.Addr())
|
||||
|
||||
// This server can potentially be terminated by an external signal from the
|
||||
// Operating System. The following catches those signals and calls s.Stop().
|
||||
// This causes the s.Serve() call to return and run main()'s defers,
|
||||
// including the observability.End() call that ensures any pending
|
||||
// observability data is sent to Cloud Operations.
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-c
|
||||
s.Stop()
|
||||
}()
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
17
examples/features/observability/server/serverConfig.json
Normal file
17
examples/features/observability/server/serverConfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"cloud_monitoring": {},
|
||||
"cloud_trace": {
|
||||
"sampling_rate": 1.0
|
||||
},
|
||||
"cloud_logging": {
|
||||
"client_rpc_events": [{
|
||||
"methods": ["*"]
|
||||
}],
|
||||
"server_rpc_events": [{
|
||||
"methods": ["*"]
|
||||
}]
|
||||
},
|
||||
"labels": {
|
||||
"environment" : "example-server"
|
||||
}
|
||||
}
|
||||
39
examples/features/opentelemetry/README.md
Normal file
39
examples/features/opentelemetry/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# OpenTelemetry
|
||||
|
||||
This example shows how to configure OpenTelemetry on a client and server, and
|
||||
shows what type of telemetry data it can produce for certain RPCs.
|
||||
This example shows how to enable experimental gRPC metrics, which are disabled
|
||||
by default and must be explicitly configured on the client and/or server.
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
```
|
||||
curl localhost:9464/metrics
|
||||
curl localhost:9465/metrics
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
The client continuously makes RPCs to a server. The client and server both
|
||||
expose a prometheus exporter to listen and provide metrics. This defaults to
|
||||
:9464 for the server and :9465 for the client. The client and server are also
|
||||
configured to output traces directly to their standard output streams using
|
||||
`stdouttrace`.
|
||||
|
||||
OpenTelemetry is configured on both the client and the server, and exports to
|
||||
the Prometheus exporter. The exporter exposes metrics on the Prometheus ports
|
||||
described above. OpenTelemetry exports traces using the `stdouttrace` exporter,
|
||||
which prints structured trace data to the console output of both the client and
|
||||
server. Each RPC call produces trace information that captures the execution
|
||||
flow and timing of operations.
|
||||
|
||||
Curling to the exposed Prometheus ports outputs the metrics recorded on the
|
||||
client and server.
|
||||
100
examples/features/opentelemetry/client/main.go
Normal file
100
examples/features/opentelemetry/client/main.go
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary client is a client for the OpenTelemetry example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||
otelstdouttrace "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
otelpropagation "go.opentelemetry.io/otel/propagation"
|
||||
otelmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
otelresource "go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/examples/features/proto/echo"
|
||||
oteltracing "google.golang.org/grpc/experimental/opentelemetry"
|
||||
"google.golang.org/grpc/stats/opentelemetry"
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", "localhost:50051", "the server address to connect to")
|
||||
prometheusEndpoint = flag.String("prometheus_endpoint", ":9465", "the Prometheus exporter endpoint for metrics")
|
||||
)
|
||||
|
||||
func main() {
|
||||
exporter, err := prometheus.New()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start prometheus exporter: %v", err)
|
||||
}
|
||||
// Configure meter provider for metrics
|
||||
meterProvider := otelmetric.NewMeterProvider(otelmetric.WithReader(exporter))
|
||||
// Configure exporter for traces
|
||||
traceExporter, err := otelstdouttrace.New(otelstdouttrace.WithPrettyPrint())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create stdouttrace exporter: %v", err)
|
||||
}
|
||||
traceProvider := sdktrace.NewTracerProvider(sdktrace.WithBatcher(traceExporter), sdktrace.WithResource(otelresource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("grpc-client"))))
|
||||
// Configure W3C Trace Context Propagator for traces
|
||||
textMapPropagator := otelpropagation.TraceContext{}
|
||||
do := opentelemetry.DialOption(opentelemetry.Options{
|
||||
MetricsOptions: opentelemetry.MetricsOptions{
|
||||
MeterProvider: meterProvider,
|
||||
// These are example experimental gRPC metrics, which are disabled
|
||||
// by default and must be explicitly enabled. For the full,
|
||||
// up-to-date list of metrics, see:
|
||||
// https://grpc.io/docs/guides/opentelemetry-metrics/#instruments
|
||||
Metrics: opentelemetry.DefaultMetrics().Add(
|
||||
"grpc.subchannel.connection_attempts_succeeded",
|
||||
"grpc.subchannel.connection_attempts_failed",
|
||||
),
|
||||
},
|
||||
TraceOptions: oteltracing.TraceOptions{TracerProvider: traceProvider, TextMapPropagator: textMapPropagator},
|
||||
})
|
||||
|
||||
go http.ListenAndServe(*prometheusEndpoint, promhttp.Handler())
|
||||
|
||||
cc, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()), do)
|
||||
if err != nil {
|
||||
log.Fatalf("grpc.NewClient() failed: %v", err)
|
||||
}
|
||||
defer cc.Close()
|
||||
c := echo.NewEchoClient(cc)
|
||||
ctx := context.Background()
|
||||
|
||||
// Make an RPC every second. This should trigger telemetry to be emitted from
|
||||
// the client and the server.
|
||||
for {
|
||||
r, err := c.UnaryEcho(ctx, &echo.EchoRequest{Message: "this is examples/opentelemetry"})
|
||||
if err != nil {
|
||||
log.Fatalf("UnaryEcho failed: %v", err)
|
||||
}
|
||||
fmt.Println(r)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
93
examples/features/opentelemetry/server/main.go
Normal file
93
examples/features/opentelemetry/server/main.go
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2024 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Binary server is a server for the OpenTelemetry example.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"go.opentelemetry.io/otel/exporters/prometheus"
|
||||
otelstdouttrace "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
|
||||
otelpropagation "go.opentelemetry.io/otel/propagation"
|
||||
otelmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||
otelresource "go.opentelemetry.io/otel/sdk/resource"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
"google.golang.org/grpc"
|
||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
||||
oteltracing "google.golang.org/grpc/experimental/opentelemetry"
|
||||
"google.golang.org/grpc/stats/opentelemetry"
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", "localhost:50051", "the server address to connect to")
|
||||
prometheusEndpoint = flag.String("prometheus_endpoint", ":9464", "the Prometheus exporter endpoint for metrics")
|
||||
)
|
||||
|
||||
type echoServer struct {
|
||||
pb.UnimplementedEchoServer
|
||||
addr string
|
||||
}
|
||||
|
||||
func (s *echoServer) UnaryEcho(_ context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
|
||||
return &pb.EchoResponse{Message: fmt.Sprintf("%s (from %s)", req.Message, s.addr)}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
exporter, err := prometheus.New()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to start prometheus exporter: %v", err)
|
||||
}
|
||||
// Configure meter provider for metrics
|
||||
meterProvider := otelmetric.NewMeterProvider(otelmetric.WithReader(exporter))
|
||||
// Configure exporter for traces
|
||||
traceExporter, err := otelstdouttrace.New(otelstdouttrace.WithPrettyPrint())
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create stdouttrace exporter: %v", err)
|
||||
}
|
||||
traceProvider := sdktrace.NewTracerProvider(sdktrace.WithBatcher(traceExporter), sdktrace.WithResource(otelresource.NewWithAttributes(semconv.SchemaURL, semconv.ServiceName("grpc-server"))))
|
||||
// Configure W3C Trace Context Propagator for traces
|
||||
textMapPropagator := otelpropagation.TraceContext{}
|
||||
so := opentelemetry.ServerOption(opentelemetry.Options{
|
||||
MetricsOptions: opentelemetry.MetricsOptions{
|
||||
MeterProvider: meterProvider,
|
||||
},
|
||||
TraceOptions: oteltracing.TraceOptions{TracerProvider: traceProvider, TextMapPropagator: textMapPropagator}})
|
||||
|
||||
go http.ListenAndServe(*prometheusEndpoint, promhttp.Handler())
|
||||
|
||||
lis, err := net.Listen("tcp", *addr)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer(so)
|
||||
pb.RegisterEchoServer(s, &echoServer{addr: *addr})
|
||||
|
||||
log.Printf("Serving on %s\n", *addr)
|
||||
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("Failed to serve: %v", err)
|
||||
}
|
||||
}
|
||||
48
examples/features/orca/README.md
Normal file
48
examples/features/orca/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# ORCA Load Reporting
|
||||
|
||||
ORCA is a protocol for reporting load between servers and clients. This
|
||||
example shows how to implement this from both the client and server side. For
|
||||
more details, please see [gRFC
|
||||
A51](https://github.com/grpc/proposal/blob/master/A51-custom-backend-metrics.md)
|
||||
|
||||
## Try it
|
||||
|
||||
```
|
||||
go run server/main.go
|
||||
```
|
||||
|
||||
```
|
||||
go run client/main.go
|
||||
```
|
||||
|
||||
## Explanation
|
||||
|
||||
gRPC ORCA support provides two different ways to report load data to clients
|
||||
from servers: out-of-band and per-RPC. Out-of-band metrics are reported
|
||||
regularly at some interval on a stream, while per-RPC metrics are reported
|
||||
along with the trailers at the end of a call. Both of these mechanisms are
|
||||
optional and work independently.
|
||||
|
||||
The full ORCA API documentation is available here:
|
||||
https://pkg.go.dev/google.golang.org/grpc/orca
|
||||
|
||||
### Out-of-band Metrics
|
||||
|
||||
The server registers an ORCA service that is used for out-of-band metrics. It
|
||||
does this by using `orca.Register()` and then setting metrics on the returned
|
||||
`orca.Service` using its methods.
|
||||
|
||||
The client receives out-of-band metrics via the LB policy. It receives
|
||||
callbacks to a listener by registering the listener on a `SubConn` via
|
||||
`orca.RegisterOOBListener`.
|
||||
|
||||
### Per-RPC Metrics
|
||||
|
||||
The server is set up to report query cost metrics in its RPC handler. For
|
||||
per-RPC metrics to be reported, the gRPC server must be created with the
|
||||
`orca.CallMetricsServerOption()` option, and metrics are set by calling methods
|
||||
on the returned `orca.CallMetricRecorder` from
|
||||
`orca.CallMetricRecorderFromContext()`.
|
||||
|
||||
The client performs one RPC per second. Per-RPC metrics are available for each
|
||||
call via the `Done()` callback returned from the LB policy's picker.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user