api_hammer 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +28 -1
- data/bin/hc +22 -128
- data/lib/api_hammer/faraday/outputter.rb +137 -0
- data/lib/api_hammer/rails.rb +1 -0
- data/lib/api_hammer/rails_request_logging.rb +59 -0
- data/lib/api_hammer/request_logger.rb +29 -9
- data/lib/api_hammer/unmunged_request_params.rb +26 -0
- data/lib/api_hammer/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d674265eaf92948690ef36086f4e6858103e0d92
|
4
|
+
data.tar.gz: 00af75f0635b6e4fcd1a373936d2524e74852afb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91be2d6f0a923086524397f7aed0c00029db8c049f59029cbf9e9c3d0f4754009b76702af132a2797484a80111a24d13eb7b3017367f668389172dabbd882038
|
7
|
+
data.tar.gz: 6d46171d830d5c41794569e2aa73762fa448b72a831e01d2b146cb6e4850998845eb69048a770bb0c75b80048a895b4aa95e98e1e62ce47f40dacb559d03eb49
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
# 0.2.0
|
2
|
+
- Rails#unmunged_request_params
|
3
|
+
- hc --pretty
|
4
|
+
- hc default User-Agent set
|
5
|
+
- hc doc improved, middlewares moved lib
|
6
|
+
- RequestLogger improved
|
7
|
+
- ApiHammer::RailsRequestLogging railtie
|
8
|
+
- README improved marginally
|
9
|
+
|
1
10
|
# 0.1.0
|
2
11
|
- Object#public_instance_exec
|
3
12
|
- Obect#public_instance_eval
|
data/README.md
CHANGED
@@ -1,3 +1,30 @@
|
|
1
1
|
# ApiHammer
|
2
2
|
|
3
|
-
|
3
|
+
a collection of little tools I have used when creating APIs. these are generally too small to have their own
|
4
|
+
library, so have been collected here.
|
5
|
+
|
6
|
+
## ApiHammer::Rails
|
7
|
+
|
8
|
+
A module for inclusion in a Rails application controller inheriting from ActionController::Base (Rocketpants
|
9
|
+
should also work, but no guarantees).
|
10
|
+
|
11
|
+
### #halt
|
12
|
+
|
13
|
+
You know that pattern in rails, `render(some_stuff) and return`?
|
14
|
+
|
15
|
+
Don't use it. It's wrong. `and` is used to short-circuit, but you don't actually want your `return` to be conditional on the `render`. Since it is conditional on the return value of `render`, if the return value of `render` is false-ish (`false` or `nil`), you may end end up failing to return. The actual return value of `render` is undocumented. If you follow all the control paths that can affect the return value, last time I looked there were 37 different paths. None of them returns false, but if any control path changes to return false then `render and return` will break.
|
16
|
+
|
17
|
+
What you really mean is `render(some_stuff); return`, but semicolons are (rightly) frowned upon
|
18
|
+
in ruby. Really you should just put the `return` on the next line.
|
19
|
+
|
20
|
+
Or, use `#halt`, kindly provided by ApiHammer.
|
21
|
+
|
22
|
+
`ApiHammer#halt` is based on the Sinatra pattern of `throw(:halt, response)`. It uses exceptions because Rails has baked-in exception rescuing (but not, at least as far as I have found, throw catching), but that is hidden away.
|
23
|
+
|
24
|
+
### #check_required_params
|
25
|
+
|
26
|
+
Pass it an parameters which are required, and if they are missing or incorrect it will halt with 422 (not 400 - 400 is wrong).
|
27
|
+
|
28
|
+
## Other
|
29
|
+
|
30
|
+
Various other things. This readme is incomplete and will be updated soon.
|
data/bin/hc
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
lib = File.expand_path('../lib', File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.any? { |lp| File.expand_path(lp) == File.expand_path(lib) }
|
5
|
+
|
3
6
|
require 'rubygems'
|
4
7
|
require 'faraday'
|
5
8
|
require 'logger'
|
6
9
|
require 'yaml'
|
7
10
|
|
11
|
+
require 'api_hammer/faraday/outputter'
|
12
|
+
require 'api_hammer/version'
|
13
|
+
|
8
14
|
# OPTION PARSER
|
9
15
|
|
10
16
|
require 'optparse'
|
@@ -13,6 +19,7 @@ require 'optparse'
|
|
13
19
|
$options = {
|
14
20
|
:verbose => true,
|
15
21
|
:color => nil,
|
22
|
+
:pretty => nil,
|
16
23
|
:no_ssl_verify => false,
|
17
24
|
:headers => {},
|
18
25
|
}
|
@@ -31,29 +38,32 @@ opt_parser = OptionParser.new do |opts|
|
|
31
38
|
opts.on("--[no-]color", "Color the output (defaults to color if the output device is a TTY)") do |v|
|
32
39
|
$options[:color] = v
|
33
40
|
end
|
34
|
-
opts.on("
|
41
|
+
opts.on("--[no-]pretty", "Format the output prettily if supported. (JSON is supported.) defaults to true.") do |v|
|
42
|
+
$options[:pretty] = v
|
43
|
+
end
|
44
|
+
opts.on("-t", "--content-type CONTENT-TYPE", "Sets the Content-Type header of the request. This defaults to application/json if a body is included.") do |v|
|
35
45
|
$options[:headers]['Content-Type'.downcase] = v
|
36
46
|
end
|
37
|
-
opts.on("--oauth-token TOKEN") do |token|
|
47
|
+
opts.on("--oauth-token TOKEN", "OAuth 1.0 token") do |token|
|
38
48
|
$oauth[:token] = token
|
39
49
|
end
|
40
|
-
opts.on("--oauth-token-secret TOKEN_SECRET") do |token_secret|
|
50
|
+
opts.on("--oauth-token-secret TOKEN_SECRET", "OAuth 1.0 token secret") do |token_secret|
|
41
51
|
$oauth[:token_secret] = token_secret
|
42
52
|
end
|
43
|
-
opts.on("--oauth-consumer-key CONSUMER_KEY") do |consumer_key|
|
53
|
+
opts.on("--oauth-consumer-key CONSUMER_KEY", "OAuth 1.0 consumer key") do |consumer_key|
|
44
54
|
$oauth[:consumer_key] = consumer_key
|
45
55
|
end
|
46
|
-
opts.on("--oauth-consumer-secret CONSUMER_SECRET") do |consumer_secret|
|
56
|
+
opts.on("--oauth-consumer-secret CONSUMER_SECRET", "OAuth 1.0 consumer secret") do |consumer_secret|
|
47
57
|
$oauth[:consumer_secret] = consumer_secret
|
48
58
|
end
|
49
|
-
opts.on("--oauth-signature-method SIGNATURE_METHOD") do |signature_method|
|
59
|
+
opts.on("--oauth-signature-method SIGNATURE_METHOD", "OAuth 1.0 signature method - defaults to HMAC-SHA1") do |signature_method|
|
50
60
|
$oauth[:signature_method] = signature_method
|
51
61
|
end
|
52
62
|
|
53
63
|
opts.on("--no-ssl-verify", "Disables SSL verification - use cautiously!") do
|
54
64
|
$options[:no_ssl_verify] = true
|
55
65
|
end
|
56
|
-
opts.on("-H", "--header HEADER",
|
66
|
+
opts.on("-H", "--header HEADER", %q(Set a header, e.g. -H "User-Agent: mycoolapp")) do |header|
|
57
67
|
if header =~ /\A([^:]+):\s*(.*)\z/m # this could be more strictly conformant to rfc, but whatever
|
58
68
|
field_name = $1
|
59
69
|
field_value = $2
|
@@ -66,126 +76,6 @@ end
|
|
66
76
|
opt_parser.parse!
|
67
77
|
abort(opt_parser.help) unless (2..3).include?(ARGV.size)
|
68
78
|
|
69
|
-
# OUTPUTTERS FOR FARADAY THAT SHOULD MOVE TO A LIB SOMEWHERE
|
70
|
-
|
71
|
-
# outputs the response body to the given output device (defaulting to STDOUT)
|
72
|
-
class FaradayOutputter < Faraday::Middleware
|
73
|
-
def initialize(app, outdev=STDOUT)
|
74
|
-
@app=app
|
75
|
-
@outdev = outdev
|
76
|
-
end
|
77
|
-
|
78
|
-
def call(request_env)
|
79
|
-
@app.call(request_env).on_complete do |response_env|
|
80
|
-
@outdev.puts(response_env[:body] || '')
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
|
86
|
-
# the real text written and read for request and response. whatever, close enough.
|
87
|
-
class FaradayCurlVOutputter < FaradayOutputter
|
88
|
-
|
89
|
-
# defines a method with the given name, applying coloring defined by any additional arguments.
|
90
|
-
# if $options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
|
91
|
-
def self.color(name, *color_args)
|
92
|
-
define_method(name) do |arg|
|
93
|
-
if color?
|
94
|
-
require 'term/ansicolor'
|
95
|
-
color_args.inject(arg) do |result, color_arg|
|
96
|
-
Term::ANSIColor.send(color_arg, result)
|
97
|
-
end
|
98
|
-
else
|
99
|
-
arg
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
color :info, :intense_yellow
|
105
|
-
color :info_body, :yellow
|
106
|
-
color :protocol
|
107
|
-
|
108
|
-
color :request, :intense_cyan
|
109
|
-
color :request_verb, :bold
|
110
|
-
color :request_header
|
111
|
-
color :request_blankline, :intense_cyan, :bold
|
112
|
-
|
113
|
-
color :response, :intense_green
|
114
|
-
color :response_status, :bold, :green
|
115
|
-
color :response_header
|
116
|
-
color :response_blankline, :intense_green, :bold
|
117
|
-
|
118
|
-
def call(request_env)
|
119
|
-
@outdev.puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
|
120
|
-
@outdev.puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
|
121
|
-
@outdev.puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].request_uri} #{protocol('HTTP/1.1' || 'or something - TODO')}"
|
122
|
-
request_env[:request_headers].each do |k, v|
|
123
|
-
@outdev.puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
|
124
|
-
end
|
125
|
-
@outdev.puts "#{request_blankline('>')} "
|
126
|
-
request_body = color_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
|
127
|
-
(request_body || '').split("\n", -1).each do |line|
|
128
|
-
@outdev.puts "#{request('>')} #{line}"
|
129
|
-
end
|
130
|
-
@app.call(request_env).on_complete do |response_env|
|
131
|
-
@outdev.puts "#{response('<')} #{protocol('HTTP/1.1' || 'or something - TODO')} #{response_status(response_env[:status].to_s)}"
|
132
|
-
request_env[:response_headers].each do |k, v|
|
133
|
-
@outdev.puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
|
134
|
-
end
|
135
|
-
@outdev.puts "#{response_blankline ('<')} "
|
136
|
-
response_body = color_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
|
137
|
-
(response_body || '').split("\n", -1).each do |line|
|
138
|
-
@outdev.puts "#{response('<')} #{line}"
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
# whether to use color
|
144
|
-
def color?
|
145
|
-
$options[:color].nil? ? @outdev.tty? : $options[:color]
|
146
|
-
end
|
147
|
-
|
148
|
-
# a mapping for each registered CodeRay scanner to the Media Types which represent
|
149
|
-
# that language. extremely incomplete!
|
150
|
-
CodeRayForMediaTypes = {
|
151
|
-
:c => [],
|
152
|
-
:cpp => [],
|
153
|
-
:clojure => [],
|
154
|
-
:css => ['text/css', 'application/css-stylesheet'],
|
155
|
-
:delphi => [],
|
156
|
-
:diff => [],
|
157
|
-
:erb => [],
|
158
|
-
:groovy => [],
|
159
|
-
:haml => [],
|
160
|
-
:html => ['text/html'],
|
161
|
-
:java => [],
|
162
|
-
:java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
|
163
|
-
:json => ['application/json'],
|
164
|
-
:php => [],
|
165
|
-
:python => ['text/x-python'],
|
166
|
-
:ruby => [],
|
167
|
-
:sql => [],
|
168
|
-
:xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
|
169
|
-
:yaml => [],
|
170
|
-
}
|
171
|
-
|
172
|
-
# takes a body and a content type; returns the body, with coloring (ansi colors for terminals)
|
173
|
-
# possibly added, if it's a recognized content type and #color? is true
|
174
|
-
def color_body_by_content_type(body, content_type)
|
175
|
-
if body && color?
|
176
|
-
# kinda hacky way to get the media_type. faraday should supply this ...
|
177
|
-
require 'rack'
|
178
|
-
media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
|
179
|
-
coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
|
180
|
-
if coderay_scanner
|
181
|
-
require 'coderay'
|
182
|
-
body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
body
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
79
|
# CONFIGURE THE FARADAY CONNECTION
|
190
80
|
faraday_options = {}
|
191
81
|
if $options[:no_ssl_verify]
|
@@ -198,7 +88,7 @@ connection = Faraday.new(faraday_options) do |builder|
|
|
198
88
|
OAuthenticator::FaradaySigner
|
199
89
|
builder.use OAuthenticator::FaradaySigner, $oauth
|
200
90
|
end
|
201
|
-
builder.use
|
91
|
+
builder.use($options[:verbose] ? FaradayCurlVOutputter : FaradayOutputter, $options)
|
202
92
|
builder.adapter Faraday.default_adapter
|
203
93
|
end
|
204
94
|
|
@@ -215,6 +105,10 @@ if body && !headers['Content-Type'.downcase]
|
|
215
105
|
headers['Content-Type'.downcase] = 'application/json'
|
216
106
|
end
|
217
107
|
|
108
|
+
unless headers.keys.any? { |key| key.downcase == 'user-agent' }
|
109
|
+
headers['User-Agent'] = "ApiHammer #{ApiHammer::VERSION} hc"
|
110
|
+
end
|
111
|
+
|
218
112
|
# OH LOOK IT'S FINALLY ACTUALLY CONNECTING TO SOMETHING
|
219
113
|
|
220
114
|
response = connection.run_request(httpmethod.downcase.to_sym, url, body, headers)
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
# outputs the response body to the given output device (defaulting to STDOUT)
|
5
|
+
class FaradayOutputter < Faraday::Middleware
|
6
|
+
def initialize(app, options={})
|
7
|
+
@app=app
|
8
|
+
@options = options
|
9
|
+
@outdev = @options[:outdev] || STDOUT
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(request_env)
|
13
|
+
@app.call(request_env).on_complete do |response_env|
|
14
|
+
@outdev.puts(response_env[:body] || '')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
|
20
|
+
# the real text written and read for request and response. whatever, close enough.
|
21
|
+
class FaradayCurlVOutputter < FaradayOutputter
|
22
|
+
|
23
|
+
# defines a method with the given name, applying coloring defined by any additional arguments.
|
24
|
+
# if @options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
|
25
|
+
def self.color(name, *color_args)
|
26
|
+
define_method(name) do |arg|
|
27
|
+
if color?
|
28
|
+
require 'term/ansicolor'
|
29
|
+
color_args.inject(arg) do |result, color_arg|
|
30
|
+
Term::ANSIColor.send(color_arg, result)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
arg
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
color :info, :intense_yellow
|
39
|
+
color :info_body, :yellow
|
40
|
+
color :protocol
|
41
|
+
|
42
|
+
color :request, :intense_cyan
|
43
|
+
color :request_verb, :bold
|
44
|
+
color :request_header
|
45
|
+
color :request_blankline, :intense_cyan, :bold
|
46
|
+
|
47
|
+
color :response, :intense_green
|
48
|
+
color :response_status, :bold, :green
|
49
|
+
color :response_header
|
50
|
+
color :response_blankline, :intense_green, :bold
|
51
|
+
|
52
|
+
def call(request_env)
|
53
|
+
@outdev.puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
|
54
|
+
@outdev.puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
|
55
|
+
@outdev.puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].request_uri} #{protocol("HTTP/#{Net::HTTP::HTTPVersion}")}"
|
56
|
+
request_env[:request_headers].each do |k, v|
|
57
|
+
@outdev.puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
|
58
|
+
end
|
59
|
+
@outdev.puts "#{request_blankline('>')} "
|
60
|
+
request_body = alter_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
|
61
|
+
(request_body || '').split("\n", -1).each do |line|
|
62
|
+
@outdev.puts "#{request('>')} #{line}"
|
63
|
+
end
|
64
|
+
@app.call(request_env).on_complete do |response_env|
|
65
|
+
@outdev.puts "#{response('<')} #{protocol('HTTP/1.1' || 'or something - TODO')} #{response_status(response_env[:status].to_s)}"
|
66
|
+
request_env[:response_headers].each do |k, v|
|
67
|
+
@outdev.puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
|
68
|
+
end
|
69
|
+
@outdev.puts "#{response_blankline ('<')} "
|
70
|
+
response_body = alter_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
|
71
|
+
(response_body || '').split("\n", -1).each do |line|
|
72
|
+
@outdev.puts "#{response('<')} #{line}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def pretty?
|
78
|
+
@options[:pretty].nil? ? true : @options[:pretty]
|
79
|
+
end
|
80
|
+
|
81
|
+
# whether to use color
|
82
|
+
def color?
|
83
|
+
@options[:color].nil? ? @outdev.tty? : @options[:color]
|
84
|
+
end
|
85
|
+
|
86
|
+
# a mapping for each registered CodeRay scanner to the Media Types which represent
|
87
|
+
# that language. extremely incomplete!
|
88
|
+
CodeRayForMediaTypes = {
|
89
|
+
:c => [],
|
90
|
+
:cpp => [],
|
91
|
+
:clojure => [],
|
92
|
+
:css => ['text/css', 'application/css-stylesheet'],
|
93
|
+
:delphi => [],
|
94
|
+
:diff => [],
|
95
|
+
:erb => [],
|
96
|
+
:groovy => [],
|
97
|
+
:haml => [],
|
98
|
+
:html => ['text/html'],
|
99
|
+
:java => [],
|
100
|
+
:java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
|
101
|
+
:json => ['application/json'],
|
102
|
+
:php => [],
|
103
|
+
:python => ['text/x-python'],
|
104
|
+
:ruby => [],
|
105
|
+
:sql => [],
|
106
|
+
:xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
|
107
|
+
:yaml => [],
|
108
|
+
}
|
109
|
+
|
110
|
+
# takes a body and a content type; returns the body, altered according to options.
|
111
|
+
#
|
112
|
+
# - with coloring (ansi colors for terminals) possibly added, if it's a recognized content type and
|
113
|
+
# #color? is true
|
114
|
+
# - formatted prettily if #pretty? is true
|
115
|
+
def alter_body_by_content_type(body, content_type)
|
116
|
+
return body unless body.is_a?(String)
|
117
|
+
media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
|
118
|
+
if pretty?
|
119
|
+
case media_type
|
120
|
+
when 'application/json'
|
121
|
+
require 'json'
|
122
|
+
begin
|
123
|
+
body = JSON.pretty_generate(JSON.parse(body))
|
124
|
+
rescue JSON::ParserError
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
if color?
|
129
|
+
coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
|
130
|
+
if coderay_scanner
|
131
|
+
require 'coderay'
|
132
|
+
body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
body
|
136
|
+
end
|
137
|
+
end
|
data/lib/api_hammer/rails.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'api_hammer'
|
2
|
+
require 'active_support/log_subscriber'
|
3
|
+
|
4
|
+
require 'rails/rack/log_tailer'
|
5
|
+
# fix up this class to tail the log when the body is closed rather than when its own #call is done.
|
6
|
+
module Rails
|
7
|
+
module Rack
|
8
|
+
class LogTailer
|
9
|
+
def call(env)
|
10
|
+
status, headers, body = @app.call(env)
|
11
|
+
body_proxy = ::Rack::BodyProxy.new(body) { tail! }
|
12
|
+
[status, headers, body_proxy]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ApiHammer
|
19
|
+
class RequestLogSubscriber < ActiveSupport::LogSubscriber
|
20
|
+
def process_action(event)
|
21
|
+
if event.payload[:request_env]
|
22
|
+
info = (event.payload[:request_env]['request_logger.info'] ||= {})
|
23
|
+
else
|
24
|
+
# if an exception occurs in the action, append_info_to_payload isn't called and
|
25
|
+
# event.payload[:request_env] doesn't get set. fall back to use Thread.current.
|
26
|
+
info = (Thread.current['request_logger.info'] ||= {})
|
27
|
+
end
|
28
|
+
info.update(event.payload.slice(:controller, :action, :exception, :format, :formats, :view_runtime, :db_runtime))
|
29
|
+
info.update(:transaction_id => event.transaction_id)
|
30
|
+
info.update(event.payload['request_logger.info']) if event.payload['request_logger.info']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module AddRequestToPayload
|
36
|
+
def append_info_to_payload(payload)
|
37
|
+
super
|
38
|
+
payload[:request_env] = request.env
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module ApiHammer
|
43
|
+
class RailsRequestLogging < ::Rails::Railtie
|
44
|
+
initializer :api_hammer_request_logging do |app|
|
45
|
+
# use the bits we want from Lograge.setup, disabling existing active* log things.
|
46
|
+
# but don't actually enable lograge.
|
47
|
+
require 'lograge'
|
48
|
+
require 'lograge/rails_ext/rack/logger'
|
49
|
+
app.config.action_dispatch.rack_cache[:verbose] = false if app.config.action_dispatch.rack_cache
|
50
|
+
Lograge.remove_existing_log_subscriptions
|
51
|
+
|
52
|
+
ApiHammer::RequestLogSubscriber.attach_to :action_controller
|
53
|
+
|
54
|
+
app.config.middleware.insert_after(::Rails::Rack::Logger, ApiHammer::RequestLogger, ::Rails.logger)
|
55
|
+
|
56
|
+
ActionController::Base.send(:include, AddRequestToPayload)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -23,10 +23,13 @@ module ApiHammer
|
|
23
23
|
@request_body = env["rack.input"].read
|
24
24
|
env["rack.input"].rewind
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
log_tags = Thread.current[:activesupport_tagged_logging_tags]
|
27
|
+
@log_tags = log_tags.dup if log_tags
|
28
|
+
|
29
|
+
status, headers, body = @app.call(env)
|
30
|
+
headers = ::Rack::Utils::HeaderHash.new(headers)
|
31
|
+
body_proxy = ::Rack::BodyProxy.new(body) { log(env, status, headers, began_at, body) }
|
32
|
+
[status, headers, body_proxy]
|
30
33
|
end
|
31
34
|
|
32
35
|
def log(env, status, headers, began_at, body)
|
@@ -53,7 +56,6 @@ module ApiHammer
|
|
53
56
|
:white
|
54
57
|
end
|
55
58
|
status_s = bold(send(status_color, status.to_s))
|
56
|
-
@logger.info "#{status_s} : #{bold(intense_cyan(request.request_method))} #{intense_cyan(request_uri.normalize)}"
|
57
59
|
data = {
|
58
60
|
'request' => {
|
59
61
|
'method' => request.request_method,
|
@@ -63,6 +65,12 @@ module ApiHammer
|
|
63
65
|
'remote_addr' => env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"],
|
64
66
|
'User-Agent' => request.user_agent,
|
65
67
|
'body' => @request_body,
|
68
|
+
# these come from the OAuthenticator gem/middleware
|
69
|
+
'oauth.authenticated' => env['oauth.authenticated'],
|
70
|
+
'oauth.consumer_key' => env['oauth.consumer_key'],
|
71
|
+
'oauth.token' => env['oauth.token'],
|
72
|
+
# airbrake
|
73
|
+
'airbrake.error_id' => env['airbrake.error_id'],
|
66
74
|
}.reject{|k,v| v.nil? },
|
67
75
|
'response' => {
|
68
76
|
'status' => status,
|
@@ -70,12 +78,24 @@ module ApiHammer
|
|
70
78
|
'Location' => response.location,
|
71
79
|
'Content-Type' => response.content_type,
|
72
80
|
}.reject{|k,v| v.nil? },
|
73
|
-
'
|
74
|
-
|
81
|
+
'processing' => {
|
82
|
+
'began_at' => began_at.utc.to_i,
|
83
|
+
'duration' => now - began_at,
|
84
|
+
'activesupport_tagged_logging_tags' => @log_tags,
|
85
|
+
}.merge(env['request_logger.info'] || {}).merge(Thread.current['request_logger.info'] || {}).reject{|k,v| v.nil? },
|
75
86
|
}
|
87
|
+
Thread.current['request_logger.info'] = nil
|
76
88
|
json_data = JSON.dump(data)
|
77
|
-
|
78
|
-
|
89
|
+
dolog = proc do
|
90
|
+
@logger.info "#{status_s} : #{bold(intense_cyan(request.request_method))} #{intense_cyan(request_uri.normalize)}"
|
91
|
+
@logger.info json_data
|
92
|
+
end
|
93
|
+
# do the logging with tags that applied to the request if appropriate
|
94
|
+
if @logger.respond_to?(:tagged) && @log_tags
|
95
|
+
@logger.tagged(@log_tags, &dolog)
|
96
|
+
else
|
97
|
+
dolog.call
|
98
|
+
end
|
79
99
|
end
|
80
100
|
end
|
81
101
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ApiHammer::Rails
|
2
|
+
# request parameters (not query parameters) without the nil/empty array munging that rails does
|
3
|
+
def unmunged_request_params
|
4
|
+
# Thread.exclusive is not optimal but we need to ensure that any other params parsing occurring in other
|
5
|
+
# threads is not affected by disabling munging
|
6
|
+
#
|
7
|
+
# TODO when we are on a rails which has ActionDispatch::Request::Utils.perform_deep_munge, use that instead
|
8
|
+
# of clobbering methods
|
9
|
+
@unmunged_params ||= Thread.exclusive do
|
10
|
+
unless ActionDispatch::Request.method_defined?(:real_deep_munge)
|
11
|
+
ActionDispatch::Request.send(:alias_method, :real_deep_munge, :deep_munge)
|
12
|
+
end
|
13
|
+
ActionDispatch::Request.send(:define_method, :deep_munge) { |hash| hash }
|
14
|
+
begin
|
15
|
+
unmunged_params = nil
|
16
|
+
newenv = request.env.merge('action_dispatch.request.request_parameters' => nil)
|
17
|
+
ActionDispatch::ParamsParser.new(proc do |env|
|
18
|
+
unmunged_params = env['action_dispatch.request.request_parameters']
|
19
|
+
end).call(newenv)
|
20
|
+
unmunged_params || ActionDispatch::Request.new(newenv).request_parameters
|
21
|
+
ensure
|
22
|
+
ActionDispatch::Request.send(:alias_method, :deep_munge, :real_deep_munge)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/api_hammer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_hammer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ethan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-05-
|
11
|
+
date: 2014-05-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: term-ansicolor
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,15 +182,18 @@ files:
|
|
168
182
|
- bin/hc
|
169
183
|
- lib/api_hammer.rb
|
170
184
|
- lib/api_hammer/check_required_params.rb
|
185
|
+
- lib/api_hammer/faraday/outputter.rb
|
171
186
|
- lib/api_hammer/halt.rb
|
172
187
|
- lib/api_hammer/public_instance_exec.rb
|
173
188
|
- lib/api_hammer/rails.rb
|
189
|
+
- lib/api_hammer/rails_request_logging.rb
|
174
190
|
- lib/api_hammer/request_logger.rb
|
175
191
|
- lib/api_hammer/show_text_exceptions.rb
|
176
192
|
- lib/api_hammer/tasks.rb
|
177
193
|
- lib/api_hammer/tasks/cucumber_pretty.rb
|
178
194
|
- lib/api_hammer/tasks/gem_available_updates.rb
|
179
195
|
- lib/api_hammer/trailing_newline.rb
|
196
|
+
- lib/api_hammer/unmunged_request_params.rb
|
180
197
|
- lib/api_hammer/version.rb
|
181
198
|
- lib/api_hammer/weblink.rb
|
182
199
|
- test/check_required_params_test.rb
|