Slimane
OverView
Slimane is an express inspired web framework for Swift that works on OSX and Ubuntu.
- Completely Asynchronous
- Unopinionated and Minimalist
- Adopts Open Swift
- Feature/Promise Support
A Work In Progress
Slimane is currently in active development.
Feel free to contribute, pull requests are welcome!
Attension
It works as a completely asynchronous and the core runtime is created with libuv.
So Don't stop the event loop with the CPU heavy tasks.
Guide of working with the cpu heavy tasks
๐
Slimae Project Page Various types of libraries are available from here.
https://github.com/slimane-swift
Considering/In developing
- Streaming Response
- Deal with Mysql via asynchronous networking
- Promise Support
- Command line interface
Getting Started
Install Guide
Here is install guides for each operating systems
Documentation
Here is Documentation for Slimane.
Usage
Starting the application takes slight lines.
import Slimane
let app = Slimane()
app.get("/") { req, responder in
responder {
Response(body: "Welcome Slimane!")
}
}
try! app.listen()
Example Project
https://github.com/slimane-swift/slimane-example
Routing
app.get("/articles/:id") { req, responder in
responder {
Response(body: "Article ID is: \(req.params["id"]!)")
}
}
Methods
- get
- options
- post
- put
- patch
- delete
Middlewares
Middleware is functions that have access to the http request, the http response, and the next function in the application' s request-response cycle. We offers 3 types of registration ways for that.
See more to visit https://github.com/slimane-swift/Middleware
MiddlewareType
struct AccessLogMiddleware: MiddlewareType {
func respond(req: Request, res: Response, next: MiddlewareChain) {
print("[\(Suv.Time())] \(req.uri.path ?? "/")")
next(.Chain(req, res))
}
}
app.use(AccessLogMiddleware())
AsyncMiddleware
Can pass the Open-Swift's AsyncMiddleware confirming Middleware
struct AccessLogMiddleware: AsyncMiddleware {
func respond(to request: Request, chainingTo next: AsyncResponder, result: (Void throws -> Response) -> Void) {
print("[\(Suv.Time())] \(request.path ?? "/")")
next.respond(to: request) { response in
result {
try response()
}
}
}
}
app.use(AccessLogMiddleware())
Handy
app.use { req, res, next in
print("[\(Suv.Time())] \(req.uri.path ?? "/")")
next(.Chain(req, res))
}
Intercept Response
Can intercept response at the middleware with passing any values into the Response.body. It means respond soon and never reaches next middleware chains and the route.
app.use { req, res, next in
var res = res
res.body("I'm intercepted at the middleware")
next(.Chain(req, res))
}
Request/Response
We are using S4.Request and S4.Response See more detail, please visit https://github.com/open-swift/S4
Static Files/Assets
Just register the Slimane.Static()
into middleware chains
app.use(Slimane.Static(root: "/path/to/your/public"))
Cookie
Set<Cookie>
request.cookies: request.cookies is Readonly.
req.cookies["session-id"]
Cookie is declared in S4. See more to visit https://github.com/open-swift/S4
Set<AttributedCookie>
response.cookies: response.cookies is Writable.
let setCookie = AttributedCookie(....)
res.cookies = Set<setCookie>
AttributedCookie is declared in S4. See more to visit https://github.com/open-swift/S4
Session
Register SessionMiddleware into the middleware chains. See more detail for SessionMiddleware to visit https://github.com/slimane-swift/SessionMiddleware
import Slimane
import SessionMiddleware
let app = Slimane()
// SessionConfig
let sesConf = SessionConfig(
secret: "my-secret-value",
expires: 180,
HTTPOnly: true
)
// Enable to use session in Slimane
app.use(SessionMiddleware(conf: sesConf))
app.get("/") { req, responder
// set data into the session
req.session["foo"] = "bar"
req.session.id // show session id
responder {
Response()
}
}
Available Session Stores
- MemoryStore
- SessionRedisStore
Body Data
Register BodyParser into the middleware chains.
app.use(BodyParser())
Zewo.JSON
request.json Can get parsed json data throgh the req.json when content-type is application/json
req.json?["foo"]
[String: String]
request.formData Can get parsed form data throgh the req.formData when content-type is application/x-www-form-urlencoded
req.formData?["foo"]
Views/Template Engines
- Add the Render module into the Package.swift
- Add the MustacheViewEngine module into the Package.swift
Then, You can use render function in Slimane. and pass the render object to the custome
label for Response initializer.
app.get("/render") { req, responder in
responder {
let render = Render(engine: MustacheViewEngine(templateData: ["foo": "bar"]), path "index")
Response(custome: render)
}
}
Create your own ViewEngine
Getting ready
Working with Cluster
A single instance of Slimane runs in a single thread. To take advantage of multi-core systems the user will sometimes want to launch a cluster of Slimane processes to handle the load.
Here is an easy example for working with Suv.Cluster
// For Cluster app
if Cluster.isMaster {
for _ in 0..<OS.cpuCount {
var worker = try! Cluster.fork(silent: false)
observeWorker(&worker)
}
try! Slimane().listen()
} else {
let app = Slimane()
app.use("/") { req, responder in
responder {
Response(body: "Hello! I'm \(Process.pid)")
}
}
try! app.listen()
}
IPC between Master and Worker Processes
Inter process message between master and workers
On Master
var worker = try! Cluster.fork(silent: false)
// Send message to the worker
worker.send(.Message("message from master"))
// Receive event from the worker
worker.on { event in
if case .Message(let str) = event {
print(str)
}
else if case .Online = event {
print("Worker: \(worker.id) is online")
}
else if case .Exit(let status) = event {
print("Worker: \(worker.id) is dead. status: \(status)")
worker = try! Cluster.fork(silent: false)
observeWorker(&worker)
}
}
On Worker
// Receive event from the master
Process.on { event in
if case .Message(let str) = event {
print(str)
}
}
// Send message to the master
Process.send(.Message("Hey!"))
Handling Errors
Easy to override Default Error Handler with replace app.errorHandler
to your costume handler.
All of the errors that occurred in Slimane's lifecycles are passed as first argument of errorHandler even RouteNotFound.
let app = Slimane()
app.errorHandler = myErrorHandler
func myErrorHandler(error: ErrorProtocol) -> Response {
let response: Response
switch error {
case Costume.InvalidPrivilegeError
response = Response(status: .forbidden, body: "Forbidden")
case Costume.ResourceNotFoundError(let name)
response = Response(status: .notFound, body: "\(name) is not found")
default:
response = Response(status: .badRequest, body: "\(error)")
}
return response
}
try! app.listen()
Note
Synchronous style ErrorHandler will be replaced to asynchronous.
Versions
- 0.1x: https://github.com/noppoMan/Slimane/tree/0.1.2
- 0.2x: Here. There should be significant changes from 0.1x due to adopting open-swift.
Package.swift
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
.Package(url: "https://github.com/noppoMan/Slimane.git", majorVersion: 0, minor: 2),
]
)
License
Slimane is released under the MIT license. See LICENSE for details.