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.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +277 -0
  3. data/CLA.md +9 -0
  4. data/CONTRIBUTING.md +1 -0
  5. data/LICENSE.md +79 -0
  6. data/README.md +4 -0
  7. data/bin/skylight +3 -0
  8. data/ext/extconf.rb +186 -0
  9. data/ext/libskylight.yml +6 -0
  10. data/ext/skylight_memprof.c +115 -0
  11. data/ext/skylight_native.c +416 -0
  12. data/ext/skylight_native.h +20 -0
  13. data/lib/skylight.rb +2 -0
  14. data/lib/skylight/api.rb +79 -0
  15. data/lib/skylight/cli.rb +146 -0
  16. data/lib/skylight/compat.rb +47 -0
  17. data/lib/skylight/config.rb +498 -0
  18. data/lib/skylight/core.rb +122 -0
  19. data/lib/skylight/data/cacert.pem +3894 -0
  20. data/lib/skylight/formatters/http.rb +17 -0
  21. data/lib/skylight/gc.rb +107 -0
  22. data/lib/skylight/helpers.rb +137 -0
  23. data/lib/skylight/instrumenter.rb +290 -0
  24. data/lib/skylight/middleware.rb +75 -0
  25. data/lib/skylight/native.rb +69 -0
  26. data/lib/skylight/normalizers.rb +133 -0
  27. data/lib/skylight/normalizers/action_controller/process_action.rb +35 -0
  28. data/lib/skylight/normalizers/action_controller/send_file.rb +76 -0
  29. data/lib/skylight/normalizers/action_view/render_collection.rb +18 -0
  30. data/lib/skylight/normalizers/action_view/render_partial.rb +18 -0
  31. data/lib/skylight/normalizers/action_view/render_template.rb +18 -0
  32. data/lib/skylight/normalizers/active_record/sql.rb +79 -0
  33. data/lib/skylight/normalizers/active_support/cache.rb +50 -0
  34. data/lib/skylight/normalizers/active_support/cache_clear.rb +16 -0
  35. data/lib/skylight/normalizers/active_support/cache_decrement.rb +16 -0
  36. data/lib/skylight/normalizers/active_support/cache_delete.rb +16 -0
  37. data/lib/skylight/normalizers/active_support/cache_exist.rb +16 -0
  38. data/lib/skylight/normalizers/active_support/cache_fetch_hit.rb +16 -0
  39. data/lib/skylight/normalizers/active_support/cache_generate.rb +16 -0
  40. data/lib/skylight/normalizers/active_support/cache_increment.rb +16 -0
  41. data/lib/skylight/normalizers/active_support/cache_read.rb +16 -0
  42. data/lib/skylight/normalizers/active_support/cache_read_multi.rb +16 -0
  43. data/lib/skylight/normalizers/active_support/cache_write.rb +16 -0
  44. data/lib/skylight/normalizers/default.rb +21 -0
  45. data/lib/skylight/normalizers/moped/query.rb +141 -0
  46. data/lib/skylight/probes.rb +91 -0
  47. data/lib/skylight/probes/excon.rb +25 -0
  48. data/lib/skylight/probes/excon/middleware.rb +65 -0
  49. data/lib/skylight/probes/net_http.rb +44 -0
  50. data/lib/skylight/probes/redis.rb +30 -0
  51. data/lib/skylight/probes/sequel.rb +30 -0
  52. data/lib/skylight/probes/sinatra.rb +74 -0
  53. data/lib/skylight/probes/tilt.rb +27 -0
  54. data/lib/skylight/railtie.rb +122 -0
  55. data/lib/skylight/sinatra.rb +4 -0
  56. data/lib/skylight/subscriber.rb +92 -0
  57. data/lib/skylight/trace.rb +191 -0
  58. data/lib/skylight/util.rb +16 -0
  59. data/lib/skylight/util/allocation_free.rb +17 -0
  60. data/lib/skylight/util/clock.rb +53 -0
  61. data/lib/skylight/util/gzip.rb +15 -0
  62. data/lib/skylight/util/hostname.rb +17 -0
  63. data/lib/skylight/util/http.rb +218 -0
  64. data/lib/skylight/util/inflector.rb +110 -0
  65. data/lib/skylight/util/logging.rb +87 -0
  66. data/lib/skylight/util/multi_io.rb +21 -0
  67. data/lib/skylight/util/native_ext_fetcher.rb +205 -0
  68. data/lib/skylight/util/platform.rb +67 -0
  69. data/lib/skylight/util/ssl.rb +50 -0
  70. data/lib/skylight/vendor/active_support/notifications.rb +207 -0
  71. data/lib/skylight/vendor/active_support/notifications/fanout.rb +159 -0
  72. data/lib/skylight/vendor/active_support/notifications/instrumenter.rb +72 -0
  73. data/lib/skylight/vendor/active_support/per_thread_registry.rb +52 -0
  74. data/lib/skylight/vendor/cli/highline.rb +1034 -0
  75. data/lib/skylight/vendor/cli/highline/color_scheme.rb +134 -0
  76. data/lib/skylight/vendor/cli/highline/compatibility.rb +16 -0
  77. data/lib/skylight/vendor/cli/highline/import.rb +41 -0
  78. data/lib/skylight/vendor/cli/highline/menu.rb +381 -0
  79. data/lib/skylight/vendor/cli/highline/question.rb +481 -0
  80. data/lib/skylight/vendor/cli/highline/simulate.rb +48 -0
  81. data/lib/skylight/vendor/cli/highline/string_extensions.rb +111 -0
  82. data/lib/skylight/vendor/cli/highline/style.rb +181 -0
  83. data/lib/skylight/vendor/cli/highline/system_extensions.rb +242 -0
  84. data/lib/skylight/vendor/cli/thor.rb +473 -0
  85. data/lib/skylight/vendor/cli/thor/actions.rb +318 -0
  86. data/lib/skylight/vendor/cli/thor/actions/create_file.rb +105 -0
  87. data/lib/skylight/vendor/cli/thor/actions/create_link.rb +60 -0
  88. data/lib/skylight/vendor/cli/thor/actions/directory.rb +119 -0
  89. data/lib/skylight/vendor/cli/thor/actions/empty_directory.rb +137 -0
  90. data/lib/skylight/vendor/cli/thor/actions/file_manipulation.rb +314 -0
  91. data/lib/skylight/vendor/cli/thor/actions/inject_into_file.rb +109 -0
  92. data/lib/skylight/vendor/cli/thor/base.rb +652 -0
  93. data/lib/skylight/vendor/cli/thor/command.rb +136 -0
  94. data/lib/skylight/vendor/cli/thor/core_ext/hash_with_indifferent_access.rb +80 -0
  95. data/lib/skylight/vendor/cli/thor/core_ext/io_binary_read.rb +12 -0
  96. data/lib/skylight/vendor/cli/thor/core_ext/ordered_hash.rb +100 -0
  97. data/lib/skylight/vendor/cli/thor/error.rb +28 -0
  98. data/lib/skylight/vendor/cli/thor/group.rb +282 -0
  99. data/lib/skylight/vendor/cli/thor/invocation.rb +172 -0
  100. data/lib/skylight/vendor/cli/thor/parser.rb +4 -0
  101. data/lib/skylight/vendor/cli/thor/parser/argument.rb +74 -0
  102. data/lib/skylight/vendor/cli/thor/parser/arguments.rb +171 -0
  103. data/lib/skylight/vendor/cli/thor/parser/option.rb +121 -0
  104. data/lib/skylight/vendor/cli/thor/parser/options.rb +218 -0
  105. data/lib/skylight/vendor/cli/thor/rake_compat.rb +72 -0
  106. data/lib/skylight/vendor/cli/thor/runner.rb +322 -0
  107. data/lib/skylight/vendor/cli/thor/shell.rb +88 -0
  108. data/lib/skylight/vendor/cli/thor/shell/basic.rb +393 -0
  109. data/lib/skylight/vendor/cli/thor/shell/color.rb +148 -0
  110. data/lib/skylight/vendor/cli/thor/shell/html.rb +127 -0
  111. data/lib/skylight/vendor/cli/thor/util.rb +270 -0
  112. data/lib/skylight/vendor/cli/thor/version.rb +3 -0
  113. data/lib/skylight/vendor/thread_safe.rb +126 -0
  114. data/lib/skylight/vendor/thread_safe/non_concurrent_cache_backend.rb +133 -0
  115. data/lib/skylight/vendor/thread_safe/synchronized_cache_backend.rb +76 -0
  116. data/lib/skylight/version.rb +4 -0
  117. data/lib/skylight/vm/gc.rb +70 -0
  118. data/lib/sql_lexer.rb +6 -0
  119. data/lib/sql_lexer/lexer.rb +579 -0
  120. data/lib/sql_lexer/string_scanner.rb +11 -0
  121. data/lib/sql_lexer/version.rb +3 -0
  122. metadata +179 -0
@@ -0,0 +1,4 @@
1
+ require 'skylight'
2
+ require 'skylight/probes/sinatra'
3
+ require 'skylight/probes/tilt'
4
+ require 'skylight/probes/sequel'
@@ -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,17 @@
1
+ module Skylight
2
+ module Util
3
+ module AllocationFree
4
+ def array_find(array)
5
+ i = 0
6
+
7
+ while i < array.size
8
+ item = array[i]
9
+ return item if yield item
10
+ i += 1
11
+ end
12
+
13
+ nil
14
+ end
15
+ end
16
+ end
17
+ 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,15 @@
1
+ require 'zlib'
2
+
3
+ module Skylight
4
+ module Util
5
+ module Gzip
6
+ def self.compress(str)
7
+ output = StringIO.new
8
+ gz = Zlib::GzipWriter.new(output)
9
+ gz.write(str)
10
+ gz.close
11
+ output.string
12
+ end
13
+ end
14
+ end
15
+ 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