Skip to content

Instantly share code, notes, and snippets.

@keynmol
Last active July 19, 2024 13:52
Show Gist options
  • Save keynmol/513b239b21066d1d48bbd1fba443a8c5 to your computer and use it in GitHub Desktop.
Save keynmol/513b239b21066d1d48bbd1fba443a8c5 to your computer and use it in GitHub Desktop.
Using smithy4s-curl to call smithy4s-defined services from Scala Native using libcurl

https://github.com/neandertech/smithy4s-curl

This scala-cli script has two entrypoints, so you can produce two binaries from it - one using curl, and one using http4s Ember.

Because native dependencies are required for both entrypoints, I prefer to build them using my sn-vcpkg CLI:

To produce a binary using curl:

$ sn-vcpkg scala-cli curl --rename curl=libcurl -- package . -f -o ./native-curl -M helloCurl

To produce a binary using http4s ember:

$ sn-vcpkg scala-cli s2n openssl zlib -- package . -f -o ./native-ember -M helloEmber

Sizes comparison:

.rwxr-xr-x 11M 19 Jul 13:53 native-curl
.rwxr-xr-x 42M 19 Jul 13:56 native-ember

Basic hyperfine perf measurement against locally running httpbin:

> hyperfine './native-ember http://localhost:8081' './native-curl http://localhost:8081' -w 10
Benchmark 1: ./native-ember http://localhost:8081
  Time (mean ± σ):     136.2 ms ±   5.3 ms    [User: 124.9 ms, System: 6.5 ms]
  Range (min … max):   133.0 ms … 158.0 ms    22 runs

  Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.

Benchmark 2: ./native-curl http://localhost:8081
  Time (mean ± σ):      11.9 ms ±   1.9 ms    [User: 2.9 ms, System: 2.1 ms]
  Range (min … max):     9.5 ms …  25.8 ms    207 runs

Summary
  ./native-curl http://localhost:8081 ran
   11.44 ± 1.90 times faster than ./native-ember http://localhost:8081

Note that it's cheating a bit because we're linking against a release-built curl library, whereas the pure scala code of ember has no optimisations.

//> using dep co.fs2::fs2-io::3.10-365636d
//> using dep com.disneystreaming.smithy4s::smithy4s-http4s::0.18.23
//> using dep org.http4s::http4s-ember-client::0.23.27
//> using dep tech.neander::smithy4s-curl::0.0.2
//> using dep tech.neander::smithy4s-deriving::0.0.3
//> using nativeVersion 0.4.17
//> using option -Wunused:all
//> using platform scala-native
//> using scala 3.5.0-RC5
import smithy4s.*
import scala.annotation.experimental
import deriving.{given, *}, aliases.*
import cats.effect.IO
import scala.util.*
import cats.effect.IOApp
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.implicits.*
import smithy4s.http4s.SimpleRestJsonBuilder
import smithy4s_curl.*
import org.http4s.Uri
import cats.effect.ExitCode
case class Response(headers: Map[String, String], origin: String, url: String)
derives Schema
@experimental
@simpleRestJson
trait HttpbinServiceIO derives API:
@readonly
@httpGet("/get")
def get(): IO[Response]
@experimental
trait HttpbinServiceTry derives API:
@readonly
@httpGet("/get")
def get(): Try[Response]
@main @experimental
def helloCurl(url: String) =
val service =
SimpleRestJsonCurlClient(
API.service[HttpbinServiceTry],
url,
SyncCurlClient()
).make.unliftService
println(service.get())
@experimental
object helloEmber extends IOApp:
def run(args: List[String]) =
EmberClientBuilder
.default[IO]
.build
.flatMap: client =>
SimpleRestJsonBuilder(API.service[HttpbinServiceIO])
.client(client)
.uri(Uri.unsafeFromString(args.head))
.resource
.map(_.unliftService)
.use: client =>
client.get().flatMap(IO.println)
.as(ExitCode.Success)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment