├── node ├── .dockerignore ├── .gitignore ├── Dockerfile ├── package.json ├── calculator.proto ├── server.js ├── client.js └── README.md ├── python ├── requirements.txt ├── .dockerignore ├── .gitignore ├── Dockerfile ├── calculator.proto ├── server.py ├── client.py └── README.md ├── rust ├── src │ ├── protos │ │ └── mod.rs │ └── main.rs ├── .gitignore ├── Cargo.toml ├── build.rs ├── protos │ └── calculator.proto ├── Dockerfile ├── README.md └── Cargo.lock ├── golang ├── .gitignore ├── go.mod ├── protos │ └── calculator.proto ├── server │ ├── main.go │ └── server.go ├── client │ ├── client.go │ └── main.go ├── Dockerfile ├── go.sum └── README.md ├── calculator.proto ├── .sanity_check.sh ├── deploy.sh └── README.md /node/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json -------------------------------------------------------------------------------- /node/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json -------------------------------------------------------------------------------- /python/requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | grpcio 3 | grpcio-tools 4 | protobuf 5 | -------------------------------------------------------------------------------- /rust/src/protos/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod calculator; 2 | pub mod calculator_grpc; 3 | -------------------------------------------------------------------------------- /python/.dockerignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__/ 3 | .git/ 4 | *_pb2.py 5 | *_pb2_grpc.py 6 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | *_pb2.py 2 | *_pb2_grpc.py 3 | *.pyc 4 | venv/ 5 | __pycache__/ 6 | -------------------------------------------------------------------------------- /golang/.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude Protobuf Compiler 2 | protoc-3.11.4-linux-x86_64/ 3 | 4 | # Exclude Protobuf-generated Golang sources 5 | *.pb.go 6 | -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude protoc-generated Rust sources 2 | src/protos/calculator_grpc.rs 3 | src/protos/calculator.rs 4 | 5 | # Exclude ./target 6 | target -------------------------------------------------------------------------------- /node/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.14 2 | 3 | WORKDIR /srv/grpc 4 | 5 | COPY server.js *.proto package.json ./ 6 | 7 | RUN npm install 8 | 9 | CMD ["node", "server.js"] -------------------------------------------------------------------------------- /golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grpc-ecosystem/grpc-cloud-run-example/golang 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/golang/protobuf v1.3.5 7 | google.golang.org/grpc v1.28.0 8 | ) 9 | -------------------------------------------------------------------------------- /python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | WORKDIR /srv/grpc 4 | 5 | COPY server.py *.proto requirements.txt ./ 6 | 7 | RUN pip install -r requirements.txt && \ 8 | python -m grpc_tools.protoc \ 9 | -I. \ 10 | --python_out=. \ 11 | --grpc_python_out=. \ 12 | calculator.proto 13 | 14 | CMD ["python", "server.py"] 15 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grpc-cloud-run-example-rust" 3 | version = "0.0.1" 4 | edition = "2018" 5 | 6 | [[bin]] 7 | name = "server" 8 | path = "src/main.rs" 9 | 10 | [build-dependencies] 11 | protoc-rust-grpc = "0.6.1" 12 | 13 | [dependencies] 14 | futures = "0.3.4" 15 | futures-cpupool = "0.1.8" 16 | grpc = "0.6.2" 17 | protobuf = "2.8.2" 18 | -------------------------------------------------------------------------------- /rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | protoc_rust_grpc::run(protoc_rust_grpc::Args { 3 | out_dir: "src/protos", 4 | includes: &["./"], 5 | input: &["protos/calculator.proto"], 6 | rust_protobuf: true, // also generate protobuf messages, not just services 7 | ..Default::default() 8 | }) 9 | .expect("protoc-rust-grpc"); 10 | } 11 | -------------------------------------------------------------------------------- /node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grpc-cloud-run-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "Apache-2.0", 11 | "dependencies": { 12 | "@grpc/proto-loader": "^0.5.3", 13 | "grpc": "^1.24.2", 14 | "yargs": "^15.1.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /calculator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | enum Operation { 4 | ADD = 0; 5 | SUBTRACT = 1; 6 | } 7 | 8 | message BinaryOperation { 9 | float first_operand = 1; 10 | float second_operand = 2; 11 | Operation operation = 3; 12 | }; 13 | 14 | message CalculationResult { 15 | float result = 1; 16 | }; 17 | 18 | service Calculator { 19 | rpc Calculate (BinaryOperation) returns (CalculationResult); 20 | }; 21 | -------------------------------------------------------------------------------- /node/calculator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | enum Operation { 4 | ADD = 0; 5 | SUBTRACT = 1; 6 | } 7 | 8 | message BinaryOperation { 9 | float first_operand = 1; 10 | float second_operand = 2; 11 | Operation operation = 3; 12 | }; 13 | 14 | message CalculationResult { 15 | float result = 1; 16 | }; 17 | 18 | service Calculator { 19 | rpc Calculate (BinaryOperation) returns (CalculationResult); 20 | }; 21 | -------------------------------------------------------------------------------- /python/calculator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | enum Operation { 4 | ADD = 0; 5 | SUBTRACT = 1; 6 | } 7 | 8 | message BinaryOperation { 9 | float first_operand = 1; 10 | float second_operand = 2; 11 | Operation operation = 3; 12 | }; 13 | 14 | message CalculationResult { 15 | float result = 1; 16 | }; 17 | 18 | service Calculator { 19 | rpc Calculate (BinaryOperation) returns (CalculationResult); 20 | }; 21 | -------------------------------------------------------------------------------- /rust/protos/calculator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | enum Operation { 4 | ADD = 0; 5 | SUBTRACT = 1; 6 | } 7 | 8 | message BinaryOperation { 9 | float first_operand = 1; 10 | float second_operand = 2; 11 | Operation operation = 3; 12 | }; 13 | 14 | message CalculationResult { 15 | float result = 1; 16 | }; 17 | 18 | service Calculator { 19 | rpc Calculate (BinaryOperation) returns (CalculationResult); 20 | }; 21 | -------------------------------------------------------------------------------- /.sanity_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROTO_CHECKSUMS=$(find . -name calculator.proto -exec md5sum {} \;) 4 | UNIQUE_COUNT=$(echo "$PROTO_CHECKSUMS" | \ 5 | awk '{print $1;}' | \ 6 | sort | \ 7 | uniq | \ 8 | wc -l) 9 | 10 | if [ "${UNIQUE_COUNT}" != "1" ]; then 11 | printf "Not all proto files are identical:\n%s\n" \ 12 | "${PROTO_CHECKSUMS}" 1>&2 13 | exit 1 14 | fi 15 | 16 | exit 0 17 | -------------------------------------------------------------------------------- /golang/protos/calculator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Ensures that generated sources are in a Golang package called 'protos' 4 | option go_package = "protos"; 5 | 6 | enum Operation { 7 | ADD = 0; 8 | SUBTRACT = 1; 9 | } 10 | 11 | message BinaryOperation { 12 | float first_operand = 1; 13 | float second_operand = 2; 14 | Operation operation = 3; 15 | }; 16 | 17 | message CalculationResult { 18 | float result = 1; 19 | }; 20 | 21 | service Calculator { 22 | rpc Calculate (BinaryOperation) returns (CalculationResult); 23 | }; 24 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" != "2" ]; then 4 | echo "Usage: $0 GCP_PROJECT LANGUAGE" 1>&2 5 | exit 1 6 | fi 7 | 8 | LANGUAGES="node python golang rust" 9 | 10 | if ! grep -oh "$2" 2>&1 1>/dev/null <<< "${LANGUAGES}"; then 11 | echo "Unsupported language \"$2\"" 1>&2 12 | echo "Supported languages: ${LANGUAGES}" 1>&2 13 | exit 1 14 | fi 15 | 16 | 17 | GCP_PROJECT="$1" 18 | LANGUAGE="$2" 19 | CONTAINER_TAG="gcr.io/${GCP_PROJECT}/grpc-calculator:latest" 20 | 21 | set -x 22 | (cd "${LANGUAGE}" 23 | docker build --tag="${CONTAINER_TAG}" . 24 | docker push "${CONTAINER_TAG}" 25 | gcloud run deploy --image="${CONTAINER_TAG}" --platform=managed --project=${GCP_PROJECT} 26 | ) 27 | -------------------------------------------------------------------------------- /golang/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "os" 8 | 9 | pb "github.com/grpc-ecosystem/grpc-cloud-run-example/golang/protos" 10 | 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | func main() { 15 | port := os.Getenv("PORT") 16 | if port == "" { 17 | port = "8080" 18 | } 19 | 20 | grpcEndpoint := fmt.Sprintf(":%s", port) 21 | log.Printf("gRPC endpoint [%s]", grpcEndpoint) 22 | 23 | grpcServer := grpc.NewServer() 24 | pb.RegisterCalculatorServer(grpcServer, NewServer()) 25 | 26 | listen, err := net.Listen("tcp", grpcEndpoint) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | log.Printf("Starting: gRPC Listener [%s]\n", grpcEndpoint) 31 | log.Fatal(grpcServer.Serve(listen)) 32 | } 33 | -------------------------------------------------------------------------------- /golang/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | pb "github.com/grpc-ecosystem/grpc-cloud-run-example/golang/protos" 7 | 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // Client is a struct that implements the pb.CalculatorClient 12 | type Client struct { 13 | client pb.CalculatorClient 14 | } 15 | 16 | // NewClient returns a new Client 17 | func NewClient(conn *grpc.ClientConn) *Client { 18 | return &Client{ 19 | client: pb.NewCalculatorClient(conn), 20 | } 21 | } 22 | 23 | // Calculate performs an operation on operands defined by pb.BinaryOperation returning pb.CalculationResult 24 | func (c *Client) Calculate(ctx context.Context, r *pb.BinaryOperation) (*pb.CalculationResult, error) { 25 | return c.client.Calculate(ctx, r) 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPC in Google Cloud Run 2 | 3 | *Estimated Reading Time: 20 minutes* 4 | 5 | Google Cloud Run makes it easy to deploy and run REST servers, but it also 6 | supports gRPC servers out of the box. This article will show you how to 7 | deploy a gRPC service written in Python to Cloud Run. For the full code, [check 8 | out the Github repo.](https://github.com/grpc-ecosystem/grpc-cloud-run-example) 9 | 10 | We'll be writing a simple remote calculator service. For the moment, it will 11 | just support adding and subtracting floating point numbers, but once this is up 12 | and running, you could easily extend it to add other features. 13 | 14 | To get started, choose the language you'll be building your server in. 15 | 16 | - [Node](node/README.md) 17 | - [Python](python/README.md) 18 | - [Golang](golang/README.md) 19 | - [Rust](rust/README.md) 20 | -------------------------------------------------------------------------------- /rust/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust@sha256:de00dbf06ed1a9426bd044f619e6f782e78b83bcfefb1570cfd342f84d6f424a AS builder 2 | 3 | ARG VERS="3.11.4" 4 | ARG ARCH="linux-x86_64" 5 | 6 | RUN apt update && apt -y install wget unzip && \ 7 | wget https://github.com/protocolbuffers/protobuf/releases/download/v${VERS}/protoc-${VERS}-${ARCH}.zip \ 8 | --output-document=/protoc-${VERS}-${ARCH}.zip && \ 9 | unzip -o protoc-${VERS}-${ARCH}.zip -d /protoc-${VERS}-${ARCH} 10 | 11 | ENV PATH="${PATH}:/protoc-${VERS}-${ARCH}/bin" 12 | 13 | WORKDIR /srv/grpc 14 | 15 | # Thanks https://alexbrand.dev/post/how-to-package-rust-applications-into-minimal-docker-containers/ 16 | RUN rustup target add x86_64-unknown-linux-musl 17 | 18 | COPY . . 19 | 20 | # Build a static binary using musl libs 21 | RUN cargo install --target x86_64-unknown-linux-musl --path . 22 | 23 | FROM scratch AS runtime 24 | 25 | COPY --from=builder /usr/local/cargo/bin/server . 26 | 27 | ENTRYPOINT ["./server"] -------------------------------------------------------------------------------- /node/server.js: -------------------------------------------------------------------------------- 1 | // The package @grpc/grpc-js can also be used instead of grpc here 2 | const grpc = require('grpc'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | 5 | const packageDefinition = protoLoader.loadSync( 6 | __dirname + '/calculator.proto', 7 | {keepCase: true, 8 | longs: String, 9 | enums: String, 10 | defaults: true, 11 | oneofs: true 12 | }); 13 | const calculatorProto = grpc.loadPackageDefinition(packageDefinition); 14 | 15 | const PORT = process.env.PORT; 16 | 17 | function calculate(call, callback) { 18 | const request = call.request; 19 | let result; 20 | if (request.operation === 'ADD') { 21 | result = request.first_operand + request.second_operand; 22 | } else { 23 | result = request.first_operand - request.second_operand; 24 | } 25 | callback(null, {result}); 26 | } 27 | 28 | function main() { 29 | const server = new grpc.Server(); 30 | server.addService(calculatorProto.Calculator.service, {calculate}); 31 | server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (error, port) => { 32 | if (error) { 33 | throw error; 34 | } 35 | server.start(); 36 | }); 37 | } 38 | 39 | main(); -------------------------------------------------------------------------------- /golang/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang@sha256:205d5cf61216a16da4431dd5796793c650236159fa04e055459940ddc4c6389c as build 2 | 3 | WORKDIR /srv/grpc 4 | 5 | COPY go.mod . 6 | COPY protos/calculator.proto ./protos/ 7 | COPY server/*.go ./server/ 8 | 9 | # Installs protoc and protoc-gen-go plugin 10 | ARG VERS="3.11.4" 11 | ARG ARCH="linux-x86_64" 12 | RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${VERS}/protoc-${VERS}-${ARCH}.zip \ 13 | --output-document=./protoc-${VERS}-${ARCH}.zip && \ 14 | apt update && apt install -y unzip && \ 15 | unzip -o protoc-${VERS}-${ARCH}.zip -d protoc-${VERS}-${ARCH} && \ 16 | mv protoc-${VERS}-${ARCH}/bin/* /usr/local/bin && \ 17 | mv protoc-${VERS}-${ARCH}/include/* /usr/local/include && \ 18 | go get -u github.com/golang/protobuf/protoc-gen-go 19 | 20 | # Generate Golang protobuf files 21 | RUN protoc \ 22 | --proto_path=. \ 23 | --go_out=plugins=grpc:. \ 24 | ./protos/calculator.proto 25 | 26 | # Build static binary 27 | RUN CGO_ENABLED=0 GOOS=linux \ 28 | go build -a -installsuffix cgo \ 29 | -o /go/bin/server \ 30 | github.com/grpc-ecosystem/grpc-cloud-run-example/golang/server 31 | 32 | 33 | FROM scratch 34 | 35 | COPY --from=build /go/bin/server /server 36 | 37 | ENTRYPOINT ["/server"] 38 | -------------------------------------------------------------------------------- /python/server.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from concurrent import futures 4 | 5 | from typing import Text 6 | 7 | import calculator_pb2 8 | import calculator_pb2_grpc 9 | 10 | import grpc 11 | 12 | _PORT = os.environ["PORT"] 13 | 14 | class Calculator(calculator_pb2_grpc.CalculatorServicer): 15 | 16 | def Calculate(self, 17 | request: calculator_pb2.BinaryOperation, 18 | context: grpc.ServicerContext) -> None: 19 | logging.info("Received request: %s", request) 20 | if request.operation == calculator_pb2.ADD: 21 | result = request.first_operand + request.second_operand 22 | else: 23 | result = request.first_operand - request.second_operand 24 | return calculator_pb2.CalculationResult(result=result) 25 | 26 | 27 | def _serve(port: Text): 28 | bind_address = f"[::]:{port}" 29 | server = grpc.server(futures.ThreadPoolExecutor()) 30 | calculator_pb2_grpc.add_CalculatorServicer_to_server(Calculator(), server) 31 | server.add_insecure_port(bind_address) 32 | server.start() 33 | logging.info("Listening on %s.", bind_address) 34 | server.wait_for_termination() 35 | 36 | 37 | if __name__ == "__main__": 38 | logging.basicConfig(level=logging.INFO) 39 | _serve(_PORT) 40 | -------------------------------------------------------------------------------- /golang/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | 8 | pb "github.com/grpc-ecosystem/grpc-cloud-run-example/golang/protos" 9 | ) 10 | 11 | // Prove that Server implements pb.CalculatorServer by instantiating a Server 12 | var _ pb.CalculatorServer = (*Server)(nil) 13 | 14 | // Server is a struct implements the pb.CalculatorServer 15 | type Server struct { 16 | } 17 | 18 | // NewServer returns a new Server 19 | func NewServer() *Server { 20 | return &Server{} 21 | } 22 | 23 | // Calculate performs an operation on operands defined by pb.BinaryOperation returning pb.CalculationResult 24 | func (s *Server) Calculate(ctx context.Context, r *pb.BinaryOperation) (*pb.CalculationResult, error) { 25 | log.Println("[server:Calculate] Started") 26 | if ctx.Err() == context.Canceled { 27 | return &pb.CalculationResult{}, fmt.Errorf("client cancelled: abandoning") 28 | } 29 | 30 | switch r.GetOperation() { 31 | case pb.Operation_ADD: 32 | return &pb.CalculationResult{ 33 | Result: r.GetFirstOperand() + r.GetSecondOperand(), 34 | }, nil 35 | case pb.Operation_SUBTRACT: 36 | return &pb.CalculationResult{ 37 | Result: r.GetFirstOperand() - r.GetSecondOperand(), 38 | }, nil 39 | default: 40 | return &pb.CalculationResult{}, fmt.Errorf("undefined operation") 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /golang/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "flag" 7 | "log" 8 | "math/rand" 9 | "time" 10 | 11 | pb "github.com/grpc-ecosystem/grpc-cloud-run-example/golang/protos" 12 | 13 | "google.golang.org/grpc" 14 | "google.golang.org/grpc/credentials" 15 | ) 16 | 17 | var ( 18 | grpcEndpoint = flag.String("grpc_endpoint", "", "The gRPC Endpoint of the Server") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | if *grpcEndpoint == "" { 24 | log.Fatal("[main] unable to start client without gRPC endpoint to server") 25 | } 26 | 27 | creds := credentials.NewTLS(&tls.Config{ 28 | InsecureSkipVerify: true, 29 | }) 30 | 31 | opts := []grpc.DialOption{ 32 | grpc.WithTransportCredentials(creds), 33 | } 34 | 35 | log.Printf("Connecting to gRPC Service [%s]", *grpcEndpoint) 36 | conn, err := grpc.Dial(*grpcEndpoint, opts...) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | defer conn.Close() 41 | 42 | client := NewClient(conn) 43 | ctx := context.Background() 44 | 45 | // Loop indefinitely 46 | s := rand.NewSource(time.Now().UnixNano()) 47 | r := rand.New(s) 48 | for { 49 | o1 := r.Float32() 50 | o2 := r.Float32() 51 | op := randomOp(r.Float32()) 52 | rqst := &pb.BinaryOperation{ 53 | FirstOperand: o1, 54 | SecondOperand: o2, 55 | Operation: op, 56 | } 57 | resp, err := client.Calculate(ctx, rqst) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | log.Printf("[main] %0.4f %s %0.4f = %0.4f", o1, stringOp(op), o2, resp.GetResult()) 62 | time.Sleep(1 * time.Second) 63 | } 64 | } 65 | func randomOp(r float32) pb.Operation { 66 | if r < 0.5 { 67 | return pb.Operation_ADD 68 | } 69 | return pb.Operation_SUBTRACT 70 | } 71 | func stringOp(op pb.Operation) string { 72 | if op == pb.Operation_ADD { 73 | return "+" 74 | } 75 | return "-" 76 | } 77 | -------------------------------------------------------------------------------- /rust/src/main.rs: -------------------------------------------------------------------------------- 1 | mod protos; 2 | 3 | use std::{env, thread}; 4 | 5 | use protos::calculator::{BinaryOperation, CalculationResult, Operation}; 6 | use protos::calculator_grpc::{Calculator, CalculatorServer}; 7 | 8 | use grpc::{RequestOptions, ServerBuilder, SingleResponse}; 9 | 10 | pub struct CalculatorImpl; 11 | impl Calculator for CalculatorImpl { 12 | fn calculate( 13 | &self, 14 | _: RequestOptions, 15 | rqst: BinaryOperation, 16 | ) -> SingleResponse { 17 | let op1: f32 = rqst.get_first_operand(); 18 | let op2: f32 = rqst.get_second_operand(); 19 | let result: f32 = match rqst.get_operation() { 20 | Operation::ADD => op1 + op2, 21 | Operation::SUBTRACT => op1 - op2, 22 | }; 23 | let resp = CalculationResult { 24 | result: result, 25 | ..Default::default() 26 | }; 27 | return SingleResponse::completed(resp); 28 | } 29 | } 30 | 31 | fn main() { 32 | let err = "Unable to parse `PORT` environment variable value, expecting a value that parses to a 16-bit integer (0..65535)"; 33 | let key = "PORT"; 34 | let port = match env::var_os(key) { 35 | Some(val) => match val.to_str() { 36 | Some(s) => match s.parse::() { 37 | Ok(p) => p, 38 | Err(_) => panic!(err), 39 | }, 40 | None => panic!(err), 41 | }, 42 | // `PORT` environment variable unset; defaulting to... 43 | None => 50051, 44 | }; 45 | 46 | let mut server = ServerBuilder::new_plain(); 47 | server.http.set_port(port); 48 | server.add_service(CalculatorServer::new_service_def(CalculatorImpl)); 49 | let _server = server.build().expect("server"); 50 | 51 | println!("Starting: gRPC Listener [{}]", port); 52 | 53 | loop { 54 | thread::park(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /python/client.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import functools 3 | 4 | from typing import Text 5 | 6 | import grpc 7 | 8 | import calculator_pb2 9 | import calculator_pb2_grpc 10 | 11 | 12 | _OPERATIONS = { 13 | "add": calculator_pb2.ADD, 14 | "subtract": calculator_pb2.SUBTRACT, 15 | } 16 | 17 | 18 | def _calculate(server_address: Text, 19 | operation: calculator_pb2.Operation, 20 | a: float, 21 | b: float, 22 | plaintext: bool) -> float: 23 | if plaintext: 24 | channel = grpc.insecure_channel(server_address) 25 | else: 26 | channel = grpc.secure_channel(server_address, grpc.ssl_channel_credentials()) 27 | try: 28 | stub = calculator_pb2_grpc.CalculatorStub(channel) 29 | request = calculator_pb2.BinaryOperation(first_operand=a, 30 | second_operand=b, 31 | operation=operation) 32 | return stub.Calculate(request).result 33 | finally: 34 | channel.close() 35 | 36 | 37 | if __name__ == "__main__": 38 | parser = argparse.ArgumentParser() 39 | parser.add_argument("server", 40 | help="The address of the calculator server.") 41 | parser.add_argument("operation", 42 | choices=_OPERATIONS.keys(), 43 | help="The operation to perform") 44 | parser.add_argument("a", type=float, help="The first operand.") 45 | parser.add_argument("b", type=float, help="The second operand.") 46 | parser.add_argument("-k", "--plaintext", 47 | action="store_true", 48 | help="When set, establishes a plaintext connection. " + 49 | "Useful for debugging locally.") 50 | args = parser.parse_args() 51 | print(_calculate(args.server, 52 | _OPERATIONS[args.operation], 53 | args.a, args.b, args.plaintext)) 54 | -------------------------------------------------------------------------------- /node/client.js: -------------------------------------------------------------------------------- 1 | // The package @grpc/grpc-js can also be used instead of grpc here 2 | const grpc = require('grpc'); 3 | const protoLoader = require('@grpc/proto-loader'); 4 | 5 | const packageDefinition = protoLoader.loadSync( 6 | __dirname + '/calculator.proto', 7 | {keepCase: true, 8 | longs: String, 9 | enums: String, 10 | defaults: true, 11 | oneofs: true 12 | }); 13 | const calculatorProto = grpc.loadPackageDefinition(packageDefinition); 14 | 15 | function calculate(serverAddress, operation, a, b, plaintext) { 16 | let credentials; 17 | if (plaintext) { 18 | credentials = grpc.credentials.createInsecure(); 19 | } else { 20 | credentials = grpc.credentials.createSsl(); 21 | } 22 | const calculator = new calculatorProto.Calculator(serverAddress, credentials); 23 | const binaryOperation = { 24 | operation: operation, 25 | first_operand: a, 26 | second_operand: b 27 | } 28 | return new Promise((resolve, reject) => { 29 | calculator.calculate(binaryOperation, (error, response) => { 30 | if (error) { 31 | reject(error); 32 | } else { 33 | resolve(response.result); 34 | } 35 | }) 36 | }) 37 | } 38 | 39 | function main() { 40 | const argv = require('yargs') 41 | .option({ 42 | server: { 43 | describe: 'The address of the calculator server.', 44 | demandOption: true, 45 | type: 'string' 46 | }, 47 | operation: { 48 | describe: 'The operation to perform', 49 | demandOption: true, 50 | choices: ['add', 'subtract'], 51 | type: 'string' 52 | }, 53 | a: { 54 | describe: 'The first operand', 55 | demandOption: true, 56 | type: 'number' 57 | }, 58 | b: { 59 | describe: 'The second operand', 60 | demandOption: true, 61 | type: 'number' 62 | }, 63 | plaintext: { 64 | alias: 'k', 65 | describe: 'When set, establishes a plaintext connection. Useful for debugging locally.', 66 | type: 'boolean' 67 | } 68 | }).argv; 69 | calculate(argv.server, argv.operation.toUpperCase(), argv.a, argv.b, argv.plaintext).then((value) => { 70 | console.log(value); 71 | }, (error) => { 72 | console.error(error); 73 | }); 74 | } 75 | 76 | main(); -------------------------------------------------------------------------------- /golang/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 6 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 7 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 8 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 9 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 10 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 11 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 14 | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= 15 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 16 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 17 | github.com/grpc-ecosystem/grpc-cloud-run-example v0.0.0-20200320200053-1c4c67892ad7 h1:XGaJsCyXml5apNjz9akSaQ0yGibqPFRrPfXcTCLcSUI= 18 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 19 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 20 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 21 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 22 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 23 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 24 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 25 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 26 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 27 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 28 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 29 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 30 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 31 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 35 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 38 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 39 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 40 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 41 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 42 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 43 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 44 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 45 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 46 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 47 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 48 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 49 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 50 | google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= 51 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 52 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 53 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 54 | -------------------------------------------------------------------------------- /node/README.md: -------------------------------------------------------------------------------- 1 | # gRPC in Google Cloud Run 2 | 3 | *Estimated Reading Time: 20 minutes* 4 | 5 | Google Cloud Run makes it easy to deploy and run REST servers, but it also 6 | supports gRPC servers out of the box. This article will show you how to 7 | deploy a gRPC service written in Node.js to Cloud Run. For the full code, [check 8 | out the Github repo.](https://github.com/grpc-ecosystem/grpc-cloud-run-example) 9 | 10 | We'll be writing a simple remote calculator service. For the moment, it will 11 | just support adding and subtracting floating point numbers, but once this is up 12 | and running, you could easily extend it to add other features. 13 | 14 | ## The Protocol Buffer Definition 15 | 16 | Take a look in [`calculator.proto`](calculator.proto) to see the full protocol buffer definition. If 17 | you're not familiar with protocol buffers, 18 | [take a moment to get acquainted.](https://developers.google.com/protocol-buffers) 19 | 20 | ```protobuf 21 | enum Operation { 22 | ADD = 0; 23 | SUBTRACT = 1; 24 | } 25 | 26 | message BinaryOperation { 27 | float first_operand = 1; 28 | float second_operand = 2; 29 | Operation operation = 3; 30 | }; 31 | 32 | message CalculationResult { 33 | float result = 1; 34 | }; 35 | 36 | service Calculator { 37 | rpc Calculate (BinaryOperation) returns (CalculationResult); 38 | }; 39 | ``` 40 | 41 | Our service will be a simple unary RPC. We'll take two floats and one of two 42 | operations. Then, we'll return the result of that operation. 43 | 44 | ## The Server 45 | 46 | Let's start with the server. Take a look at [`server.js`](server.js) for the full code. 47 | Google Cloud Run will set up an environment variable called `PORT` on which your 48 | server should listen. The first thing we do is pull that from the environment: 49 | 50 | ```node 51 | const PORT = process.env.PORT; 52 | ``` 53 | 54 | Next, we set up a server bound to that port, listening on all interfaces. 55 | 56 | ```node 57 | function main() { 58 | const server = new grpc.Server(); 59 | server.addService(calculatorProto.Calculator.service, {calculate}); 60 | server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (error, port) => { 61 | if (error) { 62 | throw error; 63 | } 64 | server.start(); 65 | }); 66 | } 67 | ``` 68 | 69 | Notice that we use the `createInsecure` method here. Google Cloud Run's proxy 70 | provides us with a TLS-encrypted proxy that handles the messy business of 71 | setting up certs for us. The traffic from the proxy to the container with our 72 | gRPC server in it goes through an encrypted tunnel, so we don't need to worry 73 | about handling it ourselves. Cloud Run natively handles HTTP/2, so gRPC's 74 | transport is well-supported. 75 | 76 | ## Connecting 77 | 78 | Now let's test the server out locally. First, we install dependencies. 79 | 80 | ```bash 81 | npm install 82 | ``` 83 | 84 | And then we start the server: 85 | 86 | ```bash 87 | export PORT=50051 88 | node server.js 89 | ``` 90 | 91 | Now the server should be listening on port `50051`. We'll use the tool 92 | [`grpcurl`](https://github.com/fullstorydev/grpcurl) to manually interact with it. 93 | On Linux and Mac you can install it with `curl -s https://grpc.io/get_grpcurl | bash`. 94 | 95 | ```bash 96 | grpcurl \ 97 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 98 | --plaintext \ 99 | -proto calculator.proto \ 100 | localhost:50051 \ 101 | Calculator.Calculate 102 | ``` 103 | 104 | We tell `grpcurl` where to find the protocol buffer definitions and server. 105 | Then, we supply the request. `grpcurl` gives us a nice mapping from JSON to 106 | protobuf. We can even supply the operation enumeration as a string. Finally, we 107 | invoke the `Calculate` method on the `Calculator` service. If all goes well, you 108 | should see: 109 | 110 | ```bash 111 | { 112 | "result": 5 113 | } 114 | ``` 115 | 116 | Great! We've got a working calculator server. Next, let's put it inside a 117 | Docker container. 118 | 119 | ## Containerizing the Server 120 | 121 | We're going to use the official Dockerhub Node 12.14 image as our base image. 122 | 123 | ```Dockerfile 124 | FROM node:12.14 125 | ``` 126 | 127 | We'll put all of our code in `/srv/grpc/`. 128 | 129 | ```Dockerfile 130 | WORKDIR /srv/grpc 131 | 132 | COPY server.js *.proto package.json . 133 | ``` 134 | 135 | We install our Node dependencies into the container. 136 | 137 | ```Dockerfile 138 | RUN npm install 139 | ``` 140 | 141 | Finally, we set our container up to run the server by default. 142 | 143 | ```Dockerfile 144 | CMD ["node", "server.js"] 145 | ``` 146 | 147 | Now we can build our image. In order to deploy to Cloud Run, we'll be pushing to 148 | the `gcr.io` container registry, so we'll tag it accordingly. 149 | 150 | ```bash 151 | export GCP_PROJECT= 152 | docker build -t gcr.io/$GCP_PROJECT/grpc-calculator:latest 153 | ``` 154 | 155 | The tag above will change based on your GCP project name. We're calling the 156 | service `grpc-calculator` and using the `latest` tag. 157 | 158 | Now, before we deploy to Cloud Run, let's make sure that we've containerized our 159 | application properly. We'll test it by spinning up a local container. 160 | 161 | ```bash 162 | docker run -d -p 50051:50051 -e PORT=50051 gcr.io/$GCP_PROJECT/grpc-calculator:latest 163 | ``` 164 | 165 | If all goes well, `grpcurl` will give us the same result as before: 166 | 167 | ```bash 168 | grpcurl \ 169 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 170 | --plaintext \ 171 | -proto calculator.proto \ 172 | localhost:50051 \ 173 | Calculator.Calculate 174 | ``` 175 | 176 | ## Deploying to Cloud Run 177 | 178 | Cloud Run needs to pull our application from a container registry, so the first 179 | step is to push the image we just built. 180 | 181 | Make sure that [you can use `gcloud`](https://cloud.google.com/sdk/gcloud/reference/auth/login) 182 | and are [able to push to `gcr.io`.](https://cloud.google.com/container-registry/docs/pushing-and-pulling) 183 | 184 | ```bash 185 | gcloud auth login 186 | gcloud auth configure-docker 187 | ``` 188 | 189 | Now we can push our image. 190 | 191 | ```bash 192 | docker push gcr.io/$GCP_PROJECT/grpc-calculator:latest 193 | ``` 194 | 195 | Finally, we deploy our application to Cloud Run: 196 | 197 | ```bash 198 | gcloud run deploy --image gcr.io/$GCP_PROJECT/grpc-calculator:latest --platform managed 199 | ``` 200 | 201 | You may be prompted for auth. If so, choose the unauthenticated option. 202 | 203 | This command will give you a message like 204 | ``` 205 | Service [grpc-calculator] revision [grpc-calculator-00001-baw] has been deployed and is serving 100 percent of traffic at https://grpc-calculator-xyspwhk3xq-uc.a.run.app 206 | ``` 207 | 208 | We can programmatically determine the gRPC service's endpoint: 209 | 210 | ```bash 211 | ENDPOINT=$(\ 212 | gcloud run services list \ 213 | --project=${GCP_PROJECT} \ 214 | --region=${GCP_REGION} \ 215 | --platform=managed \ 216 | --format="value(status.address.url)" \ 217 | --filter="metadata.name=grpc-calculator") 218 | ENDPOINT=${ENDPOINT#https://} && echo ${ENDPOINT} 219 | ``` 220 | 221 | Notice that this endpoint is secured with TLS even though the server we wrote uses a plaintext connection. Cloud Run provides a proxy that provides TLS for us. 222 | 223 | We'll account for this in our `grpcurl` invocation by omitting the `-plaintext` flag: 224 | 225 | ```bash 226 | grpcurl \ 227 | -proto calculator.proto \ 228 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 229 | ${ENDPOINT}:443 \ 230 | Calculator.Calculate 231 | ``` 232 | 233 | Ensure that you run the above command in a directory relative to the file specified under the `-proto` flag. 234 | 235 | And now you've got an auto-scaling calculator gRPC service! 236 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # gRPC in Google Cloud Run 2 | 3 | *Estimated Reading Time: 20 minutes* 4 | 5 | Google Cloud Run makes it easy to deploy and run REST servers, but it also 6 | supports gRPC servers out of the box. This article will show you how to 7 | deploy a gRPC service written in Python to Cloud Run. For the full code, [check 8 | out the Github repo.](https://github.com/grpc-ecosystem/grpc-cloud-run-example) 9 | 10 | We'll be writing a simple remote calculator service. For the moment, it will 11 | just support adding and subtracting floating point numbers, but once this is up 12 | and running, you could easily extend it to add other features. 13 | 14 | ## The Protocol Buffer Definition 15 | 16 | Take a look in [`calculator.proto`](calculator.proto) to see the full protocol buffer definition. If 17 | you're not familiar with protocol buffers, 18 | [take a moment to get acquainted.](https://developers.google.com/protocol-buffers) 19 | 20 | ```protobuf 21 | enum Operation { 22 | ADD = 0; 23 | SUBTRACT = 1; 24 | } 25 | 26 | message BinaryOperation { 27 | float first_operand = 1; 28 | float second_operand = 2; 29 | Operation operation = 3; 30 | }; 31 | 32 | message CalculationResult { 33 | float result = 1; 34 | }; 35 | 36 | service Calculator { 37 | rpc Calculate (BinaryOperation) returns (CalculationResult); 38 | }; 39 | ``` 40 | 41 | Our service will be a simple unary RPC. We'll take two floats and one of two 42 | operations. Then, we'll return the result of that operation. 43 | 44 | ## The Server 45 | 46 | Let's start with the server. Take a look at [`server.py`](server.py) for the full code. 47 | Google Cloud Run will set up an environment variable called `PORT` on which your 48 | server should listen. The first thing we do is pull that from the environment: 49 | 50 | ```python 51 | _PORT = os.environ["PORT"] 52 | ``` 53 | 54 | Next, we set up a server bound to that port, listening on all interfaces. 55 | 56 | ```python 57 | def _serve(port: Text): 58 | bind_address = f"[::]:{port}" 59 | server = grpc.server(futures.ThreadPoolExecutor()) 60 | calculator_pb2_grpc.add_CalculatorServicer_to_server(Calculator(), server) 61 | server.add_insecure_port(bind_address) 62 | server.start() 63 | logging.info("Listening on %s.", bind_address) 64 | server.wait_for_termination() 65 | ``` 66 | 67 | Notice that we use the `add_insecure_port` method here. Google Cloud Run's proxy 68 | provides us with a TLS-encrypted proxy that handles the messy business of 69 | setting up certs for us. The traffic from the proxy to the container with our 70 | gRPC server in it goes through an encrypted tunnel, so we don't need to worry 71 | about handling it ourselves. Cloud Run natively handles HTTP/2, so gRPC's 72 | transport is well-supported. 73 | 74 | 75 | ## Connecting 76 | 77 | Now let's test the server out locally. First, we install dependencies. 78 | 79 | ```bash 80 | virtualenv venv -p python3 81 | source venv/bin/activate 82 | pip install -r requirements.txt 83 | ``` 84 | 85 | Now we generate Python code from our `calculator.proto` file. This is how 86 | we get the definitions for our `calculator_pb2` and `calculator_pb2_grpc` 87 | modules. It's considered poor form to check these into source code, so they're 88 | included in our `.gitignore` file. 89 | 90 | ```bash 91 | python -m grpc_tools.protoc \ 92 | -I. \ 93 | --python_out=. \ 94 | --grpc_python_out=. \ 95 | calculator.proto 96 | ``` 97 | 98 | Finally, we start the server: 99 | 100 | ```bash 101 | export PORT=50051 102 | python server.py 103 | ``` 104 | 105 | Now the server should be listening on port `50051`. We'll use the tool 106 | [`grpcurl`](https://github.com/fullstorydev/grpcurl) to manually interact with it. 107 | On Linux and Mac you can install it with `curl -s https://grpc.io/get_grpcurl | bash`. 108 | 109 | ```bash 110 | grpcurl \ 111 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 112 | --plaintext \ 113 | -proto calculator.proto \ 114 | localhost:50051 \ 115 | Calculator.Calculate 116 | ``` 117 | 118 | We tell `grpcurl` where to find the protocol buffer definitions and server. 119 | Then, we supply the request. `grpcurl` gives us a nice mapping from JSON to 120 | protobuf. We can even supply the operation enumeration as a string. Finally, we 121 | invoke the `Calculate` method on the `Calculator` service. If all goes well, you 122 | should see: 123 | 124 | ```bash 125 | { 126 | "result": 5 127 | } 128 | ``` 129 | 130 | Great! We've got a working calculator server. Next, let's put it inside a 131 | Docker container. 132 | 133 | ## Containerizing the Server 134 | 135 | We're going to use the official Dockerhub Python 3.8 image as our base image. 136 | 137 | ```Dockerfile 138 | FROM python:3.8 139 | ``` 140 | 141 | We'll put all of our code in `/srv/grpc/`. 142 | 143 | ```Dockerfile 144 | WORKDIR /srv/grpc 145 | 146 | COPY server.py *.proto requirements.txt . 147 | ``` 148 | 149 | We install our Python package dependencies into the container. 150 | 151 | 152 | ```Dockerfile 153 | RUN pip install -r requirements.txt && \ 154 | python -m grpc_tools.protoc \ 155 | -I. \ 156 | --python_out=. \ 157 | --grpc_python_out=. \ 158 | calculator.proto 159 | ``` 160 | 161 | Finally, we set our container up to run the server by default. 162 | 163 | ```Dockerfile 164 | CMD ["python", "server.py"] 165 | ``` 166 | 167 | Now we can build our image. In order to deploy to Cloud Run, we'll be pushing to 168 | the `gcr.io` container registry, so we'll tag it accordingly. 169 | 170 | ```bash 171 | export GCP_PROJECT= 172 | docker build -t gcr.io/$GCP_PROJECT/grpc-calculator:latest 173 | ``` 174 | 175 | The tag above will change based on your GCP project name. We're calling the 176 | service `grpc-calculator` and using the `latest` tag. 177 | 178 | Now, before we deploy to Cloud Run, let's make sure that we've containerized our 179 | application properly. We'll test it by spinning up a local container. 180 | 181 | ```bash 182 | docker run -d -p 50051:50051 -e PORT=50051 gcr.io/$GCP_PROJECT/grpc-calculator:latest 183 | ``` 184 | 185 | If all goes well, `grpcurl` will give us the same result as before: 186 | 187 | ```bash 188 | grpcurl \ 189 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 190 | --plaintext \ 191 | -proto calculator.proto \ 192 | localhost:50051 \ 193 | Calculator.Calculate 194 | ``` 195 | 196 | ## Deploying to Cloud Run 197 | 198 | Cloud Run needs to pull our application from a container registry, so the first 199 | step is to push the image we just built. 200 | 201 | Make sure that [you can use `gcloud`](https://cloud.google.com/sdk/gcloud/reference/auth/login) 202 | and are [able to push to `gcr.io`.](https://cloud.google.com/container-registry/docs/pushing-and-pulling) 203 | 204 | ```bash 205 | gcloud auth login 206 | gcloud auth configure-docker 207 | ``` 208 | 209 | Now we can push our image. 210 | 211 | ```bash 212 | docker push gcr.io/$GCP_PROJECT/grpc-calculator:latest 213 | ``` 214 | 215 | Finally, we deploy our application to Cloud Run: 216 | 217 | ```bash 218 | gcloud run deploy --image gcr.io/$GCP_PROJECT/grpc-calculator:latest --platform managed 219 | ``` 220 | 221 | You may be prompted for auth. If so, choose the unauthenticated option. 222 | 223 | This command will give you a message like 224 | ``` 225 | Service [grpc-calculator] revision [grpc-calculator-00001-baw] has been deployed and is serving 100 percent of traffic at https://grpc-calculator-xyspwhk3xq-uc.a.run.app 226 | ``` 227 | 228 | We can programmatically determine the gRPC service's endpoint: 229 | 230 | ```bash 231 | ENDPOINT=$(\ 232 | gcloud run services list \ 233 | --project=${GCP_PROJECT} \ 234 | --region=${GCP_REGION} \ 235 | --platform=managed \ 236 | --format="value(status.address.url)" \ 237 | --filter="metadata.name=grpc-calculator") 238 | ENDPOINT=${ENDPOINT#https://} && echo ${ENDPOINT} 239 | ``` 240 | 241 | Notice that this endpoint is secured with TLS even though the server we wrote uses a plaintext connection. Cloud Run provides a proxy that provides TLS for us. 242 | 243 | We'll account for this in our `grpcurl` invocation by omitting the `-plaintext` flag: 244 | 245 | ```bash 246 | grpcurl \ 247 | -proto protos/calculator.proto \ 248 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 249 | ${ENDPOINT}:443 \ 250 | Calculator.Calculate 251 | ``` 252 | 253 | And now you've got an auto-scaling calculator gRPC service! 254 | -------------------------------------------------------------------------------- /golang/README.md: -------------------------------------------------------------------------------- 1 | # gRPC in Google Cloud Run 2 | 3 | *Estimated Reading Time: 20 minutes* 4 | 5 | Google Cloud Run makes it easy to deploy and run REST servers, but it also 6 | supports gRPC servers out of the box. This article will show you how to 7 | deploy a gRPC service written in Golang to Cloud Run. For the full code, [check 8 | out the Github repo.](https://github.com/grpc-ecosystem/grpc-cloud-run-example) 9 | 10 | We'll be writing a simple remote calculator service. For the moment, it will 11 | just support adding and subtracting floating point numbers, but once this is up 12 | and running, you could easily extend it to add other features. 13 | 14 | ## The Protocol Buffer Definition 15 | 16 | Take a look in [`calculator.proto`](protos/calculator.proto) to see the full protocol buffer definition. If 17 | you're not familiar with protocol buffers, 18 | [take a moment to get acquainted.](https://developers.google.com/protocol-buffers) 19 | 20 | ```protobuf 21 | enum Operation { 22 | ADD = 0; 23 | SUBTRACT = 1; 24 | } 25 | 26 | message BinaryOperation { 27 | float first_operand = 1; 28 | float second_operand = 2; 29 | Operation operation = 3; 30 | }; 31 | 32 | message CalculationResult { 33 | float result = 1; 34 | }; 35 | 36 | service Calculator { 37 | rpc Calculate (BinaryOperation) returns (CalculationResult); 38 | }; 39 | ``` 40 | 41 | Our service will be a simple unary RPC. We'll take two floats and one of two 42 | operations. Then, we'll return the result of that operation. 43 | 44 | ## The Server 45 | 46 | Let's start with the server. Take a look at [`main.go`](main.go) and [`server.go`](server.go). 47 | Google Cloud Run will set up an environment variable called `PORT` on which your 48 | server should listen. The first thing we do is pull that from the environment: 49 | 50 | ```golang 51 | port := os.Getenv("PORT") 52 | ``` 53 | 54 | Next, we set up a server bound to that port, listening on all interfaces. 55 | 56 | ```golang 57 | grpcServer := grpc.NewServer() 58 | pb.RegisterCalculatorServer(grpcServer, NewServer()) 59 | 60 | ... 61 | 62 | listen, err := net.Listen("tcp", grpcEndpoint) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | log.Printf("Starting: gRPC Listener [%s]\n", grpcEndpoint) 67 | log.Fatal(grpcServer.Serve(listen)) 68 | ``` 69 | 70 | Notice that we're not using TLS. Google Cloud Run's proxy 71 | provides us with a TLS-encrypted proxy that handles the messy business of 72 | setting up certs for us. The traffic from the proxy to the container with our 73 | gRPC server in it goes through an encrypted tunnel, so we don't need to worry 74 | about handling it ourselves. Cloud Run natively handles HTTP/2, so gRPC's 75 | transport is well-supported. 76 | 77 | ## Connecting 78 | 79 | Now let's test the server out locally. First, we install dependencies. 80 | 81 | ```bash 82 | # The current version is 3.11.4 83 | VERS="3.11.4" 84 | # This value is for Linux x84-64 85 | ARCH="linux-x86_64" 86 | wget https://github.com/protocolbuffers/protobuf/releases/download/v${VERS}/protoc-${VERS}-${ARCH}.zip \ 87 | --output-document=./protoc-${VERS}-${ARCH}.zip 88 | 89 | unzip -o protoc-${VERS}-${ARCH}.zip -d protoc-${VERS}-${ARCH} 90 | 91 | # This compiles the Golang plugin to ${GOPATH}/bin 92 | go get -u github.com/golang/protobuf/protoc-gen-go 93 | ``` 94 | 95 | Now we generate Golang code from the `calculator.proto` file. This is how 96 | we get the definitions for our `calculator.pb.go`. 97 | 98 | ```bash 99 | protoc \ 100 | --proto_path=. \ 101 | --go_out=plugins=grpc:. \ 102 | ./protos/calculator.proto 103 | ``` 104 | 105 | Finally, we start the server: 106 | 107 | ```bash 108 | export PORT=50051 109 | go run github.com/grpc-ecosystem/grpc-cloud-run-example/golang/server 110 | ``` 111 | 112 | Now the server should be listening on port `50051`. We'll use the tool 113 | [`grpcurl`](https://github.com/fullstorydev/grpcurl) to manually interact with it. 114 | On Linux and Mac you can install it with `curl -s https://grpc.io/get_grpcurl | bash`. 115 | 116 | ```bash 117 | grpcurl \ 118 | -plaintext \ 119 | -proto protos/calculator.proto \ 120 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 121 | localhost:50051 \ 122 | Calculator.Calculate 123 | ``` 124 | 125 | We tell `grpcurl` where to find the protocol buffer definitions and server. 126 | Then, we supply the request. `grpcurl` gives us a nice mapping from JSON to 127 | protobuf. We can even supply the operation enumeration as a string. Finally, we 128 | invoke the `Calculate` method on the `Calculator` service. If all goes well, you 129 | should see: 130 | 131 | ```bash 132 | { 133 | "result": 5 134 | } 135 | ``` 136 | 137 | Great! We've got a working calculator server. Next, let's put it inside a 138 | Docker container. 139 | 140 | ## Containerizing the Server 141 | 142 | We're going to use the official Dockerhub Golang [1.13.9-buster](https://hub.docker.com/layers/golang/library/golang/1.13.9-buster/images/sha256-205d5cf61216a16da4431dd5796793c650236159fa04e055459940ddc4c6389c?context=explore) image as our base image. 143 | 144 | ```Dockerfile 145 | FROM golang@sha256:205d5cf61216a16da4431dd5796793c650236159fa04e055459940ddc4c6389c 146 | ``` 147 | 148 | We'll put all of our code in `/srv/grpc/`. 149 | 150 | ```Dockerfile 151 | WORKDIR /srv/grpc 152 | 153 | COPY go.mod . 154 | COPY protos/calculator.proto ./protos/ 155 | COPY server/*.go ./server/ 156 | ``` 157 | 158 | We install protoc and protoc-gen-go and generate the Golang sources. 159 | 160 | 161 | ```Dockerfile 162 | ARG VERS="3.11.4" 163 | ARG ARCH="linux-x86_64" 164 | RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${VERS}/protoc-${VERS}-${ARCH}.zip \ 165 | --output-document=./protoc-${VERS}-${ARCH}.zip && \ 166 | apt update && apt install -y unzip && \ 167 | unzip -o protoc-${VERS}-${ARCH}.zip -d protoc-${VERS}-${ARCH} && \ 168 | mv protoc-${VERS}-${ARCH}/bin/* /usr/local/bin && \ 169 | mv protoc-${VERS}-${ARCH}/include/* /usr/local/include && \ 170 | go get -u github.com/golang/protobuf/protoc-gen-go 171 | 172 | # Generates the Golang protobuf files 173 | RUN protoc \ 174 | --proto_path=. \ 175 | --go_out=plugins=grpc:. \ 176 | ./protos/*.proto 177 | ``` 178 | 179 | We compile to a static binary: 180 | 181 | ```Dockerfile 182 | RUN CGO_ENABLED=0 GOOS=linux \ 183 | go build -a -installsuffix cgo \ 184 | -o /go/bin/server \ 185 | github.com/grpc-ecosystem/grpc-cloud-run-example/golang/server 186 | ``` 187 | 188 | Then we move the binary into a runtime container: 189 | 190 | ```Dockerfile 191 | FROM scratch 192 | 193 | COPY --from=build /go/bin/server /server 194 | ``` 195 | 196 | And set the container to run the server by default. 197 | 198 | ```Dockerfile 199 | ENTRYPOINT ["/server"] 200 | ``` 201 | 202 | Now we can build our image. In order to deploy to Cloud Run, we'll be pushing to 203 | the `gcr.io` container registry, so we'll tag it accordingly. 204 | 205 | ```bash 206 | GCP_PROJECT= 207 | docker build \ 208 | --tag=gcr.io/${GCP_PROJECT}/grpc-calculator:latest \ 209 | --file=./Dockerfile \ 210 | . 211 | ``` 212 | 213 | > **NB** Don't forget that final `.`, it's critical 214 | 215 | The tag above will change based on your GCP project name. We're calling the 216 | service `grpc-calculator` and using the `latest` tag. 217 | 218 | Now, before we deploy to Cloud Run, let's make sure that we've containerized our 219 | application properly. We'll test it by spinning up a local container. 220 | 221 | ```bash 222 | PORT="50051" # Cloud Run will use `8080` 223 | docker run \ 224 | --interactive --tty \ 225 | --publish=50051:${PORT} \ 226 | --env=PORT=${PORT} \ 227 | gcr.io/${GCP_PROJECT}/grpc-calculator:latest 228 | ``` 229 | 230 | If all goes well, `grpcurl` will give us the same result as before: 231 | 232 | ```bash 233 | grpcurl \ 234 | --plaintext \ 235 | -proto calculator.proto \ 236 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 237 | localhost:50051 \ 238 | Calculator.Calculate 239 | ``` 240 | 241 | ## Deploying to Cloud Run 242 | 243 | Cloud Run needs to pull our application from a container registry, so the first 244 | step is to push the image we just built. 245 | 246 | Make sure that [you can use `gcloud`](https://cloud.google.com/sdk/gcloud/reference/auth/login) 247 | and are [able to push to `gcr.io`.](https://cloud.google.com/container-registry/docs/pushing-and-pulling) 248 | 249 | ```bash 250 | gcloud auth login 251 | gcloud auth configure-docker 252 | ``` 253 | 254 | Now we can push our image. 255 | 256 | ```bash 257 | docker push gcr.io/$GCP_PROJECT/grpc-calculator:latest 258 | ``` 259 | 260 | Finally, we deploy our application to Cloud Run: 261 | 262 | ```bash 263 | GCP_REGION="us-west1" # Or ... 264 | gcloud run deploy grpc-calculator \ 265 | --image=gcr.io/$GCP_PROJECT/grpc-calculator:latest \ 266 | --platform=managed \ 267 | --allow-unauthenticated \ 268 | --project=${GCP_PROJECT} \ 269 | --region=${GCP_REGION} 270 | ``` 271 | 272 | This command will give you a message like 273 | ``` 274 | Service [grpc-calculator] revision [grpc-calculator-00001-baw] has been deployed and is serving 100 percent of traffic at https://grpc-calculator-xyspwhk3xq-uc.a.run.app 275 | ``` 276 | 277 | We can programmatically determine the gRPC service's endpoint: 278 | 279 | ```bash 280 | ENDPOINT=$(\ 281 | gcloud run services list \ 282 | --project=${GCP_PROJECT} \ 283 | --region=${GCP_REGION} \ 284 | --platform=managed \ 285 | --format="value(status.address.url)" \ 286 | --filter="metadata.name=grpc-calculator") 287 | ENDPOINT=${ENDPOINT#https://} && echo ${ENDPOINT} 288 | ``` 289 | 290 | Notice that this endpoint is secured with TLS even though the server we wrote 291 | uses a plaintext connection. Cloud Run provides a proxy that provides TLS for us. 292 | 293 | We'll account for that in our `grpcurl` invocation by omitting the `-plaintext` flag: 294 | 295 | ```bash 296 | grpcurl \ 297 | -proto protos/calculator.proto \ 298 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 299 | ${ENDPOINT}:443 \ 300 | Calculator.Calculate 301 | ``` 302 | 303 | There's a simple Golang client too: 304 | 305 | ```bash 306 | go run github.com/grpc-ecosystem/grpc-cloud-run-example/golang/client \ 307 | --gprc_endpoint=${ENDPOINT}:443 308 | ``` 309 | 310 | You have an auto-scaling gRPC-based calculator service! 311 | -------------------------------------------------------------------------------- /rust/README.md: -------------------------------------------------------------------------------- 1 | # gRPC in Google Cloud Run 2 | 3 | *Estimated Reading Time: 20 minutes* 4 | 5 | Google Cloud Run makes it easy to deploy and run REST servers, but it also 6 | supports gRPC servers out of the box. This article will show you how to 7 | deploy a gRPC service written in Rust to Cloud Run. For the full code, [check 8 | out the Github repo.](https://github.com/grpc-ecosystem/grpc-cloud-run-example) 9 | 10 | We'll be writing a simple remote calculator service. For the moment, it will 11 | just support adding and subtracting floating point numbers, but once this is up 12 | and running, you could easily extend it to add other features. 13 | 14 | ## The Protocol Buffer Definition 15 | 16 | Take a look in [`calculator.proto`](calculator.proto) to see the full protocol buffer definition. If 17 | you're not familiar with protocol buffers, 18 | [take a moment to get acquainted.](https://developers.google.com/protocol-buffers) 19 | 20 | ```protobuf 21 | enum Operation { 22 | ADD = 0; 23 | SUBTRACT = 1; 24 | } 25 | 26 | message BinaryOperation { 27 | float first_operand = 1; 28 | float second_operand = 2; 29 | Operation operation = 3; 30 | }; 31 | 32 | message CalculationResult { 33 | float result = 1; 34 | }; 35 | 36 | service Calculator { 37 | rpc Calculate (BinaryOperation) returns (CalculationResult); 38 | }; 39 | ``` 40 | 41 | Our service will be a simple unary RPC. We'll take two floats and one of two 42 | operations. Then, we'll return the result of that operation. 43 | 44 | ## The Server 45 | 46 | Let's start with the server. 47 | 48 | Here's the `Cargo.toml` file: 49 | 50 | ```TOML 51 | [package] 52 | name = "grpc-cloud-run-example-rust" 53 | version = "0.0.1" 54 | edition = "2018" 55 | 56 | [[bin]] 57 | name = "server" 58 | path = "src/main.rs" 59 | 60 | [build-dependencies] 61 | protoc-rust-grpc = "0.6.1" 62 | 63 | [dependencies] 64 | blake2 = "0.8.1" 65 | futures = "0.3.4" 66 | futures-cpupool = "0.1.8" 67 | grpc = "0.6.2" 68 | hex = "0.4.2" 69 | protobuf = "2.8.2" 70 | ``` 71 | 72 | Two things to note about this: 73 | 74 | 1. `cargo` will build a binary called `server` 75 | 2. The build dependency (`protoc-rust-grpc`) compiles the protobuf automatically; the build dependency is itself dependent on 'protoc' being available in the path 76 | 77 | Take a look at [`main.rs`](main.rs). 78 | 79 | We define a struct type `CalculatorImpl` and implement the `Calculator` trait 80 | that's required by the code generated from the protobuf file. This requires a 81 | single function, `calculator`: 82 | 83 | ```rust 84 | pub struct CalculatorImpl; 85 | impl Calculator for CalculatorImpl { 86 | fn calculate( 87 | &self, 88 | _: RequestOptions, 89 | rqst: BinaryOperation, 90 | ) -> SingleResponse { 91 | let op1: f32 = rqst.get_first_operand(); 92 | let op2: f32 = rqst.get_second_operand(); 93 | let result: f32 = match rqst.get_operation() { 94 | Operation::ADD => op1 + op2, 95 | Operation::SUBTRACT => op1 - op2, 96 | }; 97 | let resp = CalculationResult { 98 | result: result, 99 | ..Default::default() 100 | }; 101 | return SingleResponse::completed(resp); 102 | } 103 | } 104 | ``` 105 | 106 | The `main` function is straightforward. 107 | Google Cloud Run will set up an environment variable called `PORT` on which your 108 | server should listen. The first thing we do is pull that from the environment: 109 | 110 | ```rust 111 | let key = "PORT"; 112 | let port = match env::var_os(key) { 113 | Some(val) => match val.to_str() { 114 | Some(s) => match s.parse::() { 115 | Ok(p) => p, 116 | Err(e) => return Err(e), 117 | }, 118 | None => 50051, 119 | }, 120 | None => 50051, 121 | }; 122 | ``` 123 | > **NB** This code is longer than the gRPC server! 124 | 125 | Next, we set up a server bound to that port, listening on all interfaces. 126 | 127 | ```rust 128 | let mut server = ServerBuilder::new_plain(); 129 | server.http.set_port(port); 130 | server.add_service(CalculatorServer::new_service_def(CalculatorImpl)); 131 | let _server = server.build().expect("server"); 132 | 133 | println!("Starting: gRPC Listener [{}]", port); 134 | 135 | loop { 136 | thread::park(); 137 | }``` 138 | 139 | Notice that we're not using TLS. Google Cloud Run's proxy 140 | provides us with a TLS-encrypted proxy that handles the messy business of 141 | setting up certs for us. The traffic from the proxy to the container with our 142 | gRPC server in it goes through an encrypted tunnel, so we don't need to worry 143 | about handling it ourselves. Cloud Run natively handles HTTP/2, so gRPC's 144 | transport is well-supported. 145 | 146 | ## Connecting 147 | 148 | Now let's test the server out locally. First, we install dependencies. 149 | 150 | ```bash 151 | # The current version is 3.11.4 152 | VERS="3.11.4" 153 | # This value is for Linux x84-64 154 | ARCH="linux-x86_64" 155 | wget https://github.com/protocolbuffers/protobuf/releases/download/v${VERS}/protoc-${VERS}-${ARCH}.zip \ 156 | --output-document=./protoc-${VERS}-${ARCH}.zip 157 | 158 | unzip -o protoc-${VERS}-${ARCH}.zip -d protoc-${VERS}-${ARCH} 159 | ``` 160 | 161 | Add `protoc` to the path: 162 | 163 | ```bash 164 | PATH=${PATH}:${PWD}/protoc-${VERS}-${ARCH}/bin 165 | ``` 166 | 167 | The project includes a `build.rs` that generates the rust code from the `calculator.proto` file. 168 | This is how we get the definitions for our `calculator.rs` and `calculator_grpc.rs`. 169 | 170 | ```rust 171 | fn main() { 172 | protoc_rust_grpc::run(protoc_rust_grpc::Args { 173 | out_dir: "src/protos", 174 | includes: &["./"], 175 | input: &["protos/calculator.proto"], 176 | rust_protobuf: true, // also generate protobuf messages, not just services 177 | ..Default::default() 178 | }) 179 | .expect("protoc-rust-grpc"); 180 | } 181 | ``` 182 | 183 | Finally, we start the server: 184 | 185 | ```bash 186 | export PORT=50051 187 | cargo run 188 | ``` 189 | 190 | Now the server should be listening on port `50051`. We'll use the tool 191 | [`grpcurl`](https://github.com/fullstorydev/grpcurl) to manually interact with it. 192 | On Linux and Mac you can install it with `curl -s https://grpc.io/get_grpcurl | bash`. 193 | 194 | ```bash 195 | grpcurl \ 196 | -plaintext \ 197 | -proto protos/calculator.proto \ 198 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 199 | localhost:50051 \ 200 | Calculator.Calculate 201 | ``` 202 | 203 | We tell `grpcurl` where to find the protocol buffer definitions and server. 204 | Then, we supply the request. `grpcurl` gives us a nice mapping from JSON to 205 | protobuf. We can even supply the operation enumeration as a string. Finally, we 206 | invoke the `Calculate` method on the `Calculator` service. If all goes well, you 207 | should see: 208 | 209 | ```bash 210 | { 211 | "result": 5 212 | } 213 | ``` 214 | 215 | Great! We've got a working calculator server. Next, let's put it inside a 216 | Docker container. 217 | 218 | ## Containerizing the Server 219 | 220 | We're going to use the official Dockerhub rust [slim-buster](https://hub.docker.com/layers/rust/library/rust/slim-buster/images/sha256-de00dbf06ed1a9426bd044f619e6f782e78b83bcfefb1570cfd342f84d6f424a?context=explore) image as our base image. 221 | 222 | 223 | ```Dockerfile 224 | FROM rust@sha256:de00dbf06ed1a9426bd044f619e6f782e78b83bcfefb1570cfd342f84d6f424a AS builder 225 | 226 | ARG VERS="3.11.4" 227 | ARG ARCH="linux-x86_64" 228 | 229 | RUN apt update && apt -y install wget && \ 230 | wget https://github.com/protocolbuffers/protobuf/releases/download/v${VERS}/protoc-${VERS}-${ARCH}.zip \ 231 | --output-document=/protoc-${VERS}-${ARCH}.zip && \ 232 | apt update && apt install -y unzip && \ 233 | unzip -o protoc-${VERS}-${ARCH}.zip -d /protoc-${VERS}-${ARCH} 234 | ENV PATH="${PATH}:/protoc/bin" 235 | 236 | WORKDIR /srv/grpc 237 | 238 | RUN rustup target add x86_64-unknown-linux-musl 239 | 240 | COPY . . 241 | 242 | RUN cargo install --target x86_64-unknown-linux-musl --path . 243 | ``` 244 | 245 | > **NB** Thanks to [alexbrand](https://alexbrand.dev/post/how-to-package-rust-applications-into-minimal-docker-containers/) for guidance building static binaries in Rust 246 | 247 | Finally, we move the binary into a runtime container: 248 | 249 | ```Dockerfile 250 | FROM scratch AS runtime 251 | 252 | COPY --from=builder /usr/local/cargo/bin/server . 253 | ``` 254 | 255 | And set the container to run the server by default. 256 | 257 | ```Dockerfile 258 | ENTRYPOINT ["./server"] 259 | ``` 260 | 261 | Now we can build our image. In order to deploy to Cloud Run, we'll be pushing to 262 | the `gcr.io` container registry, so we'll tag it accordingly. 263 | 264 | ```bash 265 | GCP_PROJECT= 266 | 267 | cargo clean # Remove ./target 268 | docker build \ 269 | --tag=gcr.io/${GCP_PROJECT}/grpc-calculator:latest \ 270 | --file=./Dockerfile \ 271 | . 272 | ``` 273 | 274 | > **NB** Don't forget that final `.`, it's critical. 275 | 276 | The tag above will change based on your GCP project name. We're calling the 277 | service `grpc-calculator` and using the `latest` tag. 278 | 279 | Now, before we deploy to Cloud Run, let's make sure that we've containerized our 280 | application properly. We'll test it by spinning up a local container. 281 | 282 | ```bash 283 | PORT="50051" # Cloud Run will use `8080` 284 | docker run \ 285 | --interactive --tty \ 286 | --publish=50051:${PORT} \ 287 | --env=PORT=${PORT} \ 288 | gcr.io/${GCP_PROJECT}/grpc-calculator:latest 289 | ``` 290 | 291 | If all goes well, `grpcurl` will give us the same result as before: 292 | 293 | ```bash 294 | grpcurl \ 295 | --plaintext \ 296 | -proto calculator.proto \ 297 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 298 | localhost:50051 \ 299 | Calculator.Calculate 300 | ``` 301 | 302 | ## Deploying to Cloud Run 303 | 304 | Cloud Run needs to pull our application from a container registry, so the first 305 | step is to push the image we just built. 306 | 307 | Make sure that [you can use `gcloud`](https://cloud.google.com/sdk/gcloud/reference/auth/login) 308 | and are [able to push to `gcr.io`.](https://cloud.google.com/container-registry/docs/pushing-and-pulling) 309 | 310 | ```bash 311 | gcloud auth login 312 | gcloud auth configure-docker 313 | ``` 314 | 315 | Now we can push our image. 316 | 317 | ```bash 318 | docker push gcr.io/$GCP_PROJECT/grpc-calculator:latest 319 | ``` 320 | 321 | Finally, we deploy our application to Cloud Run: 322 | 323 | ```bash 324 | GCP_REGION="us-west1" # Or ... 325 | gcloud run deploy grpc-calculator \ 326 | --image=gcr.io/$GCP_PROJECT/grpc-calculator:latest \ 327 | --platform=managed \ 328 | --allow-unauthenticated \ 329 | --project=${GCP_PROJECT} \ 330 | --region=${GCP_REGION} 331 | ``` 332 | 333 | This command will give you a message like 334 | ``` 335 | Service [grpc-calculator] revision [grpc-calculator-00001-baw] has been deployed and is serving 100 percent of traffic at https://grpc-calculator-xyspwhk3xq-uc.a.run.app 336 | ``` 337 | 338 | We can programmatically determine the gRPC service's endpoint: 339 | 340 | ```bash 341 | ENDPOINT=$(\ 342 | gcloud run services list \ 343 | --project=${GCP_PROJECT} \ 344 | --region=${GCP_REGION} \ 345 | --platform=managed \ 346 | --format="value(status.address.url)" \ 347 | --filter="metadata.name=grpc-calculator") 348 | ENDPOINT=${ENDPOINT#https://} && echo ${ENDPOINT} 349 | ``` 350 | 351 | Notice that this endpoint is secured with TLS even though the server we wrote 352 | uses a plaintext connection. Cloud Run provides a proxy that provides TLS for us. 353 | 354 | We'll account for that in our `grpcurl` invocation by omitting the `-plaintext` flag: 355 | 356 | ```bash 357 | grpcurl \ 358 | -proto protos/calculator.proto \ 359 | -d '{"first_operand": 2.0, "second_operand": 3.0, "operation": "ADD"}' \ 360 | ${ENDPOINT}:443 \ 361 | Calculator.Calculate 362 | ``` 363 | 364 | You have an auto-scaling gRPC-based calculator service! 365 | -------------------------------------------------------------------------------- /rust/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "autocfg" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 8 | 9 | [[package]] 10 | name = "base64" 11 | version = "0.9.3" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" 14 | dependencies = [ 15 | "byteorder", 16 | "safemem", 17 | ] 18 | 19 | [[package]] 20 | name = "bitflags" 21 | version = "1.2.1" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 24 | 25 | [[package]] 26 | name = "byteorder" 27 | version = "1.3.4" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 30 | 31 | [[package]] 32 | name = "bytes" 33 | version = "0.4.12" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 36 | dependencies = [ 37 | "byteorder", 38 | "iovec", 39 | ] 40 | 41 | [[package]] 42 | name = "cfg-if" 43 | version = "0.1.10" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 46 | 47 | [[package]] 48 | name = "cloudabi" 49 | version = "0.0.3" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 52 | dependencies = [ 53 | "bitflags", 54 | ] 55 | 56 | [[package]] 57 | name = "crossbeam-deque" 58 | version = "0.7.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 61 | dependencies = [ 62 | "crossbeam-epoch", 63 | "crossbeam-utils", 64 | "maybe-uninit", 65 | ] 66 | 67 | [[package]] 68 | name = "crossbeam-epoch" 69 | version = "0.8.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 72 | dependencies = [ 73 | "autocfg", 74 | "cfg-if", 75 | "crossbeam-utils", 76 | "lazy_static", 77 | "maybe-uninit", 78 | "memoffset", 79 | "scopeguard", 80 | ] 81 | 82 | [[package]] 83 | name = "crossbeam-queue" 84 | version = "0.2.1" 85 | source = "registry+https://github.com/rust-lang/crates.io-index" 86 | checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" 87 | dependencies = [ 88 | "cfg-if", 89 | "crossbeam-utils", 90 | ] 91 | 92 | [[package]] 93 | name = "crossbeam-utils" 94 | version = "0.7.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 97 | dependencies = [ 98 | "autocfg", 99 | "cfg-if", 100 | "lazy_static", 101 | ] 102 | 103 | [[package]] 104 | name = "fnv" 105 | version = "1.0.6" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 108 | 109 | [[package]] 110 | name = "fuchsia-cprng" 111 | version = "0.1.1" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 114 | 115 | [[package]] 116 | name = "fuchsia-zircon" 117 | version = "0.3.3" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 120 | dependencies = [ 121 | "bitflags", 122 | "fuchsia-zircon-sys", 123 | ] 124 | 125 | [[package]] 126 | name = "fuchsia-zircon-sys" 127 | version = "0.3.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 130 | 131 | [[package]] 132 | name = "futures" 133 | version = "0.1.29" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" 136 | 137 | [[package]] 138 | name = "futures" 139 | version = "0.3.4" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" 142 | dependencies = [ 143 | "futures-channel", 144 | "futures-core", 145 | "futures-executor", 146 | "futures-io", 147 | "futures-sink", 148 | "futures-task", 149 | "futures-util", 150 | ] 151 | 152 | [[package]] 153 | name = "futures-channel" 154 | version = "0.3.4" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" 157 | dependencies = [ 158 | "futures-core", 159 | "futures-sink", 160 | ] 161 | 162 | [[package]] 163 | name = "futures-core" 164 | version = "0.3.4" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" 167 | 168 | [[package]] 169 | name = "futures-cpupool" 170 | version = "0.1.8" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" 173 | dependencies = [ 174 | "futures 0.1.29", 175 | "num_cpus", 176 | ] 177 | 178 | [[package]] 179 | name = "futures-executor" 180 | version = "0.3.4" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" 183 | dependencies = [ 184 | "futures-core", 185 | "futures-task", 186 | "futures-util", 187 | ] 188 | 189 | [[package]] 190 | name = "futures-io" 191 | version = "0.3.4" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" 194 | 195 | [[package]] 196 | name = "futures-macro" 197 | version = "0.3.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" 200 | dependencies = [ 201 | "proc-macro-hack", 202 | "proc-macro2", 203 | "quote", 204 | "syn", 205 | ] 206 | 207 | [[package]] 208 | name = "futures-sink" 209 | version = "0.3.4" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" 212 | 213 | [[package]] 214 | name = "futures-task" 215 | version = "0.3.4" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" 218 | 219 | [[package]] 220 | name = "futures-util" 221 | version = "0.3.4" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" 224 | dependencies = [ 225 | "futures-channel", 226 | "futures-core", 227 | "futures-io", 228 | "futures-macro", 229 | "futures-sink", 230 | "futures-task", 231 | "memchr", 232 | "pin-utils", 233 | "proc-macro-hack", 234 | "proc-macro-nested", 235 | "slab 0.4.2", 236 | ] 237 | 238 | [[package]] 239 | name = "getrandom" 240 | version = "0.1.14" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 243 | dependencies = [ 244 | "cfg-if", 245 | "libc", 246 | "wasi", 247 | ] 248 | 249 | [[package]] 250 | name = "grpc" 251 | version = "0.6.2" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "2aaf1d741fe6f3413f1f9f71b99f5e4e26776d563475a8a53ce53a73a8534c1d" 254 | dependencies = [ 255 | "base64", 256 | "bytes", 257 | "futures 0.1.29", 258 | "futures-cpupool", 259 | "httpbis", 260 | "log 0.4.8", 261 | "protobuf", 262 | "tls-api", 263 | "tls-api-stub", 264 | "tokio-core", 265 | "tokio-io", 266 | "tokio-tls-api", 267 | ] 268 | 269 | [[package]] 270 | name = "grpc-cloud-run-example-rust" 271 | version = "0.0.1" 272 | dependencies = [ 273 | "futures 0.3.4", 274 | "futures-cpupool", 275 | "grpc", 276 | "protobuf", 277 | "protoc-rust-grpc", 278 | ] 279 | 280 | [[package]] 281 | name = "grpc-compiler" 282 | version = "0.6.2" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "907274ce8ee7b40a0d0b0db09022ea22846a47cfb1fc8ad2c983c70001b4ffb1" 285 | dependencies = [ 286 | "protobuf", 287 | "protobuf-codegen", 288 | ] 289 | 290 | [[package]] 291 | name = "hermit-abi" 292 | version = "0.1.8" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" 295 | dependencies = [ 296 | "libc", 297 | ] 298 | 299 | [[package]] 300 | name = "httpbis" 301 | version = "0.7.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "7689cfa896b2a71da4f16206af167542b75d242b6906313e53857972a92d5614" 304 | dependencies = [ 305 | "bytes", 306 | "futures 0.1.29", 307 | "futures-cpupool", 308 | "log 0.4.8", 309 | "net2", 310 | "tls-api", 311 | "tls-api-stub", 312 | "tokio-core", 313 | "tokio-io", 314 | "tokio-timer 0.1.2", 315 | "tokio-tls-api", 316 | "tokio-uds 0.1.7", 317 | "unix_socket", 318 | "void", 319 | ] 320 | 321 | [[package]] 322 | name = "iovec" 323 | version = "0.1.4" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 326 | dependencies = [ 327 | "libc", 328 | ] 329 | 330 | [[package]] 331 | name = "kernel32-sys" 332 | version = "0.2.2" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 335 | dependencies = [ 336 | "winapi 0.2.8", 337 | "winapi-build", 338 | ] 339 | 340 | [[package]] 341 | name = "lazy_static" 342 | version = "1.4.0" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 345 | 346 | [[package]] 347 | name = "libc" 348 | version = "0.2.68" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" 351 | 352 | [[package]] 353 | name = "lock_api" 354 | version = "0.3.3" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b" 357 | dependencies = [ 358 | "scopeguard", 359 | ] 360 | 361 | [[package]] 362 | name = "log" 363 | version = "0.3.9" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 366 | dependencies = [ 367 | "log 0.4.8", 368 | ] 369 | 370 | [[package]] 371 | name = "log" 372 | version = "0.4.8" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 375 | dependencies = [ 376 | "cfg-if", 377 | ] 378 | 379 | [[package]] 380 | name = "maybe-uninit" 381 | version = "2.0.0" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 384 | 385 | [[package]] 386 | name = "memchr" 387 | version = "2.3.3" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 390 | 391 | [[package]] 392 | name = "memoffset" 393 | version = "0.5.4" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" 396 | dependencies = [ 397 | "autocfg", 398 | ] 399 | 400 | [[package]] 401 | name = "mio" 402 | version = "0.6.21" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" 405 | dependencies = [ 406 | "cfg-if", 407 | "fuchsia-zircon", 408 | "fuchsia-zircon-sys", 409 | "iovec", 410 | "kernel32-sys", 411 | "libc", 412 | "log 0.4.8", 413 | "miow", 414 | "net2", 415 | "slab 0.4.2", 416 | "winapi 0.2.8", 417 | ] 418 | 419 | [[package]] 420 | name = "mio-uds" 421 | version = "0.6.7" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" 424 | dependencies = [ 425 | "iovec", 426 | "libc", 427 | "mio", 428 | ] 429 | 430 | [[package]] 431 | name = "miow" 432 | version = "0.2.1" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 435 | dependencies = [ 436 | "kernel32-sys", 437 | "net2", 438 | "winapi 0.2.8", 439 | "ws2_32-sys", 440 | ] 441 | 442 | [[package]] 443 | name = "net2" 444 | version = "0.2.33" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 447 | dependencies = [ 448 | "cfg-if", 449 | "libc", 450 | "winapi 0.3.8", 451 | ] 452 | 453 | [[package]] 454 | name = "num_cpus" 455 | version = "1.12.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" 458 | dependencies = [ 459 | "hermit-abi", 460 | "libc", 461 | ] 462 | 463 | [[package]] 464 | name = "parking_lot" 465 | version = "0.9.0" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" 468 | dependencies = [ 469 | "lock_api", 470 | "parking_lot_core", 471 | "rustc_version", 472 | ] 473 | 474 | [[package]] 475 | name = "parking_lot_core" 476 | version = "0.6.2" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" 479 | dependencies = [ 480 | "cfg-if", 481 | "cloudabi", 482 | "libc", 483 | "redox_syscall", 484 | "rustc_version", 485 | "smallvec", 486 | "winapi 0.3.8", 487 | ] 488 | 489 | [[package]] 490 | name = "pin-utils" 491 | version = "0.1.0-alpha.4" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" 494 | 495 | [[package]] 496 | name = "ppv-lite86" 497 | version = "0.2.6" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 500 | 501 | [[package]] 502 | name = "proc-macro-hack" 503 | version = "0.5.14" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "fcfdefadc3d57ca21cf17990a28ef4c0f7c61383a28cb7604cf4a18e6ede1420" 506 | 507 | [[package]] 508 | name = "proc-macro-nested" 509 | version = "0.1.4" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" 512 | 513 | [[package]] 514 | name = "proc-macro2" 515 | version = "1.0.9" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" 518 | dependencies = [ 519 | "unicode-xid", 520 | ] 521 | 522 | [[package]] 523 | name = "protobuf" 524 | version = "2.8.2" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "70731852eec72c56d11226c8a5f96ad5058a3dab73647ca5f7ee351e464f2571" 527 | dependencies = [ 528 | "bytes", 529 | ] 530 | 531 | [[package]] 532 | name = "protobuf-codegen" 533 | version = "2.8.2" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "3d74b9cbbf2ac9a7169c85a3714ec16c51ee9ec7cfd511549527e9a7df720795" 536 | dependencies = [ 537 | "protobuf", 538 | ] 539 | 540 | [[package]] 541 | name = "protoc" 542 | version = "2.8.2" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "50d9500ea1488a61aa96da139039b78a92eef64a0f3c82d38173729f0ad73cf8" 545 | dependencies = [ 546 | "log 0.4.8", 547 | ] 548 | 549 | [[package]] 550 | name = "protoc-rust" 551 | version = "2.8.2" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "bea851ddc77c57935a586099f6e1f8bd7b4d366379498f25b8882ed02e0222bf" 554 | dependencies = [ 555 | "protobuf", 556 | "protobuf-codegen", 557 | "protoc", 558 | "tempfile", 559 | ] 560 | 561 | [[package]] 562 | name = "protoc-rust-grpc" 563 | version = "0.6.2" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "6b959e379834057693e0e5a228bc3939aa8e4fee895da1531f69b6e7e74c80d6" 566 | dependencies = [ 567 | "grpc-compiler", 568 | "protobuf", 569 | "protoc", 570 | "protoc-rust", 571 | "tempdir", 572 | ] 573 | 574 | [[package]] 575 | name = "quote" 576 | version = "1.0.3" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" 579 | dependencies = [ 580 | "proc-macro2", 581 | ] 582 | 583 | [[package]] 584 | name = "rand" 585 | version = "0.4.6" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 588 | dependencies = [ 589 | "fuchsia-cprng", 590 | "libc", 591 | "rand_core 0.3.1", 592 | "rdrand", 593 | "winapi 0.3.8", 594 | ] 595 | 596 | [[package]] 597 | name = "rand" 598 | version = "0.7.3" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 601 | dependencies = [ 602 | "getrandom", 603 | "libc", 604 | "rand_chacha", 605 | "rand_core 0.5.1", 606 | "rand_hc", 607 | ] 608 | 609 | [[package]] 610 | name = "rand_chacha" 611 | version = "0.2.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 614 | dependencies = [ 615 | "ppv-lite86", 616 | "rand_core 0.5.1", 617 | ] 618 | 619 | [[package]] 620 | name = "rand_core" 621 | version = "0.3.1" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 624 | dependencies = [ 625 | "rand_core 0.4.2", 626 | ] 627 | 628 | [[package]] 629 | name = "rand_core" 630 | version = "0.4.2" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 633 | 634 | [[package]] 635 | name = "rand_core" 636 | version = "0.5.1" 637 | source = "registry+https://github.com/rust-lang/crates.io-index" 638 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 639 | dependencies = [ 640 | "getrandom", 641 | ] 642 | 643 | [[package]] 644 | name = "rand_hc" 645 | version = "0.2.0" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 648 | dependencies = [ 649 | "rand_core 0.5.1", 650 | ] 651 | 652 | [[package]] 653 | name = "rdrand" 654 | version = "0.4.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 657 | dependencies = [ 658 | "rand_core 0.3.1", 659 | ] 660 | 661 | [[package]] 662 | name = "redox_syscall" 663 | version = "0.1.56" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 666 | 667 | [[package]] 668 | name = "remove_dir_all" 669 | version = "0.5.2" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 672 | dependencies = [ 673 | "winapi 0.3.8", 674 | ] 675 | 676 | [[package]] 677 | name = "rustc_version" 678 | version = "0.2.3" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 681 | dependencies = [ 682 | "semver", 683 | ] 684 | 685 | [[package]] 686 | name = "safemem" 687 | version = "0.3.3" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" 690 | 691 | [[package]] 692 | name = "scoped-tls" 693 | version = "0.1.2" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" 696 | 697 | [[package]] 698 | name = "scopeguard" 699 | version = "1.1.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 702 | 703 | [[package]] 704 | name = "semver" 705 | version = "0.9.0" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 708 | dependencies = [ 709 | "semver-parser", 710 | ] 711 | 712 | [[package]] 713 | name = "semver-parser" 714 | version = "0.7.0" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 717 | 718 | [[package]] 719 | name = "slab" 720 | version = "0.3.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" 723 | 724 | [[package]] 725 | name = "slab" 726 | version = "0.4.2" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 729 | 730 | [[package]] 731 | name = "smallvec" 732 | version = "0.6.13" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" 735 | dependencies = [ 736 | "maybe-uninit", 737 | ] 738 | 739 | [[package]] 740 | name = "syn" 741 | version = "1.0.17" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" 744 | dependencies = [ 745 | "proc-macro2", 746 | "quote", 747 | "unicode-xid", 748 | ] 749 | 750 | [[package]] 751 | name = "tempdir" 752 | version = "0.3.7" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" 755 | dependencies = [ 756 | "rand 0.4.6", 757 | "remove_dir_all", 758 | ] 759 | 760 | [[package]] 761 | name = "tempfile" 762 | version = "3.1.0" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 765 | dependencies = [ 766 | "cfg-if", 767 | "libc", 768 | "rand 0.7.3", 769 | "redox_syscall", 770 | "remove_dir_all", 771 | "winapi 0.3.8", 772 | ] 773 | 774 | [[package]] 775 | name = "tls-api" 776 | version = "0.1.22" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "049c03787a0595182357fbd487577947f4351b78ce20c3668f6d49f17feb13d1" 779 | dependencies = [ 780 | "log 0.4.8", 781 | ] 782 | 783 | [[package]] 784 | name = "tls-api-stub" 785 | version = "0.1.22" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "c9a0cc8c149724db9de7d73a0e1bc80b1a74f5394f08c6f301e11f9c35fa061e" 788 | dependencies = [ 789 | "tls-api", 790 | "void", 791 | ] 792 | 793 | [[package]] 794 | name = "tokio" 795 | version = "0.1.22" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" 798 | dependencies = [ 799 | "bytes", 800 | "futures 0.1.29", 801 | "mio", 802 | "num_cpus", 803 | "tokio-codec", 804 | "tokio-current-thread", 805 | "tokio-executor", 806 | "tokio-fs", 807 | "tokio-io", 808 | "tokio-reactor", 809 | "tokio-sync", 810 | "tokio-tcp", 811 | "tokio-threadpool", 812 | "tokio-timer 0.2.13", 813 | "tokio-udp", 814 | "tokio-uds 0.2.6", 815 | ] 816 | 817 | [[package]] 818 | name = "tokio-codec" 819 | version = "0.1.2" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" 822 | dependencies = [ 823 | "bytes", 824 | "futures 0.1.29", 825 | "tokio-io", 826 | ] 827 | 828 | [[package]] 829 | name = "tokio-core" 830 | version = "0.1.17" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "aeeffbbb94209023feaef3c196a41cbcdafa06b4a6f893f68779bb5e53796f71" 833 | dependencies = [ 834 | "bytes", 835 | "futures 0.1.29", 836 | "iovec", 837 | "log 0.4.8", 838 | "mio", 839 | "scoped-tls", 840 | "tokio", 841 | "tokio-executor", 842 | "tokio-io", 843 | "tokio-reactor", 844 | "tokio-timer 0.2.13", 845 | ] 846 | 847 | [[package]] 848 | name = "tokio-current-thread" 849 | version = "0.1.7" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" 852 | dependencies = [ 853 | "futures 0.1.29", 854 | "tokio-executor", 855 | ] 856 | 857 | [[package]] 858 | name = "tokio-executor" 859 | version = "0.1.10" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" 862 | dependencies = [ 863 | "crossbeam-utils", 864 | "futures 0.1.29", 865 | ] 866 | 867 | [[package]] 868 | name = "tokio-fs" 869 | version = "0.1.7" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" 872 | dependencies = [ 873 | "futures 0.1.29", 874 | "tokio-io", 875 | "tokio-threadpool", 876 | ] 877 | 878 | [[package]] 879 | name = "tokio-io" 880 | version = "0.1.13" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" 883 | dependencies = [ 884 | "bytes", 885 | "futures 0.1.29", 886 | "log 0.4.8", 887 | ] 888 | 889 | [[package]] 890 | name = "tokio-reactor" 891 | version = "0.1.12" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" 894 | dependencies = [ 895 | "crossbeam-utils", 896 | "futures 0.1.29", 897 | "lazy_static", 898 | "log 0.4.8", 899 | "mio", 900 | "num_cpus", 901 | "parking_lot", 902 | "slab 0.4.2", 903 | "tokio-executor", 904 | "tokio-io", 905 | "tokio-sync", 906 | ] 907 | 908 | [[package]] 909 | name = "tokio-sync" 910 | version = "0.1.8" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" 913 | dependencies = [ 914 | "fnv", 915 | "futures 0.1.29", 916 | ] 917 | 918 | [[package]] 919 | name = "tokio-tcp" 920 | version = "0.1.4" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" 923 | dependencies = [ 924 | "bytes", 925 | "futures 0.1.29", 926 | "iovec", 927 | "mio", 928 | "tokio-io", 929 | "tokio-reactor", 930 | ] 931 | 932 | [[package]] 933 | name = "tokio-threadpool" 934 | version = "0.1.18" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" 937 | dependencies = [ 938 | "crossbeam-deque", 939 | "crossbeam-queue", 940 | "crossbeam-utils", 941 | "futures 0.1.29", 942 | "lazy_static", 943 | "log 0.4.8", 944 | "num_cpus", 945 | "slab 0.4.2", 946 | "tokio-executor", 947 | ] 948 | 949 | [[package]] 950 | name = "tokio-timer" 951 | version = "0.1.2" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "6131e780037787ff1b3f8aad9da83bca02438b72277850dd6ad0d455e0e20efc" 954 | dependencies = [ 955 | "futures 0.1.29", 956 | "slab 0.3.0", 957 | ] 958 | 959 | [[package]] 960 | name = "tokio-timer" 961 | version = "0.2.13" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" 964 | dependencies = [ 965 | "crossbeam-utils", 966 | "futures 0.1.29", 967 | "slab 0.4.2", 968 | "tokio-executor", 969 | ] 970 | 971 | [[package]] 972 | name = "tokio-tls-api" 973 | version = "0.1.22" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "68d0e040d5b1f4cfca70ec4f371229886a5de5bb554d272a4a8da73004a7b2c9" 976 | dependencies = [ 977 | "futures 0.1.29", 978 | "tls-api", 979 | "tokio-io", 980 | ] 981 | 982 | [[package]] 983 | name = "tokio-udp" 984 | version = "0.1.6" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" 987 | dependencies = [ 988 | "bytes", 989 | "futures 0.1.29", 990 | "log 0.4.8", 991 | "mio", 992 | "tokio-codec", 993 | "tokio-io", 994 | "tokio-reactor", 995 | ] 996 | 997 | [[package]] 998 | name = "tokio-uds" 999 | version = "0.1.7" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "65ae5d255ce739e8537221ed2942e0445f4b3b813daebac1c0050ddaaa3587f9" 1002 | dependencies = [ 1003 | "bytes", 1004 | "futures 0.1.29", 1005 | "iovec", 1006 | "libc", 1007 | "log 0.3.9", 1008 | "mio", 1009 | "mio-uds", 1010 | "tokio-core", 1011 | "tokio-io", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "tokio-uds" 1016 | version = "0.2.6" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "5076db410d6fdc6523df7595447629099a1fdc47b3d9f896220780fa48faf798" 1019 | dependencies = [ 1020 | "bytes", 1021 | "futures 0.1.29", 1022 | "iovec", 1023 | "libc", 1024 | "log 0.4.8", 1025 | "mio", 1026 | "mio-uds", 1027 | "tokio-codec", 1028 | "tokio-io", 1029 | "tokio-reactor", 1030 | ] 1031 | 1032 | [[package]] 1033 | name = "unicode-xid" 1034 | version = "0.2.0" 1035 | source = "registry+https://github.com/rust-lang/crates.io-index" 1036 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1037 | 1038 | [[package]] 1039 | name = "unix_socket" 1040 | version = "0.5.0" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "6aa2700417c405c38f5e6902d699345241c28c0b7ade4abaad71e35a87eb1564" 1043 | dependencies = [ 1044 | "cfg-if", 1045 | "libc", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "void" 1050 | version = "1.0.2" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1053 | 1054 | [[package]] 1055 | name = "wasi" 1056 | version = "0.9.0+wasi-snapshot-preview1" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1059 | 1060 | [[package]] 1061 | name = "winapi" 1062 | version = "0.2.8" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1065 | 1066 | [[package]] 1067 | name = "winapi" 1068 | version = "0.3.8" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1071 | dependencies = [ 1072 | "winapi-i686-pc-windows-gnu", 1073 | "winapi-x86_64-pc-windows-gnu", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "winapi-build" 1078 | version = "0.1.1" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1081 | 1082 | [[package]] 1083 | name = "winapi-i686-pc-windows-gnu" 1084 | version = "0.4.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1087 | 1088 | [[package]] 1089 | name = "winapi-x86_64-pc-windows-gnu" 1090 | version = "0.4.0" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1093 | 1094 | [[package]] 1095 | name = "ws2_32-sys" 1096 | version = "0.2.1" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1099 | dependencies = [ 1100 | "winapi 0.2.8", 1101 | "winapi-build", 1102 | ] 1103 | --------------------------------------------------------------------------------