xenon-routing 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +18 -0
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +20 -0
- data/Guardfile +16 -0
- data/LICENSE +22 -0
- data/README.md +116 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/examples/hello_world/config.ru +3 -0
- data/examples/hello_world/hello_world.rb +27 -0
- data/xenon-http/lib/xenon/auth.rb +63 -0
- data/xenon-http/lib/xenon/errors.rb +5 -0
- data/xenon-http/lib/xenon/etag.rb +48 -0
- data/xenon-http/lib/xenon/headers.rb +112 -0
- data/xenon-http/lib/xenon/headers/accept.rb +34 -0
- data/xenon-http/lib/xenon/headers/accept_charset.rb +59 -0
- data/xenon-http/lib/xenon/headers/accept_encoding.rb +63 -0
- data/xenon-http/lib/xenon/headers/accept_language.rb +59 -0
- data/xenon-http/lib/xenon/headers/authorization.rb +50 -0
- data/xenon-http/lib/xenon/headers/cache_control.rb +56 -0
- data/xenon-http/lib/xenon/headers/content_type.rb +23 -0
- data/xenon-http/lib/xenon/headers/if_match.rb +53 -0
- data/xenon-http/lib/xenon/headers/if_modified_since.rb +22 -0
- data/xenon-http/lib/xenon/headers/if_none_match.rb +53 -0
- data/xenon-http/lib/xenon/headers/if_range.rb +45 -0
- data/xenon-http/lib/xenon/headers/if_unmodified_since.rb +22 -0
- data/xenon-http/lib/xenon/headers/user_agent.rb +65 -0
- data/xenon-http/lib/xenon/headers/www_authenticate.rb +71 -0
- data/xenon-http/lib/xenon/http.rb +7 -0
- data/xenon-http/lib/xenon/http_version.rb +3 -0
- data/xenon-http/lib/xenon/media_type.rb +162 -0
- data/xenon-http/lib/xenon/parsers/basic_rules.rb +86 -0
- data/xenon-http/lib/xenon/parsers/header_rules.rb +60 -0
- data/xenon-http/lib/xenon/parsers/media_type.rb +53 -0
- data/xenon-http/lib/xenon/quoted_string.rb +20 -0
- data/xenon-http/spec/spec_helper.rb +94 -0
- data/xenon-http/spec/xenon/etag_spec.rb +19 -0
- data/xenon-http/spec/xenon/headers/accept_charset_spec.rb +31 -0
- data/xenon-http/spec/xenon/headers/accept_encoding_spec.rb +40 -0
- data/xenon-http/spec/xenon/headers/accept_language_spec.rb +33 -0
- data/xenon-http/spec/xenon/headers/accept_spec.rb +54 -0
- data/xenon-http/spec/xenon/headers/authorization_spec.rb +47 -0
- data/xenon-http/spec/xenon/headers/cache_control_spec.rb +64 -0
- data/xenon-http/spec/xenon/headers/if_match_spec.rb +73 -0
- data/xenon-http/spec/xenon/headers/if_modified_since_spec.rb +19 -0
- data/xenon-http/spec/xenon/headers/if_none_match_spec.rb +79 -0
- data/xenon-http/spec/xenon/headers/if_range_spec.rb +45 -0
- data/xenon-http/spec/xenon/headers/if_unmodified_since_spec.rb +19 -0
- data/xenon-http/spec/xenon/headers/user_agent_spec.rb +67 -0
- data/xenon-http/spec/xenon/headers/www_authenticate_spec.rb +43 -0
- data/xenon-http/spec/xenon/media_type_spec.rb +267 -0
- data/xenon-http/xenon-http.gemspec +25 -0
- data/xenon-routing/lib/xenon/api.rb +118 -0
- data/xenon-routing/lib/xenon/marshallers.rb +48 -0
- data/xenon-routing/lib/xenon/request.rb +40 -0
- data/xenon-routing/lib/xenon/response.rb +29 -0
- data/xenon-routing/lib/xenon/routing.rb +6 -0
- data/xenon-routing/lib/xenon/routing/context.rb +35 -0
- data/xenon-routing/lib/xenon/routing/directives.rb +14 -0
- data/xenon-routing/lib/xenon/routing/header_directives.rb +32 -0
- data/xenon-routing/lib/xenon/routing/method_directives.rb +26 -0
- data/xenon-routing/lib/xenon/routing/param_directives.rb +22 -0
- data/xenon-routing/lib/xenon/routing/path_directives.rb +37 -0
- data/xenon-routing/lib/xenon/routing/route_directives.rb +51 -0
- data/xenon-routing/lib/xenon/routing/security_directives.rb +34 -0
- data/xenon-routing/lib/xenon/routing_version.rb +3 -0
- data/xenon-routing/spec/spec_helper.rb +94 -0
- data/xenon-routing/xenon-routing.gemspec +25 -0
- data/xenon.gemspec +26 -0
- metadata +145 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'active_support/core_ext/string'
|
2
|
+
|
3
|
+
module Xenon
|
4
|
+
class Headers
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@hash = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_dup(other)
|
12
|
+
super
|
13
|
+
@hash = @hash.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
def freeze
|
17
|
+
@hash.freeze
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
def each(&block)
|
22
|
+
@hash.values.each(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def set!(header)
|
26
|
+
@hash[header.name] = header
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def add!(header)
|
31
|
+
existing = @hash[header.name]
|
32
|
+
if existing
|
33
|
+
if existing.respond_to?(:merge)
|
34
|
+
set!(existing.merge(header))
|
35
|
+
else
|
36
|
+
raise "Unmergeable header '#{header.name}' already exists"
|
37
|
+
end
|
38
|
+
else
|
39
|
+
set!(header)
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
%i(set add).each do |name|
|
45
|
+
define_method name do |header|
|
46
|
+
dup.send("#{name}!", header)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :<<, :add!
|
51
|
+
|
52
|
+
class << self
|
53
|
+
def register(klass)
|
54
|
+
(@registered ||= {})[klass.const_get(:NAME)] = klass
|
55
|
+
end
|
56
|
+
|
57
|
+
def header_class(name)
|
58
|
+
(@registered || {})[name]
|
59
|
+
end
|
60
|
+
|
61
|
+
def Header(name)
|
62
|
+
klass = Class.new do
|
63
|
+
def name
|
64
|
+
self.class.const_get(:NAME)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.inherited(base)
|
68
|
+
Headers.register(base)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
Headers.const_set("#{name.tr('-', '_').classify}Header", klass)
|
72
|
+
klass.const_set(:NAME, name)
|
73
|
+
klass
|
74
|
+
end
|
75
|
+
|
76
|
+
def ListHeader(name)
|
77
|
+
klass = Header(name)
|
78
|
+
klass.class_eval do
|
79
|
+
attr_reader :values
|
80
|
+
|
81
|
+
def initialize(values)
|
82
|
+
@values = values
|
83
|
+
end
|
84
|
+
|
85
|
+
def merge(other)
|
86
|
+
self.class.new(*(@values + other.values))
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_s
|
90
|
+
@values.map(&:to_s).join(', ')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
klass
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Raw
|
98
|
+
attr_reader :name, :value
|
99
|
+
|
100
|
+
def initialize(name, value)
|
101
|
+
@name = name
|
102
|
+
@value = value
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_s
|
106
|
+
@value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
Dir[File.join(__dir__, 'headers', '*.rb')].each { |f| require f }
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
require 'xenon/parsers/media_type'
|
4
|
+
|
5
|
+
module Xenon
|
6
|
+
class Headers
|
7
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.2
|
8
|
+
class Accept < ListHeader 'Accept'
|
9
|
+
def initialize(*media_ranges)
|
10
|
+
super(media_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
11
|
+
end
|
12
|
+
|
13
|
+
alias_method :media_ranges, :values
|
14
|
+
|
15
|
+
def self.parse(s)
|
16
|
+
tree = Parsers::AcceptHeader.new.parse(s)
|
17
|
+
Parsers::AcceptHeaderTransform.new.apply(tree)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module Parsers
|
23
|
+
class AcceptHeader < Parslet::Parser
|
24
|
+
include HeaderRules, MediaTypeRules
|
25
|
+
rule(:accept) { (media_range >> (list_sep >> media_range).repeat).as(:accept) }
|
26
|
+
root(:accept)
|
27
|
+
end
|
28
|
+
|
29
|
+
class AcceptHeaderTransform < MediaTypeTransform
|
30
|
+
rule(accept: sequence(:mr)) { Headers::Accept.new(*mr) }
|
31
|
+
rule(accept: simple(:mr)) { Headers::Accept.new(mr) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class CharsetRange
|
6
|
+
attr_reader :charset, :q
|
7
|
+
|
8
|
+
DEFAULT_Q = 1.0
|
9
|
+
|
10
|
+
def initialize(charset, q = DEFAULT_Q)
|
11
|
+
@charset = charset
|
12
|
+
@q = Float(q) || DEFAULT_Q
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=>(other)
|
16
|
+
@q <=> other.q
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
s = @charset.dup
|
21
|
+
s << "; q=#{@q}" if @q != DEFAULT_Q
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Headers
|
27
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.3
|
28
|
+
class AcceptCharset < ListHeader 'Accept-Charset'
|
29
|
+
def initialize(*charset_ranges)
|
30
|
+
super(charset_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :charset_ranges, :values
|
34
|
+
|
35
|
+
def self.parse(s)
|
36
|
+
tree = Parsers::AcceptCharsetHeader.new.parse(s)
|
37
|
+
Parsers::AcceptCharsetHeaderTransform.new.apply(tree)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Parsers
|
43
|
+
class AcceptCharsetHeader < Parslet::Parser
|
44
|
+
include HeaderRules
|
45
|
+
rule(:charset) { token.as(:charset) >> sp? }
|
46
|
+
rule(:wildcard) { str('*').as(:charset) >> sp? }
|
47
|
+
rule(:charset_range) { (charset | wildcard) >> weight.maybe }
|
48
|
+
rule(:accept_charset) { (charset_range >> (list_sep >> charset_range).repeat).as(:accept_charset) }
|
49
|
+
root(:accept_charset)
|
50
|
+
end
|
51
|
+
|
52
|
+
class AcceptCharsetHeaderTransform < HeaderTransform
|
53
|
+
rule(charset: simple(:c), q: simple(:q)) { CharsetRange.new(c, q) }
|
54
|
+
rule(charset: simple(:c)) { CharsetRange.new(c) }
|
55
|
+
rule(accept_charset: sequence(:cr)) { Headers::AcceptCharset.new(*cr) }
|
56
|
+
rule(accept_charset: simple(:cr)) { Headers::AcceptCharset.new(cr) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class ContentCodingRange
|
6
|
+
attr_reader :coding, :q
|
7
|
+
|
8
|
+
DEFAULT_Q = 1.0
|
9
|
+
|
10
|
+
def initialize(coding, q = DEFAULT_Q)
|
11
|
+
@coding = coding
|
12
|
+
@q = Float(q) || DEFAULT_Q
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=>(other)
|
16
|
+
@q <=> other.q
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
s = @coding.dup
|
21
|
+
s << "; q=#{@q}" if @q != DEFAULT_Q
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Headers
|
27
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.4
|
28
|
+
class AcceptEncoding < ListHeader 'Accept-Encoding'
|
29
|
+
def initialize(*coding_ranges)
|
30
|
+
super(coding_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :coding_ranges, :values
|
34
|
+
|
35
|
+
def self.parse(s)
|
36
|
+
tree = Parsers::AcceptEncodingHeader.new.parse(s)
|
37
|
+
Parsers::AcceptEncodingHeaderTransform.new.apply(tree)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Parsers
|
43
|
+
class AcceptEncodingHeader < Parslet::Parser
|
44
|
+
include HeaderRules
|
45
|
+
%w(identity compress x-compress deflate gzip x-gzip).each do |c|
|
46
|
+
rule(c.tr('-', '_').to_sym) { str(c).as(:coding) >> sp? }
|
47
|
+
end
|
48
|
+
rule(:coding) { compress | x_compress | deflate | gzip | x_gzip }
|
49
|
+
rule(:wildcard) { str('*').as(:coding) >> sp? }
|
50
|
+
rule(:coding_range) { (coding | identity | wildcard) >> weight.maybe }
|
51
|
+
rule(:accept_encoding) { (coding_range >> (list_sep >> coding_range).repeat).maybe.as(:accept_encoding) }
|
52
|
+
root(:accept_encoding)
|
53
|
+
end
|
54
|
+
|
55
|
+
class AcceptEncodingHeaderTransform < HeaderTransform
|
56
|
+
rule(coding: simple(:c), q: simple(:q)) { ContentCodingRange.new(c, q) }
|
57
|
+
rule(coding: simple(:c)) { ContentCodingRange.new(c) }
|
58
|
+
rule(accept_encoding: sequence(:er)) { Headers::AcceptEncoding.new(*er) }
|
59
|
+
rule(accept_encoding: simple(:cc)) { Headers::AcceptEncoding.new(cc) }
|
60
|
+
rule(accept_encoding: nil) { Headers::AcceptEncoding.new }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class LanguageRange
|
6
|
+
attr_reader :language, :q
|
7
|
+
|
8
|
+
DEFAULT_Q = 1.0
|
9
|
+
|
10
|
+
def initialize(language, q = DEFAULT_Q)
|
11
|
+
@language = language
|
12
|
+
@q = Float(q) || DEFAULT_Q
|
13
|
+
end
|
14
|
+
|
15
|
+
def <=>(other)
|
16
|
+
@q <=> other.q
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
s = @language.dup
|
21
|
+
s << "; q=#{@q}" if @q != DEFAULT_Q
|
22
|
+
s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Headers
|
27
|
+
# http://tools.ietf.org/html/rfc7231#section-5.3.5
|
28
|
+
class AcceptLanguage < ListHeader 'Accept-Language'
|
29
|
+
def initialize(*language_ranges)
|
30
|
+
super(language_ranges.sort_by.with_index { |mr, i| [mr, -i] }.reverse!)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias_method :language_ranges, :values
|
34
|
+
|
35
|
+
def self.parse(s)
|
36
|
+
tree = Parsers::AcceptLanguageHeader.new.parse(s)
|
37
|
+
Parsers::AcceptLanguageHeaderTransform.new.apply(tree)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module Parsers
|
43
|
+
class AcceptLanguageHeader < Parslet::Parser
|
44
|
+
include HeaderRules
|
45
|
+
rule(:language) { (alpha.repeat(1, 8) >> (str('-') >> alphanum.repeat(1, 8)).maybe).as(:language) >> sp? }
|
46
|
+
rule(:wildcard) { str('*').as(:language) >> sp? }
|
47
|
+
rule(:language_range) { (language | wildcard) >> weight.maybe }
|
48
|
+
rule(:accept_language) { (language_range >> (list_sep >> language_range).repeat).as(:accept_language) }
|
49
|
+
root(:accept_language)
|
50
|
+
end
|
51
|
+
|
52
|
+
class AcceptLanguageHeaderTransform < HeaderTransform
|
53
|
+
rule(language: simple(:e), q: simple(:q)) { LanguageRange.new(e, q) }
|
54
|
+
rule(language: simple(:e)) { LanguageRange.new(e) }
|
55
|
+
rule(accept_language: sequence(:lr)) { Headers::AcceptLanguage.new(*lr) }
|
56
|
+
rule(accept_language: simple(:lr)) { Headers::AcceptLanguage.new(lr) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'xenon/auth'
|
3
|
+
require 'xenon/headers'
|
4
|
+
require 'xenon/parsers/header_rules'
|
5
|
+
|
6
|
+
module Xenon
|
7
|
+
class Headers
|
8
|
+
# http://tools.ietf.org/html/rfc7235#section-4.2
|
9
|
+
class Authorization < Header 'Authorization'
|
10
|
+
attr_reader :credentials
|
11
|
+
|
12
|
+
def initialize(credentials)
|
13
|
+
@credentials = credentials
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse(s)
|
17
|
+
tree = Parsers::AuthorizationHeader.new.parse(s)
|
18
|
+
Parsers::AuthorizationHeaderTransform.new.apply(tree)
|
19
|
+
rescue Parslet::ParseFailed
|
20
|
+
raise Xenon::ParseError.new("Invalid Authorization header (#{s}).")
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
@credentials.to_s
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Parsers
|
30
|
+
class AuthorizationHeader < Parslet::Parser
|
31
|
+
include AuthHeaderRules
|
32
|
+
rule(:credentials) { auth_scheme >> sp >> (token68 | auth_params) }
|
33
|
+
rule(:authorization) { credentials.as(:authorization) }
|
34
|
+
root(:authorization)
|
35
|
+
end
|
36
|
+
|
37
|
+
class AuthorizationHeaderTransform < HeaderTransform
|
38
|
+
rule(auth_param: { name: simple(:n), value: simple(:v) }) { [n, v] }
|
39
|
+
rule(auth_params: subtree(:x)) { { foo: x } }
|
40
|
+
rule(auth_scheme: simple(:s), token: simple(:t)) {
|
41
|
+
case s
|
42
|
+
when 'Basic' then BasicCredentials.decode(t)
|
43
|
+
else GenericCredentials.new(s, token: t)
|
44
|
+
end
|
45
|
+
}
|
46
|
+
rule(auth_scheme: simple(:s), auth_params: subtree(:p)) { GenericCredentials.new(s, params: Hash[p]) }
|
47
|
+
rule(authorization: simple(:c)) { Headers::Authorization.new(c) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/parsers/header_rules'
|
3
|
+
require 'xenon/quoted_string'
|
4
|
+
|
5
|
+
module Xenon
|
6
|
+
class CacheDirective
|
7
|
+
using QuotedString
|
8
|
+
|
9
|
+
attr_reader :name, :value
|
10
|
+
|
11
|
+
def initialize(name, value = nil)
|
12
|
+
@name = name
|
13
|
+
@value = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
s = @name.dup
|
18
|
+
s << '=' << @value.quote if @value
|
19
|
+
s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Headers
|
24
|
+
# http://tools.ietf.org/html/rfc7234#section-5.2
|
25
|
+
class CacheControl < ListHeader 'Cache-Control'
|
26
|
+
def initialize(*directives)
|
27
|
+
super(directives)
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :directives, :values
|
31
|
+
|
32
|
+
def self.parse(s)
|
33
|
+
tree = Parsers::CacheControlHeader.new.parse(s)
|
34
|
+
Parsers::CacheControlHeaderTransform.new.apply(tree)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Parsers
|
40
|
+
class CacheControlHeader < Parslet::Parser
|
41
|
+
include HeaderRules
|
42
|
+
rule(:name) { token.as(:name) }
|
43
|
+
rule(:value) { str('=') >> (token | quoted_string).as(:value) }
|
44
|
+
rule(:directive) { (name >> value.maybe).as(:directive) >> sp? }
|
45
|
+
rule(:cache_control) { (directive >> (list_sep >> directive).repeat).as(:cache_control) }
|
46
|
+
root(:cache_control)
|
47
|
+
end
|
48
|
+
|
49
|
+
class CacheControlHeaderTransform < HeaderTransform
|
50
|
+
rule(directive: { name: simple(:n), value: simple(:v) }) { CacheDirective.new(n, v) }
|
51
|
+
rule(directive: { name: simple(:n) }) { CacheDirective.new(n) }
|
52
|
+
rule(cache_control: sequence(:d)) { Headers::CacheControl.new(*d) }
|
53
|
+
rule(cache_control: simple(:d)) { Headers::CacheControl.new(d) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'xenon/headers'
|
2
|
+
require 'xenon/media_type'
|
3
|
+
|
4
|
+
module Xenon
|
5
|
+
class Headers
|
6
|
+
# http://tools.ietf.org/html/rfc7231#section-3.1.1.5
|
7
|
+
class ContentType < Header 'Content-Type'
|
8
|
+
attr_reader :content_type
|
9
|
+
|
10
|
+
def initialize(content_type)
|
11
|
+
@content_type = content_type
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(s)
|
15
|
+
new(Xenon::ContentType.parse(s))
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@content_type.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|