This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch release_fory_1.2.0
in repository https://gitbox.apache.org/repos/asf/fory-site.git
The following commit(s) were added to refs/heads/release_fory_1.2.0 by this
push:
new ac3cdee05e Improve gRPC support docs
ac3cdee05e is described below
commit ac3cdee05ecd9ed5f24645a2e11df3bdab6a4354
Author: 慕白 <[email protected]>
AuthorDate: Tue Jun 16 23:11:35 2026 +0800
Improve gRPC support docs
---
docs/guide/java/grpc-support.md | 170 +++++++++++++-
docs/guide/javascript/grpc-support.md | 29 +++
docs/guide/kotlin/grpc-support.md | 127 +++++++++--
docs/guide/python/grpc-support.md | 64 +++++-
docs/guide/rust/grpc-support.md | 115 +++++++++-
.../current/guide/java/grpc-support.md | 253 +++++++++++++++++++--
.../current/guide/javascript/grpc-support.md | 108 +++++++++
.../current/guide/kotlin/grpc-support.md | 123 ++++++++--
.../current/guide/python/grpc-support.md | 131 +++++++++--
.../current/guide/rust/grpc-support.md | 114 +++++++++-
.../version-1.2.0/guide/java/grpc-support.md | 253 +++++++++++++++++++--
.../version-1.2.0/guide/javascript/grpc-support.md | 108 +++++++++
.../version-1.2.0/guide/kotlin/grpc-support.md | 123 ++++++++--
.../version-1.2.0/guide/python/grpc-support.md | 131 +++++++++--
.../version-1.2.0/guide/rust/grpc-support.md | 114 +++++++++-
.../version-1.2.0/guide/java/grpc-support.md | 170 +++++++++++++-
.../version-1.2.0/guide/javascript/grpc-support.md | 29 +++
.../version-1.2.0/guide/kotlin/grpc-support.md | 127 +++++++++--
.../version-1.2.0/guide/python/grpc-support.md | 64 +++++-
.../version-1.2.0/guide/rust/grpc-support.md | 115 +++++++++-
20 files changed, 2290 insertions(+), 178 deletions(-)
diff --git a/docs/guide/java/grpc-support.md b/docs/guide/java/grpc-support.md
index 7bebb010f9..826f2326e7 100644
--- a/docs/guide/java/grpc-support.md
+++ b/docs/guide/java/grpc-support.md
@@ -201,12 +201,170 @@ service Greeter {
Generated Java service methods follow grpc-java conventions:
-- Unary and server-streaming methods receive a request object and a
- `StreamObserver` for responses.
-- Client-streaming and bidirectional methods return a `StreamObserver` for
- incoming requests and receive a `StreamObserver` for outgoing responses.
-- Blocking stubs expose the grpc-java blocking APIs for supported streaming
- shapes.
+| IDL shape | Server method shape
| Client method shape |
+| ----------------------------------------- |
-------------------------------------------------------- |
-------------------------------------- |
+| `rpc A (Req) returns (Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking, async, and future unary stub |
+| `rpc A (Req) returns (stream Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking iterator or async observer |
+| `rpc A (stream Req) returns (Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+| `rpc A (stream Req) returns (stream Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+
+Server implementations can use the generated streaming method shapes directly:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayList;
+import java.util.List;
+
+final class GreeterService extends GreeterGrpc.GreeterImplBase {
+ @Override
+ public void lotsOfReplies(
+ HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+ HelloReply first = new HelloReply();
+ first.setReply("Hello, " + request.getName());
+ responseObserver.onNext(first);
+
+ HelloReply second = new HelloReply();
+ second.setReply("Welcome, " + request.getName());
+ responseObserver.onNext(second);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> lotsOfGreetings(
+ StreamObserver<HelloReply> responseObserver) {
+ List<String> names = new ArrayList<>();
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ names.add(request.getName());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ HelloReply reply = new HelloReply();
+ reply.setReply(String.join(", ", names));
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> chat(
+ StreamObserver<HelloReply> responseObserver) {
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ HelloReply reply = new HelloReply();
+ reply.setReply("Hello, " + request.getName());
+ responseObserver.onNext(reply);
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+ };
+ }
+}
+```
+
+Generated clients return the standard grpc-java call shapes:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+final class StreamingClient {
+ private final GreeterGrpc.GreeterBlockingStub blockingStub;
+ private final GreeterGrpc.GreeterStub asyncStub;
+
+ StreamingClient(
+ GreeterGrpc.GreeterBlockingStub blockingStub,
+ GreeterGrpc.GreeterStub asyncStub) {
+ this.blockingStub = blockingStub;
+ this.asyncStub = asyncStub;
+ }
+
+ void run() throws InterruptedException {
+ Iterator<HelloReply> replies =
+ blockingStub.lotsOfReplies(newRequest("Fory"));
+ while (replies.hasNext()) {
+ System.out.println(replies.next().getReply());
+ }
+
+ CountDownLatch greetingsDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> greetings =
+ asyncStub.lotsOfGreetings(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ greetingsDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ greetingsDone.countDown();
+ }
+ });
+ greetings.onNext(newRequest("Ada"));
+ greetings.onNext(newRequest("Grace"));
+ greetings.onCompleted();
+ greetingsDone.await(5, TimeUnit.SECONDS);
+
+ CountDownLatch chatDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> chat =
+ asyncStub.chat(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ chatDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ chatDone.countDown();
+ }
+ });
+ chat.onNext(newRequest("Fory"));
+ chat.onCompleted();
+ chatDone.await(5, TimeUnit.SECONDS);
+ }
+
+ private static HelloRequest newRequest(String name) {
+ HelloRequest request = new HelloRequest();
+ request.setName(name);
+ return request;
+ }
+}
+```
+
+The generated descriptors preserve the exact IDL service and method names for
+the gRPC path.
## Operations
diff --git a/docs/guide/javascript/grpc-support.md
b/docs/guide/javascript/grpc-support.md
index 32ca805171..696ecef24a 100644
--- a/docs/guide/javascript/grpc-support.md
+++ b/docs/guide/javascript/grpc-support.md
@@ -23,6 +23,12 @@ Fory can generate JavaScript service companions for schemas
that define
services. The generated service code uses normal gRPC transports while request
and response objects are serialized with Fory instead of protobuf.
+Use this mode when both RPC peers are generated from the same Fory IDL,
+protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message
+bodies. Use normal protobuf gRPC generation for APIs that must be consumed by
+generic protobuf clients, reflection tools, or components that expect protobuf
+message bytes.
+
Use `--grpc` for Node.js server and client code. Use `--grpc-web` for browser
clients that call a gRPC-Web compatible server or proxy.
@@ -47,6 +53,9 @@ package used by your application.
## Define a Service
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
```protobuf
package demo.greeter;
@@ -292,3 +301,23 @@ gRPC operational features still belong to the transport
package:
- Deadlines and cancellation
- Client and server interceptors
- Load balancing and deployment-specific proxy configuration
+
+## Troubleshooting
+
+### Missing gRPC Packages
+
+Add `@grpc/grpc-js` for Node.js companions or `grpc-web` for browser
+companions. `@apache-fory/core` intentionally does not depend on either
+transport package.
+
+### gRPC-Web Client-Streaming or Bidirectional RPCs Are Rejected
+
+gRPC-Web does not support client-streaming or bidirectional streaming. Generate
+Node.js companions with `--grpc` for those shapes, or expose unary and
+server-streaming methods to browser clients.
+
+### Protobuf Clients Cannot Decode the Service
+
+Fory gRPC companions do not use protobuf wire encoding for messages. Use a
+Fory-generated client for Fory-generated services, or provide a separate
+protobuf service endpoint for generic protobuf clients.
diff --git a/docs/guide/kotlin/grpc-support.md
b/docs/guide/kotlin/grpc-support.md
index ae12f9e0f1..2a33ec3d43 100644
--- a/docs/guide/kotlin/grpc-support.md
+++ b/docs/guide/kotlin/grpc-support.md
@@ -23,7 +23,13 @@ Fory IDL can generate Kotlin coroutine gRPC companions. The
generated gRPC
files use normal grpc-java and grpc-kotlin APIs, while each request and
response
message is serialized with Fory.
-## Dependencies
+Use this mode when both RPC peers are generated from the same Fory IDL,
+protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message
+bodies. Use normal protobuf gRPC generation for APIs that must be consumed by
+generic protobuf clients, reflection tools, or components that expect protobuf
+message bytes.
+
+## Add Dependencies
Add Fory Kotlin, KSP, grpc-java, grpc-kotlin, coroutines, and one grpc-java
transport to the application or service module that compiles the generated
@@ -51,9 +57,10 @@ Use a different grpc-java transport if your application
already standardizes on
one. Generated Kotlin Fory gRPC does not require `grpc-protobuf` for payload
encoding.
-## Generate Code
+## Define a Service
-For a schema such as:
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
```protobuf
package demo.greeter;
@@ -71,18 +78,27 @@ service Greeter {
}
```
-run:
+Generate Kotlin model and gRPC companion code with `--grpc`:
```bash
foryc service.fdl --kotlin_out=./generated/kotlin --grpc
```
-The compiler writes Kotlin model files, a schema module such as
-`ServiceForyModule.kt`, and one service companion such as `GreeterGrpcKt.kt`.
+For this schema, the Kotlin generator emits:
+
+| File | Purpose |
+| -------------------- | -------------------------------------------- |
+| `HelloRequest.kt` | Fory model type for the request |
+| `HelloReply.kt` | Fory model type for the response |
+| `ServiceForyModule` | Fory registration module for generated types |
+| `GreeterGrpcKt.kt` | Coroutine service base, stubs, and codecs |
+
Run KSP when compiling the generated model files so the schema serializers are
-available at runtime.
+available at runtime. Generated request and response types are registered by
+the generated schema module used by the service companion, so service
+implementations do not perform manual serializer registration.
-## Server
+## Implement a Server
Implement the generated coroutine base class and register it with a normal
grpc-java server.
@@ -108,7 +124,7 @@ val server = ServerBuilder
Unimplemented generated methods fail with gRPC `UNIMPLEMENTED`. Exceptions
thrown by your service method follow grpc-kotlin server behavior.
-## Client
+## Create a Client
Construct the generated coroutine stub directly from a grpc-java channel.
@@ -130,7 +146,18 @@ Channel construction, shutdown, deadlines, credentials,
interceptors, load
balancing, retries, and server lifecycle stay normal grpc-java/grpc-kotlin
responsibilities.
-## Streaming
+## Streaming RPCs
+
+Fory service definitions can use the same gRPC streaming shapes:
+
+```protobuf
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+ rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+ rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+ rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
Streaming RPCs use `kotlinx.coroutines.flow.Flow`.
@@ -144,6 +171,78 @@ Streaming RPCs use `kotlinx.coroutines.flow.Flow`.
The generated method path keeps the exact service and method names from the
schema, for example `/demo.greeter.Greeter/SayHello`.
+Server implementations can return or consume `Flow` values directly:
+
+```kotlin
+import demo.greeter.GreeterGrpcKt
+import demo.greeter.HelloReply
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+
+class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
+ override fun lotsOfReplies(request: HelloRequest): Flow<HelloReply> = flow {
+ emit(HelloReply(reply = "Hello, ${request.name}"))
+ emit(HelloReply(reply = "Welcome, ${request.name}"))
+ }
+
+ override suspend fun lotsOfGreetings(
+ requests: Flow<HelloRequest>
+ ): HelloReply {
+ val names = requests.toList().joinToString(", ") { it.name }
+ return HelloReply(reply = names)
+ }
+
+ override fun chat(requests: Flow<HelloRequest>): Flow<HelloReply> =
+ requests.map { request ->
+ HelloReply(reply = "Hello, ${request.name}")
+ }
+}
+```
+
+Generated clients expose the matching coroutine and Flow APIs:
+
+```kotlin
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.flowOf
+
+stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply ->
+ println(reply.reply)
+}
+
+val summary = stub.lotsOfGreetings(
+ flowOf(
+ HelloRequest(name = "Ada"),
+ HelloRequest(name = "Grace"),
+ )
+)
+println(summary.reply)
+
+stub.chat(
+ flowOf(
+ HelloRequest(name = "Fory"),
+ HelloRequest(name = "RPC"),
+ )
+).collect { reply ->
+ println(reply.reply)
+}
+```
+
+## Operations
+
+The generated service code only replaces request and response serialization.
+All normal gRPC operational features still belong to grpc-java and
+grpc-kotlin:
+
+- Deadlines and cancellations
+- TLS and authentication
+- Name resolution and load balancing
+- Client and server interceptors
+- Status codes and metadata
+- Channel pooling and lifecycle management
+
## Interoperability
Generated Kotlin service companions use Fory binary payloads inside gRPC
@@ -158,22 +257,22 @@ messages.
## Troubleshooting
-**Generated service file is missing**
+### Generated service file is missing
Pass `--grpc` together with `--kotlin_out`. Schemas without service definitions
only generate model files and the schema module.
-**Serializer class not found at runtime**
+### Serializer class not found at runtime
Ensure KSP runs for the generated Kotlin model sources and that
`fory-kotlin-ksp` uses the same Fory version as `fory-kotlin`.
-**gRPC classes are unresolved**
+### gRPC classes are unresolved
Add grpc-java and grpc-kotlin dependencies to the application module. Fory
Kotlin artifacts do not add those dependencies automatically.
-**A protobuf client cannot read responses**
+### A protobuf client cannot read responses
Fory gRPC uses Fory binary protocol payloads, not protobuf wire-format
messages.
Use generated Fory gRPC companions on both sides for the same service schema.
diff --git a/docs/guide/python/grpc-support.md
b/docs/guide/python/grpc-support.md
index c5106f91b0..946602be30 100644
--- a/docs/guide/python/grpc-support.md
+++ b/docs/guide/python/grpc-support.md
@@ -28,6 +28,13 @@ IDL, or FlatBuffers IDL and you want gRPC transport
semantics with Fory payload
encoding. Use standard protobuf gRPC code generation when clients or tools must
consume protobuf message bytes directly.
+Generated Python companions currently target the synchronous `grpcio` API. Use
+regular `def` servicer methods, `grpc.server(...)`,
`grpc.insecure_channel(...)`,
+and Python iterators or generators for streaming RPCs. The compiler does not
+generate `grpc.aio` stubs or service bases, so do not implement generated
+servicer methods as `async def` unless you add a custom adapter outside the
+generated companion.
+
## Install Dependencies
Install `grpcio` alongside `pyfory`. The generated companion imports `grpc`,
but
@@ -151,22 +158,57 @@ service Greeter {
Generated Python code follows `grpcio` conventions:
-- Unary stubs call `channel.unary_unary(...)`.
-- Server-streaming stubs return an iterator over response objects.
-- Client-streaming stubs accept an iterator of request objects.
-- Bidirectional stubs accept a request iterator and return a response iterator.
-- Servicer methods use snake_case names and return either a single response or
- an iterator, depending on the streaming shape.
+| IDL shape | Servicer method shape
| Stub method shape |
+| ----------------------------------------- |
------------------------------------------- |
---------------------------------- |
+| `rpc A (Req) returns (Res)` | returns one response object
| returns one response object |
+| `rpc A (Req) returns (stream Res)` | yields response objects
| returns an iterator of responses |
+| `rpc A (stream Req) returns (Res)` | consumes an iterator and returns
a response | accepts an iterator of requests |
+| `rpc A (stream Req) returns (stream Res)` | consumes and yields iterators
| accepts and returns iterators |
+
+Servicer methods use snake_case names, while generated descriptors preserve the
+exact IDL service and method names for the gRPC path.
-Example server-streaming implementation:
+Server implementations can use Python iterators directly:
```python
class Greeter(demo_greeter_grpc.GreeterServicer):
def lots_of_replies(self, request, context):
- for index in range(3):
- yield demo_greeter.HelloReply(
- reply=f"Hello {request.name}, response {index}"
- )
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+ yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}")
+
+ def lots_of_greetings(self, request_iterator, context):
+ names = [request.name for request in request_iterator]
+ return demo_greeter.HelloReply(reply=", ".join(names))
+
+ def chat(self, request_iterator, context):
+ for request in request_iterator:
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+```
+
+Generated clients use the standard `grpcio` streaming call shapes:
+
+```python
+with grpc.insecure_channel("localhost:50051") as channel:
+ stub = demo_greeter_grpc.GreeterStub(channel)
+
+ for reply in stub.lots_of_replies(
+ demo_greeter.HelloRequest(name="Fory")
+ ):
+ print(reply.reply)
+
+ def greeting_requests():
+ yield demo_greeter.HelloRequest(name="Ada")
+ yield demo_greeter.HelloRequest(name="Grace")
+
+ summary = stub.lots_of_greetings(greeting_requests())
+ print(summary.reply)
+
+ def chat_requests():
+ yield demo_greeter.HelloRequest(name="Fory")
+ yield demo_greeter.HelloRequest(name="RPC")
+
+ for reply in stub.chat(chat_requests()):
+ print(reply.reply)
```
## Operations
diff --git a/docs/guide/rust/grpc-support.md b/docs/guide/rust/grpc-support.md
index ed636344a1..4c9ca55f6c 100644
--- a/docs/guide/rust/grpc-support.md
+++ b/docs/guide/rust/grpc-support.md
@@ -33,7 +33,7 @@ consume protobuf message bytes directly.
Add `tonic` and `bytes` to the crate that compiles the generated service files.
Fory Rust crates do not add gRPC as a hard dependency. Add `tokio` for async
servers and clients, and `tokio-stream` when your service implementation needs
-to build streaming responses.
+to build streaming responses or request streams.
```toml
[dependencies]
@@ -180,8 +180,117 @@ Generated Rust code follows tonic conventions:
- The generated codec is used for every message frame, including streaming
frames.
-Use the generated trait signatures as the source of truth for the concrete
-associated stream types in your service implementation.
+Use the generated trait signatures as the source of truth for concrete
+associated stream types in your service implementation:
+
+```rust
+use demo_greeter::{HelloReply, HelloRequest};
+use demo_greeter_service::Greeter;
+use std::pin::Pin;
+use tokio_stream::{self as stream, Stream, StreamExt};
+use tonic::{Request, Response, Status};
+
+#[derive(Default)]
+struct MyGreeter;
+
+type ReplyStream =
+ Pin<Box<dyn Stream<Item = Result<HelloReply, Status>> + Send + 'static>>;
+
+#[tonic::async_trait]
+impl Greeter for MyGreeter {
+ type LotsOfRepliesStream = ReplyStream;
+ type ChatStream = ReplyStream;
+
+ async fn lots_of_replies(
+ &self,
+ request: Request<HelloRequest>,
+ ) -> Result<Response<Self::LotsOfRepliesStream>, Status> {
+ let name = request.into_inner().name;
+ let replies = vec![
+ Ok(HelloReply {
+ reply: format!("Hello, {name}"),
+ }),
+ Ok(HelloReply {
+ reply: format!("Welcome, {name}"),
+ }),
+ ];
+ Ok(Response::new(Box::pin(stream::iter(replies))))
+ }
+
+ async fn lots_of_greetings(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<HelloReply>, Status> {
+ let mut requests = request.into_inner();
+ let mut names = Vec::new();
+ while let Some(request) = requests.next().await {
+ names.push(request?.name);
+ }
+ Ok(Response::new(HelloReply {
+ reply: names.join(", "),
+ }))
+ }
+
+ async fn chat(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<Self::ChatStream>, Status> {
+ let replies = request.into_inner().map(|request| {
+ request.map(|request| HelloReply {
+ reply: format!("Hello, {}", request.name),
+ })
+ });
+ Ok(Response::new(Box::pin(replies)))
+ }
+}
+```
+
+Generated clients return tonic streaming responses:
+
+```rust
+use demo_greeter::HelloRequest;
+use demo_greeter_service_grpc::greeter_client::GreeterClient;
+use tokio_stream as stream;
+
+let mut client = GreeterClient::connect("http://[::1]:50051").await?;
+
+let mut replies = client
+ .lots_of_replies(HelloRequest {
+ name: "Fory".to_string(),
+ })
+ .await?
+ .into_inner();
+while let Some(reply) = replies.message().await? {
+ println!("{}", reply.reply);
+}
+
+let greetings = stream::iter(vec![
+ HelloRequest {
+ name: "Ada".to_string(),
+ },
+ HelloRequest {
+ name: "Grace".to_string(),
+ },
+]);
+let summary = client.lots_of_greetings(greetings).await?.into_inner();
+println!("{}", summary.reply);
+
+let chat_requests = stream::iter(vec![
+ HelloRequest {
+ name: "Fory".to_string(),
+ },
+ HelloRequest {
+ name: "RPC".to_string(),
+ },
+]);
+let mut chat = client.chat(chat_requests).await?.into_inner();
+while let Some(reply) = chat.message().await? {
+ println!("{}", reply.reply);
+}
+```
+
+The generated descriptors preserve the exact IDL service and method names for
+the gRPC path.
## Thread Safety and Payload Types
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md
index 7ceae91a1f..77ac4ff649 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/java/grpc-support.md
@@ -36,6 +36,11 @@ coroutine stub 和 service base 见 [Kotlin gRPC
支持](../kotlin/grpc-support.
因此请在应用中添加 grpc-java 依赖:
```xml
+<dependency>
+ <groupId>org.apache.fory</groupId>
+ <artifactId>fory-core</artifactId>
+ <version>${fory.version}</version>
+</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
@@ -88,14 +93,25 @@ service Greeter {
foryc service.fdl --java_out=./generated/java --grpc
```
-生成结果包含 model 类型、schema module 和 grpc-java service companion。生成的 method
descriptor
-使用 Fory-backed `MethodDescriptor.Marshaller`,因此不会调用 protobuf parser。
+该 schema 会生成:
+
+| 文件 | 用途 |
+| ------------------------ | ------------------------------------- |
+| `HelloRequest.java` | request 的 Fory model 类型 |
+| `HelloReply.java` | response 的 Fory model 类型 |
+| `GreeterForyModule.java` | 生成类型的 Fory 注册 module |
+| `GreeterGrpc.java` | grpc-java service base、stub 和 codec |
+
+生成的 method descriptor 使用 Fory-backed `MethodDescriptor.Marshaller`,因此不会调用
protobuf
+parser。
## 实现 Server
实现生成的 service base,并注册到标准 grpc-java `Server`:
```java
+package demo.greeter;
+
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
@@ -103,18 +119,24 @@ import io.grpc.stub.StreamObserver;
final class GreeterService extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(
- HelloRequest request,
- StreamObserver<HelloReply> responseObserver) {
- responseObserver.onNext(new HelloReply("Hello, " + request.name()));
+ HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+ HelloReply reply = new HelloReply();
+ reply.setReply("Hello, " + request.getName());
+ responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
-Server server = ServerBuilder
- .forPort(50051)
- .addService(new GreeterService())
- .build()
- .start();
+public final class GreeterServer {
+ public static void main(String[] args) throws Exception {
+ Server server =
+ ServerBuilder.forPort(50051)
+ .addService(new GreeterService())
+ .build()
+ .start();
+ server.awaitTermination();
+ }
+}
```
生成代码负责注册和序列化 request/response 类型,service 实现不需要手动创建 Fory 实例。
@@ -124,16 +146,30 @@ Server server = ServerBuilder
使用普通 grpc-java channel 和生成 stub:
```java
+package demo.greeter;
+
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
-ManagedChannel channel = ManagedChannelBuilder
- .forAddress("localhost", 50051)
- .usePlaintext()
- .build();
-
-GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
-HelloReply reply = stub.sayHello(new HelloRequest("Fory"));
+public final class GreeterClient {
+ public static void main(String[] args) {
+ ManagedChannel channel =
+ ManagedChannelBuilder.forAddress("localhost", 50051)
+ .usePlaintext()
+ .build();
+ try {
+ GreeterGrpc.GreeterBlockingStub stub =
+ GreeterGrpc.newBlockingStub(channel);
+
+ HelloRequest request = new HelloRequest();
+ request.setName("Fory");
+ HelloReply reply = stub.sayHello(request);
+ System.out.println(reply.getReply());
+ } finally {
+ channel.shutdownNow();
+ }
+ }
+}
```
Channel lifecycle、deadline、credential、metadata、load balancing、retry 和
interceptor 都保持
@@ -154,10 +190,180 @@ service Greeter {
生成 Java service 方法遵循 grpc-java 约定:
-- Unary 方法使用 request 参数和 response `StreamObserver`。
-- Server-streaming 方法向 response observer 多次 `onNext`。
-- Client-streaming 与 bidirectional streaming 返回 request `StreamObserver`。
-- Blocking stub 暴露 grpc-java 支持的 blocking API。
+| IDL shape | Server 方法形态
| Client 方法形态 |
+| ----------------------------------------- |
------------------------------------------------------- |
---------------------------------- |
+| `rpc A (Req) returns (Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking、async、future unary stub |
+| `rpc A (Req) returns (stream Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking iterator 或 async observer |
+| `rpc A (stream Req) returns (Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+| `rpc A (stream Req) returns (stream Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+
+Server 可以直接实现生成的 streaming 方法:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayList;
+import java.util.List;
+
+final class GreeterService extends GreeterGrpc.GreeterImplBase {
+ @Override
+ public void lotsOfReplies(
+ HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+ HelloReply first = new HelloReply();
+ first.setReply("Hello, " + request.getName());
+ responseObserver.onNext(first);
+
+ HelloReply second = new HelloReply();
+ second.setReply("Welcome, " + request.getName());
+ responseObserver.onNext(second);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> lotsOfGreetings(
+ StreamObserver<HelloReply> responseObserver) {
+ List<String> names = new ArrayList<>();
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ names.add(request.getName());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ HelloReply reply = new HelloReply();
+ reply.setReply(String.join(", ", names));
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> chat(
+ StreamObserver<HelloReply> responseObserver) {
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ HelloReply reply = new HelloReply();
+ reply.setReply("Hello, " + request.getName());
+ responseObserver.onNext(reply);
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+ };
+ }
+}
+```
+
+生成的 client 返回标准 grpc-java 调用形态:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+final class StreamingClient {
+ private final GreeterGrpc.GreeterBlockingStub blockingStub;
+ private final GreeterGrpc.GreeterStub asyncStub;
+
+ StreamingClient(
+ GreeterGrpc.GreeterBlockingStub blockingStub,
+ GreeterGrpc.GreeterStub asyncStub) {
+ this.blockingStub = blockingStub;
+ this.asyncStub = asyncStub;
+ }
+
+ void run() throws InterruptedException {
+ Iterator<HelloReply> replies =
+ blockingStub.lotsOfReplies(newRequest("Fory"));
+ while (replies.hasNext()) {
+ System.out.println(replies.next().getReply());
+ }
+
+ CountDownLatch greetingsDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> greetings =
+ asyncStub.lotsOfGreetings(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ greetingsDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ greetingsDone.countDown();
+ }
+ });
+ greetings.onNext(newRequest("Ada"));
+ greetings.onNext(newRequest("Grace"));
+ greetings.onCompleted();
+ greetingsDone.await(5, TimeUnit.SECONDS);
+
+ CountDownLatch chatDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> chat =
+ asyncStub.chat(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ chatDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ chatDone.countDown();
+ }
+ });
+ chat.onNext(newRequest("Fory"));
+ chat.onCompleted();
+ chatDone.await(5, TimeUnit.SECONDS);
+ }
+
+ private static HelloRequest newRequest(String name) {
+ HelloRequest request = new HelloRequest();
+ request.setName(name);
+ return request;
+ }
+}
+```
+
+生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 gRPC path。
+
+## 运维语义
+
+生成的 service code 只替换 request/response 序列化。常规 gRPC 运维能力仍由 grpc-java 提供:
+
+- Deadline 和取消
+- TLS 和认证
+- 名称解析与负载均衡
+- Client/server interceptor
+- Status code 和 metadata
+- Channel 池化与生命周期管理
## 故障排查
@@ -166,6 +372,11 @@ service Greeter {
添加上面的 grpc-java 依赖。生成的 Fory service 文件导入 grpc-java API,但 Fory Java artifact
不会自动依赖 gRPC。
+### `UNIMPLEMENTED`
+
+确认生成的 service 实现已通过 `ServerBuilder.addService(...)` 注册,并且 client 与 server 来自相同
+package、service 和 method 名称。
+
### Protobuf Client 无法解码
Fory gRPC companion 不使用 protobuf wire encoding。请使用 Fory 生成的 client 调用 Fory 生成的
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md
index c0df229c4f..261f310900 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/javascript/grpc-support.md
@@ -22,6 +22,10 @@ license: |
Fory 可以为包含 service 定义的 schema 生成 JavaScript service companion。生成的 service code
使用普通 gRPC transport,但 request 和 response 对象使用 Fory 序列化,而不是 protobuf。
+当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且两端都期望
+Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 protobuf client、
+reflection 工具或期望 protobuf message bytes 的组件消费,请使用标准 protobuf gRPC 代码生成。
+
使用 `--grpc` 生成 Node.js server/client 代码;使用 `--grpc-web` 生成调用 gRPC-Web 兼容
server 或 proxy 的浏览器 client。
@@ -45,6 +49,9 @@ Fory 不会把 gRPC package 作为硬依赖。只需添加应用实际使用的
## 定义 Service
+Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。Fory IDL
service
+示例如下:
+
```protobuf
package demo.greeter;
@@ -186,12 +193,113 @@ service Greeter {
Node.js server 实现使用普通 `@grpc/grpc-js` streaming call object。生成 companion 只负责把
Fory
payload 接入 gRPC,deadline、credential、metadata、interceptor 和错误语义仍遵循 gRPC 库行为。
+```ts
+const greeter: GreeterHandlers = {
+ sayHello(call, callback) {
+ callback(null, { reply: `Hello, ${call.request.name}` });
+ },
+
+ lotsOfReplies(call) {
+ call.write({ reply: `Hello, ${call.request.name}` });
+ call.write({ reply: `Welcome, ${call.request.name}` });
+ call.end();
+ },
+
+ lotsOfGreetings(call, callback) {
+ const names: string[] = [];
+ call.on("data", (request) => {
+ names.push(request.name);
+ });
+ call.on("end", () => {
+ callback(null, { reply: `Hello, ${names.join(", ")}` });
+ });
+ },
+
+ chat(call) {
+ call.on("data", (request) => {
+ call.write({ reply: `Hello, ${request.name}` });
+ });
+ call.on("end", () => {
+ call.end();
+ });
+ },
+};
+```
+
+Node.js client 使用与 RPC shape 对应的生成方法:
+
+```ts
+const replies = client.lotsOfReplies({ name: "Fory" });
+replies.on("data", (reply) => {
+ console.log(reply.reply);
+});
+
+const greetings = client.lotsOfGreetings((error, reply) => {
+ if (error) {
+ throw error;
+ }
+ console.log(reply.reply);
+});
+greetings.write({ name: "Alice" });
+greetings.write({ name: "Bob" });
+greetings.end();
+
+const chat = client.chat();
+chat.on("data", (reply) => {
+ console.log(reply.reply);
+});
+chat.write({ name: "Alice" });
+chat.write({ name: "Bob" });
+chat.end();
+```
+
+包含 server-streaming 方法的 service 中,生成的 gRPC-Web companion 默认使用 `grpcwebtext`
+编码格式。只有 unary 方法的 service 默认使用 `grpcweb`。也可以显式指定:
+
+```ts
+const client = createGreeterWebClient("https://api.example.com", {
+ wireFormat: "grpcwebtext",
+});
+```
+
+浏览器 client 可以用 callback client 消费 server-streaming RPC:
+
+```ts
+const stream = client.lotsOfReplies({ name: "Fory" });
+
+stream.on("data", (reply) => {
+ console.log(reply.reply);
+});
+stream.on("error", (error) => {
+ console.error(error.message);
+});
+stream.on("end", () => {
+ console.log("stream ended");
+});
+```
+
+## 运维语义
+
+生成的 service code 只替换 request/response 序列化。常规 gRPC 运维能力仍由 transport package
+提供:
+
+- TLS 和 credential
+- Metadata 和 status code
+- Deadline 和取消
+- Client/server interceptor
+- 负载均衡和部署相关 proxy 配置
+
## 故障排查
### 缺少 gRPC Package
Node.js 添加 `@grpc/grpc-js`,浏览器添加 `grpc-web`。生成 model 仍需要 `@apache-fory/core`。
+### gRPC-Web Client-Streaming 或 Bidirectional RPC 被拒绝
+
+gRPC-Web 不支持 client-streaming 或 bidirectional streaming。对于这些 shape,请使用 `--grpc`
+生成 Node.js companion,或只向浏览器 client 暴露 unary 和 server-streaming 方法。
+
### Protobuf Client 无法读取响应
Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。请在两端使用同一份
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md
index e369199092..d2e81792d3 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/kotlin/grpc-support.md
@@ -22,7 +22,11 @@ license: |
Fory IDL 可以生成 Kotlin coroutine gRPC companion。生成的 gRPC 文件使用普通 grpc-java 和
grpc-kotlin API,每个 request/response message 使用 Fory 序列化。
-## 依赖
+当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且两端都期望
+Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 protobuf client、
+reflection 工具或期望 protobuf message bytes 的组件消费,请使用标准 protobuf gRPC 代码生成。
+
+## 添加依赖
在编译生成源码的应用或 service module 中添加 Fory Kotlin、KSP、grpc-java、grpc-kotlin、
coroutines 和一个 grpc-java transport:
@@ -48,7 +52,10 @@ dependencies {
如果应用已经统一使用其他 grpc-java transport,可以替换 `grpc-netty-shaded`。生成的 Kotlin
Fory gRPC 不需要 `grpc-protobuf` 来编码 payload。
-## 生成代码
+## 定义 Service
+
+Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。Fory IDL
service
+示例如下:
```protobuf
package demo.greeter;
@@ -66,17 +73,25 @@ service Greeter {
}
```
-运行:
+使用 `--grpc` 生成 Kotlin model 和 gRPC companion:
```bash
foryc service.fdl --kotlin_out=./generated/kotlin --grpc
```
-Compiler 会生成 Kotlin model 文件、schema module(例如 `ServiceForyModule.kt`)以及 service
-companion(例如 `GreeterGrpcKt.kt`)。编译生成 model 文件时需要运行 KSP,以便 runtime 可以使用
-schema serializer。
+该 schema 会生成:
+
+| 文件 | 用途 |
+| ------------------- | ------------------------------------- |
+| `HelloRequest.kt` | request 的 Fory model 类型 |
+| `HelloReply.kt` | response 的 Fory model 类型 |
+| `ServiceForyModule` | 生成类型的 Fory 注册 module |
+| `GreeterGrpcKt.kt` | Coroutine service base、stub 和 codec |
-## Server
+编译生成 model 文件时需要运行 KSP,以便 runtime 可以使用 schema serializer。生成的 request 和
+response 类型由 service companion 使用的 schema module 注册,service 实现不需要手动注册
serializer。
+
+## 实现 Server
实现生成的 coroutine base class,并注册到普通 grpc-java server:
@@ -100,7 +115,7 @@ val server = ServerBuilder
未实现的生成方法会返回 gRPC `UNIMPLEMENTED`。Service 方法抛出的异常遵循 grpc-kotlin server 行为。
-## Client
+## 创建 Client
从 grpc-java channel 直接构造生成的 coroutine stub:
@@ -121,7 +136,18 @@ val reply = stub.sayHello(HelloRequest(name = "Fory"))
Channel 构造、关闭、deadline、credential、interceptor、load balancing、retry 和 server
lifecycle
仍由 grpc-java/grpc-kotlin 负责。
-## Streaming
+## Streaming RPC
+
+Fory service 可以使用 gRPC 的所有 streaming shape:
+
+```protobuf
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+ rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+ rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+ rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
Streaming RPC 使用 `kotlinx.coroutines.flow.Flow`。
@@ -134,6 +160,77 @@ Streaming RPC 使用 `kotlinx.coroutines.flow.Flow`。
生成 method path 保留 schema 中的 service 和 method 名称,例如
`/demo.greeter.Greeter/SayHello`。
+Server 可以直接返回或消费 `Flow`:
+
+```kotlin
+import demo.greeter.GreeterGrpcKt
+import demo.greeter.HelloReply
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+
+class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
+ override fun lotsOfReplies(request: HelloRequest): Flow<HelloReply> = flow {
+ emit(HelloReply(reply = "Hello, ${request.name}"))
+ emit(HelloReply(reply = "Welcome, ${request.name}"))
+ }
+
+ override suspend fun lotsOfGreetings(
+ requests: Flow<HelloRequest>
+ ): HelloReply {
+ val names = requests.toList().joinToString(", ") { it.name }
+ return HelloReply(reply = names)
+ }
+
+ override fun chat(requests: Flow<HelloRequest>): Flow<HelloReply> =
+ requests.map { request ->
+ HelloReply(reply = "Hello, ${request.name}")
+ }
+}
+```
+
+生成的 client 暴露对应的 coroutine 和 `Flow` API:
+
+```kotlin
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.flowOf
+
+stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply ->
+ println(reply.reply)
+}
+
+val summary = stub.lotsOfGreetings(
+ flowOf(
+ HelloRequest(name = "Ada"),
+ HelloRequest(name = "Grace"),
+ )
+)
+println(summary.reply)
+
+stub.chat(
+ flowOf(
+ HelloRequest(name = "Fory"),
+ HelloRequest(name = "RPC"),
+ )
+).collect { reply ->
+ println(reply.reply)
+}
+```
+
+## 运维语义
+
+生成的 service code 只替换 request/response 序列化。常规 gRPC 运维能力仍由 grpc-java 和
+grpc-kotlin 提供:
+
+- Deadline 和取消
+- TLS 和认证
+- 名称解析与负载均衡
+- Client/server interceptor
+- Status code 和 metadata
+- Channel 池化与生命周期管理
+
## 互操作性
生成的 Kotlin service companion 在 gRPC frame 中使用 Fory 二进制 payload。它可以与从同一 schema
@@ -142,19 +239,19 @@ client 无法解码这些 payload。
## 故障排查
-**缺少生成的 service 文件**
+### 缺少生成的 service 文件
同时传入 `--grpc` 和 `--kotlin_out`。没有 service 定义的 schema 只会生成 model 文件和 schema
module。
-**运行时找不到 serializer class**
+### 运行时找不到 serializer class
确保生成的 Kotlin model source 运行了 KSP,并且 `fory-kotlin-ksp` 与 `fory-kotlin` 使用同一个
Fory 版本。
-**gRPC 类无法解析**
+### gRPC 类无法解析
向应用 module 添加 grpc-java 和 grpc-kotlin 依赖。Fory Kotlin artifact 不会自动添加这些依赖。
-**Protobuf client 无法读取响应**
+### Protobuf client 无法读取响应
Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。请在两端使用同一份
schema 生成的 Fory gRPC companion。
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md
index a6aed054c4..a144babf3f 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/python/grpc-support.md
@@ -26,6 +26,11 @@ Fory 可以为包含 service 定义的 schema 生成 Python gRPC companion modul
传输语义与 Fory payload 编码时,可以使用这种模式。如果客户端或工具必须直接消费 protobuf
message bytes,请使用标准 protobuf gRPC 代码生成。
+当前生成的 Python companion 面向同步 `grpcio` API。请使用普通 `def` servicer 方法、
+`grpc.server(...)`、`grpc.insecure_channel(...)`,并用 Python iterator/generator 处理
+streaming RPC。Compiler 不会生成 `grpc.aio` stub 或 service base,因此不要把生成 servicer
+方法实现成 `async def`,除非你在生成 companion 外自行封装 adapter。
+
## 添加依赖
```bash
@@ -59,29 +64,42 @@ service Greeter {
foryc service.fdl --python_out=./generated/python --grpc
```
-生成结果通常包含 model module 和 `<module>_grpc.py` companion。Companion 会导入生成的
-`to_bytes`/`from_bytes` 辅助逻辑,并把它们接入 `grpcio` serializer/deserializer。
+该 schema 会生成:
+
+| 文件 | 用途 |
+| ---------------------- | --------------------------------- |
+| `demo_greeter.py` | Fory dataclass 和注册辅助逻辑 |
+| `demo_greeter_grpc.py` | `grpcio` stub、servicer base 和注册函数 |
+
+Module 名称来自 Fory package,点号会替换成下划线;没有 package 的 schema 使用 `generated.py` 和
+`generated_grpc.py`。
## 实现 Server
```python
from concurrent import futures
+
import grpc
-from generated.python.service import HelloReply
-from generated.python import service_grpc
+import demo_greeter
+import demo_greeter_grpc
-class Greeter(service_grpc.GreeterServicer):
- def SayHello(self, request, context):
- return HelloReply(reply=f"Hello, {request.name}")
+class Greeter(demo_greeter_grpc.GreeterServicer):
+ def say_hello(self, request, context):
+ return demo_greeter.HelloReply(reply=f"Hello, {request.name}")
-server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
-service_grpc.add_GreeterServicer_to_server(Greeter(), server)
-server.add_insecure_port("[::]:50051")
-server.start()
-server.wait_for_termination()
+def serve():
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=8))
+ demo_greeter_grpc.add_servicer(Greeter(), server)
+ server.add_insecure_port("[::]:50051")
+ server.start()
+ server.wait_for_termination()
+
+
+if __name__ == "__main__":
+ serve()
```
## 创建 Client
@@ -89,13 +107,19 @@ server.wait_for_termination()
```python
import grpc
-from generated.python.service import HelloRequest
-from generated.python import service_grpc
+import demo_greeter
+import demo_greeter_grpc
-with grpc.insecure_channel("localhost:50051") as channel:
- stub = service_grpc.GreeterStub(channel)
- reply = stub.SayHello(HelloRequest(name="Fory"))
- print(reply.reply)
+
+def main():
+ with grpc.insecure_channel("localhost:50051") as channel:
+ stub = demo_greeter_grpc.GreeterStub(channel)
+ reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory"))
+ print(reply.reply)
+
+
+if __name__ == "__main__":
+ main()
```
Channel、credential、deadline、metadata、interceptor、retry 和 server lifecycle 都保持
`grpcio`
@@ -114,8 +138,71 @@ service Greeter {
}
```
-生成 Python companion 使用 `grpcio` 的 iterator/generator 约定处理 streaming。每个 message
-frame 都通过 Fory serializer/deserializer 编码。
+生成 Python companion 遵循 `grpcio` 的 iterator/generator 约定:
+
+| IDL shape | Servicer 方法形态
| Stub 方法形态 |
+| ----------------------------------------- |
----------------------------------------- | ------------------------- |
+| `rpc A (Req) returns (Res)` | 返回一个 response 对象
| 返回一个 response 对象 |
+| `rpc A (Req) returns (stream Res)` | yield 多个 response 对象
| 返回 response iterator |
+| `rpc A (stream Req) returns (Res)` | 消费 request iterator 并返回一个
response | 接收 request iterator |
+| `rpc A (stream Req) returns (stream Res)` | 消费 request iterator 并 yield
response | 接收并返回 iterator |
+
+Servicer 方法使用 snake_case 名称;生成 descriptor 会保留 IDL 中的 service 和 method 名称作为
+gRPC path。每个 message frame 都通过 Fory serializer/deserializer 编码。
+
+Server 可以直接使用 Python iterator:
+
+```python
+class Greeter(demo_greeter_grpc.GreeterServicer):
+ def lots_of_replies(self, request, context):
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+ yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}")
+
+ def lots_of_greetings(self, request_iterator, context):
+ names = [request.name for request in request_iterator]
+ return demo_greeter.HelloReply(reply=", ".join(names))
+
+ def chat(self, request_iterator, context):
+ for request in request_iterator:
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+```
+
+生成的 client 使用标准 `grpcio` streaming 调用形态:
+
+```python
+with grpc.insecure_channel("localhost:50051") as channel:
+ stub = demo_greeter_grpc.GreeterStub(channel)
+
+ for reply in stub.lots_of_replies(
+ demo_greeter.HelloRequest(name="Fory")
+ ):
+ print(reply.reply)
+
+ def greeting_requests():
+ yield demo_greeter.HelloRequest(name="Ada")
+ yield demo_greeter.HelloRequest(name="Grace")
+
+ summary = stub.lots_of_greetings(greeting_requests())
+ print(summary.reply)
+
+ def chat_requests():
+ yield demo_greeter.HelloRequest(name="Fory")
+ yield demo_greeter.HelloRequest(name="RPC")
+
+ for reply in stub.chat(chat_requests()):
+ print(reply.reply)
+```
+
+## 运维语义
+
+生成的 service companion 只提供 Fory serialization callback。运维行为仍遵循标准 `grpcio`:
+
+- Deadline 和取消
+- TLS 和认证 credential
+- Client/server interceptor
+- Status code、details 和 metadata
+- Channel/server 生命周期
+- 同步 server 的线程池大小
## 故障排查
@@ -127,6 +214,10 @@ frame 都通过 Fory serializer/deserializer 编码。
pip install grpcio
```
+### `UNIMPLEMENTED`
+
+确认生成的 servicer 已注册到 server,并且 client 与 server 来自相同 package、service 和 method 名称。
+
### Protobuf Client 无法解码
Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。两端应使用同一份
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md
index c3dc73452d..b6c603b61f 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/current/guide/rust/grpc-support.md
@@ -30,7 +30,7 @@ protobuf message bytes,请继续使用标准 protobuf gRPC 代码生成。
编译生成 service 文件的 crate 需要添加 `tonic` 和 `bytes`。Fory Rust crate 不会将
gRPC 作为硬依赖。异步 server/client 还需要 `tokio`;如果 service 实现需要构造
-streaming response,可添加 `tokio-stream`。
+streaming response 或 request stream,可添加 `tokio-stream`。
```toml
[dependencies]
@@ -168,6 +168,118 @@ service Greeter {
- 生成的 client module 暴露与 service 方法对应的异步方法。
- 每个 message frame 都使用生成 codec,包括 streaming frame。
+生成 trait 签名是 service 实现中具体 associated stream type 的准确信息来源:
+
+```rust
+use demo_greeter::{HelloReply, HelloRequest};
+use demo_greeter_service::Greeter;
+use std::pin::Pin;
+use tokio_stream::{self as stream, Stream, StreamExt};
+use tonic::{Request, Response, Status};
+
+#[derive(Default)]
+struct MyGreeter;
+
+type ReplyStream =
+ Pin<Box<dyn Stream<Item = Result<HelloReply, Status>> + Send + 'static>>;
+
+#[tonic::async_trait]
+impl Greeter for MyGreeter {
+ type LotsOfRepliesStream = ReplyStream;
+ type ChatStream = ReplyStream;
+
+ async fn lots_of_replies(
+ &self,
+ request: Request<HelloRequest>,
+ ) -> Result<Response<Self::LotsOfRepliesStream>, Status> {
+ let name = request.into_inner().name;
+ let replies = vec![
+ Ok(HelloReply {
+ reply: format!("Hello, {name}"),
+ }),
+ Ok(HelloReply {
+ reply: format!("Welcome, {name}"),
+ }),
+ ];
+ Ok(Response::new(Box::pin(stream::iter(replies))))
+ }
+
+ async fn lots_of_greetings(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<HelloReply>, Status> {
+ let mut requests = request.into_inner();
+ let mut names = Vec::new();
+ while let Some(request) = requests.next().await {
+ names.push(request?.name);
+ }
+ Ok(Response::new(HelloReply {
+ reply: names.join(", "),
+ }))
+ }
+
+ async fn chat(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<Self::ChatStream>, Status> {
+ let replies = request.into_inner().map(|request| {
+ request.map(|request| HelloReply {
+ reply: format!("Hello, {}", request.name),
+ })
+ });
+ Ok(Response::new(Box::pin(replies)))
+ }
+}
+```
+
+生成的 client 返回 tonic streaming response:
+
+```rust
+use demo_greeter::HelloRequest;
+use demo_greeter_service_grpc::greeter_client::GreeterClient;
+use tokio_stream as stream;
+
+let mut client = GreeterClient::connect("http://[::1]:50051").await?;
+
+let mut replies = client
+ .lots_of_replies(HelloRequest {
+ name: "Fory".to_string(),
+ })
+ .await?
+ .into_inner();
+while let Some(reply) = replies.message().await? {
+ println!("{}", reply.reply);
+}
+
+let greetings = stream::iter(vec![
+ HelloRequest {
+ name: "Ada".to_string(),
+ },
+ HelloRequest {
+ name: "Grace".to_string(),
+ },
+]);
+let summary = client.lots_of_greetings(greetings).await?.into_inner();
+println!("{}", summary.reply);
+
+let chat_requests = stream::iter(vec![
+ HelloRequest {
+ name: "Fory".to_string(),
+ },
+ HelloRequest {
+ name: "RPC".to_string(),
+ },
+]);
+let mut chat = client.chat(chat_requests).await?.into_inner();
+while let Some(reply) = chat.message().await? {
+ println!("{}", reply.reply);
+}
+```
+
+生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 gRPC path。
+
+## 线程安全与 Payload 类型
+
Rust gRPC payload 必须满足 `Send + 'static`,这样 tonic 才能在线程间移动 request/response。
如果 request 或 response schema 使用非线程安全的引用元信息,Rust gRPC 生成会拒绝该 service。
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/java/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/java/grpc-support.md
index 7ceae91a1f..77ac4ff649 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/java/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/java/grpc-support.md
@@ -36,6 +36,11 @@ coroutine stub 和 service base 见 [Kotlin gRPC
支持](../kotlin/grpc-support.
因此请在应用中添加 grpc-java 依赖:
```xml
+<dependency>
+ <groupId>org.apache.fory</groupId>
+ <artifactId>fory-core</artifactId>
+ <version>${fory.version}</version>
+</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
@@ -88,14 +93,25 @@ service Greeter {
foryc service.fdl --java_out=./generated/java --grpc
```
-生成结果包含 model 类型、schema module 和 grpc-java service companion。生成的 method
descriptor
-使用 Fory-backed `MethodDescriptor.Marshaller`,因此不会调用 protobuf parser。
+该 schema 会生成:
+
+| 文件 | 用途 |
+| ------------------------ | ------------------------------------- |
+| `HelloRequest.java` | request 的 Fory model 类型 |
+| `HelloReply.java` | response 的 Fory model 类型 |
+| `GreeterForyModule.java` | 生成类型的 Fory 注册 module |
+| `GreeterGrpc.java` | grpc-java service base、stub 和 codec |
+
+生成的 method descriptor 使用 Fory-backed `MethodDescriptor.Marshaller`,因此不会调用
protobuf
+parser。
## 实现 Server
实现生成的 service base,并注册到标准 grpc-java `Server`:
```java
+package demo.greeter;
+
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
@@ -103,18 +119,24 @@ import io.grpc.stub.StreamObserver;
final class GreeterService extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(
- HelloRequest request,
- StreamObserver<HelloReply> responseObserver) {
- responseObserver.onNext(new HelloReply("Hello, " + request.name()));
+ HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+ HelloReply reply = new HelloReply();
+ reply.setReply("Hello, " + request.getName());
+ responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
-Server server = ServerBuilder
- .forPort(50051)
- .addService(new GreeterService())
- .build()
- .start();
+public final class GreeterServer {
+ public static void main(String[] args) throws Exception {
+ Server server =
+ ServerBuilder.forPort(50051)
+ .addService(new GreeterService())
+ .build()
+ .start();
+ server.awaitTermination();
+ }
+}
```
生成代码负责注册和序列化 request/response 类型,service 实现不需要手动创建 Fory 实例。
@@ -124,16 +146,30 @@ Server server = ServerBuilder
使用普通 grpc-java channel 和生成 stub:
```java
+package demo.greeter;
+
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
-ManagedChannel channel = ManagedChannelBuilder
- .forAddress("localhost", 50051)
- .usePlaintext()
- .build();
-
-GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
-HelloReply reply = stub.sayHello(new HelloRequest("Fory"));
+public final class GreeterClient {
+ public static void main(String[] args) {
+ ManagedChannel channel =
+ ManagedChannelBuilder.forAddress("localhost", 50051)
+ .usePlaintext()
+ .build();
+ try {
+ GreeterGrpc.GreeterBlockingStub stub =
+ GreeterGrpc.newBlockingStub(channel);
+
+ HelloRequest request = new HelloRequest();
+ request.setName("Fory");
+ HelloReply reply = stub.sayHello(request);
+ System.out.println(reply.getReply());
+ } finally {
+ channel.shutdownNow();
+ }
+ }
+}
```
Channel lifecycle、deadline、credential、metadata、load balancing、retry 和
interceptor 都保持
@@ -154,10 +190,180 @@ service Greeter {
生成 Java service 方法遵循 grpc-java 约定:
-- Unary 方法使用 request 参数和 response `StreamObserver`。
-- Server-streaming 方法向 response observer 多次 `onNext`。
-- Client-streaming 与 bidirectional streaming 返回 request `StreamObserver`。
-- Blocking stub 暴露 grpc-java 支持的 blocking API。
+| IDL shape | Server 方法形态
| Client 方法形态 |
+| ----------------------------------------- |
------------------------------------------------------- |
---------------------------------- |
+| `rpc A (Req) returns (Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking、async、future unary stub |
+| `rpc A (Req) returns (stream Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking iterator 或 async observer |
+| `rpc A (stream Req) returns (Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+| `rpc A (stream Req) returns (stream Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+
+Server 可以直接实现生成的 streaming 方法:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayList;
+import java.util.List;
+
+final class GreeterService extends GreeterGrpc.GreeterImplBase {
+ @Override
+ public void lotsOfReplies(
+ HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+ HelloReply first = new HelloReply();
+ first.setReply("Hello, " + request.getName());
+ responseObserver.onNext(first);
+
+ HelloReply second = new HelloReply();
+ second.setReply("Welcome, " + request.getName());
+ responseObserver.onNext(second);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> lotsOfGreetings(
+ StreamObserver<HelloReply> responseObserver) {
+ List<String> names = new ArrayList<>();
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ names.add(request.getName());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ HelloReply reply = new HelloReply();
+ reply.setReply(String.join(", ", names));
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> chat(
+ StreamObserver<HelloReply> responseObserver) {
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ HelloReply reply = new HelloReply();
+ reply.setReply("Hello, " + request.getName());
+ responseObserver.onNext(reply);
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+ };
+ }
+}
+```
+
+生成的 client 返回标准 grpc-java 调用形态:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+final class StreamingClient {
+ private final GreeterGrpc.GreeterBlockingStub blockingStub;
+ private final GreeterGrpc.GreeterStub asyncStub;
+
+ StreamingClient(
+ GreeterGrpc.GreeterBlockingStub blockingStub,
+ GreeterGrpc.GreeterStub asyncStub) {
+ this.blockingStub = blockingStub;
+ this.asyncStub = asyncStub;
+ }
+
+ void run() throws InterruptedException {
+ Iterator<HelloReply> replies =
+ blockingStub.lotsOfReplies(newRequest("Fory"));
+ while (replies.hasNext()) {
+ System.out.println(replies.next().getReply());
+ }
+
+ CountDownLatch greetingsDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> greetings =
+ asyncStub.lotsOfGreetings(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ greetingsDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ greetingsDone.countDown();
+ }
+ });
+ greetings.onNext(newRequest("Ada"));
+ greetings.onNext(newRequest("Grace"));
+ greetings.onCompleted();
+ greetingsDone.await(5, TimeUnit.SECONDS);
+
+ CountDownLatch chatDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> chat =
+ asyncStub.chat(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ chatDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ chatDone.countDown();
+ }
+ });
+ chat.onNext(newRequest("Fory"));
+ chat.onCompleted();
+ chatDone.await(5, TimeUnit.SECONDS);
+ }
+
+ private static HelloRequest newRequest(String name) {
+ HelloRequest request = new HelloRequest();
+ request.setName(name);
+ return request;
+ }
+}
+```
+
+生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 gRPC path。
+
+## 运维语义
+
+生成的 service code 只替换 request/response 序列化。常规 gRPC 运维能力仍由 grpc-java 提供:
+
+- Deadline 和取消
+- TLS 和认证
+- 名称解析与负载均衡
+- Client/server interceptor
+- Status code 和 metadata
+- Channel 池化与生命周期管理
## 故障排查
@@ -166,6 +372,11 @@ service Greeter {
添加上面的 grpc-java 依赖。生成的 Fory service 文件导入 grpc-java API,但 Fory Java artifact
不会自动依赖 gRPC。
+### `UNIMPLEMENTED`
+
+确认生成的 service 实现已通过 `ServerBuilder.addService(...)` 注册,并且 client 与 server 来自相同
+package、service 和 method 名称。
+
### Protobuf Client 无法解码
Fory gRPC companion 不使用 protobuf wire encoding。请使用 Fory 生成的 client 调用 Fory 生成的
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/javascript/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/javascript/grpc-support.md
index c0df229c4f..261f310900 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/javascript/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/javascript/grpc-support.md
@@ -22,6 +22,10 @@ license: |
Fory 可以为包含 service 定义的 schema 生成 JavaScript service companion。生成的 service code
使用普通 gRPC transport,但 request 和 response 对象使用 Fory 序列化,而不是 protobuf。
+当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且两端都期望
+Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 protobuf client、
+reflection 工具或期望 protobuf message bytes 的组件消费,请使用标准 protobuf gRPC 代码生成。
+
使用 `--grpc` 生成 Node.js server/client 代码;使用 `--grpc-web` 生成调用 gRPC-Web 兼容
server 或 proxy 的浏览器 client。
@@ -45,6 +49,9 @@ Fory 不会把 gRPC package 作为硬依赖。只需添加应用实际使用的
## 定义 Service
+Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。Fory IDL
service
+示例如下:
+
```protobuf
package demo.greeter;
@@ -186,12 +193,113 @@ service Greeter {
Node.js server 实现使用普通 `@grpc/grpc-js` streaming call object。生成 companion 只负责把
Fory
payload 接入 gRPC,deadline、credential、metadata、interceptor 和错误语义仍遵循 gRPC 库行为。
+```ts
+const greeter: GreeterHandlers = {
+ sayHello(call, callback) {
+ callback(null, { reply: `Hello, ${call.request.name}` });
+ },
+
+ lotsOfReplies(call) {
+ call.write({ reply: `Hello, ${call.request.name}` });
+ call.write({ reply: `Welcome, ${call.request.name}` });
+ call.end();
+ },
+
+ lotsOfGreetings(call, callback) {
+ const names: string[] = [];
+ call.on("data", (request) => {
+ names.push(request.name);
+ });
+ call.on("end", () => {
+ callback(null, { reply: `Hello, ${names.join(", ")}` });
+ });
+ },
+
+ chat(call) {
+ call.on("data", (request) => {
+ call.write({ reply: `Hello, ${request.name}` });
+ });
+ call.on("end", () => {
+ call.end();
+ });
+ },
+};
+```
+
+Node.js client 使用与 RPC shape 对应的生成方法:
+
+```ts
+const replies = client.lotsOfReplies({ name: "Fory" });
+replies.on("data", (reply) => {
+ console.log(reply.reply);
+});
+
+const greetings = client.lotsOfGreetings((error, reply) => {
+ if (error) {
+ throw error;
+ }
+ console.log(reply.reply);
+});
+greetings.write({ name: "Alice" });
+greetings.write({ name: "Bob" });
+greetings.end();
+
+const chat = client.chat();
+chat.on("data", (reply) => {
+ console.log(reply.reply);
+});
+chat.write({ name: "Alice" });
+chat.write({ name: "Bob" });
+chat.end();
+```
+
+包含 server-streaming 方法的 service 中,生成的 gRPC-Web companion 默认使用 `grpcwebtext`
+编码格式。只有 unary 方法的 service 默认使用 `grpcweb`。也可以显式指定:
+
+```ts
+const client = createGreeterWebClient("https://api.example.com", {
+ wireFormat: "grpcwebtext",
+});
+```
+
+浏览器 client 可以用 callback client 消费 server-streaming RPC:
+
+```ts
+const stream = client.lotsOfReplies({ name: "Fory" });
+
+stream.on("data", (reply) => {
+ console.log(reply.reply);
+});
+stream.on("error", (error) => {
+ console.error(error.message);
+});
+stream.on("end", () => {
+ console.log("stream ended");
+});
+```
+
+## 运维语义
+
+生成的 service code 只替换 request/response 序列化。常规 gRPC 运维能力仍由 transport package
+提供:
+
+- TLS 和 credential
+- Metadata 和 status code
+- Deadline 和取消
+- Client/server interceptor
+- 负载均衡和部署相关 proxy 配置
+
## 故障排查
### 缺少 gRPC Package
Node.js 添加 `@grpc/grpc-js`,浏览器添加 `grpc-web`。生成 model 仍需要 `@apache-fory/core`。
+### gRPC-Web Client-Streaming 或 Bidirectional RPC 被拒绝
+
+gRPC-Web 不支持 client-streaming 或 bidirectional streaming。对于这些 shape,请使用 `--grpc`
+生成 Node.js companion,或只向浏览器 client 暴露 unary 和 server-streaming 方法。
+
### Protobuf Client 无法读取响应
Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。请在两端使用同一份
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/kotlin/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/kotlin/grpc-support.md
index e369199092..d2e81792d3 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/kotlin/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/kotlin/grpc-support.md
@@ -22,7 +22,11 @@ license: |
Fory IDL 可以生成 Kotlin coroutine gRPC companion。生成的 gRPC 文件使用普通 grpc-java 和
grpc-kotlin API,每个 request/response message 使用 Fory 序列化。
-## 依赖
+当 RPC 两端都由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且两端都期望
+Fory 编码的 message body 时,可以使用这种模式。如果 API 必须被通用 protobuf client、
+reflection 工具或期望 protobuf message bytes 的组件消费,请使用标准 protobuf gRPC 代码生成。
+
+## 添加依赖
在编译生成源码的应用或 service module 中添加 Fory Kotlin、KSP、grpc-java、grpc-kotlin、
coroutines 和一个 grpc-java transport:
@@ -48,7 +52,10 @@ dependencies {
如果应用已经统一使用其他 grpc-java transport,可以替换 `grpc-netty-shaded`。生成的 Kotlin
Fory gRPC 不需要 `grpc-protobuf` 来编码 payload。
-## 生成代码
+## 定义 Service
+
+Service 定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers `rpc_service`。Fory IDL
service
+示例如下:
```protobuf
package demo.greeter;
@@ -66,17 +73,25 @@ service Greeter {
}
```
-运行:
+使用 `--grpc` 生成 Kotlin model 和 gRPC companion:
```bash
foryc service.fdl --kotlin_out=./generated/kotlin --grpc
```
-Compiler 会生成 Kotlin model 文件、schema module(例如 `ServiceForyModule.kt`)以及 service
-companion(例如 `GreeterGrpcKt.kt`)。编译生成 model 文件时需要运行 KSP,以便 runtime 可以使用
-schema serializer。
+该 schema 会生成:
+
+| 文件 | 用途 |
+| ------------------- | ------------------------------------- |
+| `HelloRequest.kt` | request 的 Fory model 类型 |
+| `HelloReply.kt` | response 的 Fory model 类型 |
+| `ServiceForyModule` | 生成类型的 Fory 注册 module |
+| `GreeterGrpcKt.kt` | Coroutine service base、stub 和 codec |
-## Server
+编译生成 model 文件时需要运行 KSP,以便 runtime 可以使用 schema serializer。生成的 request 和
+response 类型由 service companion 使用的 schema module 注册,service 实现不需要手动注册
serializer。
+
+## 实现 Server
实现生成的 coroutine base class,并注册到普通 grpc-java server:
@@ -100,7 +115,7 @@ val server = ServerBuilder
未实现的生成方法会返回 gRPC `UNIMPLEMENTED`。Service 方法抛出的异常遵循 grpc-kotlin server 行为。
-## Client
+## 创建 Client
从 grpc-java channel 直接构造生成的 coroutine stub:
@@ -121,7 +136,18 @@ val reply = stub.sayHello(HelloRequest(name = "Fory"))
Channel 构造、关闭、deadline、credential、interceptor、load balancing、retry 和 server
lifecycle
仍由 grpc-java/grpc-kotlin 负责。
-## Streaming
+## Streaming RPC
+
+Fory service 可以使用 gRPC 的所有 streaming shape:
+
+```protobuf
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+ rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+ rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+ rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
Streaming RPC 使用 `kotlinx.coroutines.flow.Flow`。
@@ -134,6 +160,77 @@ Streaming RPC 使用 `kotlinx.coroutines.flow.Flow`。
生成 method path 保留 schema 中的 service 和 method 名称,例如
`/demo.greeter.Greeter/SayHello`。
+Server 可以直接返回或消费 `Flow`:
+
+```kotlin
+import demo.greeter.GreeterGrpcKt
+import demo.greeter.HelloReply
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+
+class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
+ override fun lotsOfReplies(request: HelloRequest): Flow<HelloReply> = flow {
+ emit(HelloReply(reply = "Hello, ${request.name}"))
+ emit(HelloReply(reply = "Welcome, ${request.name}"))
+ }
+
+ override suspend fun lotsOfGreetings(
+ requests: Flow<HelloRequest>
+ ): HelloReply {
+ val names = requests.toList().joinToString(", ") { it.name }
+ return HelloReply(reply = names)
+ }
+
+ override fun chat(requests: Flow<HelloRequest>): Flow<HelloReply> =
+ requests.map { request ->
+ HelloReply(reply = "Hello, ${request.name}")
+ }
+}
+```
+
+生成的 client 暴露对应的 coroutine 和 `Flow` API:
+
+```kotlin
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.flowOf
+
+stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply ->
+ println(reply.reply)
+}
+
+val summary = stub.lotsOfGreetings(
+ flowOf(
+ HelloRequest(name = "Ada"),
+ HelloRequest(name = "Grace"),
+ )
+)
+println(summary.reply)
+
+stub.chat(
+ flowOf(
+ HelloRequest(name = "Fory"),
+ HelloRequest(name = "RPC"),
+ )
+).collect { reply ->
+ println(reply.reply)
+}
+```
+
+## 运维语义
+
+生成的 service code 只替换 request/response 序列化。常规 gRPC 运维能力仍由 grpc-java 和
+grpc-kotlin 提供:
+
+- Deadline 和取消
+- TLS 和认证
+- 名称解析与负载均衡
+- Client/server interceptor
+- Status code 和 metadata
+- Channel 池化与生命周期管理
+
## 互操作性
生成的 Kotlin service companion 在 gRPC frame 中使用 Fory 二进制 payload。它可以与从同一 schema
@@ -142,19 +239,19 @@ client 无法解码这些 payload。
## 故障排查
-**缺少生成的 service 文件**
+### 缺少生成的 service 文件
同时传入 `--grpc` 和 `--kotlin_out`。没有 service 定义的 schema 只会生成 model 文件和 schema
module。
-**运行时找不到 serializer class**
+### 运行时找不到 serializer class
确保生成的 Kotlin model source 运行了 KSP,并且 `fory-kotlin-ksp` 与 `fory-kotlin` 使用同一个
Fory 版本。
-**gRPC 类无法解析**
+### gRPC 类无法解析
向应用 module 添加 grpc-java 和 grpc-kotlin 依赖。Fory Kotlin artifact 不会自动添加这些依赖。
-**Protobuf client 无法读取响应**
+### Protobuf client 无法读取响应
Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。请在两端使用同一份
schema 生成的 Fory gRPC companion。
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/python/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/python/grpc-support.md
index a6aed054c4..a144babf3f 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/python/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/python/grpc-support.md
@@ -26,6 +26,11 @@ Fory 可以为包含 service 定义的 schema 生成 Python gRPC companion modul
传输语义与 Fory payload 编码时,可以使用这种模式。如果客户端或工具必须直接消费 protobuf
message bytes,请使用标准 protobuf gRPC 代码生成。
+当前生成的 Python companion 面向同步 `grpcio` API。请使用普通 `def` servicer 方法、
+`grpc.server(...)`、`grpc.insecure_channel(...)`,并用 Python iterator/generator 处理
+streaming RPC。Compiler 不会生成 `grpc.aio` stub 或 service base,因此不要把生成 servicer
+方法实现成 `async def`,除非你在生成 companion 外自行封装 adapter。
+
## 添加依赖
```bash
@@ -59,29 +64,42 @@ service Greeter {
foryc service.fdl --python_out=./generated/python --grpc
```
-生成结果通常包含 model module 和 `<module>_grpc.py` companion。Companion 会导入生成的
-`to_bytes`/`from_bytes` 辅助逻辑,并把它们接入 `grpcio` serializer/deserializer。
+该 schema 会生成:
+
+| 文件 | 用途 |
+| ---------------------- | --------------------------------- |
+| `demo_greeter.py` | Fory dataclass 和注册辅助逻辑 |
+| `demo_greeter_grpc.py` | `grpcio` stub、servicer base 和注册函数 |
+
+Module 名称来自 Fory package,点号会替换成下划线;没有 package 的 schema 使用 `generated.py` 和
+`generated_grpc.py`。
## 实现 Server
```python
from concurrent import futures
+
import grpc
-from generated.python.service import HelloReply
-from generated.python import service_grpc
+import demo_greeter
+import demo_greeter_grpc
-class Greeter(service_grpc.GreeterServicer):
- def SayHello(self, request, context):
- return HelloReply(reply=f"Hello, {request.name}")
+class Greeter(demo_greeter_grpc.GreeterServicer):
+ def say_hello(self, request, context):
+ return demo_greeter.HelloReply(reply=f"Hello, {request.name}")
-server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
-service_grpc.add_GreeterServicer_to_server(Greeter(), server)
-server.add_insecure_port("[::]:50051")
-server.start()
-server.wait_for_termination()
+def serve():
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=8))
+ demo_greeter_grpc.add_servicer(Greeter(), server)
+ server.add_insecure_port("[::]:50051")
+ server.start()
+ server.wait_for_termination()
+
+
+if __name__ == "__main__":
+ serve()
```
## 创建 Client
@@ -89,13 +107,19 @@ server.wait_for_termination()
```python
import grpc
-from generated.python.service import HelloRequest
-from generated.python import service_grpc
+import demo_greeter
+import demo_greeter_grpc
-with grpc.insecure_channel("localhost:50051") as channel:
- stub = service_grpc.GreeterStub(channel)
- reply = stub.SayHello(HelloRequest(name="Fory"))
- print(reply.reply)
+
+def main():
+ with grpc.insecure_channel("localhost:50051") as channel:
+ stub = demo_greeter_grpc.GreeterStub(channel)
+ reply = stub.say_hello(demo_greeter.HelloRequest(name="Fory"))
+ print(reply.reply)
+
+
+if __name__ == "__main__":
+ main()
```
Channel、credential、deadline、metadata、interceptor、retry 和 server lifecycle 都保持
`grpcio`
@@ -114,8 +138,71 @@ service Greeter {
}
```
-生成 Python companion 使用 `grpcio` 的 iterator/generator 约定处理 streaming。每个 message
-frame 都通过 Fory serializer/deserializer 编码。
+生成 Python companion 遵循 `grpcio` 的 iterator/generator 约定:
+
+| IDL shape | Servicer 方法形态
| Stub 方法形态 |
+| ----------------------------------------- |
----------------------------------------- | ------------------------- |
+| `rpc A (Req) returns (Res)` | 返回一个 response 对象
| 返回一个 response 对象 |
+| `rpc A (Req) returns (stream Res)` | yield 多个 response 对象
| 返回 response iterator |
+| `rpc A (stream Req) returns (Res)` | 消费 request iterator 并返回一个
response | 接收 request iterator |
+| `rpc A (stream Req) returns (stream Res)` | 消费 request iterator 并 yield
response | 接收并返回 iterator |
+
+Servicer 方法使用 snake_case 名称;生成 descriptor 会保留 IDL 中的 service 和 method 名称作为
+gRPC path。每个 message frame 都通过 Fory serializer/deserializer 编码。
+
+Server 可以直接使用 Python iterator:
+
+```python
+class Greeter(demo_greeter_grpc.GreeterServicer):
+ def lots_of_replies(self, request, context):
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+ yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}")
+
+ def lots_of_greetings(self, request_iterator, context):
+ names = [request.name for request in request_iterator]
+ return demo_greeter.HelloReply(reply=", ".join(names))
+
+ def chat(self, request_iterator, context):
+ for request in request_iterator:
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+```
+
+生成的 client 使用标准 `grpcio` streaming 调用形态:
+
+```python
+with grpc.insecure_channel("localhost:50051") as channel:
+ stub = demo_greeter_grpc.GreeterStub(channel)
+
+ for reply in stub.lots_of_replies(
+ demo_greeter.HelloRequest(name="Fory")
+ ):
+ print(reply.reply)
+
+ def greeting_requests():
+ yield demo_greeter.HelloRequest(name="Ada")
+ yield demo_greeter.HelloRequest(name="Grace")
+
+ summary = stub.lots_of_greetings(greeting_requests())
+ print(summary.reply)
+
+ def chat_requests():
+ yield demo_greeter.HelloRequest(name="Fory")
+ yield demo_greeter.HelloRequest(name="RPC")
+
+ for reply in stub.chat(chat_requests()):
+ print(reply.reply)
+```
+
+## 运维语义
+
+生成的 service companion 只提供 Fory serialization callback。运维行为仍遵循标准 `grpcio`:
+
+- Deadline 和取消
+- TLS 和认证 credential
+- Client/server interceptor
+- Status code、details 和 metadata
+- Channel/server 生命周期
+- 同步 server 的线程池大小
## 故障排查
@@ -127,6 +214,10 @@ frame 都通过 Fory serializer/deserializer 编码。
pip install grpcio
```
+### `UNIMPLEMENTED`
+
+确认生成的 servicer 已注册到 server,并且 client 与 server 来自相同 package、service 和 method 名称。
+
### Protobuf Client 无法解码
Fory gRPC 使用 Fory 二进制协议 payload,不是 protobuf wire-format message。两端应使用同一份
diff --git
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/rust/grpc-support.md
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/rust/grpc-support.md
index c3dc73452d..b6c603b61f 100644
---
a/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/rust/grpc-support.md
+++
b/i18n/zh-CN/docusaurus-plugin-content-docs/version-1.2.0/guide/rust/grpc-support.md
@@ -30,7 +30,7 @@ protobuf message bytes,请继续使用标准 protobuf gRPC 代码生成。
编译生成 service 文件的 crate 需要添加 `tonic` 和 `bytes`。Fory Rust crate 不会将
gRPC 作为硬依赖。异步 server/client 还需要 `tokio`;如果 service 实现需要构造
-streaming response,可添加 `tokio-stream`。
+streaming response 或 request stream,可添加 `tokio-stream`。
```toml
[dependencies]
@@ -168,6 +168,118 @@ service Greeter {
- 生成的 client module 暴露与 service 方法对应的异步方法。
- 每个 message frame 都使用生成 codec,包括 streaming frame。
+生成 trait 签名是 service 实现中具体 associated stream type 的准确信息来源:
+
+```rust
+use demo_greeter::{HelloReply, HelloRequest};
+use demo_greeter_service::Greeter;
+use std::pin::Pin;
+use tokio_stream::{self as stream, Stream, StreamExt};
+use tonic::{Request, Response, Status};
+
+#[derive(Default)]
+struct MyGreeter;
+
+type ReplyStream =
+ Pin<Box<dyn Stream<Item = Result<HelloReply, Status>> + Send + 'static>>;
+
+#[tonic::async_trait]
+impl Greeter for MyGreeter {
+ type LotsOfRepliesStream = ReplyStream;
+ type ChatStream = ReplyStream;
+
+ async fn lots_of_replies(
+ &self,
+ request: Request<HelloRequest>,
+ ) -> Result<Response<Self::LotsOfRepliesStream>, Status> {
+ let name = request.into_inner().name;
+ let replies = vec![
+ Ok(HelloReply {
+ reply: format!("Hello, {name}"),
+ }),
+ Ok(HelloReply {
+ reply: format!("Welcome, {name}"),
+ }),
+ ];
+ Ok(Response::new(Box::pin(stream::iter(replies))))
+ }
+
+ async fn lots_of_greetings(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<HelloReply>, Status> {
+ let mut requests = request.into_inner();
+ let mut names = Vec::new();
+ while let Some(request) = requests.next().await {
+ names.push(request?.name);
+ }
+ Ok(Response::new(HelloReply {
+ reply: names.join(", "),
+ }))
+ }
+
+ async fn chat(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<Self::ChatStream>, Status> {
+ let replies = request.into_inner().map(|request| {
+ request.map(|request| HelloReply {
+ reply: format!("Hello, {}", request.name),
+ })
+ });
+ Ok(Response::new(Box::pin(replies)))
+ }
+}
+```
+
+生成的 client 返回 tonic streaming response:
+
+```rust
+use demo_greeter::HelloRequest;
+use demo_greeter_service_grpc::greeter_client::GreeterClient;
+use tokio_stream as stream;
+
+let mut client = GreeterClient::connect("http://[::1]:50051").await?;
+
+let mut replies = client
+ .lots_of_replies(HelloRequest {
+ name: "Fory".to_string(),
+ })
+ .await?
+ .into_inner();
+while let Some(reply) = replies.message().await? {
+ println!("{}", reply.reply);
+}
+
+let greetings = stream::iter(vec![
+ HelloRequest {
+ name: "Ada".to_string(),
+ },
+ HelloRequest {
+ name: "Grace".to_string(),
+ },
+]);
+let summary = client.lots_of_greetings(greetings).await?.into_inner();
+println!("{}", summary.reply);
+
+let chat_requests = stream::iter(vec![
+ HelloRequest {
+ name: "Fory".to_string(),
+ },
+ HelloRequest {
+ name: "RPC".to_string(),
+ },
+]);
+let mut chat = client.chat(chat_requests).await?.into_inner();
+while let Some(reply) = chat.message().await? {
+ println!("{}", reply.reply);
+}
+```
+
+生成 descriptor 会保留 IDL 中的 service 和 method 名称作为 gRPC path。
+
+## 线程安全与 Payload 类型
+
Rust gRPC payload 必须满足 `Send + 'static`,这样 tonic 才能在线程间移动 request/response。
如果 request 或 response schema 使用非线程安全的引用元信息,Rust gRPC 生成会拒绝该 service。
diff --git a/versioned_docs/version-1.2.0/guide/java/grpc-support.md
b/versioned_docs/version-1.2.0/guide/java/grpc-support.md
index 7bebb010f9..826f2326e7 100644
--- a/versioned_docs/version-1.2.0/guide/java/grpc-support.md
+++ b/versioned_docs/version-1.2.0/guide/java/grpc-support.md
@@ -201,12 +201,170 @@ service Greeter {
Generated Java service methods follow grpc-java conventions:
-- Unary and server-streaming methods receive a request object and a
- `StreamObserver` for responses.
-- Client-streaming and bidirectional methods return a `StreamObserver` for
- incoming requests and receive a `StreamObserver` for outgoing responses.
-- Blocking stubs expose the grpc-java blocking APIs for supported streaming
- shapes.
+| IDL shape | Server method shape
| Client method shape |
+| ----------------------------------------- |
-------------------------------------------------------- |
-------------------------------------- |
+| `rpc A (Req) returns (Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking, async, and future unary stub |
+| `rpc A (Req) returns (stream Res)` | `void a(Req request,
StreamObserver<Res> responses)` | blocking iterator or async observer |
+| `rpc A (stream Req) returns (Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+| `rpc A (stream Req) returns (stream Res)` | `StreamObserver<Req>
a(StreamObserver<Res> responses)` | async request observer |
+
+Server implementations can use the generated streaming method shapes directly:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayList;
+import java.util.List;
+
+final class GreeterService extends GreeterGrpc.GreeterImplBase {
+ @Override
+ public void lotsOfReplies(
+ HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+ HelloReply first = new HelloReply();
+ first.setReply("Hello, " + request.getName());
+ responseObserver.onNext(first);
+
+ HelloReply second = new HelloReply();
+ second.setReply("Welcome, " + request.getName());
+ responseObserver.onNext(second);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> lotsOfGreetings(
+ StreamObserver<HelloReply> responseObserver) {
+ List<String> names = new ArrayList<>();
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ names.add(request.getName());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ HelloReply reply = new HelloReply();
+ reply.setReply(String.join(", ", names));
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+ };
+ }
+
+ @Override
+ public StreamObserver<HelloRequest> chat(
+ StreamObserver<HelloReply> responseObserver) {
+ return new StreamObserver<>() {
+ @Override
+ public void onNext(HelloRequest request) {
+ HelloReply reply = new HelloReply();
+ reply.setReply("Hello, " + request.getName());
+ responseObserver.onNext(reply);
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ responseObserver.onError(error);
+ }
+
+ @Override
+ public void onCompleted() {
+ responseObserver.onCompleted();
+ }
+ };
+ }
+}
+```
+
+Generated clients return the standard grpc-java call shapes:
+
+```java
+package demo.greeter;
+
+import io.grpc.stub.StreamObserver;
+import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+final class StreamingClient {
+ private final GreeterGrpc.GreeterBlockingStub blockingStub;
+ private final GreeterGrpc.GreeterStub asyncStub;
+
+ StreamingClient(
+ GreeterGrpc.GreeterBlockingStub blockingStub,
+ GreeterGrpc.GreeterStub asyncStub) {
+ this.blockingStub = blockingStub;
+ this.asyncStub = asyncStub;
+ }
+
+ void run() throws InterruptedException {
+ Iterator<HelloReply> replies =
+ blockingStub.lotsOfReplies(newRequest("Fory"));
+ while (replies.hasNext()) {
+ System.out.println(replies.next().getReply());
+ }
+
+ CountDownLatch greetingsDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> greetings =
+ asyncStub.lotsOfGreetings(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ greetingsDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ greetingsDone.countDown();
+ }
+ });
+ greetings.onNext(newRequest("Ada"));
+ greetings.onNext(newRequest("Grace"));
+ greetings.onCompleted();
+ greetingsDone.await(5, TimeUnit.SECONDS);
+
+ CountDownLatch chatDone = new CountDownLatch(1);
+ StreamObserver<HelloRequest> chat =
+ asyncStub.chat(new StreamObserver<>() {
+ @Override
+ public void onNext(HelloReply reply) {
+ System.out.println(reply.getReply());
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ chatDone.countDown();
+ }
+
+ @Override
+ public void onCompleted() {
+ chatDone.countDown();
+ }
+ });
+ chat.onNext(newRequest("Fory"));
+ chat.onCompleted();
+ chatDone.await(5, TimeUnit.SECONDS);
+ }
+
+ private static HelloRequest newRequest(String name) {
+ HelloRequest request = new HelloRequest();
+ request.setName(name);
+ return request;
+ }
+}
+```
+
+The generated descriptors preserve the exact IDL service and method names for
+the gRPC path.
## Operations
diff --git a/versioned_docs/version-1.2.0/guide/javascript/grpc-support.md
b/versioned_docs/version-1.2.0/guide/javascript/grpc-support.md
index 32ca805171..696ecef24a 100644
--- a/versioned_docs/version-1.2.0/guide/javascript/grpc-support.md
+++ b/versioned_docs/version-1.2.0/guide/javascript/grpc-support.md
@@ -23,6 +23,12 @@ Fory can generate JavaScript service companions for schemas
that define
services. The generated service code uses normal gRPC transports while request
and response objects are serialized with Fory instead of protobuf.
+Use this mode when both RPC peers are generated from the same Fory IDL,
+protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message
+bodies. Use normal protobuf gRPC generation for APIs that must be consumed by
+generic protobuf clients, reflection tools, or components that expect protobuf
+message bytes.
+
Use `--grpc` for Node.js server and client code. Use `--grpc-web` for browser
clients that call a gRPC-Web compatible server or proxy.
@@ -47,6 +53,9 @@ package used by your application.
## Define a Service
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
+
```protobuf
package demo.greeter;
@@ -292,3 +301,23 @@ gRPC operational features still belong to the transport
package:
- Deadlines and cancellation
- Client and server interceptors
- Load balancing and deployment-specific proxy configuration
+
+## Troubleshooting
+
+### Missing gRPC Packages
+
+Add `@grpc/grpc-js` for Node.js companions or `grpc-web` for browser
+companions. `@apache-fory/core` intentionally does not depend on either
+transport package.
+
+### gRPC-Web Client-Streaming or Bidirectional RPCs Are Rejected
+
+gRPC-Web does not support client-streaming or bidirectional streaming. Generate
+Node.js companions with `--grpc` for those shapes, or expose unary and
+server-streaming methods to browser clients.
+
+### Protobuf Clients Cannot Decode the Service
+
+Fory gRPC companions do not use protobuf wire encoding for messages. Use a
+Fory-generated client for Fory-generated services, or provide a separate
+protobuf service endpoint for generic protobuf clients.
diff --git a/versioned_docs/version-1.2.0/guide/kotlin/grpc-support.md
b/versioned_docs/version-1.2.0/guide/kotlin/grpc-support.md
index ae12f9e0f1..2a33ec3d43 100644
--- a/versioned_docs/version-1.2.0/guide/kotlin/grpc-support.md
+++ b/versioned_docs/version-1.2.0/guide/kotlin/grpc-support.md
@@ -23,7 +23,13 @@ Fory IDL can generate Kotlin coroutine gRPC companions. The
generated gRPC
files use normal grpc-java and grpc-kotlin APIs, while each request and
response
message is serialized with Fory.
-## Dependencies
+Use this mode when both RPC peers are generated from the same Fory IDL,
+protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message
+bodies. Use normal protobuf gRPC generation for APIs that must be consumed by
+generic protobuf clients, reflection tools, or components that expect protobuf
+message bytes.
+
+## Add Dependencies
Add Fory Kotlin, KSP, grpc-java, grpc-kotlin, coroutines, and one grpc-java
transport to the application or service module that compiles the generated
@@ -51,9 +57,10 @@ Use a different grpc-java transport if your application
already standardizes on
one. Generated Kotlin Fory gRPC does not require `grpc-protobuf` for payload
encoding.
-## Generate Code
+## Define a Service
-For a schema such as:
+Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers
+`rpc_service` definitions. A Fory IDL service looks like this:
```protobuf
package demo.greeter;
@@ -71,18 +78,27 @@ service Greeter {
}
```
-run:
+Generate Kotlin model and gRPC companion code with `--grpc`:
```bash
foryc service.fdl --kotlin_out=./generated/kotlin --grpc
```
-The compiler writes Kotlin model files, a schema module such as
-`ServiceForyModule.kt`, and one service companion such as `GreeterGrpcKt.kt`.
+For this schema, the Kotlin generator emits:
+
+| File | Purpose |
+| -------------------- | -------------------------------------------- |
+| `HelloRequest.kt` | Fory model type for the request |
+| `HelloReply.kt` | Fory model type for the response |
+| `ServiceForyModule` | Fory registration module for generated types |
+| `GreeterGrpcKt.kt` | Coroutine service base, stubs, and codecs |
+
Run KSP when compiling the generated model files so the schema serializers are
-available at runtime.
+available at runtime. Generated request and response types are registered by
+the generated schema module used by the service companion, so service
+implementations do not perform manual serializer registration.
-## Server
+## Implement a Server
Implement the generated coroutine base class and register it with a normal
grpc-java server.
@@ -108,7 +124,7 @@ val server = ServerBuilder
Unimplemented generated methods fail with gRPC `UNIMPLEMENTED`. Exceptions
thrown by your service method follow grpc-kotlin server behavior.
-## Client
+## Create a Client
Construct the generated coroutine stub directly from a grpc-java channel.
@@ -130,7 +146,18 @@ Channel construction, shutdown, deadlines, credentials,
interceptors, load
balancing, retries, and server lifecycle stay normal grpc-java/grpc-kotlin
responsibilities.
-## Streaming
+## Streaming RPCs
+
+Fory service definitions can use the same gRPC streaming shapes:
+
+```protobuf
+service Greeter {
+ rpc SayHello (HelloRequest) returns (HelloReply);
+ rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
+ rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
+ rpc Chat (stream HelloRequest) returns (stream HelloReply);
+}
+```
Streaming RPCs use `kotlinx.coroutines.flow.Flow`.
@@ -144,6 +171,78 @@ Streaming RPCs use `kotlinx.coroutines.flow.Flow`.
The generated method path keeps the exact service and method names from the
schema, for example `/demo.greeter.Greeter/SayHello`.
+Server implementations can return or consume `Flow` values directly:
+
+```kotlin
+import demo.greeter.GreeterGrpcKt
+import demo.greeter.HelloReply
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.toList
+
+class GreeterService : GreeterGrpcKt.GreeterCoroutineImplBase() {
+ override fun lotsOfReplies(request: HelloRequest): Flow<HelloReply> = flow {
+ emit(HelloReply(reply = "Hello, ${request.name}"))
+ emit(HelloReply(reply = "Welcome, ${request.name}"))
+ }
+
+ override suspend fun lotsOfGreetings(
+ requests: Flow<HelloRequest>
+ ): HelloReply {
+ val names = requests.toList().joinToString(", ") { it.name }
+ return HelloReply(reply = names)
+ }
+
+ override fun chat(requests: Flow<HelloRequest>): Flow<HelloReply> =
+ requests.map { request ->
+ HelloReply(reply = "Hello, ${request.name}")
+ }
+}
+```
+
+Generated clients expose the matching coroutine and Flow APIs:
+
+```kotlin
+import demo.greeter.HelloRequest
+import kotlinx.coroutines.flow.flowOf
+
+stub.lotsOfReplies(HelloRequest(name = "Fory")).collect { reply ->
+ println(reply.reply)
+}
+
+val summary = stub.lotsOfGreetings(
+ flowOf(
+ HelloRequest(name = "Ada"),
+ HelloRequest(name = "Grace"),
+ )
+)
+println(summary.reply)
+
+stub.chat(
+ flowOf(
+ HelloRequest(name = "Fory"),
+ HelloRequest(name = "RPC"),
+ )
+).collect { reply ->
+ println(reply.reply)
+}
+```
+
+## Operations
+
+The generated service code only replaces request and response serialization.
+All normal gRPC operational features still belong to grpc-java and
+grpc-kotlin:
+
+- Deadlines and cancellations
+- TLS and authentication
+- Name resolution and load balancing
+- Client and server interceptors
+- Status codes and metadata
+- Channel pooling and lifecycle management
+
## Interoperability
Generated Kotlin service companions use Fory binary payloads inside gRPC
@@ -158,22 +257,22 @@ messages.
## Troubleshooting
-**Generated service file is missing**
+### Generated service file is missing
Pass `--grpc` together with `--kotlin_out`. Schemas without service definitions
only generate model files and the schema module.
-**Serializer class not found at runtime**
+### Serializer class not found at runtime
Ensure KSP runs for the generated Kotlin model sources and that
`fory-kotlin-ksp` uses the same Fory version as `fory-kotlin`.
-**gRPC classes are unresolved**
+### gRPC classes are unresolved
Add grpc-java and grpc-kotlin dependencies to the application module. Fory
Kotlin artifacts do not add those dependencies automatically.
-**A protobuf client cannot read responses**
+### A protobuf client cannot read responses
Fory gRPC uses Fory binary protocol payloads, not protobuf wire-format
messages.
Use generated Fory gRPC companions on both sides for the same service schema.
diff --git a/versioned_docs/version-1.2.0/guide/python/grpc-support.md
b/versioned_docs/version-1.2.0/guide/python/grpc-support.md
index c5106f91b0..946602be30 100644
--- a/versioned_docs/version-1.2.0/guide/python/grpc-support.md
+++ b/versioned_docs/version-1.2.0/guide/python/grpc-support.md
@@ -28,6 +28,13 @@ IDL, or FlatBuffers IDL and you want gRPC transport
semantics with Fory payload
encoding. Use standard protobuf gRPC code generation when clients or tools must
consume protobuf message bytes directly.
+Generated Python companions currently target the synchronous `grpcio` API. Use
+regular `def` servicer methods, `grpc.server(...)`,
`grpc.insecure_channel(...)`,
+and Python iterators or generators for streaming RPCs. The compiler does not
+generate `grpc.aio` stubs or service bases, so do not implement generated
+servicer methods as `async def` unless you add a custom adapter outside the
+generated companion.
+
## Install Dependencies
Install `grpcio` alongside `pyfory`. The generated companion imports `grpc`,
but
@@ -151,22 +158,57 @@ service Greeter {
Generated Python code follows `grpcio` conventions:
-- Unary stubs call `channel.unary_unary(...)`.
-- Server-streaming stubs return an iterator over response objects.
-- Client-streaming stubs accept an iterator of request objects.
-- Bidirectional stubs accept a request iterator and return a response iterator.
-- Servicer methods use snake_case names and return either a single response or
- an iterator, depending on the streaming shape.
+| IDL shape | Servicer method shape
| Stub method shape |
+| ----------------------------------------- |
------------------------------------------- |
---------------------------------- |
+| `rpc A (Req) returns (Res)` | returns one response object
| returns one response object |
+| `rpc A (Req) returns (stream Res)` | yields response objects
| returns an iterator of responses |
+| `rpc A (stream Req) returns (Res)` | consumes an iterator and returns
a response | accepts an iterator of requests |
+| `rpc A (stream Req) returns (stream Res)` | consumes and yields iterators
| accepts and returns iterators |
+
+Servicer methods use snake_case names, while generated descriptors preserve the
+exact IDL service and method names for the gRPC path.
-Example server-streaming implementation:
+Server implementations can use Python iterators directly:
```python
class Greeter(demo_greeter_grpc.GreeterServicer):
def lots_of_replies(self, request, context):
- for index in range(3):
- yield demo_greeter.HelloReply(
- reply=f"Hello {request.name}, response {index}"
- )
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+ yield demo_greeter.HelloReply(reply=f"Welcome, {request.name}")
+
+ def lots_of_greetings(self, request_iterator, context):
+ names = [request.name for request in request_iterator]
+ return demo_greeter.HelloReply(reply=", ".join(names))
+
+ def chat(self, request_iterator, context):
+ for request in request_iterator:
+ yield demo_greeter.HelloReply(reply=f"Hello, {request.name}")
+```
+
+Generated clients use the standard `grpcio` streaming call shapes:
+
+```python
+with grpc.insecure_channel("localhost:50051") as channel:
+ stub = demo_greeter_grpc.GreeterStub(channel)
+
+ for reply in stub.lots_of_replies(
+ demo_greeter.HelloRequest(name="Fory")
+ ):
+ print(reply.reply)
+
+ def greeting_requests():
+ yield demo_greeter.HelloRequest(name="Ada")
+ yield demo_greeter.HelloRequest(name="Grace")
+
+ summary = stub.lots_of_greetings(greeting_requests())
+ print(summary.reply)
+
+ def chat_requests():
+ yield demo_greeter.HelloRequest(name="Fory")
+ yield demo_greeter.HelloRequest(name="RPC")
+
+ for reply in stub.chat(chat_requests()):
+ print(reply.reply)
```
## Operations
diff --git a/versioned_docs/version-1.2.0/guide/rust/grpc-support.md
b/versioned_docs/version-1.2.0/guide/rust/grpc-support.md
index ed636344a1..4c9ca55f6c 100644
--- a/versioned_docs/version-1.2.0/guide/rust/grpc-support.md
+++ b/versioned_docs/version-1.2.0/guide/rust/grpc-support.md
@@ -33,7 +33,7 @@ consume protobuf message bytes directly.
Add `tonic` and `bytes` to the crate that compiles the generated service files.
Fory Rust crates do not add gRPC as a hard dependency. Add `tokio` for async
servers and clients, and `tokio-stream` when your service implementation needs
-to build streaming responses.
+to build streaming responses or request streams.
```toml
[dependencies]
@@ -180,8 +180,117 @@ Generated Rust code follows tonic conventions:
- The generated codec is used for every message frame, including streaming
frames.
-Use the generated trait signatures as the source of truth for the concrete
-associated stream types in your service implementation.
+Use the generated trait signatures as the source of truth for concrete
+associated stream types in your service implementation:
+
+```rust
+use demo_greeter::{HelloReply, HelloRequest};
+use demo_greeter_service::Greeter;
+use std::pin::Pin;
+use tokio_stream::{self as stream, Stream, StreamExt};
+use tonic::{Request, Response, Status};
+
+#[derive(Default)]
+struct MyGreeter;
+
+type ReplyStream =
+ Pin<Box<dyn Stream<Item = Result<HelloReply, Status>> + Send + 'static>>;
+
+#[tonic::async_trait]
+impl Greeter for MyGreeter {
+ type LotsOfRepliesStream = ReplyStream;
+ type ChatStream = ReplyStream;
+
+ async fn lots_of_replies(
+ &self,
+ request: Request<HelloRequest>,
+ ) -> Result<Response<Self::LotsOfRepliesStream>, Status> {
+ let name = request.into_inner().name;
+ let replies = vec![
+ Ok(HelloReply {
+ reply: format!("Hello, {name}"),
+ }),
+ Ok(HelloReply {
+ reply: format!("Welcome, {name}"),
+ }),
+ ];
+ Ok(Response::new(Box::pin(stream::iter(replies))))
+ }
+
+ async fn lots_of_greetings(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<HelloReply>, Status> {
+ let mut requests = request.into_inner();
+ let mut names = Vec::new();
+ while let Some(request) = requests.next().await {
+ names.push(request?.name);
+ }
+ Ok(Response::new(HelloReply {
+ reply: names.join(", "),
+ }))
+ }
+
+ async fn chat(
+ &self,
+ request: Request<tonic::Streaming<HelloRequest>>,
+ ) -> Result<Response<Self::ChatStream>, Status> {
+ let replies = request.into_inner().map(|request| {
+ request.map(|request| HelloReply {
+ reply: format!("Hello, {}", request.name),
+ })
+ });
+ Ok(Response::new(Box::pin(replies)))
+ }
+}
+```
+
+Generated clients return tonic streaming responses:
+
+```rust
+use demo_greeter::HelloRequest;
+use demo_greeter_service_grpc::greeter_client::GreeterClient;
+use tokio_stream as stream;
+
+let mut client = GreeterClient::connect("http://[::1]:50051").await?;
+
+let mut replies = client
+ .lots_of_replies(HelloRequest {
+ name: "Fory".to_string(),
+ })
+ .await?
+ .into_inner();
+while let Some(reply) = replies.message().await? {
+ println!("{}", reply.reply);
+}
+
+let greetings = stream::iter(vec![
+ HelloRequest {
+ name: "Ada".to_string(),
+ },
+ HelloRequest {
+ name: "Grace".to_string(),
+ },
+]);
+let summary = client.lots_of_greetings(greetings).await?.into_inner();
+println!("{}", summary.reply);
+
+let chat_requests = stream::iter(vec![
+ HelloRequest {
+ name: "Fory".to_string(),
+ },
+ HelloRequest {
+ name: "RPC".to_string(),
+ },
+]);
+let mut chat = client.chat(chat_requests).await?.into_inner();
+while let Some(reply) = chat.message().await? {
+ println!("{}", reply.reply);
+}
+```
+
+The generated descriptors preserve the exact IDL service and method names for
+the gRPC path.
## Thread Safety and Payload Types
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]