From 1cafa1d49ee654b26120b213016fa12ee4797c27 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 29 Dec 2023 10:24:00 +0100 Subject: [PATCH 1/4] Add RequestID to generate unique Identifier for each request This will also generate unique identifiers across multiple instances of a Humminbird application --- Sources/Hummingbird/Server/Request.swift | 7 ++-- Sources/Hummingbird/Server/RequestID.swift | 42 ++++++++++++++++++++++ Tests/HummingbirdTests/RouterTests.swift | 39 +++++++++++++++++--- 3 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 Sources/Hummingbird/Server/RequestID.swift diff --git a/Sources/Hummingbird/Server/Request.swift b/Sources/Hummingbird/Server/Request.swift index f8dad55ff..fcae3b2c6 100644 --- a/Sources/Hummingbird/Server/Request.swift +++ b/Sources/Hummingbird/Server/Request.swift @@ -2,7 +2,7 @@ // // This source file is part of the Hummingbird server framework project // -// Copyright (c) 2021-2022 the Hummingbird authors +// Copyright (c) 2021-2023 the Hummingbird authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -import Atomics import HummingbirdCore import Logging import NIOConcurrencyHelpers @@ -106,7 +105,7 @@ public struct HBRequest: Sendable, HBSendableExtensible { context: context ) self.body = body - self.logger = application.logger.with(metadataKey: "hb_id", value: .stringConvertible(Self.globalRequestID.loadThenWrappingIncrement(by: 1, ordering: .relaxed))) + self.logger = application.logger.with(metadataKey: "hb_id", value: .stringConvertible(RequestID())) self.extensions = .init() } @@ -215,8 +214,6 @@ public struct HBRequest: Sendable, HBSendableExtensible { } private var _internal: _Internal - - private static let globalRequestID = ManagedAtomic(0) } extension Logger { diff --git a/Sources/Hummingbird/Server/RequestID.swift b/Sources/Hummingbird/Server/RequestID.swift new file mode 100644 index 000000000..1884e3309 --- /dev/null +++ b/Sources/Hummingbird/Server/RequestID.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Hummingbird server framework project +// +// Copyright (c) 2023 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 Atomics + +/// Generate Unique ID for each request +struct RequestID: CustomStringConvertible { + let low: UInt64 + let high: UInt64 + + init() { + self.low = Self.globalRequestID.loadThenWrappingIncrement(by: 1, ordering: .relaxed) + self.high = Self.instanceIdentifier + } + + var description: String { + self.formatAsHexWithLeadingZeros(self.high) + self.formatAsHexWithLeadingZeros(self.low) + } + + func formatAsHexWithLeadingZeros(_ value: UInt64) -> String { + let string = String(value, radix: 16) + if string.count < 16 { + return String(repeating: "0", count: 16 - string.count) + string + } else { + return string + } + } + + private static let instanceIdentifier = UInt64.random(in: .min ... .max) + private static let globalRequestID = ManagedAtomic(UInt64.random(in: .min ... .max)) +} diff --git a/Tests/HummingbirdTests/RouterTests.swift b/Tests/HummingbirdTests/RouterTests.swift index 0560a02c3..0cecf0706 100644 --- a/Tests/HummingbirdTests/RouterTests.swift +++ b/Tests/HummingbirdTests/RouterTests.swift @@ -331,25 +331,54 @@ final class RouterTests: XCTestCase { } } - /// Test we have a request id and that it increments with each request + /// Test we have a request id and that it is unique for each request func testRequestId() throws { let app = HBApplication(testing: .embedded) app.router.get("id") { $0.id } try app.XCTStart() defer { app.XCTStop() } - let idString = try app.XCTExecute(uri: "/id", method: .GET) { response -> String in + let id = try app.XCTExecute(uri: "/id", method: .GET) { response -> String in let body = try XCTUnwrap(response.body) return String(buffer: body) } - let id = try XCTUnwrap(Int(idString)) try app.XCTExecute(uri: "/id", method: .GET) { response in let body = try XCTUnwrap(response.body) - let id2 = Int(String(buffer: body)) - XCTAssertEqual(id2, id + 1) + let id2 = String(buffer: body) + XCTAssertNotEqual(id2, id) } } + /// Test we have a request id and that it is unique for each request even across instances + /// of running applications + func testRequestIdAcrossInstances() throws { + let id: String? + do { + let app = HBApplication(testing: .embedded) + app.router.get("id") { $0.id } + try app.XCTStart() + defer { app.XCTStop() } + + id = try app.XCTExecute(uri: "/id", method: .GET) { response -> String in + let body = try XCTUnwrap(response.body) + return String(buffer: body) + } + } + let id2: String? + do { + let app = HBApplication(testing: .embedded) + app.router.get("id") { $0.id } + try app.XCTStart() + defer { app.XCTStop() } + + id2 = try app.XCTExecute(uri: "/id", method: .GET) { response -> String in + let body = try XCTUnwrap(response.body) + return String(buffer: body) + } + } + XCTAssertNotEqual(id, id2) + } + // Test redirect response func testRedirect() throws { let app = HBApplication(testing: .embedded) From 3ddd3429a467ca0c599c80b8818c79f63bb5c855 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 29 Dec 2023 10:33:23 +0100 Subject: [PATCH 2/4] Don't bother with leading zeros for high value in request id --- Sources/Hummingbird/Server/RequestID.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Hummingbird/Server/RequestID.swift b/Sources/Hummingbird/Server/RequestID.swift index 1884e3309..69b45c984 100644 --- a/Sources/Hummingbird/Server/RequestID.swift +++ b/Sources/Hummingbird/Server/RequestID.swift @@ -25,7 +25,7 @@ struct RequestID: CustomStringConvertible { } var description: String { - self.formatAsHexWithLeadingZeros(self.high) + self.formatAsHexWithLeadingZeros(self.low) + String(self.high, radix: 16) + self.formatAsHexWithLeadingZeros(self.low) } func formatAsHexWithLeadingZeros(_ value: UInt64) -> String { From 476c067a33f01c1ca01792588ccad6ea46b8edd2 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Fri, 29 Dec 2023 10:45:41 +0100 Subject: [PATCH 3/4] Don't need to store high in RequestID as it is the same for all ids --- Sources/Hummingbird/Server/RequestID.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/Hummingbird/Server/RequestID.swift b/Sources/Hummingbird/Server/RequestID.swift index 69b45c984..596346160 100644 --- a/Sources/Hummingbird/Server/RequestID.swift +++ b/Sources/Hummingbird/Server/RequestID.swift @@ -17,15 +17,13 @@ import Atomics /// Generate Unique ID for each request struct RequestID: CustomStringConvertible { let low: UInt64 - let high: UInt64 init() { self.low = Self.globalRequestID.loadThenWrappingIncrement(by: 1, ordering: .relaxed) - self.high = Self.instanceIdentifier } var description: String { - String(self.high, radix: 16) + self.formatAsHexWithLeadingZeros(self.low) + String(Self.high, radix: 16) + self.formatAsHexWithLeadingZeros(self.low) } func formatAsHexWithLeadingZeros(_ value: UInt64) -> String { @@ -37,6 +35,6 @@ struct RequestID: CustomStringConvertible { } } - private static let instanceIdentifier = UInt64.random(in: .min ... .max) + private static let high = UInt64.random(in: .min ... .max) private static let globalRequestID = ManagedAtomic(UInt64.random(in: .min ... .max)) } From b96fefe603c2efa2033342b8f47f17cb2d9af820 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Sun, 31 Dec 2023 18:07:10 +0100 Subject: [PATCH 4/4] Don't keep rebuilding high string --- Sources/Hummingbird/Server/RequestID.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Hummingbird/Server/RequestID.swift b/Sources/Hummingbird/Server/RequestID.swift index 596346160..842f1ed5e 100644 --- a/Sources/Hummingbird/Server/RequestID.swift +++ b/Sources/Hummingbird/Server/RequestID.swift @@ -23,7 +23,7 @@ struct RequestID: CustomStringConvertible { } var description: String { - String(Self.high, radix: 16) + self.formatAsHexWithLeadingZeros(self.low) + Self.high + self.formatAsHexWithLeadingZeros(self.low) } func formatAsHexWithLeadingZeros(_ value: UInt64) -> String { @@ -35,6 +35,6 @@ struct RequestID: CustomStringConvertible { } } - private static let high = UInt64.random(in: .min ... .max) + private static let high = String(UInt64.random(in: .min ... .max), radix: 16) private static let globalRequestID = ManagedAtomic(UInt64.random(in: .min ... .max)) }