Skip to content

Commit 370a01f

Browse files
committed
Add example of starting both grpc and connectrpc servers together
1 parent fcbe8af commit 370a01f

File tree

11 files changed

+179
-5
lines changed

11 files changed

+179
-5
lines changed

build.sbt

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ lazy val noPublish = List(
2828
)
2929

3030
lazy val Versions = new {
31+
val cedi = "0.2.1"
3132
val grpc = "1.73.0"
3233
val http4s = "0.23.30"
3334
val logback = "1.5.18"
@@ -37,7 +38,7 @@ lazy val Versions = new {
3738
val scalatest = "3.2.19"
3839
}
3940

40-
lazy val CommonDependencies = Seq(
41+
lazy val commonDeps = Seq(
4142
libraryDependencies ++= Seq(
4243
"org.slf4j" % "slf4j-api" % Versions.slf4j,
4344
"org.scalatest" %% "scalatest" % Versions.scalatest % Test,
@@ -53,6 +54,7 @@ lazy val core = project
5354
Test / PB.targets := Seq(
5455
scalapb.gen() -> (Test / sourceManaged).value
5556
),
57+
commonDeps,
5658
libraryDependencies ++= Seq(
5759
"com.thesamet.scalapb.common-protos" %% "proto-google-common-protos-scalapb_0.11" % "2.9.6-0" % "protobuf",
5860
"com.thesamet.scalapb.common-protos" %% "proto-google-common-protos-scalapb_0.11" % "2.9.6-0",
@@ -66,7 +68,6 @@ lazy val core = project
6668
"org.http4s" %% "http4s-core" % Versions.http4s,
6769
),
6870
)
69-
.settings(CommonDependencies)
7071

7172
lazy val http4s = project
7273
.dependsOn(core)
@@ -75,42 +76,64 @@ lazy val http4s = project
7576
Test / PB.targets := Seq(
7677
scalapb.gen() -> (Test / sourceManaged).value
7778
),
79+
commonDeps,
7880
libraryDependencies ++= Seq(
7981
"org.http4s" %% "http4s-dsl" % Versions.http4s % Test,
8082
"org.http4s" %% "http4s-client" % Versions.http4s,
8183
),
8284
)
83-
.settings(CommonDependencies)
8485

8586
lazy val netty = project
8687
.dependsOn(core)
8788
.settings(
8889
name := "connect-rpc-scala-netty",
90+
commonDeps,
8991
libraryDependencies ++= Seq(
9092
"io.netty" % "netty-all" % Versions.netty
9193
),
9294
)
93-
.settings(CommonDependencies)
9495

9596
lazy val conformance = project
9697
.dependsOn(http4s, netty)
9798
.enablePlugins(Fs2Grpc, JavaAppPackaging)
9899
.settings(
99100
noPublish,
101+
commonDeps,
100102
libraryDependencies ++= Seq(
101103
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
102104
"org.http4s" %% "http4s-ember-client" % Versions.http4s,
103105
"ch.qos.logback" % "logback-classic" % Versions.logback % Runtime,
104106
),
105107
)
106-
.settings(CommonDependencies)
108+
109+
lazy val examples = project.in(file("examples"))
110+
.aggregate(
111+
example_connectrpc_grpc_servers
112+
)
113+
.settings(noPublish)
114+
115+
lazy val example_connectrpc_grpc_servers = project.in(file("examples/connectrpc_grpc_servers"))
116+
.dependsOn(http4s)
117+
.enablePlugins(Fs2Grpc)
118+
.settings(
119+
noPublish,
120+
commonDeps,
121+
libraryDependencies ++= Seq(
122+
"me.ivovk" %% "cedi" % Versions.cedi,
123+
"io.grpc" % "grpc-netty" % Versions.grpc,
124+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
125+
"ch.qos.logback" % "logback-classic" % Versions.logback % Runtime,
126+
),
127+
Compile / mainClass := Some("examples.connectrpc_grpc_servers.Main"),
128+
)
107129

108130
lazy val root = (project in file("."))
109131
.aggregate(
110132
core,
111133
http4s,
112134
netty,
113135
conformance,
136+
examples,
114137
)
115138
.settings(
116139
name := "connect-rpc-scala",

examples/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# List of examples
2+
3+
- **connectrpc_grpc_servers**: Example of starting both a gRPC server and a ConnectRPC server in the same component on
4+
different ports.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# gRPC and ConnectRPC Servers Example
2+
3+
This example demonstrates the pattern of starting both a gRPC server and a ConnectRPC server in the same component on
4+
different ports.
5+
6+
Example shows that the same service is run by both servers, and the gRPC server is used to handle
7+
gRPC requests, while the ConnectRPC server is used to handle ConnectRPC requests.
8+
9+
Dependencies are wired using [Cedi](https://github.com/igor-vovk/cedi) library.
10+
11+
Testing the ConnectRPC server:
12+
1. Start the server by running:
13+
```bash
14+
sbt example_connectrpc_grpc_servers/run
15+
```
16+
2. Use `curl` to send http requests to the ConnectRPC server:
17+
```bash
18+
curl -X POST http://localhost:8080/examples.v1.ElizaService/Say -d '{"sentence": "Hello Connectrpc"}' -H 'Content-Type: application/json'
19+
```
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Compile / mainClass := Some("examples.connectrpc_grpc_servers.Main")
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
package examples.v1;
4+
5+
message SayRequest {
6+
string sentence = 1;
7+
}
8+
9+
message SayResponse {
10+
string sentence = 1;
11+
}
12+
13+
service ElizaService {
14+
rpc Say(SayRequest) returns (SayResponse) {}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
syntax = "proto3";
2+
3+
package examples.v1;
4+
5+
import "scalapb/scalapb.proto";
6+
7+
option (scalapb.options) = {
8+
scope: PACKAGE
9+
flat_package: false
10+
lenses: false
11+
enum_value_naming: CAMEL_CASE
12+
enum_strip_prefix: true
13+
preserve_unknown_fields: false
14+
scala3_sources: true
15+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!DOCTYPE configuration>
3+
4+
<configuration>
5+
6+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
7+
<encoder>
8+
<pattern>%-4relative %-5level %logger{5} -%kvp- %msg%n</pattern>
9+
</encoder>
10+
</appender>
11+
12+
<root level="INFO">
13+
<appender-ref ref="STDOUT"/>
14+
</root>
15+
<logger name="org.ivovk" level="TRACE"/>
16+
</configuration>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package examples.connectrpc_grpc_servers
2+
3+
import cats.effect.{IO, Resource}
4+
import com.comcast.ip4s.*
5+
import examples.v1.eliza.ElizaServiceFs2Grpc
6+
import fs2.grpc.syntax.all.*
7+
import io.grpc.ServerServiceDefinition
8+
import io.grpc.netty.NettyServerBuilder
9+
import me.ivovk.cedi.Allocator
10+
import me.ivovk.cedi.syntax.*
11+
import org.http4s.HttpApp
12+
import org.http4s.ember.server.EmberServerBuilder
13+
import org.ivovk.connect_rpc_scala.http4s.ConnectHttp4sRouteBuilder
14+
15+
object Dependencies {
16+
17+
def create(): Resource[IO, Dependencies] =
18+
Allocator.create[IO]().map(Dependencies(using _))
19+
20+
}
21+
22+
class Dependencies(using AllocatorIO) {
23+
24+
lazy val elizaService: ElizaService[IO] = new ElizaService[IO]
25+
26+
// This example uses the `fs2.grpc` syntax to allocate resources for the gRPC service, but you can also use
27+
// ZIO-gRPC or any other gRPC library for Scala to obtain ServerServiceDefinition of your services.
28+
lazy val elizaServiceGrpcDefinition: ServerServiceDefinition = allocate {
29+
ElizaServiceFs2Grpc.bindServiceResource[IO](elizaService)
30+
}
31+
32+
lazy val httpApp: org.http4s.HttpApp[IO] = allocate {
33+
ConnectHttp4sRouteBuilder.forService[IO](elizaServiceGrpcDefinition).build
34+
}
35+
36+
lazy val connectRpcServer: Resource[IO, org.http4s.server.Server] =
37+
EmberServerBuilder.default[IO]
38+
.withHost(host"0.0.0.0")
39+
.withPort(port"8080")
40+
.withHttpApp(httpApp)
41+
.build
42+
43+
lazy val grpcServer: Resource[IO, io.grpc.Server] =
44+
NettyServerBuilder.forPort(9090)
45+
.addService(elizaServiceGrpcDefinition)
46+
.resource[IO]
47+
.evalMap { server =>
48+
IO(server.start())
49+
}
50+
51+
lazy val run: IO[Unit] =
52+
(grpcServer, connectRpcServer).parTupled.useForever
53+
54+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package examples.connectrpc_grpc_servers
2+
3+
import cats.effect.Sync
4+
import examples.v1.eliza.{ElizaServiceFs2Grpc, SayRequest, SayResponse}
5+
import io.grpc.Metadata
6+
7+
class ElizaService[F[_]: Sync] extends ElizaServiceFs2Grpc[F, Metadata] {
8+
9+
def say(request: SayRequest, ctx: Metadata): F[SayResponse] =
10+
Sync[F].delay {
11+
SayResponse(s"You've said: ${request.sentence}")
12+
}
13+
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package examples.connectrpc_grpc_servers
2+
3+
import cats.effect.{IO, IOApp}
4+
5+
object Main extends IOApp.Simple {
6+
7+
def run: IO[Unit] =
8+
Dependencies.create().use(_.run)
9+
10+
}

0 commit comments

Comments
 (0)