atatus 1.0.0
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/.gitignore +16 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +57 -0
- data/LICENSE +65 -0
- data/LICENSE-THIRD-PARTY +205 -0
- data/README.md +13 -0
- data/Rakefile +19 -0
- data/atatus.gemspec +36 -0
- data/atatus.yml +2 -0
- data/bench/.gitignore +2 -0
- data/bench/app.rb +53 -0
- data/bench/benchmark.rb +36 -0
- data/bench/report.rb +55 -0
- data/bench/rubyprof.rb +39 -0
- data/bench/stackprof.rb +23 -0
- data/bin/build_docs +5 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/bin/with_framework +7 -0
- data/lib/atatus.rb +325 -0
- data/lib/atatus/agent.rb +260 -0
- data/lib/atatus/central_config.rb +141 -0
- data/lib/atatus/central_config/cache_control.rb +34 -0
- data/lib/atatus/collector/base.rb +329 -0
- data/lib/atatus/collector/builder.rb +317 -0
- data/lib/atatus/collector/transport.rb +72 -0
- data/lib/atatus/config.rb +248 -0
- data/lib/atatus/config/bytes.rb +25 -0
- data/lib/atatus/config/duration.rb +23 -0
- data/lib/atatus/config/options.rb +134 -0
- data/lib/atatus/config/regexp_list.rb +13 -0
- data/lib/atatus/context.rb +33 -0
- data/lib/atatus/context/request.rb +11 -0
- data/lib/atatus/context/request/socket.rb +19 -0
- data/lib/atatus/context/request/url.rb +42 -0
- data/lib/atatus/context/response.rb +22 -0
- data/lib/atatus/context/user.rb +42 -0
- data/lib/atatus/context_builder.rb +97 -0
- data/lib/atatus/deprecations.rb +22 -0
- data/lib/atatus/error.rb +22 -0
- data/lib/atatus/error/exception.rb +46 -0
- data/lib/atatus/error/log.rb +24 -0
- data/lib/atatus/error_builder.rb +76 -0
- data/lib/atatus/instrumenter.rb +224 -0
- data/lib/atatus/internal_error.rb +6 -0
- data/lib/atatus/logging.rb +55 -0
- data/lib/atatus/metadata.rb +19 -0
- data/lib/atatus/metadata/process_info.rb +18 -0
- data/lib/atatus/metadata/service_info.rb +61 -0
- data/lib/atatus/metadata/system_info.rb +35 -0
- data/lib/atatus/metadata/system_info/container_info.rb +121 -0
- data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
- data/lib/atatus/metadata/system_info/os_info.rb +31 -0
- data/lib/atatus/metrics.rb +98 -0
- data/lib/atatus/metrics/cpu_mem.rb +240 -0
- data/lib/atatus/metrics/vm.rb +60 -0
- data/lib/atatus/metricset.rb +19 -0
- data/lib/atatus/middleware.rb +76 -0
- data/lib/atatus/naively_hashable.rb +21 -0
- data/lib/atatus/normalizers.rb +68 -0
- data/lib/atatus/normalizers/action_controller.rb +27 -0
- data/lib/atatus/normalizers/action_mailer.rb +26 -0
- data/lib/atatus/normalizers/action_view.rb +77 -0
- data/lib/atatus/normalizers/active_record.rb +45 -0
- data/lib/atatus/opentracing.rb +346 -0
- data/lib/atatus/rails.rb +61 -0
- data/lib/atatus/railtie.rb +30 -0
- data/lib/atatus/span.rb +125 -0
- data/lib/atatus/span/context.rb +40 -0
- data/lib/atatus/span_helpers.rb +44 -0
- data/lib/atatus/spies.rb +86 -0
- data/lib/atatus/spies/action_dispatch.rb +28 -0
- data/lib/atatus/spies/delayed_job.rb +68 -0
- data/lib/atatus/spies/elasticsearch.rb +36 -0
- data/lib/atatus/spies/faraday.rb +70 -0
- data/lib/atatus/spies/http.rb +44 -0
- data/lib/atatus/spies/json.rb +22 -0
- data/lib/atatus/spies/mongo.rb +87 -0
- data/lib/atatus/spies/net_http.rb +70 -0
- data/lib/atatus/spies/rake.rb +45 -0
- data/lib/atatus/spies/redis.rb +27 -0
- data/lib/atatus/spies/sequel.rb +47 -0
- data/lib/atatus/spies/sidekiq.rb +89 -0
- data/lib/atatus/spies/sinatra.rb +41 -0
- data/lib/atatus/spies/tilt.rb +27 -0
- data/lib/atatus/sql_summarizer.rb +35 -0
- data/lib/atatus/stacktrace.rb +16 -0
- data/lib/atatus/stacktrace/frame.rb +52 -0
- data/lib/atatus/stacktrace_builder.rb +104 -0
- data/lib/atatus/subscriber.rb +77 -0
- data/lib/atatus/trace_context.rb +85 -0
- data/lib/atatus/transaction.rb +100 -0
- data/lib/atatus/transport/base.rb +174 -0
- data/lib/atatus/transport/connection.rb +156 -0
- data/lib/atatus/transport/connection/http.rb +116 -0
- data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
- data/lib/atatus/transport/filters.rb +43 -0
- data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
- data/lib/atatus/transport/serializers.rb +93 -0
- data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
- data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
- data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
- data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
- data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
- data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
- data/lib/atatus/transport/worker.rb +73 -0
- data/lib/atatus/util.rb +42 -0
- data/lib/atatus/util/inflector.rb +93 -0
- data/lib/atatus/util/lru_cache.rb +48 -0
- data/lib/atatus/util/prefixed_logger.rb +18 -0
- data/lib/atatus/util/throttle.rb +35 -0
- data/lib/atatus/version.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +190 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
class Config
|
|
5
|
+
# @api private
|
|
6
|
+
class Bytes
|
|
7
|
+
MULTIPLIERS = {
|
|
8
|
+
'kb' => 1024,
|
|
9
|
+
'mb' => 1024 * 1_000,
|
|
10
|
+
'gb' => 1024 * 100_000
|
|
11
|
+
}.freeze
|
|
12
|
+
REGEX = /^(\d+)(b|kb|mb|gb)?$/i.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(default_unit: 'kb')
|
|
15
|
+
@default_unit = default_unit
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(value)
|
|
19
|
+
_, amount, unit = REGEX.match(String(value)).to_a
|
|
20
|
+
unit ||= @default_unit
|
|
21
|
+
MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
class Config
|
|
5
|
+
# @api private
|
|
6
|
+
class Duration
|
|
7
|
+
MULTIPLIERS = { 'ms' => 0.001, 'm' => 60 }.freeze
|
|
8
|
+
REGEX = /^(-)?(\d+)(m|ms|s)?$/i.freeze
|
|
9
|
+
|
|
10
|
+
def initialize(default_unit: 's')
|
|
11
|
+
@default_unit = default_unit
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(str)
|
|
15
|
+
_, negative, amount, unit = REGEX.match(String(str)).to_a
|
|
16
|
+
unit ||= @default_unit
|
|
17
|
+
seconds = MULTIPLIERS.fetch(unit.downcase, 1) * amount.to_i
|
|
18
|
+
seconds = 0 - seconds if negative
|
|
19
|
+
seconds
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
class Config
|
|
5
|
+
# @api private
|
|
6
|
+
module Options
|
|
7
|
+
# @api private
|
|
8
|
+
class Option
|
|
9
|
+
def initialize(
|
|
10
|
+
key,
|
|
11
|
+
value: nil,
|
|
12
|
+
type: :string,
|
|
13
|
+
default: nil,
|
|
14
|
+
converter: nil
|
|
15
|
+
)
|
|
16
|
+
@key = key
|
|
17
|
+
@type = type
|
|
18
|
+
@default = default
|
|
19
|
+
@converter = converter
|
|
20
|
+
|
|
21
|
+
set(value || default)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :key, :value, :default, :type
|
|
25
|
+
|
|
26
|
+
def set(value)
|
|
27
|
+
@value = normalize(value)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def env_key
|
|
31
|
+
"ATATUS_#{key.upcase}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
37
|
+
def normalize(val)
|
|
38
|
+
return unless val
|
|
39
|
+
|
|
40
|
+
if @converter
|
|
41
|
+
return @converter.call(val)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
case type
|
|
45
|
+
when :string then val.to_s
|
|
46
|
+
when :int then val.to_i
|
|
47
|
+
when :float then val.to_f
|
|
48
|
+
when :bool then normalize_bool(val)
|
|
49
|
+
when :list then normalize_list(val)
|
|
50
|
+
when :dict then normalize_dict(val)
|
|
51
|
+
else
|
|
52
|
+
# raise "Unknown options type '#{type.inspect}'"
|
|
53
|
+
val
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
|
57
|
+
|
|
58
|
+
def normalize_bool(val)
|
|
59
|
+
return val unless val.is_a?(String)
|
|
60
|
+
!%w[0 false].include?(val.strip.downcase)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def normalize_list(val)
|
|
64
|
+
return Array(val) unless val.is_a?(String)
|
|
65
|
+
val.split(/[ ,]/)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def normalize_dict(val)
|
|
69
|
+
return val unless val.is_a?(String)
|
|
70
|
+
Hash[val.split(/[&,]/).map { |kv| kv.split('=') }]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @api private
|
|
75
|
+
module ClassMethods
|
|
76
|
+
def schema
|
|
77
|
+
@schema ||= {}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def option(*args)
|
|
81
|
+
key = args.shift
|
|
82
|
+
schema[key] = *args
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @api private
|
|
87
|
+
module InstanceMethods
|
|
88
|
+
def load_schema
|
|
89
|
+
Hash[self.class.schema.map do |key, args|
|
|
90
|
+
[key, Option.new(key, *args)]
|
|
91
|
+
end]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
95
|
+
def method_missing(name, *value)
|
|
96
|
+
name_str = name.to_s
|
|
97
|
+
|
|
98
|
+
if name_str.end_with?('=')
|
|
99
|
+
key = name_str[0...-1].to_sym
|
|
100
|
+
set(key, value.first)
|
|
101
|
+
|
|
102
|
+
elsif name_str.end_with?('?')
|
|
103
|
+
key = name_str[0...-1].to_sym
|
|
104
|
+
options.key?(key) ? options[key].value : super
|
|
105
|
+
|
|
106
|
+
elsif options.key?(name)
|
|
107
|
+
options.fetch(name).value
|
|
108
|
+
|
|
109
|
+
else
|
|
110
|
+
super
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
114
|
+
|
|
115
|
+
def [](key)
|
|
116
|
+
options[key]
|
|
117
|
+
end
|
|
118
|
+
alias :get :[]
|
|
119
|
+
|
|
120
|
+
def set(key, value)
|
|
121
|
+
options.fetch(key.to_sym).set(value)
|
|
122
|
+
rescue KeyError
|
|
123
|
+
warn format("Unknown option '%s'", key)
|
|
124
|
+
nil
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.extended(kls)
|
|
129
|
+
kls.instance_eval { extend ClassMethods }
|
|
130
|
+
kls.class_eval { include InstanceMethods }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'atatus/context/request'
|
|
4
|
+
require 'atatus/context/request/socket'
|
|
5
|
+
require 'atatus/context/request/url'
|
|
6
|
+
require 'atatus/context/response'
|
|
7
|
+
require 'atatus/context/user'
|
|
8
|
+
|
|
9
|
+
module Atatus
|
|
10
|
+
# @api private
|
|
11
|
+
class Context
|
|
12
|
+
def initialize(custom: {}, labels: {}, user: nil)
|
|
13
|
+
@custom = custom
|
|
14
|
+
@labels = labels
|
|
15
|
+
@user = user || User.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
attr_accessor :request
|
|
19
|
+
attr_accessor :response
|
|
20
|
+
attr_accessor :user
|
|
21
|
+
attr_reader :custom
|
|
22
|
+
attr_reader :labels
|
|
23
|
+
|
|
24
|
+
def empty?
|
|
25
|
+
return false if labels.any?
|
|
26
|
+
return false if custom.any?
|
|
27
|
+
return false if user.any?
|
|
28
|
+
return false if request || response
|
|
29
|
+
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
# @api private
|
|
5
|
+
class Context
|
|
6
|
+
# @api private
|
|
7
|
+
class Request
|
|
8
|
+
# @api private
|
|
9
|
+
class Socket
|
|
10
|
+
def initialize(req)
|
|
11
|
+
@remote_addr = req.ip
|
|
12
|
+
@encrypted = req.scheme == 'https'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_reader :remote_addr, :encrypted
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
# @api private
|
|
5
|
+
class Context
|
|
6
|
+
# @api private
|
|
7
|
+
class Request
|
|
8
|
+
# @api private
|
|
9
|
+
class Url
|
|
10
|
+
SKIPPED_PORTS = {
|
|
11
|
+
'http' => 80,
|
|
12
|
+
'https' => 443
|
|
13
|
+
}.freeze
|
|
14
|
+
|
|
15
|
+
def initialize(req)
|
|
16
|
+
@protocol = req.scheme
|
|
17
|
+
@hostname = req.host
|
|
18
|
+
@port = req.port.to_s
|
|
19
|
+
@pathname = req.path
|
|
20
|
+
@search = req.query_string
|
|
21
|
+
@hash = nil
|
|
22
|
+
@full = build_full_url req
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
attr_reader :protocol, :hostname, :port, :pathname, :search, :hash,
|
|
26
|
+
:full
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def build_full_url(req)
|
|
31
|
+
url = "#{req.scheme}://#{req.host}"
|
|
32
|
+
|
|
33
|
+
if req.port != SKIPPED_PORTS[req.scheme]
|
|
34
|
+
url += ":#{req.port}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
url + req.fullpath
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
class Context
|
|
5
|
+
# @api private
|
|
6
|
+
class Response
|
|
7
|
+
def initialize(
|
|
8
|
+
status_code,
|
|
9
|
+
headers: {},
|
|
10
|
+
headers_sent: true,
|
|
11
|
+
finished: true
|
|
12
|
+
)
|
|
13
|
+
@status_code = status_code
|
|
14
|
+
@headers = headers
|
|
15
|
+
@headers_sent = headers_sent
|
|
16
|
+
@finished = finished
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_accessor :status_code, :headers, :headers_sent, :finished
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
class Context
|
|
5
|
+
# @api private
|
|
6
|
+
class User
|
|
7
|
+
def initialize(id: nil, email: nil, username: nil)
|
|
8
|
+
@id = id
|
|
9
|
+
@email = email
|
|
10
|
+
@username = username
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.infer(config, record)
|
|
14
|
+
return unless record
|
|
15
|
+
|
|
16
|
+
new(
|
|
17
|
+
id: safe_get(record, config.current_user_id_method)&.to_s,
|
|
18
|
+
email: safe_get(record, config.current_user_email_method),
|
|
19
|
+
username: safe_get(record, config.current_user_username_method)
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_accessor :id, :email, :username
|
|
24
|
+
|
|
25
|
+
def empty?
|
|
26
|
+
!id && !email && !username
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def any?
|
|
30
|
+
!empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def safe_get(record, method_name)
|
|
37
|
+
record.respond_to?(method_name) ? record.send(method_name) : nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Atatus
|
|
4
|
+
# @api private
|
|
5
|
+
class ContextBuilder
|
|
6
|
+
MAX_BODY_LENGTH = 2048
|
|
7
|
+
SKIPPED = '[SKIPPED]'
|
|
8
|
+
|
|
9
|
+
def initialize(config)
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :config
|
|
14
|
+
|
|
15
|
+
def build(rack_env:, for_type:)
|
|
16
|
+
Context.new.tap do |context|
|
|
17
|
+
apply_to_request(context, rack_env: rack_env, for_type: for_type)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
24
|
+
def apply_to_request(context, rack_env:, for_type:)
|
|
25
|
+
req = rails_req?(rack_env) ? rack_env : Rack::Request.new(rack_env)
|
|
26
|
+
|
|
27
|
+
context.request = Context::Request.new unless context.request
|
|
28
|
+
request = context.request
|
|
29
|
+
|
|
30
|
+
request.socket = Context::Request::Socket.new(req)
|
|
31
|
+
request.http_version = build_http_version rack_env
|
|
32
|
+
request.method = req.request_method
|
|
33
|
+
request.url = Context::Request::Url.new(req)
|
|
34
|
+
|
|
35
|
+
request.body = should_capture_body?(for_type) ? get_body(req) : SKIPPED
|
|
36
|
+
|
|
37
|
+
headers, env = get_headers_and_env(rack_env)
|
|
38
|
+
request.headers = headers if config.capture_headers?
|
|
39
|
+
request.env = env if config.capture_env?
|
|
40
|
+
|
|
41
|
+
request.cookies = req.cookies
|
|
42
|
+
|
|
43
|
+
context
|
|
44
|
+
end
|
|
45
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
46
|
+
|
|
47
|
+
def should_capture_body?(for_type)
|
|
48
|
+
option = config.capture_body
|
|
49
|
+
|
|
50
|
+
return true if option == 'all'
|
|
51
|
+
return true if option == 'transactions' && for_type == :transaction
|
|
52
|
+
return true if option == 'errors' && for_type == :error
|
|
53
|
+
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def get_body(req)
|
|
58
|
+
case req.media_type
|
|
59
|
+
when 'application/x-www-form-urlencoded', 'multipart/form-data'
|
|
60
|
+
req.POST.dup
|
|
61
|
+
else
|
|
62
|
+
body = req.body.read
|
|
63
|
+
req.body.rewind
|
|
64
|
+
body.byteslice(0, MAX_BODY_LENGTH).force_encoding('utf-8')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def rails_req?(env)
|
|
69
|
+
defined?(ActionDispatch::Request) && env.is_a?(ActionDispatch::Request)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def get_headers_and_env(rack_env)
|
|
73
|
+
# In Rails < 5 ActionDispatch::Request inherits from Hash
|
|
74
|
+
headers =
|
|
75
|
+
rack_env.respond_to?(:headers) ? rack_env.headers : rack_env
|
|
76
|
+
|
|
77
|
+
headers.each_with_object([{}, {}]) do |(key, value), (http, env)|
|
|
78
|
+
next unless key == key.upcase
|
|
79
|
+
|
|
80
|
+
if key.start_with?('HTTP_')
|
|
81
|
+
http[camel_key(key)] = value
|
|
82
|
+
else
|
|
83
|
+
env[key] = value
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def camel_key(key)
|
|
89
|
+
key.gsub(/^HTTP_/, '').split('_').map(&:capitalize).join('-')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def build_http_version(rack_env)
|
|
93
|
+
return unless (http_version = rack_env['HTTP_VERSION'])
|
|
94
|
+
http_version.gsub(%r{HTTP/}, '')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|