Giter Club home page Giter Club logo

grpc's Introduction

gRPC Library in Common Lisp

SBCL-Tests CCL-Tests

Overview

This package defines a gRPC client library for Common Lisp. It wraps gRPC core functions with CFFI calls and uses those core functions to create and use a client. client.lisp contains all the necessary functions to create a gRPC client by creating channels (connections between client and server) and calls (requests to a server).

Currently there is support for synchronous and streaming calls over

SSL, and insecure channels.

Support for implementing gRPC servers is in development.

Usage

To create a client, a channel must first be created. Depending on the expected authentication mechanism (or lack thereof), different channel creation macros are available.

Channel Creation

Insecure Channels

If using an insecure channel, use the with-insecure-channel macro. This macro expects a symbol to bind the channel to and the server address.

(with-insecure-channel (channel "localhost:8080")
;; Code that uses channel
...)

SSL Channels

If using an SSL channel, use the with-ssl-channel macro. This macro expects a symbol to bind the channel, the server address and certificate data to make the call.

(with-ssl-channel (channel
                    ("localhost:8080"
                      (:pem-root-certs pem-root-certs
                       :private-key private-key
                       :cert-chain cert-chain)))
;; Code that uses channel
...)

Sending RPC Requests

Once a channel has been created, RPC requests to the server can occur. It is possible to send binary data directly over gRPC but most applications using gRPC will expect a Protocol Buffer encoded message. For details on Protocol buffer integration please see Protocol Buffer Integration.

A message can be sent directly through gRPC using grpc-call. This expects the channel that was previously created, the service name and method to be called, and the request message serialized to bytes. The grpc-call method also takes server-stream and client-stream arguments which state whether the message should use server or client side streaming as discussed in Types of Services

(grpc:grpc-call channel
                "/serviceName/ServiceMethod"
                serialized-message server-stream client-stream)
;; Returns the response a list of byte vectors for each response

Types of Services

An RPC can support any of unary, mono-directional, or bidirectional streaming. This must be decided beforehand by the server and client.

There are two different types of mono-directional-streaming RPC's:

  1. Server Side Streaming.
  2. Client Side Streaming.

See https://grpc.io/docs/what-is-grpc/core-concepts/#rpc-life-cycle for details.

Unary RPC

A unary RPC sends one message and receives one message. The grpc-call function takes in a single vector for bytes-to-send and return a single octet-vector.

Server Side Streaming RPC

A server side streaming RPC sends one message and receives multiple messages. The grpc-call function takes in a single vector for bytes-to-send and return a list of octet-vectors corresponding to the received messages.

Client Side Streaming RPC

A client side streaming RPC sends some number of messages and receives a single message. The grpc-call function takes in a list of vectors for bytes-to-send and returns an octet-vector corresponding to the received message.

Bidirectional Streaming RPC

A bidirectional streaming RPC sends any number of messages and receives any number of messages. The grpc-call function takes in a list of vectors for bytes-to-send and returns a list of octet-vectors corresponding to the received messages.

Protocol Buffer Integration

gRPC can work with or without Protocol Buffer support. With that said, it is common to use a Protocol Buffer library in conjunction with gRPC. We have implemented support for the cl-protobufs library.

The Qitab team provides supports cl-protobufs but doesn't guarantee continued support for other data format libraries.

To use gRPC with cl-protobufs you must load cl-protobufs and gRPC with grpc-protobuf-integration.lisp into your running lisp image.

Example:

Define a protocol buffer service with methods as:

package testing;

message HelloRequest {
  optional string name = 1;
}

message HelloReply {
  optional string message = 1;
}

service Greeter {
  // Receives a HelloRequest and responds with a HelloReply.
  rpc SayHello(HelloRequest) returns (HelloReply) {}
  // Receive a HelloRequest requesting some number of responses in num_responses
  // and response with a HelloReply num_responses times.
  rpc SayHelloServerStream(HelloRequest) returns (stream HelloReply) {}
  // Receive a number of requests and concatenate the name field of each
  // HelloRequest. Return the final string in HelloReply.
  rpc SayHelloClientStream(stream HelloRequest) returns (HelloReply) {}
  // Receive a number of HelloRequest requesting some number of responses in num_responses.
  // Respond to each HelloRequest with a HelloReply num_responses times.
  rpc SayHelloBidirectionalStream(stream HelloRequest) returns (stream HelloReply) {}
}

We create two packages:

  • cl-protobufs.testing
  • cl-protobufs.testing-rpc

The package cl-protobufs.testing contains the hello-request and hello-reply protocol buffer messages.

One Shot Client Calls.

The package cl-protobufs.testing-rpc contains a stub for call-say-hello. A message can be sent to a server implementing the Greeter service with:

  (grpc:with-insecure-channel
      (channel (concatenate 'string hostname ":" (write-to-string port-number)))
    (let* ((request (cl-protobufs.testing:make-hello-request :name "Neo"))
           (response (cl-protobufs.testing-rpc:call-say-hello channel message)))
      ...))

If the service implements client-side streaming message should be a list of hello-request messages to be sent to the server. If the service implements server-side streaming then response will contain a list of hello-reply messages.

Asynchronous Client Streaming

For streaming calls we create:

  • cl-protobufs.testing-rpc:<service-name>/start
  • cl-protobufs.testing-rpc:<service-name>/send
  • cl-protobufs.testing-rpc:<service-name>/receive
  • cl-protobufs.testing-rpc:<service-name>/close
  • cl-protobufs.testing-rpc:<service-name>/cleanup

functions.

We will use SayHelloBidirectionalStream service as an example below.

(testing-rpc:say-hello-bidirectional-stream/start channel)

Takes in a channel object and returns a call object that the user must keep until the call is closed and cleanup is called.

(testing-rpc:say-hello-bidirectional-stream/send call message)

Takes in the call object and a message and sends a message to the client.

(testing-rpc:say-hello-server-stream/receive call)

Blocks until a message is received, then returns that message. NIL will be returned if the server closes the channel.

(testing-rpc:say-hello-server-stream/close call)

Will close the channel on the client side.

(testing-rpc:say-hello-server-stream/cleanup call)

Will safely cleanup any data leftover in the call object.

Example

This example can be found in examples/client/client-insecure.lisp.

Further Reading

grpc's People

Contributors

ccqpein avatar common-lisp-dev-copybara avatar groszewn avatar partmedia avatar sionescu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grpc's Issues

Unable to load foreign library (GRPC-CLIENT-WRAPPER)

I have installed the cl-protobufs and the (ql:quickload "cl-protobufs") has no error in REPL.

But when I was trying to (ql:quickload "grpc"), I get the error message below return

Unable to load foreign library (GRPC-CLIENT-WRAPPER).
  Error opening shared object "/Users/{username}/quicklisp/local-projects/grpc/grpc.dylib":
  dlopen(/Users/{username}/quicklisp/local-projects/grpc/grpc.dylib, 0x000A): tried: '/Users/{username}/quicklisp/local-projects/grpc/grpc.dylib' (no such file).

Do I mess up the path or I need to do something else to generate the grpc.dylib like the protoc-gen-cl-pb in cl-protobufs?

Thank you

grpc and cl-protobufs: master branch version.
protoc: libprotoc 3.21.4
runtime: SBCL
OS: macos 12.5

macOS cffi load path issue

I have given an issue before #44. I said I found the solution; actually, it isn't.

The PR #45 give the cffi the name of the lib it needs to load. (:default "libgrpc") let it load the libgrpc.dylib which homebrew installed. However, libraries.lisp need to load the grpc.so it compiled inside this repo.

;; Load the C wrapper directly from the source directory.
    (t (:default #.(namestring
                    (asdf:system-relative-pathname "grpc" "grpc"))))

let cffi read the grpc.dylib when in :darwin platform. But the Makefile compiles the grpc.so in the source folder rather than .dylib file. So my PR only let this package doesn't give the error, but it cannot work.

I made some changes and wrote a demo, found the way that libraries.lisp read successfully and can work.

solution 1:
let libraries.lisp read the .so file in :darwin

(:darwin #.(namestring
		      (asdf:system-relative-pathname "grpc" "grpc.so")))

it is the easiest way to make it I think.

solution 2:
let makefile compile .dylib file in :darwin.

default_target: grpc.so => default_target: grpc.dylib.

Give a condition that compiles .dylib when in macos. However, it has to change the makefile.

What do you guys think? If you guys also agree the first way is acceptable, I can give the PR to fix it (which is the issue I made, sorry about that).

How about adding support for OpenRPC?

Hi, guys!

Recently I did a wrapper around JSONRPC to generate OpenRPC spec and CL client for a service described by a spec: https://40ants.com/openrpc/

Don't know if OpenRPC is applicable to gRPC, but in case if you wanna add support, I'll be happy to extend my system and support gRPC besides JSON-RPC.

Unable to Connect to insecure server

I am looking to use this library connect a common lisp service to a bunch of gRPC servers.

As a first step, I am trying to get a minimal product working, specifically an insecure connection using unary calls.

I have the below c++ helloworld server running and can successfully connect the below c++ client to it. However the common lisp client does not connect.

My Environment is:

  • RHEL 8.6
  • SBCL 2.2.2
  • GRPC 1.45.0
  • qitab/grpc main

Client Error

Below is the error I receive when attempting to connect. I get this error regardless of whether the server is running or not.

I get this error with the common lisp client regardless of whether the server is running or not.

The C++ client works when the server is running and gives me correct error 14: failed to connect to all addresses when the server is not running.

For troubleshooting I pointed the common lisp client and the c++ client to a netcat instance to capture of the connection traffic. Unfortunately it looks like the common lisp client doesn't attempt a network connection whereas the c++ client does send some data to netcat.

I have exhausted my troubleshooting efforts, any hints from your end would be greatly appreciated.

GRPC CALL ERROR: GRPC-STATUS-UNAVAILABLE.
   [Condition of type GRPC::GRPC-CALL-ERROR]

Restarts:
 0: [RETRY] Retry SLY mREPL evaluation request.
 1: [*ABORT] Return to SLY's top level.
 2: [ABORT] abort thread (#<THREAD "sly-channel-1-mrepl-remote-1" RUNNING {1001530003}>)

Backtrace:
 0: (GRPC::CHECK-SERVER-STATUS #.(SB-SYS:INT-SAP #X7FA35C0855C0) 1)
      Locals:
        OPS = #.(SB-SYS:INT-SAP #X7FA35C0855C0)
        RECEIVE-STATUS-ON-CLIENT-INDEX = 1
 1: (GRPC::SEND-MESSAGE #S(GRPC::CALL :C-CALL #.(SB-SYS:INT-SAP #X7FA35C07CBB0) :C-TAG #.(SB-SYS:INT-SAP #X7FA35C07CAF0) :C-OPS #.(SB-SYS:INT-SAP #X7FA35C0855C0) :OPS-PLIST (:RECV-METADATA 2 :CLIENT-RE\
CV-..
      Locals:
        BYTES-TO-SEND = #(10 3 78 101 111)
        CALL = #S(GRPC::CALL ..)
 2: (GRPC:GRPC-CALL #.(SB-SYS:INT-SAP #X7FA35C085250) "/helloworld.Greeter/SayHello" #(10 3 78 101 111) NIL NIL)
      Locals:
        BYTES-TO-SEND = #(10 3 78 101 111)
        CALL = #S(GRPC::CALL ..)
        CHANNEL = #.(SB-SYS:INT-SAP #X7FA35C085250)
        CLIENT-STREAM = NIL
        SERVER-STREAM = NIL
        SERVICE-METHOD-NAME = "/helloworld.Greeter/SayHello"
 3: ((:METHOD GRPC::START-CALL (T T T T)) #.(SB-SYS:INT-SAP #X7FA35C085250) #<CL-PROTOBUFS:METHOD-DESCRIPTOR CL-PROTOBUFS.HELLOWORLD::SAY-HELLO (CL-PROTOBUFS.HELLOWORLD:HELLO-REQUEST) => (CL-PROTOBUFS.\
HEL..
      Locals:
        GRPC::CHANNEL = #.(SB-SYS:INT-SAP #X7FA35C085250)
        #:G10 = NIL
        METHOD = #<CL-PROTOBUFS:METHOD-DESCRIPTOR CL-PROTOBUFS.HELLOWORLD::SAY-HELLO (CL-PROTOBUFS.HELLOWORLD:HELLO-REQUEST) => (CL-PROTOBUFS.HELLOWORLD:HELLO-REPLY) {1002C1ACE3}>
        GRPC::OUTPUT-TYPE = CL-PROTOBUFS.HELLOWORLD:HELLO-REPLY
        GRPC::REQUEST = #S(CL-PROTOBUFS.HELLOWORLD:HELLO-REQUEST :%%SKIPPED-BYTES NIL :%NAME "Neo" :%%BYTES NIL :%%IS-SET #*)
        GRPC::RESPONSE = NIL
        GRPC::SERVER-STREAM = NIL
 4: (AGENTS::GRPC-MAIN)
      Locals:
        CHANNEL = #.(SB-SYS:INT-SAP #X7FA35C085250)
 5: (SB-INT:SIMPLE-EVAL-IN-LEXENV (AGENTS::GRPC-MAIN) #<NULL-LEXENV>)
 6: (EVAL (AGENTS::GRPC-MAIN))
 7: ((LAMBDA NIL :IN SLYNK-MREPL::MREPL-EVAL-1))
 --more--

Common Lisp Client

(defpackage agents
  (:use :cl)
  (:local-nicknames (#:helloworld #:cl-protobufs.helloworld)
                    (#:helloworld-rpc #:cl-protobufs.helloworld-rpc)))

(in-package :agents)

(defun grpc-main ()
  (grpc:init-grpc)
       (grpc:with-insecure-channel
           (channel (concatenate 'string "10.162.22.100" ":" (write-to-string 50051)))

         ;; Unary streaming
         (format nil "Trying unary call")
         (let* ((message (helloworld:make-hello-request :name "Neo"))
                (response (helloworld-rpc:call-say-hello channel message)))
           (format t "Channel: ~A~%" channel)
           (format t "Message: ~S~%"  message)
           (format t "Response: ~S~%"  response)))
  (grpc:shutdown-grpc))

Server

class GreeterServiceImpl final : public Greeter::Service {
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    std::cout << prefix + request->name() << std::endl;
    return Status::OK;
  }
};

std::string readFileIntoString(const std::string& path) {
  std::ifstream input_file(path);
  if (!input_file.is_open()) {
    std::cerr << "Could not open the file - '" << path << "'" << std::endl;
  }
  return std::string((std::istreambuf_iterator<char>(input_file)),
                     std::istreambuf_iterator<char>());
}

bool fileExists(const std::string& path) {
  std::ifstream f(path.c_str());
  return f.good();
}

void RunServer() {

  std::string server_address("0.0.0.0:50051");
  GreeterServiceImpl service;

  ServerBuilder builder;
  std::shared_ptr<grpc::ServerCredentials> creds;

  // Set up authentication mechanism (or lack therof) for the server.

  auto auth_mechanism = "insecure";

  if (auth_mechanism == "insecure") {
    creds = grpc::InsecureServerCredentials();
  }

  builder.AddListeningPort(server_address, creds);
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();
}

int main(int argc, char** argv) {
  RunServer();
  return 0;
}

C++ Client

class GreeterServiceImpl final : public Greeter::Service {
  Status SayHello(ServerContext* context, const HelloRequest* request,
                  HelloReply* reply) override {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    std::cout << prefix + request->name() << std::endl;
    return Status::OK;
  }
};

std::string readFileIntoString(const std::string& path) {
  std::ifstream input_file(path);
  if (!input_file.is_open()) {
    std::cerr << "Could not open the file - '" << path << "'" << std::endl;
  }
  return std::string((std::istreambuf_iterator<char>(input_file)),
                     std::istreambuf_iterator<char>());
}

bool fileExists(const std::string& path) {
  std::ifstream f(path.c_str());
  return f.good();
}

void RunServer() {

  std::string server_address("0.0.0.0:50051");
  GreeterServiceImpl service;

  ServerBuilder builder;
  std::shared_ptr<grpc::ServerCredentials> creds;

  // Set up authentication mechanism (or lack therof) for the server.

  auto auth_mechanism = "insecure";

  if (auth_mechanism == "insecure") {
    creds = grpc::InsecureServerCredentials();
  }

  builder.AddListeningPort(server_address, creds);
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();
}

int main(int argc, char** argv) {
  RunServer();
  return 0;
}
syntax = "proto3";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

Sending metadata alongside a request

I'm attempting to add additional metadata alongside my request.

An example utilizing ruby would be

response = stub.rpc_call(req, {metadata: {key1: value1, key2: value2}})

Does similar functionality exist within this library?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.