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..842f1ed5e --- /dev/null +++ b/Sources/Hummingbird/Server/RequestID.swift @@ -0,0 +1,40 @@ +//===----------------------------------------------------------------------===// +// +// 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 + + init() { + self.low = Self.globalRequestID.loadThenWrappingIncrement(by: 1, ordering: .relaxed) + } + + var description: String { + 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 high = String(UInt64.random(in: .min ... .max), radix: 16) + 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)