An express inspired web framework for swift
Slimane is currently in active development.
Feel free to contribute and pull requests are welcome!
-
Deal with Database/Other Data Store
-
Deploying
Here is install guides for each operating systems
Here is Documentation for Slimane.
Check out the Example Project to start development with Slimane.
The usage of Slimane is similar with express
Example Project is here
import Slimane
let app = Slimane()
app.use(BodyParser())
app.use(CookieParser())
app.use(SessionHandler(
SessionConfig(
secret: "secret",
expires: Time(tz: .UTC).addDay(7).rfc822
)
))
app.use(StaticFileServe())
app.get("/") { req, res in
res.write("Welcome to Slimane!")
}
// Bind address, port and listen http server
try! app.listen(host: "127.0.0.1", port: 3000)
Slimane's router is simple and similar with Express routing.
- get
- post
- put
- patch
- delete
struct UserCreateRoute: RouteType {
func handleRequest(req: Request, res: Response) throws {
guard let json = req.jsonBody, let _name = json["name"], userName = _name.stringValue else {
throw CustomErrorType("name is required")
}
DB.query("INSERT INTO users (name) values (?)", userName) { err, result in
if(err) {
return res.status(.BadRequest).write("Something went wrong")
}
res.write("The user \(userName) was created")
}
}
}
app.post("/user", UserCreateRoute)
app.post("/user") { req, res in
res.write("Hello world!")
}
Middleware is functions that have access to the http request, the http response, and the next function in the application' s request-response cycle.
If the next function is not called, request-response cycle is stopped or should call res.write to end request in the middleare.
struct BodyParser: MiddlewareType {
func handleRequest(req: Request, res: Response, next: MiddlewareChain) throws {
guard let body = req.bodyString, let contentType = req.contentType else {
next(nil)
return
}
switch(contentType.type) {
case "application/json":
req.context["jsonBody"] = try! JSONParser.parse(body)
case "application/x-www-form-urlencoded":
req.context["formData"] = parseURLEncodedString(body)
default:
print("Unkown content type")
}
next(nil)
}
}
app.use(BodyParser())
// of course you can register middleware as following
app.use(BodyParser().handleRequest) // app.use { req, res, next in ... }
app.use { req, res, next in
// write some middlware logic
next(nil)
}
Parse http body data depends on Content-Type.
- application/json: Ready
- application/x-www-form-urlencoded: Ready
- application/xml: Not implemented yet
- multipart/form-data: Not implemented yet
Default is application/x-www-form-urlencoded
Register CookieParser and SessionHandler into middleware chain then You can use session.
app.use(CookieParser(secret: "secret"))
let t = Time(tz: .UTC) // Time class is declared in Suv
t.addDay(7)
let sesConf = SessionConfig(
keyName: "my-session-id",
secret: "secret",
expires: t.rfc822
)
app.use(SessionHandler(sesConf))
app.use { req, res, next in
print(req.sessionId) // see current session id
req.session["bar"] = "session value"
next(nil)
}
app.get("/foo") { req, res in
res.write(req.session["bar"] as! String)
}
Just add StaticFileServe() into middelware chain.
// Pass the document root for static assets to StaticFileServe.
app.use(StaticFileServe("/path/to/public/"))
Pick a param in URI such as /users/:id
from req.params
// If The Requested URL is Get /users/200 ...
app.use { req, res, next in
req.params["id"] // 200
}
Pick a query string in URI from req.query
// If The Requested URL is Get /users?id=200 ...
app.use { req, res, next in
req.query["id"] // 200
}
Show all request headers and pick a value for key
app.use { req, res, next in
// Show all request headers
req.headers
// Get a header value for key
req.getHeader("Connection") // Keep-Alive
}
Access the JSON data through the req.jsonBody req.jsonBody is optional computed value
First, Need to add BodyParser into middleware chain
app.use { req, res, next in
req.jsonBody?["foo"]
}
Access the Form data through the req.formData? req.formData is optional computed value
First, Need to add BodyParser into middleware chain
app.use { req, res, next in
req.formData?["bar"]
}
Set response header with res.setHeader method
app.use { req, res, next in
res.setHeader("Connection", "Keep-Alive")
}
Set response status with res.status method The available Statuses are here
app.use { req, res, next in
res.status(.OK).write("foobar")
}
Currently Slimane does not provide template engine.
But that's easy to implement with existing template engine in swift.
See Example of adding render
method into response
Call res.write to write data to client. Write method can take String, Buffer and [Int8] byte array.
The Buffer class is generic data type in Suv
app.get("/") { req, res in
res.write("Welcome Slimane!")
}
Respond binary data. Here is streaming response sample
app.get("/binary-content") { req, res in
// specify Transfer-Encoding header
res.setHeader("Transfer-Encoding", "Chunked")
res.writeHead() // write head
let fs = FileSystem(path: "/path/to/unkown-size-image.png") // FileSystem is declared in Suv
fs.read(.R) {
if case .Error(let error) = result {
fs.close()
// Write Error and end response
res.write("\(error)")
res.end()
} else if case .Data(let buffer) = result {
// Write chunk
res.write(buffer)
} else {
fs.close()
// Write 0\r\n\r\n to close stream.
res.end()
}
}
}
Filters are the middleware and are individual functions that make up the request processing pipeline.
beforeWrite
: before write packet to clientafterWrite
: after write packet to client
app.use { req, res next in
req.appendBeforeWriteCallback({
print("before1")
})
req.appendBeforeWriteCallback({
print("before2")
})
req.appendAfterWriteCallback({
print("after1")
})
req.appendAfterWriteCallback({
print("after2")
})
next(nil)
}
app.get("/") { req, res in
res.write("OK")
/*
* 1. before1 printed
* 2. before2 printed
* 3. Write message(OK) packet
* 4. after1 printed
* 5. after2 printed
*/
}
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.
Suv.Cluster module enable you to easily create own child processes.(It's like node.js cluster module interface)
First, You need to bind and listen server on master process. Because current Suv.Cluster doesn't support interprocess communication between master and worker without shared connection. So worker can not send proxy request that listen server at its specified address to master.
import Suv
import Slimane
if Cluster.isMaster {
let cluster = Cluster(Process.arguments)
for i in 0..<OS.cpuCount {
let worker = try! cluster.fork(silent: false)
}
// Need to bind address and listen server on parent
try! Slimane().listen(host: "0.0.0.0", port: 3000)
} else {
let app = Slimane()
app.get("/") { req, res in
res.write("Hello! I'm a \(Process.pid)")
}
// Not need bind, cause child processes use parent established connection with IPC.
try! app.listen()
}
We have a couple of error notification styles.
struct SomeValidator: MiddlewareType {
func handleRequest(req: Request, res: Response, next: MiddlewareChain) throws {
if(someValidation(req)) {
throw CustomErrorType("Validation Error")
}
next(nil)
}
}
Our Server is based on libuv, So if you wanna take data through I/O and Network, We should use non blocking Api to take that. Anyway it means need to use Callback, Event Emitter and Promise etc...
struct StaticFileServe: MiddlewareType {
func handleRequest(req: Request, res: Response, next: MiddlewareChain) throws {
File.read("/public/foo/bar.jpg") { err, image throws in
if(err) {
throw err
}
res.setHeader("Content-Type", "image/jpeg")
res.write(image)
}
}
}
If the I/O library doesn't support throw
, you can pass the error to next callback. and it means to go to ErrorHandler.handle even the middleware chain isn't reached at the last.
struct StaticFileServe: MiddlewareType {
func handleRequest(req: Request, res: Response, next: MiddlewareChain) throws {
File.read("/public/foo/bar.jpg") { err, image in
if(err) {
return next(err)
}
res.setHeader("Content-Type", "image/jpeg")
res.write(image)
}
}
}
You can overwrite default error handler.
All of the errors which are thrown in routes and middlewares will be came here.
struct MyErrorHandler: ErrorHandler {
func handle(req req: Request, res: Response, error: ErrorType) {
switch(error) {
case Error.RouteNotFound:
res.status(.NotFound).write("path is not found")
case CustomeErrorType(let err):
res.status(.BadRequest).write(err.description)
default:
res.status(.BadRequest).write("Unhandled error was encountered")
}
}
// Overwrite default errorHandler
app.errorHandler = MyErrorHandler()
import PackageDescription
let package = Package(
name: "MyApp",
dependencies: [
.Package(url: "https://github.com/noppoMan/Slimane.git", majorVersion: 0, minor: 1),
]
)
(The MIT License)
Copyright (c) 2016 Yuki Takei(Noppoman) [email protected]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and marthis permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.