Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building a gRPC Service with Hyperf 3

Tech 1

Developing gRPC services within a Hyperf 3 application typically involves setting up a suitable environment, defining protocol buffers, generating code, and implementing both server and client components. For optimal setup and to avoid potential dependency conflicts, especially concerning GCC versions on Linux, utilizing Docker is highly recommended.

Docker Environment Setup

Starting with a Dockerized environment streamlines the development process. Below is a Dockerfile that esatblishes a PHP 8.1 FPM base image, installs essential tools, and includes critical PHP extensions like Swoole, Redis, and gRPC.

FROM php:8.1-fpm-alpine

# Install core dependencies and tools
RUN apk add --no-cache \
    git \
    openssl-dev \
    zlib-dev \
    libzip-dev \
    autoconf \
    automake \
    libtool \
    protobuf-dev \
    protobuf-compiler \
    cmake \
    vim \
    net-tools \
    zip \
    unzip \
    php8-zip \
    && rm -rf /var/cache/apk/*

# Install PHP extensions: Swoole, Redis, gRPC, PCNTL, and Composer
RUN pecl install swoole \ 
    && docker-php-ext-enable swoole \ 
    && echo "swoole.use_shortname='Off'" >> /usr/local/etc/php/conf.d/docker-php-ext-swoole.ini \ 
    && pecl install redis \ 
    && docker-php-ext-enable redis \ 
    && docker-php-ext-install pcntl \ 
    && pecl install grpc \ 
    && docker-php-ext-enable grpc \ 
    && echo 'grpc.enable_fork_support=1' >> /usr/local/etc/php/conf.d/docker-php-ext-grpc.ini \ 
    && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Set the working directory for the application
WORKDIR /app

# Expose the default Hyperf HTTP port
EXPOSE 9501

To build and run the Docker container:

# Build the Docker image named 'hyperf-grpc-env'
docker build -t hyperf-grpc-env .

# Run the container, mapping port 9501 and mounting the current directory
docker run -it -p 9501:9501 -v $(pwd):/app --name hyperf-grpc-app -d hyperf-grpc-env

# Access the shell inside the running container
docker exec -it hyperf-grpc-app sh

Hyperf Project Initialization

Once inside the container, create a new Hyperf project:

composer create-project hyperf/hyperf-skeleton hyperf-grpc-demo
cd hyperf-grpc-demo

Define Protocol Buffer (Proto) File

Create a new file, greeting.proto, in the project root to define your gRPC service and message structures:

syntax = "proto3";

package hyperf_grpc;

service GreeterService {
    rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
    string name = 1;
    int32 age = 2;
}

message HelloResponse {
    string message = 1;
    HelloRequest user_info = 2;
}

Generate PHP gRPC Code

Use the protoc compiler with the PHP plugin to generate the necessary PHP classes from your .proto file. Create a directory for the generated code, then run the command:

mkdir app/Grpc
protoc --php_out=app/Grpc/ --grpc_out=app/Grpc/ --plugin=protoc-gen-grpc=/usr/local/bin/grpc_php_plugin greeting.proto

This command generates files like GPBMetadata/Greeting.php, App/Grpc/HelloRequest.php, App/Grpc/HelloResponse.php, and App/Grpc/GreeterServiceInterface.php (and related client/server stubs) within the app/Grpc directory.

Configure Composer Autoload

Ensure your composer.json correctly loads the generated gRPC files by adding an entry under autoload.psr-4:

{
    "autoload": {
        "psr-4": {
            "App\\": "app/",
            "GPBMetadata\\": "app/Grpc/GPBMetadata/"
        }
    }
}

After modifying composer.json, run composer dump-autoload to update the autoloader.

gRPC Server Implementation

First, install the Hyperf gRPC server component:

composer require hyperf/grpc-server

Next, define the gRPC server settings in config/autoload/grpc_server.php:

<?php

use Hyperf\Server\Event;
use Hyperf\Server\Server;

return [
    'servers' => [
        [
            'name' => 'grpc',
            'type' => Server::SERVER_GRPC,
            'host' => '0.0.0.0',
            'port' => 9503, // A dedicated port for gRPC
            'sock_type' => SWOOLE_SOCK_TCP,
            'callbacks' => [
                Event::ON_RECEIVE => [Hyperf\GrpcServer\Server::class, 'onReceive'],
            ],
            'options' => [
                // gRPC specific options
            ],
        ],
    ],
];

Implement the gRPC service by creating app/Controller/GreeterServiceController.php:

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Grpc\HelloRequest;
use App\Grpc\HelloResponse;
use App\Grpc\GreeterServiceInterface;
use Hyperf\GrpcServer\Annotation\GrpcService;

#[GrpcService(name: 'GreeterService', service: GreeterServiceInterface::class)]
class GreeterServiceController implements GreeterServiceInterface
{
    public function SayHello(HelloRequest $request): HelloResponse
    {
        $response = new HelloResponse();
        $response->setMessage('Hello, ' . $request->getName() . '! You are ' . $request->getAge() . ' years old.');
        $response->setUserInfo($request);
        return $response;
    }
}

gRPC Client Ipmlementation

Install the Hyperf gRPC client component:

composer require hyperf/grpc-client

Create a client wrapper, app/Client/GreeterServiceClient.php:

<?php

declare(strict_types=1);

namespace App\Client;

use App\Grpc\GreeterServiceClient as GrpcClient;
use App\Grpc\HelloRequest;
use App\Grpc\HelloResponse;
use Hyperf\GrpcClient\GrpcClient as BaseGrpcClient;

class GreeterServiceClient
{
    private GrpcClient $client;

    public function __construct(string $host = '127.0.0.1:9503')
    {
        $this->client = new GrpcClient($host, ['credentials' => BaseGrpcClient::getDefaultCredentials()]);
    }

    public function sendGreeting(string $username, int $userAge): HelloResponse
    {
        $request = new HelloRequest();
        $request->setName($username);
        $request->setAge($userAge);

        /** @var HelloResponse $response */
        [$response, $status] = $this->client->SayHello($request);

        if ($status->code !== 0) {
            throw new \RuntimeException("gRPC call failed: {$status->details} ({$status->code})");
        }

        return $response;
    }
}

Create an HTTP endpoint to trigger the gRPC client call in app/Controller/ClientTestController.php:

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Client\GreeterServiceClient;
use Hyperf\HttpServer\Annotation\AutoController;
use Psr\Http\Message\ResponseInterface;

#[AutoController]
class ClientTestController extends AbstractController
{
    public function greet(): ResponseInterface
    {
        $client = new GreeterServiceClient();
        $response = $client->sendGreeting('Alice', 30);

        return $this->response->json([
            'received_message' => $response->getMessage(),
            'user_details' => [
                'name' => $response->getUserInfo()->getName(),
                'age' => $response->getUserInfo()->getAge(),
            ],
        ]);
    }
}

Register the HTTP route in config/routes.php:

<?php

use Hyperf\HttpServer\Router\Router;
use App\Controller\ClientTestController;

Router::get('/grpc/greet', [ClientTestController::class, 'greet']);

Testing the gRPC Service

Start the Hyperf server:

php ./bin/hyperf.php start

Open your web browser or use a tool like Postman/cURL to access the client test endpoint: http://127.0.0.1:9501/grpc/greet

This will trigger the HTTP controller, which in turn calls the gRPC client, communicating with the gRPC server running on port 9503.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.