truex-skylight 0.6.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/CHANGELOG.md +277 -0
- data/CLA.md +9 -0
- data/CONTRIBUTING.md +1 -0
- data/LICENSE.md +79 -0
- data/README.md +4 -0
- data/bin/skylight +3 -0
- data/ext/extconf.rb +186 -0
- data/ext/libskylight.yml +6 -0
- data/ext/skylight_memprof.c +115 -0
- data/ext/skylight_native.c +416 -0
- data/ext/skylight_native.h +20 -0
- data/lib/skylight.rb +2 -0
- data/lib/skylight/api.rb +79 -0
- data/lib/skylight/cli.rb +146 -0
- data/lib/skylight/compat.rb +47 -0
- data/lib/skylight/config.rb +498 -0
- data/lib/skylight/core.rb +122 -0
- data/lib/skylight/data/cacert.pem +3894 -0
- data/lib/skylight/formatters/http.rb +17 -0
- data/lib/skylight/gc.rb +107 -0
- data/lib/skylight/helpers.rb +137 -0
- data/lib/skylight/instrumenter.rb +290 -0
- data/lib/skylight/middleware.rb +75 -0
- data/lib/skylight/native.rb +69 -0
- data/lib/skylight/normalizers.rb +133 -0
- data/lib/skylight/normalizers/action_controller/process_action.rb +35 -0
- data/lib/skylight/normalizers/action_controller/send_file.rb +76 -0
- data/lib/skylight/normalizers/action_view/render_collection.rb +18 -0
- data/lib/skylight/normalizers/action_view/render_partial.rb +18 -0
- data/lib/skylight/normalizers/action_view/render_template.rb +18 -0
- data/lib/skylight/normalizers/active_record/sql.rb +79 -0
- data/lib/skylight/normalizers/active_support/cache.rb +50 -0
- data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
- data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
- data/lib/skylight/normalizers/default.rb +21 -0
- data/lib/skylight/normalizers/moped/query.rb +141 -0
- data/lib/skylight/probes.rb +91 -0
- data/lib/skylight/probes/excon.rb +25 -0
- data/lib/skylight/probes/excon/middleware.rb +65 -0
- data/lib/skylight/probes/net_http.rb +44 -0
- data/lib/skylight/probes/redis.rb +30 -0
- data/lib/skylight/probes/sequel.rb +30 -0
- data/lib/skylight/probes/sinatra.rb +74 -0
- data/lib/skylight/probes/tilt.rb +27 -0
- data/lib/skylight/railtie.rb +122 -0
- data/lib/skylight/sinatra.rb +4 -0
- data/lib/skylight/subscriber.rb +92 -0
- data/lib/skylight/trace.rb +191 -0
- data/lib/skylight/util.rb +16 -0
- data/lib/skylight/util/allocation_free.rb +17 -0
- data/lib/skylight/util/clock.rb +53 -0
- data/lib/skylight/util/gzip.rb +15 -0
- data/lib/skylight/util/hostname.rb +17 -0
- data/lib/skylight/util/http.rb +218 -0
- data/lib/skylight/util/inflector.rb +110 -0
- data/lib/skylight/util/logging.rb +87 -0
- data/lib/skylight/util/multi_io.rb +21 -0
- data/lib/skylight/util/native_ext_fetcher.rb +205 -0
- data/lib/skylight/util/platform.rb +67 -0
- data/lib/skylight/util/ssl.rb +50 -0
- data/lib/skylight/vendor/active_support/notifications.rb +207 -0
- data/lib/skylight/vendor/active_support/notifications/fanout.rb +159 -0
- data/lib/skylight/vendor/active_support/notifications/instrumenter.rb +72 -0
- data/lib/skylight/vendor/active_support/per_thread_registry.rb +52 -0
- data/lib/skylight/vendor/cli/highline.rb +1034 -0
- data/lib/skylight/vendor/cli/highline/color_scheme.rb +134 -0
- data/lib/skylight/vendor/cli/highline/compatibility.rb +16 -0
- data/lib/skylight/vendor/cli/highline/import.rb +41 -0
- data/lib/skylight/vendor/cli/highline/menu.rb +381 -0
- data/lib/skylight/vendor/cli/highline/question.rb +481 -0
- data/lib/skylight/vendor/cli/highline/simulate.rb +48 -0
- data/lib/skylight/vendor/cli/highline/string_extensions.rb +111 -0
- data/lib/skylight/vendor/cli/highline/style.rb +181 -0
- data/lib/skylight/vendor/cli/highline/system_extensions.rb +242 -0
- data/lib/skylight/vendor/cli/thor.rb +473 -0
- data/lib/skylight/vendor/cli/thor/actions.rb +318 -0
- data/lib/skylight/vendor/cli/thor/actions/create_file.rb +105 -0
- data/lib/skylight/vendor/cli/thor/actions/create_link.rb +60 -0
- data/lib/skylight/vendor/cli/thor/actions/directory.rb +119 -0
- data/lib/skylight/vendor/cli/thor/actions/empty_directory.rb +137 -0
- data/lib/skylight/vendor/cli/thor/actions/file_manipulation.rb +314 -0
- data/lib/skylight/vendor/cli/thor/actions/inject_into_file.rb +109 -0
- data/lib/skylight/vendor/cli/thor/base.rb +652 -0
- data/lib/skylight/vendor/cli/thor/command.rb +136 -0
- data/lib/skylight/vendor/cli/thor/core_ext/hash_with_indifferent_access.rb +80 -0
- data/lib/skylight/vendor/cli/thor/core_ext/io_binary_read.rb +12 -0
- data/lib/skylight/vendor/cli/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/skylight/vendor/cli/thor/error.rb +28 -0
- data/lib/skylight/vendor/cli/thor/group.rb +282 -0
- data/lib/skylight/vendor/cli/thor/invocation.rb +172 -0
- data/lib/skylight/vendor/cli/thor/parser.rb +4 -0
- data/lib/skylight/vendor/cli/thor/parser/argument.rb +74 -0
- data/lib/skylight/vendor/cli/thor/parser/arguments.rb +171 -0
- data/lib/skylight/vendor/cli/thor/parser/option.rb +121 -0
- data/lib/skylight/vendor/cli/thor/parser/options.rb +218 -0
- data/lib/skylight/vendor/cli/thor/rake_compat.rb +72 -0
- data/lib/skylight/vendor/cli/thor/runner.rb +322 -0
- data/lib/skylight/vendor/cli/thor/shell.rb +88 -0
- data/lib/skylight/vendor/cli/thor/shell/basic.rb +393 -0
- data/lib/skylight/vendor/cli/thor/shell/color.rb +148 -0
- data/lib/skylight/vendor/cli/thor/shell/html.rb +127 -0
- data/lib/skylight/vendor/cli/thor/util.rb +270 -0
- data/lib/skylight/vendor/cli/thor/version.rb +3 -0
- data/lib/skylight/vendor/thread_safe.rb +126 -0
- data/lib/skylight/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
- data/lib/skylight/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
- data/lib/skylight/version.rb +4 -0
- data/lib/skylight/vm/gc.rb +70 -0
- data/lib/sql_lexer.rb +6 -0
- data/lib/sql_lexer/lexer.rb +579 -0
- data/lib/sql_lexer/string_scanner.rb +11 -0
- data/lib/sql_lexer/version.rb +3 -0
- metadata +179 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
# @api private
|
|
3
|
+
class Subscriber
|
|
4
|
+
include Util::Logging
|
|
5
|
+
|
|
6
|
+
attr_reader :config
|
|
7
|
+
|
|
8
|
+
def initialize(config, instrumenter)
|
|
9
|
+
@config = config
|
|
10
|
+
@subscriber = nil
|
|
11
|
+
@normalizers = Normalizers.build(config)
|
|
12
|
+
@instrumenter = instrumenter
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register!
|
|
16
|
+
unregister! if @subscriber
|
|
17
|
+
@subscriber = ActiveSupport::Notifications.subscribe nil, self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def unregister!
|
|
21
|
+
ActiveSupport::Notifications.unsubscribe @subscriber
|
|
22
|
+
@subscriber = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
#
|
|
27
|
+
# ===== ActiveSupport::Notifications API
|
|
28
|
+
#
|
|
29
|
+
#
|
|
30
|
+
|
|
31
|
+
class Notification
|
|
32
|
+
attr_reader :name, :span
|
|
33
|
+
|
|
34
|
+
def initialize(name, span)
|
|
35
|
+
@name, @span = name, span
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def start(name, id, payload)
|
|
40
|
+
return if @instrumenter.disabled?
|
|
41
|
+
return unless trace = @instrumenter.current_trace
|
|
42
|
+
|
|
43
|
+
cat, title, desc, annot = normalize(trace, name, payload)
|
|
44
|
+
|
|
45
|
+
unless cat == :skip
|
|
46
|
+
span = trace.instrument(cat, title, desc, annot)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
trace.notifications << Notification.new(name, span)
|
|
50
|
+
rescue Exception => e
|
|
51
|
+
error "Subscriber#start error; msg=%s", e.message
|
|
52
|
+
debug "trace=%s", trace.inspect
|
|
53
|
+
debug "in: name=%s", name.inspect
|
|
54
|
+
debug "in: payload=%s", payload.inspect
|
|
55
|
+
debug "out: cat=%s, title=%s, desc=%s", cat.inspect, name.inspect, desc.inspect
|
|
56
|
+
debug "out: annot=%s", annot.inspect
|
|
57
|
+
t { e.backtrace.join("\n") }
|
|
58
|
+
nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def finish(name, id, payload)
|
|
62
|
+
return if @instrumenter.disabled?
|
|
63
|
+
return unless trace = @instrumenter.current_trace
|
|
64
|
+
|
|
65
|
+
while curr = trace.notifications.pop
|
|
66
|
+
if curr.name == name
|
|
67
|
+
trace.done(curr.span) if curr.span
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
rescue Exception => e
|
|
73
|
+
error "Subscriber#finish error; msg=%s", e.message
|
|
74
|
+
debug "trace=%s", trace.inspect
|
|
75
|
+
debug "in: name=%s", name.inspect
|
|
76
|
+
debug "in: payload=%s", payload.inspect
|
|
77
|
+
t { e.backtrace.join("\n") }
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def publish(name, *args)
|
|
82
|
+
# Ignored for now because nothing in rails uses it
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def normalize(*args)
|
|
88
|
+
@normalizers.normalize(*args)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
class Trace
|
|
3
|
+
GC_CAT = 'noise.gc'.freeze
|
|
4
|
+
|
|
5
|
+
include Util::Logging
|
|
6
|
+
|
|
7
|
+
attr_reader :endpoint, :notifications
|
|
8
|
+
|
|
9
|
+
def self.new(instrumenter, endpoint, start, cat, title = nil, desc = nil, annot = nil)
|
|
10
|
+
inst = native_new(normalize_time(start), "TODO", endpoint)
|
|
11
|
+
inst.send(:initialize, instrumenter, cat, title, desc, annot)
|
|
12
|
+
inst.endpoint = endpoint
|
|
13
|
+
inst
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# TODO: Move this into native
|
|
17
|
+
def self.normalize_time(time)
|
|
18
|
+
# At least one customer has extensions that cause integer division to produce rationals.
|
|
19
|
+
# Since the native code expects an integer, we force it again.
|
|
20
|
+
(time.to_i / 100_000).to_i
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(instrumenter, cat, title, desc, annot)
|
|
24
|
+
raise ArgumentError, 'instrumenter is required' unless instrumenter
|
|
25
|
+
|
|
26
|
+
@instrumenter = instrumenter
|
|
27
|
+
@submitted = false
|
|
28
|
+
@broken = false
|
|
29
|
+
@traced = false
|
|
30
|
+
|
|
31
|
+
@notifications = []
|
|
32
|
+
|
|
33
|
+
if Hash === title
|
|
34
|
+
annot = title
|
|
35
|
+
title = desc = nil
|
|
36
|
+
elsif Hash === desc
|
|
37
|
+
annot = desc
|
|
38
|
+
desc = nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# create the root node
|
|
42
|
+
@root = native_start_span(native_get_started_at, cat)
|
|
43
|
+
native_span_set_title(@root, title) if title
|
|
44
|
+
native_span_set_description(@root, desc) if desc
|
|
45
|
+
|
|
46
|
+
@gc = config.gc.track unless ENV.key?("SKYLIGHT_DISABLE_GC_TRACKING")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def endpoint=(value)
|
|
50
|
+
@endpoint = value
|
|
51
|
+
native_set_endpoint(value)
|
|
52
|
+
value
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def config
|
|
56
|
+
@instrumenter.config
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def record(cat, title=nil, desc=nil, annot=nil)
|
|
60
|
+
@return if @broken
|
|
61
|
+
|
|
62
|
+
if Hash === title
|
|
63
|
+
annot = title
|
|
64
|
+
title = desc = nil
|
|
65
|
+
elsif Hash === desc
|
|
66
|
+
annot = desc
|
|
67
|
+
desc = nil
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
title.freeze if title.is_a?(String)
|
|
71
|
+
desc.freeze if desc.is_a?(String)
|
|
72
|
+
|
|
73
|
+
desc = @instrumenter.limited_description(desc)
|
|
74
|
+
|
|
75
|
+
time = Util::Clock.nanos - gc_time
|
|
76
|
+
|
|
77
|
+
stop(start(time, cat, title, desc), time)
|
|
78
|
+
|
|
79
|
+
nil
|
|
80
|
+
rescue => e
|
|
81
|
+
error "failed to record span; msg=%s", e.message
|
|
82
|
+
@broken = true
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def instrument(cat, title=nil, desc=nil, annot=nil)
|
|
87
|
+
return if @broken
|
|
88
|
+
t { "instrument: #{cat}, #{title}" }
|
|
89
|
+
|
|
90
|
+
if Hash === title
|
|
91
|
+
annot = title
|
|
92
|
+
title = desc = nil
|
|
93
|
+
elsif Hash === desc
|
|
94
|
+
annot = desc
|
|
95
|
+
desc = nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
title.freeze if title.is_a?(String)
|
|
99
|
+
desc.freeze if desc.is_a?(String)
|
|
100
|
+
|
|
101
|
+
original_desc = desc
|
|
102
|
+
now = Util::Clock.nanos
|
|
103
|
+
desc = @instrumenter.limited_description(desc)
|
|
104
|
+
|
|
105
|
+
if desc == Instrumenter::TOO_MANY_UNIQUES
|
|
106
|
+
debug "[SKYLIGHT] [#{Skylight::VERSION}] A payload description produced <too many uniques>"
|
|
107
|
+
debug "original desc=%s", original_desc
|
|
108
|
+
debug "cat=%s, title=%s, desc=%s, annot=%s", cat, title, desc, annot.inspect
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
start(now - gc_time, cat, title, desc, annot)
|
|
112
|
+
rescue => e
|
|
113
|
+
error "failed to instrument span; msg=%s", e.message
|
|
114
|
+
@broken = true
|
|
115
|
+
nil
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def done(span)
|
|
119
|
+
return unless span
|
|
120
|
+
return if @broken
|
|
121
|
+
stop(span, Util::Clock.nanos - gc_time)
|
|
122
|
+
rescue => e
|
|
123
|
+
error "failed to close span; msg=%s", e.message
|
|
124
|
+
@broken = true
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def release
|
|
129
|
+
return unless @instrumenter.current_trace == self
|
|
130
|
+
@instrumenter.current_trace = nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def traced
|
|
134
|
+
@traced = true
|
|
135
|
+
time = gc_time
|
|
136
|
+
now = Util::Clock.nanos
|
|
137
|
+
|
|
138
|
+
if time > 0
|
|
139
|
+
t { fmt "tracking GC time; duration=%d", time }
|
|
140
|
+
stop(start(now - time, GC_CAT, nil, nil, {}), now)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
stop(@root, now)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def submit
|
|
147
|
+
return if @broken
|
|
148
|
+
|
|
149
|
+
t { "submitting trace" }
|
|
150
|
+
|
|
151
|
+
if @submitted
|
|
152
|
+
t { "already submitted" }
|
|
153
|
+
return
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
release
|
|
157
|
+
@submitted = true
|
|
158
|
+
|
|
159
|
+
traced unless @traced
|
|
160
|
+
|
|
161
|
+
@instrumenter.process(self)
|
|
162
|
+
rescue Exception => e
|
|
163
|
+
error e
|
|
164
|
+
t { e.backtrace.join("\n") }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
private
|
|
168
|
+
|
|
169
|
+
def start(time, cat, title, desc, annot=nil)
|
|
170
|
+
span(self.class.normalize_time(time), cat, title, desc, annot)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def stop(span, time)
|
|
174
|
+
native_stop_span(span, self.class.normalize_time(time))
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def span(time, cat, title=nil, desc=nil, annot=nil)
|
|
179
|
+
sp = native_start_span(time, cat.to_s)
|
|
180
|
+
native_span_set_title(sp, title.to_s) if title
|
|
181
|
+
native_span_set_description(sp, desc.to_s) if desc
|
|
182
|
+
sp
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def gc_time
|
|
186
|
+
return 0 unless @gc
|
|
187
|
+
@gc.update
|
|
188
|
+
@gc.time
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
# @api private
|
|
3
|
+
module Util
|
|
4
|
+
# Used from the main lib
|
|
5
|
+
require 'skylight/util/allocation_free'
|
|
6
|
+
require 'skylight/util/clock'
|
|
7
|
+
require 'skylight/util/hostname'
|
|
8
|
+
require 'skylight/util/logging'
|
|
9
|
+
require 'skylight/util/ssl'
|
|
10
|
+
require 'skylight/util/http'
|
|
11
|
+
|
|
12
|
+
# Used from the CLI
|
|
13
|
+
autoload :Gzip, 'skylight/util/gzip'
|
|
14
|
+
autoload :Inflector, 'skylight/util/inflector'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Skylight
|
|
2
|
+
module Util
|
|
3
|
+
class Clock
|
|
4
|
+
|
|
5
|
+
if Skylight.native?
|
|
6
|
+
def tick
|
|
7
|
+
native_hrtime
|
|
8
|
+
end
|
|
9
|
+
else
|
|
10
|
+
def tick
|
|
11
|
+
now = Time.now
|
|
12
|
+
now.to_i * 1_000_000_000 + now.usec * 1_000
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# TODO: rename to secs
|
|
17
|
+
def absolute_secs
|
|
18
|
+
Time.now.to_i
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# TODO: remove
|
|
22
|
+
def nanos
|
|
23
|
+
tick
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# TODO: remove
|
|
27
|
+
def secs
|
|
28
|
+
nanos / 1_000_000_000
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.absolute_secs
|
|
32
|
+
default.absolute_secs
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.nanos
|
|
36
|
+
default.nanos
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.secs
|
|
40
|
+
default.secs
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.default
|
|
44
|
+
@clock ||= Clock.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.default=(clock)
|
|
48
|
+
@clock = clock
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'securerandom'
|
|
3
|
+
|
|
4
|
+
module Skylight
|
|
5
|
+
module Util
|
|
6
|
+
module Hostname
|
|
7
|
+
def self.default_hostname
|
|
8
|
+
if hostname = Socket.gethostname
|
|
9
|
+
hostname.strip!
|
|
10
|
+
hostname = nil if hostname == ''
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
hostname || "gen-#{SecureRandom.uuid}"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'net/https'
|
|
6
|
+
require 'skylight/util/gzip'
|
|
7
|
+
require 'skylight/util/ssl'
|
|
8
|
+
|
|
9
|
+
module Skylight
|
|
10
|
+
module Util
|
|
11
|
+
class HTTP
|
|
12
|
+
CONTENT_ENCODING = 'content-encoding'.freeze
|
|
13
|
+
CONTENT_LENGTH = 'content-length'.freeze
|
|
14
|
+
CONTENT_TYPE = 'content-type'.freeze
|
|
15
|
+
ACCEPT = 'Accept'.freeze
|
|
16
|
+
X_VERSION_HDR = 'x-skylight-agent-version'.freeze
|
|
17
|
+
APPLICATION_JSON = 'application/json'.freeze
|
|
18
|
+
AUTHORIZATION = 'authorization'.freeze
|
|
19
|
+
DEFLATE = 'deflate'.freeze
|
|
20
|
+
GZIP = 'gzip'.freeze
|
|
21
|
+
|
|
22
|
+
include Logging
|
|
23
|
+
|
|
24
|
+
attr_accessor :authentication
|
|
25
|
+
attr_reader :host, :port
|
|
26
|
+
|
|
27
|
+
READ_EXCEPTIONS = [Timeout::Error, EOFError]
|
|
28
|
+
# This doesn't exist on Ruby 1.9.3
|
|
29
|
+
READ_EXCEPTIONS << Net::ReadTimeout if defined?(Net::ReadTimeout)
|
|
30
|
+
READ_EXCEPTIONS.freeze
|
|
31
|
+
|
|
32
|
+
class StartError < StandardError; end
|
|
33
|
+
class ReadResponseError < StandardError; end
|
|
34
|
+
|
|
35
|
+
def initialize(config, service = :auth, opts = {})
|
|
36
|
+
@config = config
|
|
37
|
+
|
|
38
|
+
unless url = config["#{service}_url"]
|
|
39
|
+
raise ArgumentError, "no URL specified"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
url = URI.parse(url)
|
|
43
|
+
|
|
44
|
+
@ssl = url.scheme == 'https'
|
|
45
|
+
@host = url.host
|
|
46
|
+
@port = url.port
|
|
47
|
+
|
|
48
|
+
if proxy_url = config[:proxy_url]
|
|
49
|
+
proxy_url = URI.parse(proxy_url)
|
|
50
|
+
@proxy_addr, @proxy_port = proxy_url.host, proxy_url.port
|
|
51
|
+
@proxy_user, @proxy_pass = (proxy_url.userinfo || '').split(/:/)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@open_timeout = get_timeout(:connect, config, service, opts)
|
|
55
|
+
@read_timeout = get_timeout(:read, config, service, opts)
|
|
56
|
+
|
|
57
|
+
@deflate = config["#{service}_http_deflate"]
|
|
58
|
+
@authentication = config[:'authentication']
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get(endpoint, hdrs = {})
|
|
62
|
+
request = build_request(Net::HTTP::Get, endpoint, hdrs)
|
|
63
|
+
execute(request)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def post(endpoint, body, hdrs = {})
|
|
67
|
+
unless body.respond_to?(:to_str)
|
|
68
|
+
hdrs[CONTENT_TYPE] = APPLICATION_JSON
|
|
69
|
+
body = body.to_json
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
request = build_request(Net::HTTP::Post, endpoint, hdrs, body.bytesize)
|
|
73
|
+
execute(request, body)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def get_timeout(type, config, service, opts)
|
|
79
|
+
config.duration_ms("#{service}_http_#{type}_timeout") ||
|
|
80
|
+
opts[:timeout] || 15
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def build_request(type, endpoint, hdrs, length=nil)
|
|
84
|
+
headers = {}
|
|
85
|
+
|
|
86
|
+
headers[CONTENT_LENGTH] = length.to_s if length
|
|
87
|
+
headers[AUTHORIZATION] = authentication if authentication
|
|
88
|
+
headers[ACCEPT] = APPLICATION_JSON
|
|
89
|
+
headers[X_VERSION_HDR] = VERSION
|
|
90
|
+
headers[CONTENT_ENCODING] = GZIP if length && @deflate
|
|
91
|
+
|
|
92
|
+
hdrs.each do |k, v|
|
|
93
|
+
headers[k] = v
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
type.new(endpoint, headers)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def do_request(http, req)
|
|
100
|
+
begin
|
|
101
|
+
client = http.start
|
|
102
|
+
rescue => e
|
|
103
|
+
# TODO: Retry here
|
|
104
|
+
raise StartError, e.inspect
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
begin
|
|
108
|
+
res = client.request(req)
|
|
109
|
+
rescue *READ_EXCEPTIONS => e
|
|
110
|
+
raise ReadResponseError, e.inspect
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
yield res
|
|
114
|
+
ensure
|
|
115
|
+
client.finish if client
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def execute(req, body=nil)
|
|
119
|
+
t { fmt "executing HTTP request; host=%s; port=%s; path=%s, body=%s",
|
|
120
|
+
@host, @port, req.path, body && body.bytesize }
|
|
121
|
+
|
|
122
|
+
if body
|
|
123
|
+
body = Gzip.compress(body) if @deflate
|
|
124
|
+
req.body = body
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
http = Net::HTTP.new(@host, @port,
|
|
128
|
+
@proxy_addr, @proxy_port, @proxy_user, @proxy_pass)
|
|
129
|
+
|
|
130
|
+
http.open_timeout = @open_timeout
|
|
131
|
+
http.read_timeout = @read_timeout
|
|
132
|
+
|
|
133
|
+
if @ssl
|
|
134
|
+
http.use_ssl = true
|
|
135
|
+
|
|
136
|
+
unless SSL.ca_cert_file?
|
|
137
|
+
http.ca_file = SSL.ca_cert_file_or_default
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
do_request(http, req) do |res|
|
|
144
|
+
unless res.code =~ /2\d\d/
|
|
145
|
+
debug "server responded with #{res.code}"
|
|
146
|
+
t { fmt "body=%s", res.body }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
Response.new(res.code.to_i, res, res.body)
|
|
150
|
+
end
|
|
151
|
+
rescue Exception => e
|
|
152
|
+
error "http %s %s failed; error=%s; msg=%s", req.method, req.path, e.class, e.message
|
|
153
|
+
t { e.backtrace.join("\n") }
|
|
154
|
+
ErrorResponse.new(req, e)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class Response
|
|
158
|
+
attr_reader :status, :headers, :body, :exception
|
|
159
|
+
|
|
160
|
+
def initialize(status, headers, body)
|
|
161
|
+
@status = status
|
|
162
|
+
@headers = headers
|
|
163
|
+
|
|
164
|
+
if (headers[CONTENT_TYPE] || "").include?(APPLICATION_JSON)
|
|
165
|
+
begin
|
|
166
|
+
@body = JSON.parse(body)
|
|
167
|
+
rescue JSON::ParserError
|
|
168
|
+
@body = body # not really JSON I guess
|
|
169
|
+
end
|
|
170
|
+
else
|
|
171
|
+
@body = body
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def success?
|
|
176
|
+
status >= 200 && status < 300
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def to_s
|
|
180
|
+
body.to_s
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def get(key)
|
|
184
|
+
return nil unless Hash === body
|
|
185
|
+
|
|
186
|
+
res = body
|
|
187
|
+
key.split('.').each do |part|
|
|
188
|
+
return unless res = res[part]
|
|
189
|
+
end
|
|
190
|
+
res
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def respond_to_missing?(name, include_all=false)
|
|
194
|
+
super || body.respond_to?(name, include_all)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def method_missing(name, *args, &blk)
|
|
198
|
+
if respond_to_missing?(name)
|
|
199
|
+
body.send(name, *args, &blk)
|
|
200
|
+
else
|
|
201
|
+
super
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
class ErrorResponse < Struct.new(:request, :exception)
|
|
207
|
+
def status
|
|
208
|
+
nil
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def success?
|
|
212
|
+
false
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|