Swiftpack.co - Package - vapor/vapor

Vapor

Documentation Team Chat MIT License Continuous Integration Swift 5.1 Twitter


Vapor is a web framework for Swift. It provides a beautifully expressive and easy to use foundation for your next website, API, or cloud project.

Take a look at some of the awesome stuff created with Vapor.

💧 Community

Join the welcoming community of fellow Vapor developers on Discord.

🚀 Contributing

To contribute a feature or idea to Vapor, create an issue explaining your idea or bring it up on Discord.

If you find a bug, please create an issue.

If you find a security vulnerability, please contact security@vapor.codes as soon as possible.

💛 Sponsors

Support Vapor's development by becoming a sponsor.

Nodes Skelpo Joannis Kyle Browning

💚 Backers

Support Vapor's development by becoming a backer.

analytics

Github

link
Stars: 17435
Help us keep the lights on

Releases

4.0.0-beta.1 - Oct 24, 2019

  • Application is now a global container. (#2079)

See below for a more in-depth explanation of this change, but these code examples explain it best:

Vapor 4 alpha:

let app = Application { s in 
    s.register(Foo.self) { ...}
}
defer { app.shutdown() }

let container = try app.makeContainer().wait()
defer { container.shutdown() }

let foo = try container.make(Foo.self)

Vapor 4 beta:

let app = Application()
defer { app.shutdown() }

app.register(Foo.self) { ... }
let foo = app.make(Foo.self)

In Vapor 3 and Vapor 4 alpha, Vapor's service architecture enforced a 1:1 relation between containers and event loops. This is a useful pattern for minimizing the amount of effort that needs to be spent on synchronizing concurrent access to services.

However, as Swift on the server continues to evolve it is becoming evident that this pattern may hinder Vapor's ability to take full advantage of packages designed to be thread-safe. For example, the swift-server/async-http-client package which recently saw a 1.0.0 release. This HTTP client supports being used across many event loops on a given event loop group. To avoid inefficiencies caused by hopping between event loops, the API allows users to specify event loop preferences with varying degrees of strictness. By giving the API consumer the ability to communicate exactly what they need, the HTTP client can choose the most performant option. This decision may change depending on the current state of its internal connection pool and other parameters.

For example, imagine the following scenario: Request A comes into your application on event loop 1 resulting in an external API request to foo.com. Request A completes and the HTTP client stores event loop 1's connection to foo.com into its pool. Request B comes into your application on event loop 2 and also wants to make an external API request to foo.com. In this situation, two things could happen:

1: Request B could make a new connection to foo.com on event loop 2 at the cost of TCP handshake. 2: Request B could re-use event loop 1's connection to foo.com at the cost of thread-hopping.

In this case option 2 is the clear winner as establishing new TCP connections is much more time consuming.

Requiring distinct copies of each service on a per event loop basis prevents these kinds of optimizations. Exceptions could be made for things like HTTP client, but that would mean adding extra complexity to the already complex services architecture. Additionally, async-http-client was built as a model for what good server-side Swift packages should look like. In other words, it's becoming clear which way the wind is blowing and Vapor should follow suit.

  • Application is now a RoutesBuilder. (#2079)

Now that services are global, Application can be a routes builder. This makes single-file Vapor applications a lot more concise and is more similar to Vapor 3.

import Vapor

let app = try Application(environment: .detect())

app.get("hello") { req in
    return "Hello, world!"
}

try app.run()
  • Request now has access to the application. (#2079)

Since all reference type, singleton services are now expected to be thread-safe, Request can safely use the Application to create services as needed. Where this is especially useful is in creating request-specific contexts into services. A good example of this is how Database works in Fluent 4 beta.

app.get("todos") { req in
    return Todo.query(on: req.db).all()
}

This is powered by the following extension to Request:

extension Request {
    var db: Database { 
        return self.application.make(Database.self).with(req)
    }
}

The key here is that Database is passed a reference to the current Request for context. This enables database operations to do things like:

  • Delegate callbacks to the request event loop
  • Log query information and errors to the request's logger
  • Report metrics information on a per-request basis

Having explicit access to the context provided by request may be critical in production use cases. (See http://asim-malik.com/the-perils-of-node-continuation-local-storage/)

  • Service creation methods no longer throw. (#2079)

Errors thrown during service creation indicate a configuration failure. These errors should only happen during development-time and to make them easier to track down, they will now result in fatalError. This also makes it easier to use providers to extend Application in ways that feel native.

For example, this is how Application now conforms to RoutesBuilder:

extension Application: RoutesBuilder {
    public var routes: Routes { self.make() }
    public func add(route: Route) {
        self.routes.add(route: route)
    }
}
  • macOS 10.14+ and Linux are now the only officially supported platforms. (#2067, #2070)
  • Multipart parsing and serialization was broken out into vapor/multipart-kit (#2080)
  • WebSocket client module was broken out into vapor/websocket-kit (#2074)
  • UUID is now LosslessStringConvertible.
  • XCTVapor is now exported as a product.
  • Vapor repos are moving to GitHub actions for CI. (#2072)
  • HTTP body stream strategy .collect now supports an optional max size. (#2076)

4.0.0-alpha.3.2 - Oct 1, 2019

  • Added onStop future to Application.Running. (#2061)

Application.runCommands() was renamed to Application.start() and no longer blocks until complete. Application.run() now calls Application.start() and waits for Application.running's onStop future to complete.

Note: This change makes it easier to test Application since you can boot and start without needing a background thread.

  • Added new Services.global method for registering application-wide singletons. (#2062)

With this addition, there are now three ways to register a service:

  • register: This factory will be called each time the service is made.
  • singleton: This factory will be called only once per container.
  • global: This factory will be called only once per application.

Note that global services will be shared across event loops and must be thread-safe.

Below is an example usage of Services.global for creating an application-wide memory cache.

final class MemoryCache {
    var storage: [String: String]
    var lock: Lock

    init() {
        self.storage = [:]
        self.lock = .init()
    }

    func get(_ key: String) -> String? {
        self.lock.lock()
        defer { self.lock.unlock() }
        return self.storage[key]
    }

    func set(_ key: String, to value: String?) {
        self.lock.lock()
        defer { self.lock.unlock() }
        self.storage[key] = value
    }
}

The service can be registered globally:

public func configure(_ s: inout Services) {
    ...
    s.global(MemoryCache.self) { _ in
        return .init()
    }
}

The same instance of MemoryCache is now shared across containers:

public func routes(_ r: Routes, _ c: Container) throws {
    ...
    let cache = try c.make(MemoryCache.self)
    r.get("cache", "get", ":key") { req -> String in
        guard let key = req.parameters.get("key") else {
            throw Abort(.internalServerError)
        }
        return "\(key) = \(cache.get(key) ?? "nil")"
    }
    r.get("cache", "set", ":key", ":value") { req -> String in
        guard let key = req.parameters.get("key") else {
            throw Abort(.internalServerError)
        }
        guard let value = req.parameters.get("value") else {
            throw Abort(.internalServerError)
        }
        cache.set(key, to: value)
        return "\(key) = \(value)"
    }
}

This results in the cached values being accessible from across event loops.

3.3.1 - Sep 18, 2019

Semver Patch release

  • Fix: Identifier not used in Abort init (#1923 @mikkelu)
  • Use FoundationNetworking on Linux for Swift 5.1 (#2028 @joscdk)

4.0.0-alpha.3.1.1 - Aug 29, 2019

  • Fixes a bug preventing --port, --host, and --bind overrides from being passed to the HTTP server. (#2043)

Notes: Prior to this fix, the console output would display the correct hostname and port, but the server would still bind to default hostname and port.

4.0.0-alpha.3.1 - Aug 27, 2019

Notes: This updated ConsoleKit release includes property wrapper support and internal cleanup. Vapor's ServeCommand, BootCommand, RouteCommand, and --env and --log flags have been updated to use the new syntax.