Skip to content

Commit 4d465f1

Browse files
authored
Add examples, fix warning re passing a Connection header from client to server (#177)
1 parent fcbe8af commit 4d465f1

File tree

19 files changed

+376
-10
lines changed

19 files changed

+376
-10
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ Features comparison:
113113

114114
| | __http4s frontend__ | __Netty frontend__ |
115115
|-----------------------|-------------------------------------|-------------------------------------|
116-
| __Status__ | 🧡 production ready | alpha |
116+
| __Status__ | production ready | alpha |
117117
| | | |
118118
| __ConnectRPC server__ |||
119119
| - JSON encoding | ✅ (fully conformant) | ✅ (fully conformant) |
@@ -148,8 +148,10 @@ Built-in [GRPC Transcoding](https://cloud.google.com/endpoints/docs/grpc/transco
148148

149149
![Maven Central](https://img.shields.io/maven-central/v/me.ivovk/connect-rpc-scala-core_3?style=flat-square&color=green)
150150

151-
Installing http4s frontend (supports server, client) with SBT, you also need to install one of `http4s` server
152-
implementations:
151+
Check [examples](https://github.com/igor-vovk/connect-rpc-scala/tree/main/example) directory for some examples of using
152+
the library.
153+
154+
In case of http4s frontend, you will also need one of `http4s` server implementations, Ember in this case:
153155

154156
```scala
155157
libraryDependencies ++= Seq(
@@ -161,7 +163,7 @@ libraryDependencies ++= Seq(
161163
)
162164
```
163165

164-
Netty frontend (server only, no client support yet) can be installed with SBT:
166+
Netty frontend (server only, no client support yet) can be installed by adding the following dependency:
165167

166168
```scala
167169
libraryDependencies ++= Seq(

build.sbt

Lines changed: 44 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,80 @@ 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+
example_client_server,
113+
)
114+
.settings(noPublish)
115+
116+
lazy val example_connectrpc_grpc_servers = project.in(file("examples/connectrpc_grpc_servers"))
117+
.dependsOn(http4s)
118+
.enablePlugins(Fs2Grpc)
119+
.settings(
120+
noPublish,
121+
commonDeps,
122+
libraryDependencies ++= Seq(
123+
"me.ivovk" %% "cedi" % Versions.cedi,
124+
"io.grpc" % "grpc-netty" % Versions.grpc,
125+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
126+
"ch.qos.logback" % "logback-classic" % Versions.logback % Runtime,
127+
),
128+
Compile / mainClass := Some("examples.connectrpc_grpc_servers.Main"),
129+
)
130+
131+
lazy val example_client_server = project.in(file("examples/client_server"))
132+
.dependsOn(http4s, netty)
133+
.enablePlugins(Fs2Grpc, JavaAppPackaging)
134+
.settings(
135+
noPublish,
136+
commonDeps,
137+
libraryDependencies ++= Seq(
138+
"me.ivovk" %% "cedi" % Versions.cedi,
139+
"org.http4s" %% "http4s-ember-server" % Versions.http4s,
140+
"org.http4s" %% "http4s-ember-client" % Versions.http4s,
141+
"ch.qos.logback" % "logback-classic" % Versions.logback % Runtime,
142+
),
143+
Compile / mainClass := Some("examples.client_server.Main"),
144+
)
107145

108146
lazy val root = (project in file("."))
109147
.aggregate(
110148
core,
111149
http4s,
112150
netty,
113151
conformance,
152+
examples,
114153
)
115154
.settings(
116155
name := "connect-rpc-scala",

examples/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# List of examples
2+
3+
- **client_server**: Shows how to create a simple client-server application communicating over HTTP using ConnectRPC.
4+
- **connectrpc_grpc_servers**: Example of starting both a gRPC server and a ConnectRPC server in the same component on
5+
different ports.

examples/client_server/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# ConnectRPC client-server communication example
2+
3+
This example demonstrates launching a ConnectRPC server and establishing communication with it via a ConnectRPC client.
4+
5+
Dependencies are wired using [Cedi](https://github.com/igor-vovk/cedi) library.
6+
7+
To run the app execute the following command in the root of the project:
8+
9+
```bash
10+
sbt example_client_server/run
11+
```
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: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package examples.client_server
2+
3+
import cats.effect.{IO, Resource}
4+
import com.comcast.ip4s.*
5+
import examples.v1.eliza.ElizaServiceFs2Grpc
6+
import io.grpc.{Channel, Metadata, ServerServiceDefinition}
7+
import me.ivovk.cedi.Allocator
8+
import me.ivovk.cedi.syntax.*
9+
import org.http4s.HttpApp
10+
import org.http4s.client.Client
11+
import org.http4s.ember.client.EmberClientBuilder
12+
import org.http4s.ember.server.EmberServerBuilder
13+
import org.http4s.implicits.uri
14+
import org.ivovk.connect_rpc_scala.http4s.{ConnectHttp4sChannelBuilder, ConnectHttp4sRouteBuilder}
15+
import scala.concurrent.duration.*
16+
17+
object Dependencies {
18+
19+
def create(): Resource[IO, Dependencies] =
20+
Allocator.create[IO]().map(Dependencies(using _))
21+
22+
}
23+
24+
class Dependencies(using AllocatorIO) {
25+
26+
// SERVER DEPENDENCIES -------------------------------------------
27+
28+
lazy val elizaServiceImpl: ElizaServiceFs2Grpc[IO, Metadata] = new ElizaService
29+
30+
// This example uses the `fs2.grpc` syntax to allocate resources for the gRPC service, but you can also use
31+
// ZIO-gRPC or any other gRPC library for Scala to obtain ServerServiceDefinition of your services.
32+
lazy val elizaServiceGrpcServerDefinition: ServerServiceDefinition = allocate {
33+
ElizaServiceFs2Grpc.bindServiceResource[IO](elizaServiceImpl)
34+
}
35+
36+
lazy val httpServerApp: org.http4s.HttpApp[IO] = allocate {
37+
ConnectHttp4sRouteBuilder.forService[IO](elizaServiceGrpcServerDefinition).build
38+
}
39+
40+
lazy val connectRpcServer: Resource[IO, org.http4s.server.Server] =
41+
EmberServerBuilder.default[IO]
42+
.withHost(host"0.0.0.0")
43+
.withPort(port"8080")
44+
.withHttpApp(httpServerApp)
45+
.withShutdownTimeout(1.second)
46+
.build
47+
48+
// CLIENT DEPENDENCIES -------------------------------------------
49+
50+
lazy val httpClient: org.http4s.client.Client[IO] = allocate {
51+
EmberClientBuilder.default[IO].build
52+
}
53+
54+
// `Channel` is a gRPC term describing a connection to a server.
55+
// Both fs2-grpc and ZIO-gRPC accept Channels in their clients to communicate with a server.
56+
lazy val connectRpcChannel: io.grpc.Channel = allocate {
57+
ConnectHttp4sChannelBuilder[IO](httpClient).build(uri"http://localhost:8080/")
58+
}
59+
60+
lazy val elizaClient: ElizaServiceFs2Grpc[IO, Metadata] = allocate {
61+
ElizaServiceFs2Grpc.stubResource[IO](connectRpcChannel)
62+
}
63+
64+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package examples.client_server
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package examples.client_server
2+
3+
import cats.effect.{IO, IOApp}
4+
import examples.v1.eliza.SayRequest
5+
import io.grpc.Metadata
6+
import org.typelevel.log4cats.slf4j.*
7+
8+
object Main extends IOApp.Simple {
9+
10+
private val logger = Slf4jFactory.create[IO].getLogger
11+
12+
def run: IO[Unit] =
13+
Dependencies.create().use { deps =>
14+
for {
15+
// Start the server
16+
serverFiber <- deps.connectRpcServer.useForever.start
17+
18+
// Call the Eliza service
19+
clientResponse <- deps.elizaClient.say(SayRequest("Hello, Eliza!"), new Metadata())
20+
_ <- logger.info(s"Received response: ${clientResponse.sentence}")
21+
22+
// Stop the server
23+
_ <- serverFiber.cancel
24+
_ <- logger.info("Server stopped")
25+
} yield ()
26+
}
27+
28+
}

0 commit comments

Comments
 (0)