Snunit
Scala Native HTTP server based on NGINX Unit
Install / Use
/learn @lolgab/SnunitREADME
SNUnit: Scala Native HTTP server based on NGINX Unit
import snunit.*
@main
def run =
SyncServerBuilder
.setRequestHandler(req =>
req.send(
statusCode = StatusCode.OK,
content = "Hello world!\n",
headers = Headers("Content-Type" -> "text/plain")
)
)
.build()
.listen()
SNUnit is a Scala Native library to write HTTP server applications on top of NGINX Unit. It allows you to write both synchronous and asynchronous web servers with automatic restart on crashes, automatic load balancing of multiple processes, great performance and all the nice NGINX Unit features.
Running your app
Once built your SNUnit binary, you need to deploy it to the unitd server.
You need to run unitd in a terminal with:
unitd --no-daemon --log /dev/stdout --control unix:control.sock
This will run unitd with a UNIX socket file named control.sock in your current directory.
Then, you need to create a json file with your configuration:
{
"listeners": {
"*:8081": {
"pass": "applications/myapp"
}
},
"applications": {
"myapp": {
"type": "external",
"executable": "snunit/binary/path"
}
}
}
Where executable is the binary path which can be absolute or relative
to the unitd working directory.
This configuration passes all requests sent to the port 8081 to the application myapp.
To know more about configuring NGINX Unit, refer to its documentation.
To deploy the setting you can use curl:
curl -X PUT --unix-socket control.sock -d @config.json localhost/config
If everything went right, you should see this response:
{
"success": "Reconfiguration done."
}
In case of problems, you will get a 4xx response like this:
{
"error": "Invalid configuration.",
"detail": "Required parameter \"executable\" is missing."
}
Further information can be found in unitd logs in the running terminal.
Sync and async support
SNUnit has two different server implementations.
With SyncServerBuilder you need to call .listen() to start listening.
It is a blocking operation so your process is stuck on listening and can't do
anything else while listening.
Moreover, all the request handlers need to respond directly and can't be implemented
using Futures or any other asyncronous mechanism since no Future will run, being
the process stuck on the listen() Unit event loop.
With http4s or tapir-cats-effect the server is automatically scheduled to run either on the
cats effect event loop, based on epoll/kqueue.
This allows you to complete requests asyncronously using whatever mechanism you prefer.
A process can accept multiple requests concurrently, allowing great parallelism.
Tapir support
SNUnit offers interpreters for Tapir server endpoints.
You can write all your application using Tapir and the convert your Tapir endpoints
with logic into a SNUnit Handler.
Currently two interpreters are available:
SNUnitIdServerInterpreterwhich works best withSyncServerHandlerfor synchronous applications- You can find an example in tests
- An interpreter for cats hidden behind
snunit.tapir.SNUnitServerBuilderin thesnunit-tapir-cats-effectartifact.- You can find an example in tests
Automatic server creation
snunit.TapirApp extends cats.effect.IOApp building the SNUnit server.
It exposes a def serverEndpoints: Resource[IO, List[ServerEndpoint[Any, IO]]] that you need to
implement with your server logic.
Here an example "Hello world" app:
import cats.effect.*
import sttp.tapir.*
object Main extends snunit.TapirApp {
def serverEndpoints = Resource.pure(
endpoint.get
.in("hello")
.in(query[String]("name"))
.out(stringBody)
.serverLogic[IO](name => IO(Right(s"Hello $name!"))) :: Nil
)
}
Http4s support
SNUnit offers a server implementation for http4s. It is based on the epollcat asynchronous event loop.
There are two ways you can build a http4s server.
Automatic server creation
snunit.Http4sApp extends cats.effect.IOApp building the SNUnit server.
It exposes a def routes: Resource[IO, HttpApp[IO]] that you need to implement with your
server logic.
Here an example "Hello world" app:
import cats.effect.*
import org.http4s.*
import org.http4s.dsl.io.*
object app extends snunit.Http4sApp {
def routes = Resource.pure(
HttpRoutes
.of[IO] { case GET -> Root =>
Ok("Hello from SNUnit Http4s!")
}
.orNotFound
)
}
Manual server creation
If you want to have more control over the server creation, you can use the
SNUnitServerBuilder and manually use it.
For example, here you see it in combination with cats.effect.IOApp
package snunit.tests
import cats.effect.*
import org.http4s.*
import org.http4s.dsl.io.*
import snunit.http4s.*
object Http4sHelloWorld extends IOApp.Simple {
def helloWorldRoutes: HttpRoutes[IO] =
HttpRoutes.of[IO] { case GET -> Root =>
Ok("Hello Http4s!")
}
def run: IO[Unit] =
SNUnitServerBuilder
.default[IO]
.withHttpApp(helloWorldRoutes.orNotFound)
.run
}
Related Skills
node-connect
345.9kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
106.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.9kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.9kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
