Do your best with gRPC with ruby ​​on the server and PHP on the client

Introduction

I would like to use gRPC, which is an RPC framework made by google, to use ruby ​​for the server and PHP for the client, and use API communication between different languages ​​with a common I/F.

The fact that different languages ​​are used is more like a conditional reflection on the reality that PHP cannot build a gRPC server, rather than wanting to enjoy the real pleasure of sharing I/F by IDL definition, which is the merit of gRPC. As a result, the real thrill will come, so I would like to taste it.

When using PHP for the client, there are many places where the server side is introduced with go, so I will use ruby ​​here (yes, go do not know confirmed).

It's not necessary to write an overview here, so let's get started. Here, as an API, I will write that if you specify a domain, the expiration date will be fetched.

INPUT: FQDN OUTPUT: yyyy/mm/dd hh:ii:ss

API (gRPC): Prepare common I/F with defined I/O Client (PHP): Request FQDN from API through server Server (ruby): Checks the expiration date of the SSL certificate from the FQDN requested by the client and responds the date via API.

Pre-work

pecl install grpc
pecl install protobuf
vi (Abbreviation)/php.ini
	extension=grpc.so
	extension=protobuf.so

gem install grpc
gem install grpc-tools

mkdir ~/projects/grpc_trial/
cd ~/projects/grpc_trial/

composer require grpc/grpc
composer require google/protobuf

mkdir {protos,php,ruby}

Actual work

vi protos/checkexpires.proto
    (See below)

grpc_tools_ruby_protoc -I ./protos --ruby_out=./ruby --grpc_out=./ruby ./protos/checkexpires.proto
ls ruby/ | grep checkexpires
	checkexpires_pb.rb
	checkexpires_services_pb.rb

protoc --proto_path=./protos --php_out=./php --grpc_out=./php ./protos/checkexpires.proto --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin
ls php/Checkexpires/
	CheckReply.php  CheckRequest.php  GetSslClient.php

vi checkexpires.rb
    (See below)

vi checkexpires.php
    (See below)

ruby ./checkexpires.rb &
php ./checkexpires.php 
php checkexpires.php example.com
	2021/12/25 23:59:59

checkexpires.proto


syntax = "proto3";

package checkexpires;

import "google/protobuf/timestamp.proto";

service GetSsl {
  rpc getExpire (CheckRequest) returns (CheckReply) {}
}

message CheckRequest {
  string fqdn = 1;
}

message CheckReply {
  google.protobuf.Timestamp timestamp = 1;
}

checkexpires.rb


this_dir = File.expand_path(File.dirname(__FILE__))
lib_dir = File.join(this_dir, 'ruby')
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir)

require 'time'
require 'grpc'
require 'checkexpires_services_pb'

class CheckexpiresServer < Checkexpires::GetSsl::Service
  def get_expire(check_req, _unused_call)
    end_at = `openssl s_client -connect #{check_req.fqdn}:443 </dev/null 2>/dev/null|openssl x509 -text | grep "Not After"`
    end_at = end_at.split(' : ').pop
    end_at = end_at.gsub("\n", '')
    end_at = Time.parse(end_at).to_i
    end_at = Time.at(end_at)
    Checkexpires::CheckReply.new(timestamp: end_at)
  end
end

def main
  s = GRPC::RpcServer.new
  s.add_http2_port('localhost:50051', :this_port_is_insecure)
  s.handle(CheckexpiresServer)
  s.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
end

main

checkexpires.php


<?php
require dirname(__FILE__).'/vendor/autoload.php';
require dirname(__FILE__).'/php/Checkexpires/GetSslClient.php';
require dirname(__FILE__).'/php/Checkexpires/CheckReply.php';
require dirname(__FILE__).'/php/Checkexpires/CheckRequest.php';
require dirname(__FILE__).'/php/GPBMetadata/Checkexpires.php';
$server = 'localhost:50051'; //checkexpires.rb

if (is_null($argv[1] ?? null)) {
    echo "need to input fqdn\n";
    exit(1);
}

try {
    $client = new Checkexpires\GetSslClient($server, [
        'credentials' => Grpc\ChannelCredentials::createInsecure(),
    ]);
    $request = new Checkexpires\CheckRequest();
    $request->setFqdn($argv[1]);
    list($reply, $status) = $client->getExpire($request)->wait();

    if (($status->code ?? null) === 0) {
        $ts = $reply->getTimestamp()->getSeconds();
        echo date('Y/m/d H:i:s', $ts);
        exit(0);
    }
} catch (Exception $e) {
    echo $e->getMessage();
    exit(1);
}

Supplement

Since the common I/F is defined in different languages, let's standardize each compiler as well.

vi ~/.bashrc
	function protoc2() {
	    grpc_tools_ruby_protoc -I ./protos --ruby_out=./ruby --grpc_out=./ruby ./protos/$@ && protoc --proto_path=./protos --php_out=./php --grpc_out=./php ./protos/$@ --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin
	}

. ~/.bashrc
cd ~/projects/grpc_trial/
protoc2 checkexpires.proto

Now you have a common I/F for ruby ​​and PHP in one proto file. After that, it feels like writing unique processing on the client side and server side respectively. I think I can do my best.

Recommended Posts

Do your best with gRPC with ruby ​​on the server and PHP on the client
Programming with ruby (on the way)
[Ruby on Rails] How to log in with only your name and password using the gem devise
[Ruby on Rails] Change the update date and creation date to your favorite notation
Publish the app made with ruby on rails
Determine the current page with Ruby on Rails
[wip] Server-side development comfortably and freely with the server
Solve the N + 1 problem with Ruby on Rails: acts-as-taggable-on
Feel the basic type and reference type easily with ruby 2
Install rbenv with apt on ubuntu and put ruby
Sample to create one-time password on server side and client side
How to run React and Rails on the same server
The difference between programming with Ruby classes and programming without it
GraphQL Client starting with Ruby
Understand code coverage with Rspec, the Ruby on Rails test framework
I tried to check the operation of gRPC server with grpcurl
How to place and share SwiftLint config files on the server
Things to remember and concepts in the Ruby on Rails tutorial
Install Ubuntu Server 20.04 in VirtualBox on Mac and connect with SSH
I want to download a file on the Internet using Ruby and save it locally (with caution)
I ran the rails server command on windows10, Ruby2.6.6 and got "cannot load such file --sqlite3 / sqlite3_native"