Conditions préalables
Gems requis pour l'implémentation du serveur:
- google-protobuf (3.2.X utilisé dans ce tutoriel)
- grpc (1.2.X utilisé dans ce tutoriel)
Téléchargez la définition de service et créez la structure de répertoires suivante:
[base_dir]
├── certificates
├── lib
├── protos
└── booking_service.proto
└── server.rb
Générez les bibliothèques Ruby à partir de la description de l'interface:
$ cd [base_dir]
$ grpc_tools_ruby_protoc -I protos --ruby_out=lib --grpc_out=lib protos/booking_service.proto
Implémenter le serveur
Implémentation du squelette:
#!/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::V2::BookingService::Service
PartnerApi = Ext::Maps::Booking::Partner::V2
def initialize(peer_cert_validator)
@peer_cert_validator = peer_cert_validator
end
def check_availability(availability_req, grpc_call)
@peer_cert_validator.call(grpc_call)
slot = availability_req.slot.dup
count_available = 0
# Perform availability check
#
# For error conditions (example):
# fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::INVALID_ARGUMENT,
# "Invalid merchant id")
#
# Happy path: populate the response
# [...]
# Populate the slot and its count_available
PartnerApi::CheckAvailabilityResponse.new(slot: slot,
count_available: count_available)
end
def create_booking(booking_req, grpc_call)
@peer_cert_validator.call(grpc_call)
booking = PartnerApi::Booking.new
user_payment_option = PartnerApi::UserPaymentOption.new
# Create a booking according to the request
#
# For error conditions (example):
# fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::INVALID_ARGUMENT,
# "Invalid merchant id")
#
# For business logic error (example):
# booking_failure = PartnerApi::BookingFailure.new(
# cause: PartnerApi::BookingFailure::Cause::SLOT_UNAVAILABLE)
# PartnerApi::CreateBookingResponse.new(booking_failure: booking_failure)
#
# Happy path: populate the response
# [...]
# Assign a booking ID
# booking.booking_id = ...
# Populate the booking and the user payment option
PartnerApi::CreateBookingResponse.new(
booking: booking,
user_payment_option: user_payment_option)
end
def update_booking(booking_req, grpc_call)
@peer_cert_validator.call(grpc_call)
booking = PartnerApi::Booking.new
user_payment_option = PartnerApi::UserPaymentOption.new
# * Look up the booking with ID booking_req.booking.booking_id.
# * Update according to the request and return the updated booking.
#
# For error conditions (example):
# fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::NOT_FOUND,
# "Unknown booking ID")
#
# For business logic error (example):
# booking_failure = PartnerApi::BookingFailure.new(
# cause: PartnerApi::BookingFailure::Cause::SLOT_UNAVAILABLE)
# PartnerApi::UpdateBookingResponse.new(booking_failure: booking_failure)
#
# Happy path: populate the response
# [...]
# Populate the updated booking and user payment option.
PartnerApi::UpdateBookingResponse.new(
booking: booking,
user_payment_option: user_payment_option)
end
def get_booking_status(status_req, grpc_call)
@peer_cert_validator.call(grpc_call)
booking_id = status_req.booking_id.dup
booking_status = PartnerApi::BookingStatus.new
prepayment_status = PartnerApi::PrepaymentStatus.new
# * Look up the booking status and the prepayment status with ID
# * status_req.booking_id.
# * Return the booking status and the prepayment status.
#
# For rror conditions (example):
# fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::NOT_FOUND,
# "Unknown booking ID")
#
# Happy path: populate the response
# [...]
# Populate the booking status and the prepayment status.
PartnerApi::GetBookingStatusResponse.new(
booking_id: booking_id,
booking_status: booking_status,
prepayment_status: prepayment_status)
end
def list_bookings(list_req, grpc_call)
@peer_cert_validator.call(grpc_call)
bookings = []
# * Look up all bookings with user ID list_req.user_id.
# * Return the bookings
#
# For rror conditions (example):
# fail GRPC::BadStatus.new(GRPC::Core::StatusCodes::NOT_FOUND,
# "Unknown user ID")
#
# Happy path: populate the response
# [...]
# Populate all bookings.
response = PartnerApi::ListBookingsResponse.new
response.bookings.extend(bookings)
response
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.v2.BookingService",
Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING)
s.handle(health_checker)
s.run_till_terminated
end
if __FILE__ == $0
main()
end
</pre>
Tester le serveur sans TLS
Pour les tests initiaux, le protocole TLS peut être désactivé:
$ cd [base_dir]
$ ruby server.rb --disable_tls
Cette méthode n'est pas adaptée à une utilisation en production.
Configurer les certificats de production
Pour activer le protocole TLS sur le serveur, les fichiers suivants sont requis:
certificates/server.pem
correspond à la chaîne de certificat du serveur au format PEM.certificates/server.key
est la clé privée de la chaîne de certificats du serveur.certificates/trusted_client_roots.pem
est les certificats racine approuvés lors de l'authentification des clients.
L'ensemble de certificats racine du client de confiance est utilisé lors de l'authentification du client. Vous pouvez choisir d'obtenir cet ensemble de racines de confiance auprès d'une autorité telle que Mozilla ou d'installer l'ensemble de racines actuellement recommandées par l'autorité Google Internet Authority G2. Dans ce dernier cas, vous devrez peut-être parfois mettre à jour manuellement le certificat racine.
Structure finale des répertoires
[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