|
| 1 | +//===----------------------------------------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Hummingbird server framework project |
| 4 | +// |
| 5 | +// Copyright (c) 2021-2021 the Hummingbird authors |
| 6 | +// Licensed under Apache License v2.0 |
| 7 | +// |
| 8 | +// See LICENSE.txt for license information |
| 9 | +// See hummingbird/CONTRIBUTORS.txt for the list of Hummingbird authors |
| 10 | +// |
| 11 | +// SPDX-License-Identifier: Apache-2.0 |
| 12 | +// |
| 13 | +//===----------------------------------------------------------------------===// |
| 14 | + |
| 15 | +import NIOCore |
| 16 | +import NIOPosix |
| 17 | + |
| 18 | +/// Current date cache. |
| 19 | +/// |
| 20 | +/// Getting the current date formatted is an expensive operation. This creates a scheduled task that will |
| 21 | +/// update a cached version of the date in the format as detailed in RFC1123 once every second. To |
| 22 | +/// avoid threading issues it is assumed that `currentDate` will only every be accessed on the same |
| 23 | +/// EventLoop that the update is running. |
| 24 | +public class HBDateCache { |
| 25 | + /// Current formatted date |
| 26 | + public var currentDate: String |
| 27 | + |
| 28 | + /// return date cache for this thread. If one doesn't exist create one scheduled on EventLoop |
| 29 | + public static func getDateCache(on eventLoop: EventLoop) -> HBDateCache { |
| 30 | + guard let dateCache = thread.currentValue else { |
| 31 | + self.thread.currentValue = .init(eventLoop: eventLoop) |
| 32 | + return self.thread.currentValue! |
| 33 | + } |
| 34 | + return dateCache |
| 35 | + } |
| 36 | + |
| 37 | + static func shutdownDateCaches(eventLoopGroup: EventLoopGroup) -> EventLoopFuture<Void> { |
| 38 | + var dateCacheShutdownFutures: [EventLoopFuture<Void>] = [] |
| 39 | + for eventLoop in eventLoopGroup.makeIterator() { |
| 40 | + let future: EventLoopFuture<Void> = eventLoop.flatSubmit { |
| 41 | + guard let dateCache = thread.currentValue else { |
| 42 | + return eventLoop.makeSucceededFuture(()) |
| 43 | + } |
| 44 | + thread.currentValue = nil |
| 45 | + return dateCache.shutdown(eventLoop: eventLoop) |
| 46 | + } |
| 47 | + dateCacheShutdownFutures.append(future) |
| 48 | + } |
| 49 | + return EventLoopFuture.andAllComplete(dateCacheShutdownFutures, on: eventLoopGroup.next()) |
| 50 | + } |
| 51 | + |
| 52 | + /// Initialize DateCache to run on a specific `EventLoop` |
| 53 | + private init(eventLoop: EventLoop) { |
| 54 | + assert(eventLoop.inEventLoop) |
| 55 | + var timeVal = timeval.init() |
| 56 | + gettimeofday(&timeVal, nil) |
| 57 | + self.currentDate = Self.formatRFC1123Date(timeVal.tv_sec) |
| 58 | + |
| 59 | + let millisecondsSinceLastSecond = Double(timeVal.tv_usec) / 1000.0 |
| 60 | + let millisecondsUntilNextSecond = Int64(1000.0 - millisecondsSinceLastSecond) |
| 61 | + self.task = eventLoop.scheduleRepeatedTask(initialDelay: .milliseconds(millisecondsUntilNextSecond), delay: .seconds(1)) { _ in |
| 62 | + self.updateDate() |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + private func shutdown(eventLoop: EventLoop) -> EventLoopFuture<Void> { |
| 67 | + let promise = eventLoop.makePromise(of: Void.self) |
| 68 | + self.task.cancel(promise: promise) |
| 69 | + return promise.futureResult |
| 70 | + } |
| 71 | + |
| 72 | + /// Render Epoch seconds as RFC1123 formatted date |
| 73 | + /// - Parameter epochTime: epoch seconds to render |
| 74 | + /// - Returns: Formatted date |
| 75 | + public static func formatRFC1123Date(_ epochTime: Int) -> String { |
| 76 | + var epochTime = epochTime |
| 77 | + var timeStruct = tm.init() |
| 78 | + gmtime_r(&epochTime, &timeStruct) |
| 79 | + let year = Int(timeStruct.tm_year + 1900) |
| 80 | + let day = self.dayNames[numericCast(timeStruct.tm_wday)] |
| 81 | + let month = self.monthNames[numericCast(timeStruct.tm_mon)] |
| 82 | + var formatted = day |
| 83 | + formatted.reserveCapacity(30) |
| 84 | + formatted += ", " |
| 85 | + formatted += timeStruct.tm_mday.description |
| 86 | + formatted += " " |
| 87 | + formatted += month |
| 88 | + formatted += " " |
| 89 | + formatted += self.numberNames[year / 100] |
| 90 | + formatted += self.numberNames[year % 100] |
| 91 | + formatted += " " |
| 92 | + formatted += self.numberNames[numericCast(timeStruct.tm_hour)] |
| 93 | + formatted += ":" |
| 94 | + formatted += self.numberNames[numericCast(timeStruct.tm_min)] |
| 95 | + formatted += ":" |
| 96 | + formatted += self.numberNames[numericCast(timeStruct.tm_sec)] |
| 97 | + formatted += " GMT" |
| 98 | + |
| 99 | + return formatted |
| 100 | + } |
| 101 | + |
| 102 | + private func updateDate() { |
| 103 | + let epochTime = time(nil) |
| 104 | + self.currentDate = Self.formatRFC1123Date(epochTime) |
| 105 | + } |
| 106 | + |
| 107 | + /// Thread-specific HBDateCache |
| 108 | + private static let thread: ThreadSpecificVariable<HBDateCache> = .init() |
| 109 | + |
| 110 | + private var task: RepeatedTask! |
| 111 | + |
| 112 | + private static let dayNames = [ |
| 113 | + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", |
| 114 | + ] |
| 115 | + |
| 116 | + private static let monthNames = [ |
| 117 | + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", |
| 118 | + ] |
| 119 | + |
| 120 | + private static let numberNames = [ |
| 121 | + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", |
| 122 | + "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", |
| 123 | + "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", |
| 124 | + "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", |
| 125 | + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", |
| 126 | + "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", |
| 127 | + "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", |
| 128 | + "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", |
| 129 | + "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", |
| 130 | + "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", |
| 131 | + ] |
| 132 | +} |
0 commit comments