Hot questions for Using Vapor in server

Question:

How do you send an API request in Vapor 3 with the HTTPRequest struct?

I tried variations of the following code..

var headers: HTTPHeaders = .init()
let body = HTTPBody(string: a)            
let httpReq = HTTPRequest(
    method: .POST,
    url: URL(string: "/post")!,
    headers: headers,
    body: body)

let httpRes: EventLoopFuture<HTTPResponse> = HTTPClient.connect(hostname: "httpbin.org", on: req).map(to: HTTPResponse.self) { client in
    return client.send(httpReq)
}

The compile error Cannot convert value of type '(HTTPClient) -> EventLoopFuture<HTTPResponse>' to expected argument type '(HTTPClient) -> _'

I have tried other variations of code that worked.

Vapor 3 Beta Example Endpoint Request

let client = try req.make(Client.self)

let response: Future<Response> = client.get("http://example.vapor.codes/json")

I read and re-read:


Answer:

Your problem is .map(to: HTTPResponse.self). Map needs to transform its result into a new result regularly, like you would map an array. However, the result of your map-closure returns an EventLoopFuture<HTTPResponse>. This results in your map function returning an EventLoopFuture<EventLoopFuture<HTTPResponse>>.

To avoid this complexity, use flatMap.

var headers: HTTPHeaders = .init()
let body = HTTPBody(string: a)            
let httpReq = HTTPRequest(
    method: .POST,
    url: URL(string: "/post")!,
    headers: headers,
    body: body)

let client = HTTPClient.connect(hostname: "httpbin.org", on: req)

let httpRes = client.flatMap(to: HTTPResponse.self) { client in
    return client.send(httpReq)
}

EDIT: If you want to use the Content APIs you can do so like this:

let data = httpRes.flatMap(to: ExampleData.self) { httpResponse in
    let response = Response(http: httpResponse, using: req)
    return try response.content.decode(ExampleData.self)
}

Question:

Can I use timer, such as NSTimer in Vapor (server-side Swift)?

I hope my server written in Vapor can do some tasks proactively once in a while. For example, polling some data from the web every 15 mins.

How to achieve this with Vapor?


Answer:

If you can accept your task timer being re-set whenever the server instance is recreated, and you only have one server instance, then you should consider the excellent Jobs library.

If you need your task to run exactly at the same time regardless of the server process, then use cron or similar to schedule a Command.

Question:

I'm suffering from server problem on both perfect and vapor ..

The server working for only one time , and once after i update the project and rebuild it, the server response does not update any more.

I got this error ... from vapor in Xcode

No command supplied, defaulting to serve...
Starting server on 0.0.0.0:8080
Serve error: Sockets Error: Failed trying to bind to the address

Identifier: Sockets.SocketsError.bindFailed
Program ended with exit code: 0

I googled alot , but did not figure out the problem

Finally : when i restart the mac , the server worked good only for one time , and the problem returned the same .


Answer:

Seems like a process is using port 8080. Try running lsof -i :8080 from the terminal and kill it's PID with kill -9 PID

Question:

I'm writing my Swift app for Ubuntu using Vapor. And my mission is to have the smallest Docker image for production. I've trimmed down my image significantly but I wanted to know, just out of curiosity, does my final executable need all the compiled .module, .doc and .build files in the same directory?


Answer:

tl;dr: No.

The folders/files you listed are byproducts of the build process and can be safely discarded.

When it comes to distribution, your application is just like any other Linux executable. You must have all dynamically linked libraries available on the target system.

These include the runtime libraries of the Swift toolchain plus any compiled C modules your application (or the framework beneath it) links with (*).

You can check the dependencies of the executable using the ldd command. Some of them are available as packages, some of them will need to be copied to the target system manually.


(*) In case of a Vapor 2 application, such C modules are libCHTTP.so and libCSQLite.so, which are placed in your build folder.

Question:

Is there a possibility of scheduling tasks on a server-side Swift framework, preferably Kitura?

I need to schedule tasks; for example, wiping a database everyday at 3AM.


Answer:

In Kitura at least, we don't provide an special functionality for that.

One thing you can consider using is Dispatch, which will work very well for your example of deleting the database everyday at 3AM. You can create a DispatchSourceTimer that dispatches some code after some interval once or repeatedly.

DispatchSourceTimer.scheduleOneshot(deadline: DispatchTimer, leeway: DispatchTimeInterval)
DispatchSourceTimer.scheduleRepeating(deadline: DispatchTime, interval: DispatchTimeInterval, leeway: DispatchTimeInterval)

Question:

I have a web app developed using server side swift vapor 3 running locally on my Mac. I want to access it using my iPhone on the same wifi as my Mac. How can I do that. I tried using http://<the ip of my Mac on local network>:8080 but didn't work, even with firewall completely off. Thanks in advance.


Answer:

Edit you Run scheme in Xcode and put --hostname 0.0.0.0 --port 8080 into Arguments Passed On Launch

Then launch you server and try to reach it by http://<the ip of your Mac on local network>:8080

NOTE: 0.0.0.0 means that your server will listen on all network interfaces

Question:

I'm looking for a Swift counterpart to Django's Celery which allows a function to execute every given amount of time.

I need a solution that works on server side Swift, meaning not all of Foundation is available, and something that's not for iOS / Mac.

I am using the Vapor framework.


Answer:

You have three main options.

For a timer managed within your server app (i.e. restarting the server resets your timers) you can use Dispatch:

import Dispatch
let timer = DispatchSource.makeTimerSource()
timer.setEventHandler() {
  // task
}
timer.scheduleRepeating(deadline: .now() + .seconds(3600), interval: .seconds(3600), leeway: .seconds(60))
timer.activate()

Similarly, you can use the third party Jobs package created by a Vapor contributor:

import Jobs
Jobs.add(interval: .hours(1)) {
  // task
}

If you want the function to be run at particular times of day independent of your server's uptime, there's no beating cron (or its relatives). Your cron job should either call a Vapor Command on the binary, or hit a protected URL-route with curl.

Question:

  1. How do I download a file using server side swift?

I have tried this:

let result = try drop.client.get("http://dropcanvas.com/ir4ok/1")

but result.body always = 0 elements

  1. How do I send a file?

I have tried this

drop.get("theFile") { request in 
   let file = NSData(contentsOf: "/Users/bob.zip")
   return file // This fails here
}

Answer:

  1. Download a file.

You are on the right track here, but the reason why result.body is always empty is because your file service is returning a 302 redirection rather than the file itself. You need to follow this redirection. Here is a simple implementation, specific to your use case only, which works:

  var url: String = "http://dropcanvas.com/ir4ok/1"
  var result: Response!
  while true {
    result = try drop.client.get(url)
    guard result.status == .found else { break }
    url = result.headers["Location"]!
  }
  let body = result.body
  1. Send a file.

The very best method is to save your file in your Vapor app's Public directory, and either have your client request the public URL directly, or return a 302 response of your own pointing to it.

If you expressly want to hide the permanent home of the file or e.g. perform authentication then you can return the file from your own route using Vapor's own FileMiddleware as a guide.

Question:

I have a route handler which returns a Future for my login page, defined as follows :

func boot(router: Router) throws {
    let authSessionRoutes = router.grouped(User.authSessionsMiddleware())
    authSessionRoutes.get("/login", use: loginHandler)
}

func loginHandler(_ req: Request) throws -> Future<View> {
    return try req.view().render("loginPage")
}

This works well when the user is not connected, but I'd like to add logic so that any authenticated user trying to access this page would be redirected to their homepage instead.

Here's what I tried :

func loginHandler(_ req: Request) throws -> Future<View> {

    if let _ = try req.authenticated(User.self) {
        return req.redirect(to: "/") <<< Cannot convert return expression of type 'Response' to return type 'EventLoopFuture<View>'
    }

    return try req.view().render("loginPage")
}

This tells me that since my loginHandler is expected to return a Future<View>, returning a Response does not conform to the method's definition.

What would be the best way to handle this case?

EDIT :

Thanks for the answer ! My final code looks like so :

func loginHandler(_ req: Request) throws -> Future<Response> {
    if let _ = try req.authenticated(User.self) {
        return req.future().map() {
            return req.redirect(to: "/")
        }
    }
    return try req.view().render("loginPage").encode(for: req)
}

Answer:

You have two options - you can either use the Abort.redirect(to:) error and throw that instead of calling the redirect.

Otherwise you can change the return type to Future<Response> and convert the rendered view to a response.

Question:

I am currently struggling with doing an upsert with vapor/fluent. I have a model something like this:

struct DeviceToken: PostgreSQLModel {
    var id: Int?
    var token: String
    var updatedAt: Date = Date()

    init(id: Int? = nil, token: String, updatedAt: Date = Date()) {
        self.id = id
        self.token = token
        self.updatedAt = updatedAt
    }
}

struct Account: PostgreSQLModel {
    var id: Int?
    let username: String
    let service: String
    ...
    let deviceTokenId: DeviceToken.ID

    init(id: Int? = nil, service: String, username: String, ..., deviceTokenId: DeviceToken.ID) {
        self.id = id
        self.username = username
        ....
        self.deviceTokenId = deviceTokenId
    }
}

From the client something like

{
    "deviceToken": {
        "token": "ab123",
        "updatedAt": "01-01-2019 10:10:10"
    },
    "account": {
        "username": "user1",
        "service": "some service"
    }
}

is send.

What I'd like to do is to insert the new models if they do not exist else update them. I saw the create(orUpdate:) method however this will only update if the id is the same (in my understanding). Since the client does not send the id i am not quite sure how to handle this.

Also I can't decode the model since the account is send without the deviceTokenId and therefore the decoding will fail. I guess I can address the latter problem by overriding NodeCovertible or by using two different models (one for decoding the json without the id and the actual model from above). However the first problem still remains.

What I exactly want to do is:

  1. Update a DeviceToken if an entry with token already exists else create it

  2. If an account with the combination of username and service already exists update its username, service and deviceTokenId else create it. DeviceTokenId is the id returned from 1.

Any chance you can help me out here ?


Answer:

For everyone who is interested: I solved it by writing an extension on PostgreSQLModel to supply an upsert method. I added a gist for you to have a look at: here.

Since these kind of links sometimes are broken when you need the information here a quick overview:

Actual upsert implementation:

extension QueryBuilder
where Result: PostgreSQLModel, Result.Database == Database {

    /// Creates the model or updates it depending on whether a model
    /// with the same ID already exists.
    internal func upsert(_ model: Result,
                         columns: [PostgreSQLColumnIdentifier]) -> Future<Result> {

        let row = SQLQueryEncoder(PostgreSQLExpression.self).encode(model)

        /// remove id from row if not available
        /// otherwise the not-null constraint will break
        row = row.filter { (key, value) -> Bool in
            if key == "id" && value.isNull { return false }
            return true
        }

        let values = row
            .map { row -> (PostgreSQLIdentifier, PostgreSQLExpression) in
                return (.identifier(row.key), row.value)
        }

        self.query.upsert = .upsert(columns, values)
        return create(model)
    }

}

Convenience methods

extension PostgreSQLModel {

    /// Creates the model or updates it depending on whether a model
    /// with the same ID already exists.
    internal func upsert(on connection: DatabaseConnectable) -> Future<Self> {
        return Self
            .query(on: connection)
            .upsert(self, columns: [.keyPath(Self.idKey)])
    }

    internal func upsert<U>(on connection: DatabaseConnectable,
                        onConflict keyPath: KeyPath<Self, U>) -> Future<Self> {
        return Self
            .query(on: connection)
            .upsert(self, columns: [.keyPath(keyPath)])
    }

    ....
}

I solved the other problem I had that my database model could not be decoded since the id was not send from the client, by using a inner struct which would hold only the properties the client would send. The id and other database generated properties are in the outer struct. Something like:

struct DatabaseModel: PostgreSQLModel {

    var id: Int?
    var someProperty: String

    init(id: Int? = nil, form: DatabaseModelForm) {

        self.id = id
        self.someProperty = form.someProperty
    }

    struct DatabaseModelForm: Content {
        let someProperty: String
    }
}

Question:

Do you have any example of how to create One to Many relation using Vapor 2? There are some examples of how to do this, but they use the old version of Vapor.

Thank you for all suggestions.


Answer:

I have found a solution. Here is simple example of an owner having many cars, maybe will be helpful for someone.

Owner:

final class Owner: Model {
    static let idKey = "id"
    static let nameKey = "name"
    static let carsKey = "cars"

    var name: String
    let storage = Storage()

    var cars: Children<Owner, Car> {
        return children()
    }


    init(name: String) {
        self.name = name
    }

    init(row: Row) throws {
        name = try row.get(Owner.nameKey)
    }

    func makeRow() throws -> Row {
        var row = Row()
        try row.set(Owner.nameKey, name)
        return row
    }
}

extension Owner: Preparation {

    static func prepare(_ database: Database) throws {
        try database.create(self) { builder in
            builder.id()
            builder.string(Owner.nameKey)
        }
    }

    static func revert(_ database: Database) throws {
        try database.delete(self)
    }
}

extension Owner: JSONConvertible {

    convenience init(json: JSON) throws {
        try self.init(
            name: json.get(Owner.nameKey)
        )
    }

    func makeJSON() throws -> JSON {
        var json = JSON()
        try json.set(Owner.idKey, id)
        try json.set(Owner.nameKey, name)
        try json.set(Owner.carsKey, try cars.all())
        return json
    }
}

extension Owner: ResponseRepresentable { }

extension Owner: Updateable {
    public static var updateableKeys: [UpdateableKey<Owner>] {
        return [
            UpdateableKey(Owner.nameKey, String.self) { owner, text in
                owner.name = name
            }
        ]
    }
}

Car:

final class Car: Model {
    static let idKey = "id"
    static let makeKey = "make"
    static let modelKey = "model"
    static let ownerIdKey = "owner_id"

    var make: String
    var model: String
    var ownerId: Identifier
    let storage = Storage()

    var owner: Parent<Car, Owner> {
        return parent(id: ownerId)
    }

    init(make: String, model: String, ownerId: Identifier) {
        self.make = make
        self.model = model
        self.ownerId = ownerId
    }

    init(row: Row) throws {
        make = try row.get(Car.makeKey)
        model = try row.get(Car.modelKey)
        ownerId = try row.get(Car.ownerIdKey)
    }

    func makeRow() throws -> Row {
        var row = Row()
        try row.set(Car.makeKey, make)
        try row.set(Car.modelKey, model)
        try row.set(Car.ownerIdKey, ownerId)
        return row
    }
}

extension Car: JSONConvertible {

    convenience init(json: JSON) throws {
        try self.init(
            make: json.get(Car.makeKey),
            model: json.get(Car.modelKey),
            ownerId: json.get(Car.ownerIdKey)
        )
    }

    func makeJSON() throws -> JSON {
        var json = JSON()
        try json.set(Car.idKey, id)
        try json.set(Car.makeKey, make)
        try json.set(Car.modelKey, model)
        try json.set(Car.ownerIdKey, ownerId)
        return json
    }
}

extension Car: ResponseRepresentable {}

extension Car: Preparation {

    static func prepare(_ database: Database) throws {
        try database.create(self) { builder in
            builder.id()
            builder.string(Car.makeKey)
            builder.string(Car.modelKey)
            builder.foreignId(for: Owner.self)
        }
    }

    static func revert(_ database: Database) throws {
        try database.delete(self)
    }
}

extension Car: Updateable {
    public static var updateableKeys: [UpdateableKey<Car>] {
        return [
            UpdateableKey(Car.makeKey, String.self) { car, make in
                car.make = make
            }
        ]
    }
}

Question:

I am getting started with Vapor in Xcode to build a simple server to support my app. I am trying to understand how to properly build JSON objects I can return for example in a get request.

I have the following:

drop.get { request in
    let date:TimeInterval = Date().timeIntervalSince1970
    let dictionary:[String:String] = ["name":"e2","age":"3"]
    return try JSON(node: [
        "time":date,
        "t1" : "abc",
        "t2" : dictionary
        ])
}

This does not work, it tells me that "argument labels (node:) do not match any available overloads"

How can I build JSON that includes strings, numbers and nested dictionary like above?


Answer:

Every value in the Node object initialiser must be a Node as well. You just need to call makeNode() on anything that isn't already a Node.

return try JSON(node: [
    "time": date.makeNode(),
    "t1" : "abc",
    "t2" : dictionary.makeNode()
])

Question:

I'm writing a web service in Swift using Vapor framework.

I use FluentSQLite to save data. I have a User Model which conforms to the SQLiteModel and Migration. I have added routes to create new user via a post methods and return the list of users via a get method like below.

When I hit the get API for first time, it returns an empty array. After I post some users, I am able to get them. But when I stop the service and run again, I am unable to get the previously saved users.

Since I am new to Vapor, I can't figure out what I am missing here and all the online searches and docs didn't help. Initially I did not have a save or query inside a transaction, after seeing that in the docs I tried that also, but same issue.


Answer:

What does your configuration for the SQLite database (typically in Sources/App/configure.swift) look like?

Is it actually persisting to disk, or just running an in-memory database (which goes away when you restart)?

Question:

I am brand new to the Vapor framework, and am trying to protect multiple routes. Basically, I want to make sure that all routes under /campaigns/:id can only be accessed if the user actually has access to that particular campaign with that ID. So that I can't just enter any ID into the url and access any campaign.

Now, instead of adding logic for this to every single route (already 6 so far), I figured I'd use a middleware for this. This is what I came up with so far, with the help of some friendly folk over at the Vapor Discord:

final class CampaignMiddleware: Middleware {
  func respond(to request: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
    let user = try request.requireAuthenticated(User.self)
    return try request.parameters.next(Campaign.self).flatMap(to: Response.self) { campaign in
      guard try campaign.userID == user.requireID() else {
        throw Abort(.forbidden, reason: "Campaign doesn't belong to you!")
      }
      return try next.respond(to: request)
    }
  }
}

struct CampaignController: RouteCollection {
  func boot(router: Router) throws {
    let route = router.grouped("campaigns")

    let tokenAuthMiddleware = User.tokenAuthMiddleware()
    let guardMiddleware = User.guardAuthMiddleware()
    let tokenAuthGroup = route.grouped(tokenAuthMiddleware, guardMiddleware)

    tokenAuthGroup.get(use: getAllHandler)
    tokenAuthGroup.post(CampaignCreateData.self, use: createHandler)

    // Everything under /campaigns/:id/*, CampaignMiddleware makes sure that the campaign actually belongs to you
    let campaignRoute = tokenAuthGroup.grouped(Campaign.parameter)
    let campaignMiddleware = CampaignMiddleware()
    let protectedCampaignRoute = campaignRoute.grouped(campaignMiddleware)

    protectedCampaignRoute.get(use: getOneHandler)
    protectedCampaignRoute.delete(use: deleteHandler)
    protectedCampaignRoute.put(use: updateHandler)

    // Add /campaigns/:id/entries routes
    let entryController = EntryController()
    try protectedCampaignRoute.register(collection: entryController)
  }

  func getAllHandler(_ req: Request) throws -> Future<[Campaign]> {
    let user = try req.requireAuthenticated(User.self)
    return try user.campaigns.query(on: req).all()
  }

  func getOneHandler(_ req: Request) throws -> Future<Campaign> {
    return try req.parameters.next(Campaign.self)
  }

  // ...deleted some other route handlers...
}

The problem here is that the middleware is "eating up" the campaign parameter by doing request.parameters.next(Campaign.self). So in getOneHandler, where it also tries to access req.parameters.next(Campaign.self), it fails with error "Insufficient parameters". Which makes sense, since .next actually removes that param from the internal array of parameters.

Now, how can I write a middleware, that uses the parameter, without eating it up? Do I need to use the raw values and query the Campaign model myself? Or can I somehow reset the parameters after using .next? Or is there another better way to deal with model authorization in Vapor 3?


Answer:

Heeey, it looks like you could get your Campaign from request without dropping it like this

guard let parameter = req.parameters.values.first else {
    throw Abort(.forbidden)
}
try Campaign.resolveParameter(parameter.value, on: req)

So your final code may look like

final class CampaignMiddleware: Middleware {
  func respond(to request: Request, chainingTo next: Responder) throws -> Future<Response> {
    let user = try request.requireAuthenticated(User.self)
    guard let parameter = request.parameters.values.first else {
        throw Abort(.forbidden)
    }
    return try Campaign.resolveParameter(parameter.value, on: request).flatMap { campaign in
      guard try campaign.userID == user.requireID() else {
        throw Abort(.forbidden, reason: "Campaign doesn't belong to you!")
      }
      return try next.respond(to: request)
    }
  }
}