Skip to content

Commit 71a2641

Browse files
committed
Add server example
1 parent 4327a37 commit 71a2641

14 files changed

+487
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "Server",
3+
"workspaceFolder": "/workspace/server",
4+
"dockerComposeFile": [
5+
"../docker-compose.yaml",
6+
"docker-compose.yaml"
7+
],
8+
"service": "server",
9+
"runServices": [
10+
"flipt",
11+
"jaeger",
12+
"otel-collector",
13+
"server"
14+
],
15+
"shutdownAction": "stopCompose",
16+
"features": {
17+
"ghcr.io/devcontainers/features/common-utils:2": {
18+
"installZsh": "false",
19+
"username": "server",
20+
"upgradePackages": "false"
21+
},
22+
"ghcr.io/devcontainers/features/git:1": {
23+
"version": "os-provided",
24+
"ppa": "false"
25+
}
26+
},
27+
"customizations": {
28+
"vscode": {
29+
"settings": {
30+
"lldb.library": "/usr/lib/liblldb.so",
31+
"swift.autoGenerateLaunchConfigurations": false
32+
},
33+
"extensions": [
34+
"swiftlang.swift-vscode"
35+
]
36+
}
37+
},
38+
"forwardPorts": [8080, 8081, 16686],
39+
"portsAttributes": {
40+
"8080": {
41+
"label": "API",
42+
"onAutoForward": "notify"
43+
},
44+
"8081": {
45+
"label": "Flipt"
46+
},
47+
"16686": {
48+
"label": "Jaeger"
49+
}
50+
},
51+
"remoteUser": "server"
52+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
services:
2+
server:
3+
image: swift:6.1
4+
command: sleep infinity
5+
volumes:
6+
- ..:/workspace:cached

Examples/server/Package.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// swift-tools-version:6.0
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "server",
6+
platforms: [.macOS(.v15)],
7+
products: [
8+
.executable(name: "server", targets: ["CTL"])
9+
],
10+
dependencies: [
11+
.package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.0.0"),
12+
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
13+
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.0.0"),
14+
.package(url: "https://github.com/swift-otel/swift-otel.git", .upToNextMinor(from: "0.11.0")),
15+
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"),
16+
.package(url: "https://github.com/hummingbird-project/hummingbird-auth.git", from: "2.0.0"),
17+
.package(url: "https://github.com/swift-open-feature/swift-open-feature.git", branch: "main"),
18+
.package(url: "https://github.com/swift-open-feature/swift-ofrep.git", branch: "main"),
19+
20+
// override HTTP Client until Tracing PR is merged
21+
.package(url: "https://github.com/slashmo/async-http-client.git", branch: "feature/tracing"),
22+
],
23+
targets: [
24+
.executableTarget(
25+
name: "CTL",
26+
dependencies: [
27+
.target(name: "API"),
28+
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
29+
.product(name: "Logging", package: "swift-log"),
30+
.product(name: "Tracing", package: "swift-distributed-tracing"),
31+
.product(name: "OTel", package: "swift-otel"),
32+
.product(name: "OTLPGRPC", package: "swift-otel"),
33+
.product(name: "Hummingbird", package: "hummingbird"),
34+
.product(name: "OpenFeature", package: "swift-open-feature"),
35+
.product(name: "OpenFeatureTracing", package: "swift-open-feature"),
36+
.product(name: "OFREP", package: "swift-ofrep"),
37+
]
38+
),
39+
.target(
40+
name: "API",
41+
dependencies: [
42+
.product(name: "ServiceLifecycle", package: "swift-service-lifecycle"),
43+
.product(name: "Logging", package: "swift-log"),
44+
.product(name: "Hummingbird", package: "hummingbird"),
45+
.product(name: "HummingbirdAuth", package: "hummingbird-auth"),
46+
.product(name: "OpenFeature", package: "swift-open-feature"),
47+
]
48+
),
49+
]
50+
)

Examples/server/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Swift OFREP server example
2+
3+
An HTTP server that can be altered at runtime using feature flags powered by Swift OFREP.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenFeature open source project
4+
//
5+
// Copyright (c) 2025 the Swift OpenFeature project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Hummingbird
15+
import OpenFeature
16+
import ServiceLifecycle
17+
18+
public struct APIService: Service {
19+
private let app: Application<RouterResponder<APIRequestContext>>
20+
private let client: OpenFeatureClient
21+
22+
public init(router: Router<APIRequestContext>) {
23+
client = OpenFeatureSystem.client()
24+
25+
router
26+
.addMiddleware {
27+
AuthMiddleware()
28+
EvaluationContextMiddleware()
29+
}
30+
.addRoutes(FeedController().routes)
31+
32+
app = Application(router: router)
33+
}
34+
35+
public func run() async throws {
36+
try await app.run()
37+
}
38+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenFeature open source project
4+
//
5+
// Copyright (c) 2025 the Swift OpenFeature project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import HTTPTypes
15+
import Hummingbird
16+
import HummingbirdAuth
17+
18+
struct AuthMiddleware: AuthenticatorMiddleware {
19+
typealias Context = APIRequestContext
20+
21+
func authenticate(request: Request, context: APIRequestContext) async throws -> User? {
22+
guard let userID = request.headers[.userID] else { return nil }
23+
return User(id: userID)
24+
}
25+
}
26+
27+
extension HTTPField.Name {
28+
fileprivate static let userID = Self("X-User-Id")!
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenFeature open source project
4+
//
5+
// Copyright (c) 2025 the Swift OpenFeature project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Hummingbird
15+
import OpenFeature
16+
17+
struct EvaluationContextMiddleware: MiddlewareProtocol {
18+
func handle(
19+
_ request: Request,
20+
context: APIRequestContext,
21+
next: (Request, APIRequestContext) async throws -> Response
22+
) async throws -> Response {
23+
var evaluationContext = OpenFeatureEvaluationContext.current ?? OpenFeatureEvaluationContext()
24+
evaluationContext.targetingKey = context.identity?.id
25+
return try await OpenFeatureEvaluationContext.$current.withValue(evaluationContext) {
26+
try await next(request, context)
27+
}
28+
}
29+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenFeature open source project
4+
//
5+
// Copyright (c) 2025 the Swift OpenFeature project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Hummingbird
15+
import OpenFeature
16+
17+
struct FeedController {
18+
private let featureFlags: OpenFeatureClient
19+
20+
init() {
21+
featureFlags = OpenFeatureSystem.client()
22+
}
23+
24+
var routes: RouteCollection<APIRequestContext> {
25+
let routes = RouteCollection(context: APIRequestContext.self)
26+
routes.get(use: list)
27+
return routes
28+
}
29+
30+
private func list(request: Request, context: APIRequestContext) async throws -> Feed {
31+
let useNewFeedAlgorithm = await featureFlags.value(for: "experimental-feed-algorithm", defaultingTo: false)
32+
33+
if useNewFeedAlgorithm {
34+
// the new algorithm is faster but unfortunately still contains some bugs
35+
if UInt.random(in: 0..<100) == 42 {
36+
throw HTTPError(.internalServerError)
37+
}
38+
try await Task.sleep(for: .seconds(1))
39+
return Feed.stub
40+
} else {
41+
try await Task.sleep(for: .seconds(2))
42+
return Feed.stub
43+
}
44+
}
45+
}
46+
47+
struct Feed: ResponseCodable {
48+
let posts: [Post]
49+
50+
struct Post: ResponseCodable {
51+
let id: String
52+
}
53+
54+
static let stub = Feed(posts: [
55+
Post(id: "1"),
56+
Post(id: "2"),
57+
Post(id: "3"),
58+
])
59+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenFeature open source project
4+
//
5+
// Copyright (c) 2025 the Swift OpenFeature project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
import Hummingbird
15+
import HummingbirdAuth
16+
17+
public struct APIRequestContext: AuthRequestContext, Sendable {
18+
public var coreContext: CoreRequestContextStorage
19+
public var identity: User?
20+
21+
public init(source: ApplicationRequestContextSource) {
22+
coreContext = .init(source: source)
23+
}
24+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift OpenFeature open source project
4+
//
5+
// Copyright (c) 2025 the Swift OpenFeature project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
public struct User: Identifiable, Sendable {
15+
public let id: String
16+
}

0 commit comments

Comments
 (0)