Skip to content

Commit

Permalink
Merge branch 'main' into noasync-waits
Browse files Browse the repository at this point in the history
  • Loading branch information
0xTim committed Apr 24, 2024
2 parents dc881da + 4c80aab commit 5c471ae
Show file tree
Hide file tree
Showing 33 changed files with 1,361 additions and 133 deletions.
2 changes: 1 addition & 1 deletion .github/contributing.md
Expand Up @@ -84,7 +84,7 @@ The release title should be concise description. For example:
- ✅ Add case-insensitive routing
- ✅ Fix `routes` command symbol usage

The release titles should use sentence capitialization and not be too verbose. They should also use present tense.
The release titles should use sentence capitalization and not be too verbose. They should also use present tense.

- ❌ Fix `routes` Command Symbol Usage
- ❌ Add new method on RouteBuilder called `caseInsensitive` which can be used to enable case-insensitive routing
Expand Down
2 changes: 1 addition & 1 deletion .github/maintainers.md
Expand Up @@ -6,7 +6,7 @@ maintainers will determine if you are a good fit and either accept and merge the
to be taken in order to be accepted.

## Perks
- Special "Maintainer" role in Discord with purple highlght.
- Special "Maintainer" role in Discord with purple highlight.
- Write access to maintained repo (including approve and merge permissions).
- Invite to "Maintainers" team on GitHub.
- Opportunity to actively participate in deciding on new features in package.
Expand Down
2 changes: 2 additions & 0 deletions Package.swift
Expand Up @@ -92,6 +92,8 @@ let package = Package(
.product(name: "WebSocketKit", package: "websocket-kit"),
.product(name: "MultipartKit", package: "multipart-kit"),
.product(name: "Atomics", package: "swift-atomics"),

.product(name: "_NIOFileSystem", package: "swift-nio"),
]),

// Development
Expand Down
2 changes: 2 additions & 0 deletions Package@swift-5.9.swift
Expand Up @@ -90,6 +90,8 @@ let package = Package(
.product(name: "WebSocketKit", package: "websocket-kit"),
.product(name: "MultipartKit", package: "multipart-kit"),
.product(name: "Atomics", package: "swift-atomics"),

.product(name: "_NIOFileSystem", package: "swift-nio"),
],
swiftSettings: [.enableExperimentalFeature("StrictConcurrency=complete")]
),
Expand Down
2 changes: 1 addition & 1 deletion Sources/Vapor/Authentication/AuthenticationCache.swift
Expand Up @@ -104,7 +104,7 @@ extension Request.Authentication {
// multiple places. But given how Vapor and its users use Authentication this should almost never
// occur and it was decided the trade-off was acceptable
// As the name implies, the usage of this is unsafe because it disables the sendable checking of the
// compiler and does not add any synchronisation.
// compiler and does not add any synchronization.
@usableFromInline
internal struct UnsafeAuthenticationBox<A>: @unchecked Sendable {
@usableFromInline
Expand Down
6 changes: 5 additions & 1 deletion Sources/Vapor/Commands/ServeCommand.swift
Expand Up @@ -85,14 +85,18 @@ public final class ServeCommand: Command, Sendable {
// setup signal sources for shutdown
let signalQueue = DispatchQueue(label: "codes.vapor.server.shutdown")
func makeSignalSource(_ code: Int32) {
#if canImport(Darwin)
/// https://github.com/swift-server/swift-service-lifecycle/blob/main/Sources/UnixSignals/UnixSignalsSequence.swift#L77-L82
signal(code, SIG_IGN)
#endif

let source = DispatchSource.makeSignalSource(signal: code, queue: signalQueue)
source.setEventHandler {
print() // clear ^C
promise.succeed(())
}
source.resume()
box.signalSources.append(source)
signal(code, SIG_IGN)
}
makeSignalSource(SIGTERM)
makeSignalSource(SIGINT)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Concurrency/RequestBody+Concurrency.swift
Expand Up @@ -120,7 +120,7 @@ extension Request.Body: AsyncSequence {
/// Generates an `AsyncIterator` to stream the body’s content as
/// `ByteBuffer` sequences. This implementation supports backpressure using
/// `NIOAsyncSequenceProducerBackPressureStrategies`
/// - Returns: `AsyncIterator` containing the `Requeset.Body` as a
/// - Returns: `AsyncIterator` containing the `Request.Body` as a
/// `ByteBuffer` sequence
public func makeAsyncIterator() -> AsyncIterator {
let delegate = AsyncSequenceDelegate(eventLoop: request.eventLoop)
Expand Down Expand Up @@ -159,7 +159,7 @@ extension Request.Body: AsyncSequence {
// return the future that we will fulfill eventually.
return promise.futureResult
case .produceMore:
// We can produce more immidately. Return a succeeded future.
// We can produce more immediately. Return a succeeded future.
return request.eventLoop.makeSucceededVoidFuture()
}
case .error(let error):
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Content/ContentCoders.swift
Expand Up @@ -27,7 +27,7 @@ public protocol ContentEncoder {

/// Conform a type to this protocol to make it usable for decoding data via Vapor's ``ContentConfiguration`` system.
public protocol ContentDecoder {
/// Legacy "decode object" method. The provided ``NIOCore/ByteBuffer`` should be decoded as a vaule of the given
/// Legacy "decode object" method. The provided ``NIOCore/ByteBuffer`` should be decoded as a value of the given
/// type, optionally guided by the provided ``NIOHTTP1/HTTPHeaders``.
///
/// Most decoders should implement this method by simply forwarding it to the decoder userInfo-aware version below,
Expand All @@ -36,7 +36,7 @@ public protocol ContentDecoder {
func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D
where D: Decodable

/// "Decode object" method. The provided ``NIOCore/ByteBuffer`` should be decoded as a vaule of the given type,
/// "Decode object" method. The provided ``NIOCore/ByteBuffer`` should be decoded as a value of the given type,
/// optionally guided by the provided ``NIOHTTP1/HTTPHeaders``. The provided ``userInfo`` dictionary must be
/// forwarded to the underlying ``Swift/Decoder`` used to perform the decoding operation.
///
Expand Down
2 changes: 1 addition & 1 deletion Sources/Vapor/Content/PlaintextEncoder.swift
Expand Up @@ -98,7 +98,7 @@ private final class _PlaintextEncoder: Encoder, SingleValueEncodingContainer {
}
}

/// This ridiculosity is a workaround for the inability of encoders to throw errors in various places. It's still better than fatalError()ing.
/// This ridiculously is a workaround for the inability of encoders to throw errors in various places. It's still better than fatalError()ing.
struct FailureEncoder<K: CodingKey>: Encoder, KeyedEncodingContainerProtocol, UnkeyedEncodingContainer, SingleValueEncodingContainer {
let codingPath = [CodingKey](), userInfo = [CodingUserInfoKey: Any](), count = 0
var error: EncodingError { .invalidValue((), .init(codingPath: [], debugDescription: "Plaintext encoding does not support nesting.")) }
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Deprecations/DotEnvFile+load.swift
Expand Up @@ -19,7 +19,7 @@ extension DotEnvFile {
public static func load(
for environment: Environment = .development,
on eventLoopGroupProvider: Application.EventLoopGroupProvider = .singleton,
logger: Logger = Logger(label: "dot-env-loggger")
logger: Logger = Logger(label: "dot-env-logger")
) {
let threadPool = NIOThreadPool(numberOfThreads: 1)
threadPool.start()
Expand Down Expand Up @@ -51,7 +51,7 @@ extension DotEnvFile {
public static func load(
path: String,
on eventLoopGroupProvider: Application.EventLoopGroupProvider = .singleton,
logger: Logger = Logger(label: "dot-env-loggger")
logger: Logger = Logger(label: "dot-env-logger")
) {
let threadPool = NIOThreadPool(numberOfThreads: 1)
threadPool.start()
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/HTTP/Headers/HTTPCookies.swift
Expand Up @@ -193,7 +193,7 @@ public struct HTTPCookies: ExpressibleByDictionaryLiteral, Sendable {

// MARK: Methods

/// Seriaizes an `HTTPCookie` to a `String`.
/// Serializes an `HTTPCookie` to a `String`.
public func serialize(name: String) -> String {
var serialized = "\(name)=\(self.string)"

Expand Down Expand Up @@ -264,7 +264,7 @@ public struct HTTPCookies: ExpressibleByDictionaryLiteral, Sendable {

// MARK: Serialize

/// Seriaizes the `Cookies` for a `Request`
/// Serializes the `Cookies` for a `Request`
var cookieHeader: String? {
guard !self.cookies.isEmpty else {
return nil
Expand Down
6 changes: 3 additions & 3 deletions Sources/Vapor/HTTP/Headers/HTTPHeaders+ContentRange.swift
Expand Up @@ -5,7 +5,7 @@ extension HTTPHeaders {

/// The unit in which `ContentRange`s and `Range`s are specified. This is usually `bytes`.
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range
public enum RangeUnit: Equatable {
public enum RangeUnit: Sendable, Equatable {
case bytes
case custom(value: String)

Expand All @@ -21,7 +21,7 @@ extension HTTPHeaders {

/// Represents the HTTP `Range` request header.
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
public struct Range: Equatable {
public struct Range: Sendable, Equatable {
public let unit: RangeUnit
public let ranges: [HTTPHeaders.Range.Value]

Expand Down Expand Up @@ -134,7 +134,7 @@ extension HTTPHeaders.Range {
/// Represents one value of the `Range` request header.
///
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range
public enum Value: Equatable {
public enum Value: Sendable, Equatable {
///Integer with single trailing dash, e.g. `25-`
case start(value: Int)
///Integer with single leading dash, e.g. `-25`
Expand Down
28 changes: 25 additions & 3 deletions Sources/Vapor/HTTP/Server/HTTPServer.swift
Expand Up @@ -119,7 +119,7 @@ public final class HTTPServer: Server, Sendable {

/// Enables decompression with default configuration.
public static var enabled: Self {
.enabled(limit: .ratio(10))
.enabled(limit: .ratio(25))
}

/// Enables decompression with custom configuration.
Expand Down Expand Up @@ -168,7 +168,7 @@ public final class HTTPServer: Server, Sendable {
reuseAddress: Bool = true,
tcpNoDelay: Bool = true,
responseCompression: CompressionConfiguration = .disabled,
requestDecompression: DecompressionConfiguration = .disabled,
requestDecompression: DecompressionConfiguration = .enabled,
supportPipelining: Bool = true,
supportVersions: Set<HTTPVersionMajor>? = nil,
tlsConfiguration: TLSConfiguration? = nil,
Expand Down Expand Up @@ -200,7 +200,7 @@ public final class HTTPServer: Server, Sendable {
reuseAddress: Bool = true,
tcpNoDelay: Bool = true,
responseCompression: CompressionConfiguration = .disabled,
requestDecompression: DecompressionConfiguration = .disabled,
requestDecompression: DecompressionConfiguration = .enabled,
supportPipelining: Bool = true,
supportVersions: Set<HTTPVersionMajor>? = nil,
tlsConfiguration: TLSConfiguration? = nil,
Expand Down Expand Up @@ -568,6 +568,28 @@ extension ChannelPipeline {
let http2 = HTTP2FramePayloadToHTTP1ServerCodec()
handlers.append(http2)

/// Add response compressor if configured.
switch configuration.responseCompression.storage {
case .enabled(let initialByteBufferCapacity):
let responseCompressionHandler = HTTPResponseCompressor(
initialByteBufferCapacity: initialByteBufferCapacity
)
handlers.append(responseCompressionHandler)
case .disabled:
break
}

/// Add request decompressor if configured.
switch configuration.requestDecompression.storage {
case .enabled(let limit):
let requestDecompressionHandler = NIOHTTPRequestDecompressor(
limit: limit
)
handlers.append(requestDecompressionHandler)
case .disabled:
break
}

/// Add NIO → HTTP request decoder.
let serverReqDecoder = HTTPServerRequestDecoder(
application: application
Expand Down
21 changes: 16 additions & 5 deletions Sources/Vapor/Middleware/FileMiddleware.swift
Expand Up @@ -9,7 +9,8 @@ public final class FileMiddleware: Middleware {
private let publicDirectory: String
private let defaultFile: String?
private let directoryAction: DirectoryAction

private let advancedETagComparison: Bool

public struct BundleSetupError: Equatable, Error {

/// The description of this error.
Expand All @@ -22,17 +23,28 @@ public final class FileMiddleware: Middleware {
public static let publicDirectoryIsNotAFolder: Self = .init(description: "Cannot find any actual folder for the given Public Directory")
}

struct ETagHashes: StorageKey {
public typealias Value = [String: FileHash]

public struct FileHash {
let lastModified: Date
let digestHex: String
}
}

/// Creates a new `FileMiddleware`.
///
/// - parameters:
/// - publicDirectory: The public directory to serve files from.
/// - defaultFile: The name of the default file to look for and serve if a request hits any public directory. Starting with `/` implies
/// an absolute path from the public directory root. If `nil`, no default files are served.
/// - directoryAction: Determines the action to take when the request doesn't have a trailing slash but matches a directory.
public init(publicDirectory: String, defaultFile: String? = nil, directoryAction: DirectoryAction = .none) {
/// - advancedETagComparison: The method used when ETags are generated. If true, a byte-by-byte hash is created (and cached), otherwise a simple comparison based on the file's last modified date and size.
public init(publicDirectory: String, defaultFile: String? = nil, directoryAction: DirectoryAction = .none, advancedETagComparison: Bool = false) {
self.publicDirectory = publicDirectory.addTrailingSlash()
self.defaultFile = defaultFile
self.directoryAction = directoryAction
self.advancedETagComparison = advancedETagComparison
}

public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
Expand Down Expand Up @@ -88,10 +100,9 @@ public final class FileMiddleware: Middleware {
return next.respond(to: request)
}
}

// stream the file
let res = request.fileio.streamFile(at: absPath)
return request.eventLoop.makeSucceededFuture(res)
return request.fileio.streamFile(at: absPath, advancedETagComparison: advancedETagComparison)
}

/// Creates a new `FileMiddleware` for a server contained in an Xcode Project.
Expand Down
2 changes: 1 addition & 1 deletion Sources/Vapor/Request/Request.swift
Expand Up @@ -57,7 +57,7 @@ public final class Request: CustomStringConvertible, Sendable {
/// A unique ID for the request.
///
/// The request identifier is set to value of the `X-Request-Id` header when present, or to a
/// uniquelly generated value otherwise.
/// uniquely generated value otherwise.
public let id: String

// MARK: Metadata
Expand Down
26 changes: 0 additions & 26 deletions Sources/Vapor/Responder/DefaultResponder.swift
Expand Up @@ -39,26 +39,6 @@ internal struct DefaultResponder: Responder {
}
}

// If the route isn't explicitly a HEAD route,
// and it's made up solely of .constant components,
// register a HEAD route with the same path
if route.method == .GET &&
route.path.allSatisfy({ component in
if case .constant(_) = component { return true }
return false
}) {
let headRoute = Route(
method: .HEAD,
path: route.path,
responder: middleware.makeResponder(chainingTo: HeadResponder()),
requestType: route.requestType,
responseType: route.responseType)

let headCachedRoute = CachedRoute(route: headRoute, responder: middleware.makeResponder(chainingTo: HeadResponder()))

router.register(headCachedRoute, at: [.constant(HTTPMethod.HEAD.string)] + path)
}

router.register(cached, at: [.constant(route.method.string)] + path)
}
self.router = router
Expand Down Expand Up @@ -155,12 +135,6 @@ internal struct DefaultResponder: Responder {
}
}

private struct HeadResponder: Responder {
func respond(to request: Request) -> EventLoopFuture<Response> {
request.eventLoop.makeSucceededFuture(.init(status: .ok))
}
}

private struct NotFoundResponder: Responder {
func respond(to request: Request) -> EventLoopFuture<Response> {
request.eventLoop.makeFailedFuture(RouteNotFound())
Expand Down
Expand Up @@ -53,7 +53,7 @@ extension Array where Element == CodingKey {
}
}

// MARK: Utilties
// MARK: Utilities

extension String {
/// Prepares a `String` for inclusion in form-urlencoded data.
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Utilities/BaseN.swift
Expand Up @@ -12,7 +12,7 @@ public struct BaseNEncoding: Sendable {
@inlinable
internal static func sizeEnc(for bits: Int, count: Int) -> Int {
let outputs = 8 >> bits.trailingZeroBitCount, inputs = bits >> bits.trailingZeroBitCount // number of output values per input bytes
return ((count * outputs - 1) / inputs) + 1 // Integer divsion rounding away from zero
return ((count * outputs - 1) / inputs) + 1 // Integer division rounding away from zero
}

/// For a given base and count, calculate the number of bytes encoded by a given count of values. Does not
Expand Down Expand Up @@ -101,7 +101,7 @@ public struct BaseNEncoding: Sendable {
internal struct BreakLoopError: Error { @inlinable internal init() {} }

// N.B.: The values used for the invalid and padding byte representations are not arbitrarily chosen; they are
// intended to be used in optimized versions of the algorithm to quickly distinguish the vairous cases.
// intended to be used in optimized versions of the algorithm to quickly distinguish the various cases.

@usableFromInline
internal static var invalidByte: UInt8 { 0b1111_1111 } // All bits set
Expand Down
4 changes: 2 additions & 2 deletions Sources/Vapor/Utilities/DotEnv.swift
Expand Up @@ -46,7 +46,7 @@ public struct DotEnvFile: Sendable {
for environment: Environment = .development,
on eventLoopGroupProvider: Application.EventLoopGroupProvider = .singleton,
fileio: NonBlockingFileIO,
logger: Logger = Logger(label: "dot-env-loggger")
logger: Logger = Logger(label: "dot-env-logger")
) {
let eventLoopGroup: EventLoopGroup

Expand Down Expand Up @@ -94,7 +94,7 @@ public struct DotEnvFile: Sendable {
path: String,
on eventLoopGroupProvider: Application.EventLoopGroupProvider = .singleton,
fileio: NonBlockingFileIO,
logger: Logger = Logger(label: "dot-env-loggger")
logger: Logger = Logger(label: "dot-env-logger")
) {
let eventLoopGroup: EventLoopGroup

Expand Down

0 comments on commit 5c471ae

Please sign in to comment.