diff --git a/lib/protocol/http/header/priority.rb b/lib/protocol/http/header/priority.rb new file mode 100644 index 0000000..6b21723 --- /dev/null +++ b/lib/protocol/http/header/priority.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024, by Samuel Williams. + +require_relative "split" + +module Protocol + module HTTP + module Header + # Represents the `priority` header, used to indicate the relative importance of an HTTP request. + # + # The `priority` header allows clients to express their preference for how resources should be prioritized by the server. It supports directives like `u=` to specify the urgency level of a request, and `i` to indicate whether a response can be delivered incrementally. The urgency levels range from 0 (highest priority) to 7 (lowest priority), while the `i` directive is a boolean flag. + class Priority < Split + # Initialize the priority header with the given value. + # + # @parameter value [String | Nil] the value of the priority header, if any. The value should be a comma-separated string of directives. + def initialize(value = nil) + super(value&.downcase) + end + + # Add a value to the priority header. + # + # @parameter value [String] the directive to add to the header. + def << value + super(value.downcase) + end + + # The default urgency level if not specified. + DEFAULT_URGENCY = 3 + + # Returns the urgency level if specified. 0 is the highest priority, and 7 is the lowest. + # + # @returns [Integer | Nil] the urgency level if specified, or `nil` if not present. + def urgency(default = DEFAULT_URGENCY) + if value = self.find { |value| value.start_with?("u=") } + _, level = value.split("=", 2) + return level.to_i + end + + return default + end + + # Checks if the response should be delivered incrementally. + # + # The `i` directive, when present, indicates that the response can be delivered incrementally as data becomes available. + # + # @returns [Boolean] whether the request should be delivered incrementally. + def incremental? + self.include?("i") + end + end + end + end +end diff --git a/lib/protocol/http/headers.rb b/lib/protocol/http/headers.rb index 36f31ab..c91e11e 100644 --- a/lib/protocol/http/headers.rb +++ b/lib/protocol/http/headers.rb @@ -13,6 +13,7 @@ require_relative "header/vary" require_relative "header/authorization" require_relative "header/date" +require_relative "header/priority" module Protocol module HTTP @@ -246,6 +247,7 @@ def []= key, value "connection" => Header::Connection, "cache-control" => Header::CacheControl, "vary" => Header::Vary, + "priority" => Header::Priority, # Headers specifically for proxies: "via" => Split, diff --git a/test/protocol/http/header/priority.rb b/test/protocol/http/header/priority.rb new file mode 100644 index 0000000..a236b4a --- /dev/null +++ b/test/protocol/http/header/priority.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +# Released under the MIT License. +# Copyright, 2024, by Samuel Williams. + +require "protocol/http/header/priority" + +describe Protocol::HTTP::Header::Priority do + let(:header) { subject.new(description) } + + with "u=1, i" do + it "correctly parses priority header" do + expect(header).to have_attributes( + urgency: be == 1, + incremental?: be == true, + ) + end + end + + with "u=0" do + it "correctly parses priority header" do + expect(header).to have_attributes( + urgency: be == 0, + incremental?: be == false, + ) + end + end + + with "i" do + it "correctly parses incremental flag" do + expect(header).to have_attributes( + # Default urgency level is used: + urgency: be == 3, + incremental?: be == true, + ) + end + end + + with "u=6" do + it "correctly parses urgency level" do + expect(header).to have_attributes( + urgency: be == 6, + ) + end + end + + with "u=9, i" do + it "gracefully handles non-standard urgency levels" do + expect(header).to have_attributes( + # Non-standard value is preserved + urgency: be == 9, + incremental?: be == true, + ) + end + end + + with "u=2, u=5" do + it "prioritizes the first urgency directive" do + expect(header).to have_attributes( + # First occurrence takes precedence + urgency: be == 2, + ) + end + end + + with "#<<" do + let(:header) { subject.new } + + it "can append values" do + header << "u=4" + expect(header).to have_attributes( + urgency: be == 4, + ) + end + + it "can append incremental flag" do + header << "i" + expect(header).to have_attributes( + incremental?: be == true, + ) + end + end +end