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

This commit is contained in:
upstream-mirror
2026-09-15 20:40:15 +05:00
commit e241b33f26
1365 changed files with 339570 additions and 0 deletions

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

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

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

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
/9XEh58NyCCGvjV0a6MHrcZRMl3y
-----END PRIVATE KEY-----

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

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

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
egtZ+t//dA1KBWPMdPk3MoWZopTVgw==
-----END PRIVATE KEY-----

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

View 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

View 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

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

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

View File

@@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
znviHVPPYZhKHQW5wGuGa6eMMWa6aog=
-----END PRIVATE KEY-----

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

View 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

View 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

View 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

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

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

View 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",
}
}

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

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

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

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

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

View 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
```

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

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

View 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
```

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

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

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

View 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"]

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

View 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"]

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

View 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)
```

View File

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

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

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

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

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

View 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
```

View 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 {}
}

View 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 {}
}

View 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
```

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

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

View 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")
}

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

View 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).

View 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")
}

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

View 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")
}

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

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

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

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

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

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

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

View 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.
```

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

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

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

View 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.")
}

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

View 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)`.

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

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

View 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`.

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

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

View 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
```

View 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.
}

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

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

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

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

View 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
```

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

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

View 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})
}
```

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

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

View 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
```

View 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")
}

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

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

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

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

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

View 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"
}
}

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

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

View 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"
}
}

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

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

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

View 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