/
Application.swift
302 lines (276 loc) · 11.7 KB
/
Application.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
//===----------------------------------------------------------------------===//
//
// This source file is part of the Hummingbird server framework project
//
// Copyright (c) 2021-2024 the Hummingbird authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import HummingbirdCore
import Logging
import NIOCore
import NIOPosix
import NIOTransportServices
import ServiceLifecycle
import UnixSignals
/// Where should the application get its EventLoopGroup from
public enum EventLoopGroupProvider {
/// Use this EventLoopGroup
case shared(EventLoopGroup)
/// Use one of the singleton EventLoopGroups
case singleton
public var eventLoopGroup: EventLoopGroup {
switch self {
case .singleton:
#if os(iOS)
return NIOTSEventLoopGroup.singleton
#else
return MultiThreadedEventLoopGroup.singleton
#endif
case .shared(let elg):
return elg
}
}
}
public protocol ApplicationProtocol: Service where Context: RequestContext {
/// Responder that generates a response from a requests and context
associatedtype Responder: HTTPResponder
/// Child Channel setup. This defaults to support HTTP1
associatedtype ChildChannel: ServerChildChannel & HTTPChannelHandler = HTTP1Channel
/// Context passed with Request to responder
typealias Context = Responder.Context
/// Build the responder
var responder: Responder { get async throws }
/// Server channel setup
var server: HTTPChannelBuilder<ChildChannel> { get }
/// event loop group used by application
var eventLoopGroup: EventLoopGroup { get }
/// Application configuration
var configuration: ApplicationConfiguration { get }
/// Logger
var logger: Logger { get }
/// This is called once the server is running and we have an active Channel
@Sendable func onServerRunning(_ channel: Channel) async
/// services attached to the application.
var services: [any Service] { get }
/// Array of processes run before we kick off the server. These tend to be processes that need
/// other services running but need to be run before the server is setup
var processesRunBeforeServerStart: [@Sendable () async throws -> Void] { get }
}
extension ApplicationProtocol {
/// Server channel setup
public var server: HTTPChannelBuilder<HTTP1Channel> { .http1() }
}
extension ApplicationProtocol {
/// Default event loop group used by application
public var eventLoopGroup: EventLoopGroup { MultiThreadedEventLoopGroup.singleton }
/// Default Configuration
public var configuration: ApplicationConfiguration { .init() }
/// Default Logger
public var logger: Logger { Logger(label: self.configuration.serverName ?? "HummingBird") }
/// Default onServerRunning that does nothing
public func onServerRunning(_: Channel) async {}
/// Default to no extra services attached to the application.
public var services: [any Service] { [] }
/// Default to no processes being run before the server is setup
public var processesRunBeforeServerStart: [@Sendable () async throws -> Void] { [] }
}
/// Conform to `Service` from `ServiceLifecycle`.
extension ApplicationProtocol {
/// Construct application and run it
public func run() async throws {
let dateCache = DateCache()
let responder = try await self.responder
// Function responding to HTTP request
@Sendable func respond(to request: Request, channel: Channel) async throws -> Response {
let context = Self.Responder.Context(
channel: channel,
logger: self.logger.with(metadataKey: "_id", value: .stringConvertible(RequestID()))
)
// respond to request
var response = try await responder.respond(to: request, context: context)
response.headers[.date] = dateCache.date
// server name header
if let serverName = self.configuration.serverName {
response.headers[.server] = serverName
}
return response
}
// get channel Setup
let channelSetup = try self.server.build(respond)
// create server
let server = Server(
childChannelSetup: channelSetup,
configuration: self.configuration.httpServer,
onServerRunning: self.onServerRunning,
eventLoopGroup: self.eventLoopGroup,
logger: self.logger
)
let serverService = PrecursorService(service: server) {
for process in self.processesRunBeforeServerStart {
try await process()
}
}
let services: [any Service] = self.services + [dateCache, serverService]
let serviceGroup = ServiceGroup(
configuration: .init(services: services, logger: self.logger)
)
try await serviceGroup.run()
}
/// Helper function that runs application inside a ServiceGroup which will gracefully
/// shutdown on signals SIGINT, SIGTERM
public func runService(gracefulShutdownSignals: [UnixSignal] = [.sigterm, .sigint]) async throws {
let serviceGroup = ServiceGroup(
configuration: .init(
services: [self],
gracefulShutdownSignals: gracefulShutdownSignals,
logger: self.logger
)
)
try await serviceGroup.run()
}
}
/// Application class. Brings together all the components of Hummingbird together
///
/// ```
/// let router = Router()
/// router.middleware.add(MyMiddleware())
/// router.get("hello") { _ in
/// return "hello"
/// }
/// let app = Application(responder: router.buildResponder())
/// try await app.runService()
/// ```
/// Editing the application setup after calling `runService` will produce undefined behaviour.
public struct Application<Responder: HTTPResponder, ChildChannel: ServerChildChannel & HTTPChannelHandler>: ApplicationProtocol where Responder.Context: RequestContext {
public typealias Context = Responder.Context
public typealias ChildChannel = ChildChannel
public typealias Responder = Responder
// MARK: Member variables
/// event loop group used by application
public let eventLoopGroup: EventLoopGroup
/// routes requests to responders based on URI
public let responder: Responder
/// Configuration
public var configuration: ApplicationConfiguration
/// Logger
public var logger: Logger
/// on server running
private var _onServerRunning: @Sendable (Channel) async -> Void
/// Server channel setup
public let server: HTTPChannelBuilder<ChildChannel>
/// services attached to the application.
public var services: [any Service]
/// Processes to be run before server is started
public private(set) var processesRunBeforeServerStart: [@Sendable () async throws -> Void]
// MARK: Initialization
/// Initialize new Application
///
/// - Parameters:
/// - responder: HTTP responder. Returns a response based off a request and context
/// - server: Server child channel setup (http1, http2, http1WithWebSocketUpgrade etc)
/// - configuration: Application configuration
/// - onServerRunning: Function called once the server is running
/// - eventLoopGroupProvider: Where to get our EventLoopGroup
/// - logger: Logger application uses
public init(
responder: Responder,
server: HTTPChannelBuilder<ChildChannel> = .http1(),
configuration: ApplicationConfiguration = ApplicationConfiguration(),
onServerRunning: @escaping @Sendable (Channel) async -> Void = { _ in },
eventLoopGroupProvider: EventLoopGroupProvider = .singleton,
logger: Logger? = nil
) {
if let logger {
self.logger = logger
} else {
var logger = Logger(label: configuration.serverName ?? "Hummingbird")
logger.logLevel = Environment().get("LOG_LEVEL").map { Logger.Level(rawValue: $0) ?? .info } ?? .info
self.logger = logger
}
self.responder = responder
self.server = server
self.configuration = configuration
self._onServerRunning = onServerRunning
self.eventLoopGroup = eventLoopGroupProvider.eventLoopGroup
self.services = []
self.processesRunBeforeServerStart = []
}
/// Initialize new Application
///
/// - Parameters:
/// - router: Router used to generate responses from requests
/// - server: Server child channel setup (http1, http2, http1WithWebSocketUpgrade etc)
/// - configuration: Application configuration
/// - onServerRunning: Function called once the server is running
/// - eventLoopGroupProvider: Where to get our EventLoopGroup
/// - logger: Logger application uses
public init<ResponderBuilder: HTTPResponderBuilder>(
router: ResponderBuilder,
server: HTTPChannelBuilder<ChildChannel> = .http1(),
configuration: ApplicationConfiguration = ApplicationConfiguration(),
onServerRunning: @escaping @Sendable (Channel) async -> Void = { _ in },
eventLoopGroupProvider: EventLoopGroupProvider = .singleton,
logger: Logger? = nil
) where Responder == ResponderBuilder.Responder {
if let logger {
self.logger = logger
} else {
var logger = Logger(label: configuration.serverName ?? "Hummingbird")
logger.logLevel = Environment().get("LOG_LEVEL").map { Logger.Level(rawValue: $0) ?? .info } ?? .info
self.logger = logger
}
self.responder = router.buildResponder()
self.server = server
self.configuration = configuration
self._onServerRunning = onServerRunning
self.eventLoopGroup = eventLoopGroupProvider.eventLoopGroup
self.services = []
self.processesRunBeforeServerStart = []
}
// MARK: Methods
/// Add service to be managed by application ServiceGroup
/// - Parameter services: list of services to be added
public mutating func addServices(_ services: any Service...) {
self.services.append(contentsOf: services)
}
/// Add a process to run before we kick off the server service
///
/// This is for processes that might need another Service running but need
/// to run before the server has started. For example a database migration
/// process might need the database connection pool running but should be
/// finished before any request to the server can be made. Also they may be
/// situations where you want another Service to have fully initialized
/// before starting the server service.
///
/// - Parameter process: Process to run before server is started
public mutating func runBeforeServerStart(_ process: @escaping @Sendable () async throws -> Void) {
self.processesRunBeforeServerStart.append(process)
}
public func buildResponder() async throws -> Responder {
return self.responder
}
public func onServerRunning(_ channel: Channel) async {
await self._onServerRunning(channel)
}
}
extension Application: CustomStringConvertible {
public var description: String { "Application" }
}
extension Logger {
/// Create new Logger with additional metadata value
/// - Parameters:
/// - metadataKey: Metadata key
/// - value: Metadata value
/// - Returns: Logger
func with(metadataKey: String, value: MetadataValue) -> Logger {
var logger = self
logger[metadataKey: metadataKey] = value
return logger
}
}