前提条件
服务器实现所需的 Gem:
- google-protobuf(本教程中使用的 3.2.X)
- grpc(本教程中使用的 1.2.X)
下载服务定义并创建以下目录结构:
[base_dir]
├── certificates
├── lib
├── protos
└── booking_service.proto
└── server.rb
根据接口说明生成 Ruby 库:
$ cd [base_dir]
$ grpc_tools_ruby_protoc -I protos --ruby_out=lib --grpc_out=lib protos/booking_service.proto
实现服务器
框架实现:
#!/usr/bin/ruby2.0
# Copyright 2017, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Sample gRPC server that implements the BookingService and enforces mutual
# authentication.
#
# Usage: $ path/to/server.rb
this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(this_dir, 'lib')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)
require 'grpc'
require 'grpc/health/checker'
require 'grpc/health/v1/health_services_pb'
require 'openssl'
require 'optparse'
require 'booking_service_services_pb.rb'
# Peer certificate validation that checks for a specific CN in the subject
# name. Add additional valid CNs, e.g. for a test client, to the array.
ACCEPTED_CNS = ["mapsbooking.businesslink-3.net"]
def check_peer_cert(grpc_call)
valid_cert = false
certificate = OpenSSL::X509::Certificate.new grpc_call.peer_cert
certificate.subject().to_a().each do |name_entry|
if (name_entry[0] == "CN") && ACCEPTED_CNS.include?(name_entry[1])
valid_cert = true
end
end
unless valid_cert
fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::UNAUTHENTICATED,
"Client cert has invalid CN")
end
end
# BookingServer is a simple server that implements the BookingService.
class BookingServer < Ext::Maps::Booking::Partner::V0::BookingService::Service
PartnerApi = Ext::Maps::Booking::Partner::V0
def initialize(peer_cert_validator)
@peer_cert_validator = peer_cert_validator
end
def create_lease(lease_req, grpc_call)
@peer_cert_validator.call(grpc_call)
lease = lease_req.lease.dup
# Perform availability check etc.
#
# For error conditions (example):
# fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED,
# "Slot unavailable")
#
# Happy path: populate the response
# [...]
# Assign a lease ID
# lease.lease_id = ...
PartnerApi::CreateLeaseResponse.new(lease: lease)
end
def create_booking(booking_req, grpc_call)
@peer_cert_validator.call(grpc_call)
booking = PartnerApi::Booking.new
# Populate booking
# [...]
# Assign a booking ID
# booking.booking_id = ...
PartnerApi::CreateBookingResponse.new(booking: booking)
end
def update_booking(booking_req, grpc_call)
@peer_cert_validator.call(grpc_call)
booking = PartnerApi::Booking.new
# * Look up the booking with ID booking_req.booking.booking_id
# * Update according to the request and return the updated booking.
PartnerApi::UpdateBookingResponse.new(booking: booking)
end
end
# Loads the certificates for the test server.
def load_server_certs
this_dir = File.expand_path(File.dirname(__FILE__))
cert_dir = File.join(this_dir, 'certificates')
# In order:
# * PEM file with trusted root certificates for client auth
# * Server private key
# * PEM file with server certificate chain
files = ['trusted_client_roots.pem', 'server.key', 'server.pem']
files.map { |f| File.open(File.join(cert_dir, f)).read }
end
# Creates ServerCredentials from certificates.
def server_credentials
certs = load_server_certs
GRPC::Core::ServerCredentials.new(
certs[0], [{private_key: certs[1], cert_chain: certs[2]}], true)
end
def main
# Parse command line arguments
disable_tls = false
OptionParser.new do |opts|
opts.on('--disable_tls',
'true to disable TLS. NOT FOR PRODUCTION USE!') do |v|
disable_tls = v
end
end.parse!
s = GRPC::RpcServer.new
# Listen on port 50051 on all interfaces. Update for production use.
s.add_http2_port('[::]:50051',
disable_tls ? :this_port_is_insecure : server_credentials)
cert_validator = disable_tls ? ->(grpc_call) {} : method(:check_peer_cert)
s.handle(BookingServer.new cert_validator)
health_checker = Grpc::Health::Checker.new
health_checker.add_status(
"ext.maps.booking.partner.v0.BookingService",
Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING)
s.handle(health_checker)
s.run_till_terminated
end
if __FILE__ == $0
main()
end
在不使用 TLS 的情况下测试服务器
在初始测试中,可以停用 TLS:
$ cd [base_dir]
$ ruby server.rb --disable_tls
这不适合用于生产用途!
配置生产证书
要在服务器上启用 TLS,您需要提供以下文件:
certificates/server.pem
:PEM 中服务器的证书链 格式certificates/server.key
:服务器证书链的私钥certificates/trusted_client_roots.pem
: 对客户端进行身份验证时信任
在对 客户端。您可以选择从授权机构获取这组可信根证书 (例如 Mozilla)或安装 Google Internet Authority 目前推荐的根目录 G2。对于后一种情况 手动更新根证书
最终目录结构
[base_dir]
├── certificates
├── server.pem
├── server.key
└── trusted_client_roots.pem
├── lib
├── booking_service_pb.rb
└── booking_service_services_pb.rb
├── protos
└── booking_service.proto
└── server.rb