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
|
# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2025, by Samuel Williams.
require_relative "split"
require_relative "../quoted_string"
require_relative "../error"
module Protocol
module HTTP
module Header
# The `server-timing` header communicates performance metrics about the request-response cycle to the client.
#
# This header allows servers to send timing information about various server-side operations, which can be useful for performance monitoring and debugging. Each metric can include a name, optional duration, and optional description.
#
# ## Examples
#
# ```ruby
# server_timing = ServerTiming.new("db;dur=53.2")
# server_timing << "cache;dur=12.1;desc=\"Redis lookup\""
# puts server_timing.to_s
# # => "db;dur=53.2, cache;dur=12.1;desc=\"Redis lookup\""
# ```
class ServerTiming < Split
ParseError = Class.new(Error)
# https://www.w3.org/TR/server-timing/
METRIC = /\A(?<name>[a-zA-Z0-9][a-zA-Z0-9_\-]*)(;(?<parameters>.*))?\z/
PARAMETER = /(?<key>dur|desc)=((?<value>#{TOKEN})|(?<quoted_value>#{QUOTED_STRING}))/
# A single metric in the Server-Timing header.
Metric = Struct.new(:name, :duration, :description) do
# Create a new server timing metric.
#
# @parameter name [String] the name of the metric.
# @parameter duration [Float | Nil] the duration in milliseconds.
# @parameter description [String | Nil] the description of the metric.
def initialize(name, duration = nil, description = nil)
super(name, duration, description)
end
# Convert the metric to its string representation.
#
# @returns [String] the formatted metric string.
def to_s
result = name.dup
result << ";dur=#{duration}" if duration
result << ";desc=\"#{description}\"" if description
result
end
end
# Parse the `server-timing` header value into a list of metrics.
#
# @returns [Array(Metric)] the list of metrics with their names, durations, and descriptions.
def metrics
self.map do |value|
if match = value.match(METRIC)
name = match[:name]
parameters = match[:parameters] || ""
duration = nil
description = nil
parameters.scan(PARAMETER) do |key, value, quoted_value|
value = QuotedString.unquote(quoted_value) if quoted_value
case key
when "dur"
duration = value.to_f
when "desc"
description = value
end
end
Metric.new(name, duration, description)
else
raise ParseError.new("Could not parse server timing metric: #{value.inspect}")
end
end
end
# Whether this header is acceptable in HTTP trailers.
# @returns [Boolean] `true`, as server-timing headers contain performance metrics that are typically calculated during response generation.
def self.trailer?
true
end
end
end
end
end
|