rack-request_police 0.0.4alpha → 0.1.0alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b19dd3ef21082440b913381f62d63a583824518
4
- data.tar.gz: 6fe2b2f75c81e661b70b87fb00bd9a27196e1892
3
+ metadata.gz: 032ca4a54509f306b4699d1461798949e6364efe
4
+ data.tar.gz: 6ce6034762b55d4fd09fafefa83b6c13a367b6a1
5
5
  SHA512:
6
- metadata.gz: 033dd061cbb11b0f89575066515d53401dc8b52e999414f1fd1e8b202f37b0ad5ff91033f5ea993a1873dd41ae7eb08bf0119ef0efeeab878c6ea4aee4f67ba0
7
- data.tar.gz: 6d9d3df6d4e7823642340d830ad157278fd08280cafb9fd285365e9e6433766534f800877663c8aa9620e222083bb93d44190ef328edf0624f921b65ffe47b5d
6
+ metadata.gz: 160d0fde3ac9edc4f6a470e1e7e742a58d544a4f7a510df86962cf7851d6c44529b700a9bd679651102c7c6762ff1ac7261adf268dfa12309d3e52d3c0d70da0
7
+ data.tar.gz: 82abba729dfbd1ecfd73cef00e5bfd5a28467908dbd409e0d1ab2b706c1ee2fa548a6cce151ead42b88e28a1d803f41f660cac9c5146a8bf1f167b24c61212a0
data/.gitignore CHANGED
@@ -13,3 +13,4 @@
13
13
  *.a
14
14
  mkmf.log
15
15
  .ruby-version
16
+ .ruby-gemset
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ ## 0.1.0alpha
2
+
3
+ **Backward incompatible changes**
4
+
5
+ - store logged headers under `headers` key (see `Storage::Unit`)
6
+ - don't log not-existing headers unless _truthy_ fallback value is provided
7
+ - display headers in web interface
8
+
9
+ ## 0.0.5alpha
10
+
11
+ - log specified headers feature (backend) #3 (@michaldarda)
12
+
13
+ ## 0.0.x
14
+
15
+ - initial drafts
data/README.md CHANGED
@@ -12,6 +12,7 @@ Features:
12
12
 
13
13
  - filter requests by method (get/post/patch/delete) and/or regular expression
14
14
  - log requests into storage of your choice (at the moment redis supported)
15
+ - choose what headers you want to store and transform them if you want to
15
16
 
16
17
  Work in progress.
17
18
 
@@ -60,6 +61,33 @@ Rack::RequestPolice.configure do |config|
60
61
  # array of methods that should be logged (all four by default)
61
62
  # requests not included in this list will be ignored
62
63
  config.method = [:get, :post, :delete, :patch]
64
+
65
+ # array of headers that you want to store for each request (empty by default)
66
+ config.headers = [
67
+ "HTTP_HEADER_NAME",
68
+ "HTTP_ANOTHER_HEADER_NAME"
69
+ ]
70
+
71
+ # each item in headers array can be also defined using helper method config.header
72
+ #
73
+ # @param original_header_name is required and it is a name of original header
74
+ #
75
+ # options:
76
+ # storage_name - name of under which http header will be stored, default - original_header_name
77
+ # fallback_value - truthy value of header that will be logged if header is missing
78
+ # (missing headers are not logged by default)
79
+ #
80
+ # @block - optional transformations of the header before it is stored
81
+ #
82
+ # Example:
83
+ # config.headers = [
84
+ # config.header("HTTP_HEADER_NAME",
85
+ # storage_name: 'HEADER_NAME',
86
+ # fallback_value: 'HEADER_IS_MISSING'
87
+ # ) { |header| header.downcase },
88
+ # config.header("HTTP_ANOTHER_HEADER_NAME"),
89
+ # config.header("HTTP_ANOTHER_FANCY_HEADER", storage_name: 'fancy_header')
90
+ # ]
63
91
  end
64
92
  ```
65
93
 
@@ -25,6 +25,10 @@ module Rack
25
25
  if %w(POST PATCH DELETE).include?(env['REQUEST_METHOD'])
26
26
  request_params.merge!('data' => utf8_input(env))
27
27
  end
28
+
29
+ headers = request_headers(env)
30
+ request_params.merge!('headers' => headers) unless headers.empty?
31
+
28
32
  ::Rack::RequestPolice.storage.log_request(request_params)
29
33
  end
30
34
  end
@@ -34,6 +38,22 @@ module Rack
34
38
 
35
39
  private
36
40
 
41
+ def request_headers(env)
42
+ Rack::RequestPolice.headers.each_with_object({}) do |header_hash, result|
43
+ header_name = header_hash.fetch(:original_header_name)
44
+ transformation = header_hash.fetch(:transformation)
45
+
46
+ fallback_value = header_hash.fetch(:fallback_value)
47
+ storage_name = header_hash.fetch(:storage_name)
48
+
49
+ header_value = env[header_name]
50
+ header_value = transformation.call(header_value) if header_value
51
+ header_value ||= fallback_value
52
+
53
+ result[storage_name] = header_value if header_value
54
+ end
55
+ end
56
+
37
57
  def utf8_input(env)
38
58
  env['rack.input'].read
39
59
  .force_encoding('utf-8')
@@ -26,7 +26,7 @@ module Rack
26
26
  total_size = redis.llen(REDIS_KEY)
27
27
  items = redis.lrange(REDIS_KEY, starting, ending).map do |json|
28
28
  hash = parser.load(json)
29
- Unit.new(hash['method'], hash['ip'], hash['url'], Time.at(hash['time']), hash['data'])
29
+ Unit.new(hash['method'], hash['ip'], hash['url'], Time.at(hash['time']), hash['data'], hash['headers'])
30
30
  end
31
31
 
32
32
  [current_page, total_size, items]
@@ -1,7 +1,7 @@
1
1
  module Rack
2
2
  module RequestPolice
3
3
  module Storage
4
- class Unit < Struct.new(:method, :ip, :url, :time, :data)
4
+ class Unit < Struct.new(:method, :ip, :url, :time, :data, :headers)
5
5
  end
6
6
  end
7
7
  end
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  module RequestPolice
3
- VERSION = "0.0.4alpha"
3
+ VERSION = "0.1.0alpha"
4
4
  end
5
5
  end
@@ -34,7 +34,7 @@ module Rack
34
34
  ::Rack::Utils.escape_html(text)
35
35
  end
36
36
 
37
- def parse_post_data(post_data)
37
+ def pretty_parse(post_data)
38
38
  JSON.pretty_generate(JSON.parse(post_data))
39
39
  rescue JSON::ParserError
40
40
  post_data
@@ -12,6 +12,7 @@ module Rack
12
12
  @@storage = nil
13
13
  @@method = [:get, :post, :delete, :patch]
14
14
  @@regex = nil
15
+ @@headers = []
15
16
 
16
17
  def self.configure
17
18
  yield self
@@ -25,6 +26,34 @@ module Rack
25
26
  @@storage || fail(NoStorageFound)
26
27
  end
27
28
 
29
+ def self.headers
30
+ @@headers
31
+ end
32
+
33
+ def self.headers=(array)
34
+ @@headers = array.map do |h|
35
+ # because headers might be simple strings
36
+ # or a hash we need to normalize them
37
+ # and store all as hashes
38
+ if h.is_a?(String)
39
+ header(h)
40
+ else
41
+ h
42
+ end
43
+ end
44
+ end
45
+
46
+ def self.header(original_header_name, options = {}, &transformation)
47
+ {
48
+ original_header_name: original_header_name,
49
+ storage_name: options.fetch(:storage_name) { original_header_name },
50
+ fallback_value: options.fetch(:fallback_value) { nil },
51
+ # if user provided no transformation,
52
+ # simply return original header value
53
+ transformation: transformation || ->(h){ h }
54
+ }
55
+ end
56
+
28
57
  def self.method
29
58
  @@method
30
59
  end
data/spec/bench.rb CHANGED
@@ -76,6 +76,43 @@ describe "Simple benchmark", type: :request do
76
76
  end
77
77
  end
78
78
 
79
+ context "with middleware (customized headers, redis storage)" do
80
+ let(:headers) {
81
+ {
82
+ "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest",
83
+ "HTTP_X_FORWARDED_FOR" => "129.78.138.66, 129.78.64.103",
84
+ "HTTP_X_CSRF_TOKEN" => "i8XNjC4b8KVok4uw5RftR38Wgp2BFwql",
85
+ "HTTP_AUTHORIZATION" => "Bearer Ym9zY236Ym9zY28="
86
+ }
87
+ }
88
+
89
+ after { REDIS.flushdb }
90
+ before do
91
+ REDIS.flushdb
92
+ Rack::RequestPolice.configure do |c|
93
+ c.storage = Rack::RequestPolice::Storage::Redis.new(REDIS_OPTIONS)
94
+ c.regex = /.*/
95
+ c.method = [:get, :post, :delete]
96
+ c.headers = [
97
+ "HTTP_X_REQUESTED_WITH",
98
+ "HTTP_X_CSRF_TOKEN",
99
+ c.header("HTTP_X_FORWARDER_FOR", storage_name: "x-forwarded-for") { |h| h.split(",").first },
100
+ c.header("HTTP_AUTHORIZATION", storage_name: 'authorization') { |h| h.gsub("Bearer ", "") }
101
+ ]
102
+ end
103
+ end
104
+
105
+ it "benchmarks it" do
106
+ puts "with middleware (customized headers, redis storage)"
107
+ Benchmark.bm(7) do |x|
108
+ x.report("get") { repeat.times { get '/', {}, headers } }
109
+ x.report("post") { repeat.times { post '/', {}, headers } }
110
+ x.report("delete") { repeat.times { delete '/', {}, headers } }
111
+ x.report("patch") { repeat.times { patch '/', {}, headers } }
112
+ end
113
+ end
114
+ end
115
+
79
116
  context "with middleware (customized, redis storage, OJ json parser)" do
80
117
  after { REDIS.flushdb }
81
118
  before do
@@ -8,13 +8,16 @@ describe "My Middleware", type: :request do
8
8
  c.storage = DummyStorage.new
9
9
  c.regex = nil
10
10
  c.method = [:get, :post, :delete, :patch]
11
+ c.headers = []
11
12
  end
12
13
  end
13
- after { Timecop.return }
14
+
15
+ after { Timecop.return }
14
16
 
15
17
  context "logging all requests" do
16
18
  let(:app){
17
19
  Sinatra.new do
20
+ set :show_exceptions, false
18
21
  use(Rack::RequestPolice::Middleware)
19
22
  get '/' do
20
23
  end
@@ -52,12 +55,15 @@ describe "My Middleware", type: :request do
52
55
  context "logging only POST requests" do
53
56
  before do
54
57
  Rack::RequestPolice.configure do |c|
58
+ c.regex = nil
59
+ c.headers = []
55
60
  c.method = [:post]
56
61
  end
57
62
  end
58
63
 
59
64
  let(:app){
60
65
  Sinatra.new do
66
+ set :show_exceptions, false
61
67
  use(Rack::RequestPolice::Middleware)
62
68
  get '/' do
63
69
  end
@@ -85,12 +91,15 @@ describe "My Middleware", type: :request do
85
91
  context "logging PATCH requests" do
86
92
  before do
87
93
  Rack::RequestPolice.configure do |c|
94
+ c.regex = nil
95
+ c.headers = []
88
96
  c.method = [:patch]
89
97
  end
90
98
  end
91
99
 
92
100
  let(:app){
93
101
  Sinatra.new do
102
+ set :show_exceptions, false
94
103
  use(Rack::RequestPolice::Middleware)
95
104
  patch '/update' do
96
105
  end
@@ -110,12 +119,15 @@ describe "My Middleware", type: :request do
110
119
  context "logging DELETE requests" do
111
120
  before do
112
121
  Rack::RequestPolice.configure do |c|
122
+ c.regex = nil
123
+ c.headers = []
113
124
  c.method = [:delete]
114
125
  end
115
126
  end
116
127
 
117
128
  let(:app){
118
129
  Sinatra.new do
130
+ set :show_exceptions, false
119
131
  use(Rack::RequestPolice::Middleware)
120
132
  delete '/destroy' do
121
133
  end
@@ -136,11 +148,13 @@ describe "My Middleware", type: :request do
136
148
  before do
137
149
  Rack::RequestPolice.configure do |c|
138
150
  c.regex = /user/
151
+ c.headers = []
139
152
  end
140
153
  end
141
154
 
142
155
  let(:app){
143
156
  Sinatra.new do
157
+ set :show_exceptions, false
144
158
  use(Rack::RequestPolice::Middleware)
145
159
  get '/user' do
146
160
  end
@@ -169,11 +183,13 @@ describe "My Middleware", type: :request do
169
183
  before do
170
184
  Rack::RequestPolice.configure do |c|
171
185
  c.regex = /user\?id=1/
186
+ c.headers = []
172
187
  end
173
188
  end
174
189
 
175
190
  let(:app){
176
191
  Sinatra.new do
192
+ set :show_exceptions, false
177
193
  use(Rack::RequestPolice::Middleware)
178
194
  get '/user' do
179
195
  end
@@ -205,6 +221,7 @@ describe "My Middleware", type: :request do
205
221
 
206
222
  let(:app){
207
223
  Sinatra.new do
224
+ set :show_exceptions, false
208
225
  use(Rack::RequestPolice::Middleware)
209
226
  end
210
227
  }
@@ -213,4 +230,214 @@ describe "My Middleware", type: :request do
213
230
  expect { get '/' }.to raise_error(Rack::RequestPolice::NoStorageFound)
214
231
  end
215
232
  end
233
+
234
+ context "logging request with custom headers" do
235
+ let(:app){
236
+ Sinatra.new do
237
+ set :show_exceptions, false
238
+ use(Rack::RequestPolice::Middleware)
239
+ get '/user' do
240
+ end
241
+ end
242
+ }
243
+
244
+ context 'request contains requested headers' do
245
+ before do
246
+ Rack::RequestPolice.configure do |c|
247
+ c.storage = DummyStorage.new
248
+ c.regex = nil
249
+
250
+ c.headers = [
251
+ "HTTP_MY_HEADER"
252
+ ]
253
+ end
254
+ end
255
+
256
+ it "logs header as it is" do
257
+ expect(Rack::RequestPolice.storage).to receive(:log_request)
258
+ .with({
259
+ "url" => "http://example.org/user",
260
+ "ip" => "127.0.0.1",
261
+ "method" => "get",
262
+ "time" => Time.now.to_i,
263
+ "headers" => { "HTTP_MY_HEADER" => "MY%HEADER%VALUE" }
264
+ })
265
+
266
+ get '/user', {}, { 'HTTP_MY_HEADER' => 'MY%HEADER%VALUE' }
267
+
268
+ expect(last_response.status).to eq 200
269
+ end
270
+ end
271
+
272
+ context 'request contains requested headers + custom storage name + transformation' do
273
+ before do
274
+ Rack::RequestPolice.configure do |c|
275
+ c.storage = DummyStorage.new
276
+ c.regex = nil
277
+
278
+ c.headers = [
279
+ c.header("HTTP_MY_HEADER", storage_name: 'my_header') { |h| h.downcase.gsub('%', '_')}
280
+ ]
281
+ end
282
+ end
283
+
284
+ it "logs header as custom name and transforms it" do
285
+ expect(Rack::RequestPolice.storage).to receive(:log_request)
286
+ .with({
287
+ "url" => "http://example.org/user",
288
+ "ip" => "127.0.0.1",
289
+ "method" => "get",
290
+ "time" => Time.now.to_i,
291
+ "headers" => { "my_header" => "my_header_value" }
292
+ })
293
+
294
+ get '/user', {}, { 'HTTP_MY_HEADER' => 'MY%HEADER%VALUE' }
295
+
296
+ expect(last_response.status).to eq 200
297
+ end
298
+ end
299
+
300
+ context "headers do not exists in request but fallback value is provided" do
301
+ before do
302
+ Rack::RequestPolice.configure do |c|
303
+ c.storage = DummyStorage.new
304
+ c.regex = nil
305
+
306
+ c.headers = [
307
+ c.header("HTTP_MY_HEADER",
308
+ storage_name: 'my_header',
309
+ fallback_value: 'HEADER_MISSING') \
310
+ { |h| h.downcase.gsub('%', '_') }
311
+ ]
312
+ end
313
+ end
314
+
315
+ it "logs headers" do
316
+ expect(Rack::RequestPolice.storage).to receive(:log_request)
317
+ .with({
318
+ "url" => "http://example.org/user",
319
+ "ip" => "127.0.0.1",
320
+ "method" => "get",
321
+ "time" => Time.now.to_i,
322
+ "headers" => { 'my_header' => 'HEADER_MISSING' }
323
+ })
324
+
325
+ get '/user', {}, {}
326
+
327
+ expect(last_response.status).to eq 200
328
+ end
329
+ end
330
+
331
+ context 'header do not exsits in request and transformation is provided' do
332
+ before do
333
+ Rack::RequestPolice.configure do |c|
334
+ c.storage = DummyStorage.new
335
+ c.regex = nil
336
+
337
+ c.headers = [
338
+ c.header("HTTP_MY_HEADER", storage_name: 'my_header') { |h| h.downcase.gsub('%', '_')}
339
+ ]
340
+ end
341
+ end
342
+
343
+ it "ignores not existing header" do
344
+ expect(Rack::RequestPolice.storage).to receive(:log_request)
345
+ .with({
346
+ "url" => "http://example.org/user",
347
+ "ip" => "127.0.0.1",
348
+ "method" => "get",
349
+ "time" => Time.now.to_i
350
+ })
351
+
352
+ get '/user', {}
353
+
354
+ expect(last_response.status).to eq 200
355
+ end
356
+ end
357
+
358
+ context 'header do not exists in request, transformation and fallback value are provided' do
359
+ before do
360
+ Rack::RequestPolice.configure do |c|
361
+ c.storage = DummyStorage.new
362
+ c.regex = nil
363
+
364
+ c.headers = [
365
+ c.header("HTTP_MY_HEADER", storage_name: 'my_header', fallback_value: 'not found') { |h| h.downcase.gsub('%', '_')}
366
+ ]
367
+ end
368
+ end
369
+
370
+ it "logs header as custom name and falls-back to default value" do
371
+ expect(Rack::RequestPolice.storage).to receive(:log_request)
372
+ .with({
373
+ "url" => "http://example.org/user",
374
+ "ip" => "127.0.0.1",
375
+ "method" => "get",
376
+ "time" => Time.now.to_i,
377
+ "headers" => { "my_header" => 'not found' }
378
+ })
379
+
380
+ get '/user', {}
381
+
382
+ expect(last_response.status).to eq 200
383
+ end
384
+ end
385
+
386
+ context 'headers do not exists in request, no transformation is provided' do
387
+ before do
388
+ Rack::RequestPolice.configure do |c|
389
+ c.storage = DummyStorage.new
390
+ c.regex = nil
391
+
392
+ c.headers = [
393
+ c.header("HTTP_MY_HEADER")
394
+ ]
395
+ end
396
+ end
397
+
398
+ it "ignores not existing header" do
399
+ expect(Rack::RequestPolice.storage).to receive(:log_request)
400
+ .with({
401
+ "url" => "http://example.org/user",
402
+ "ip" => "127.0.0.1",
403
+ "method" => "get",
404
+ "time" => Time.now.to_i
405
+ })
406
+
407
+ get '/user', {}
408
+
409
+ expect(last_response.status).to eq 200
410
+ end
411
+ end
412
+
413
+ context 'logging multiple headers' do
414
+ before do
415
+ Rack::RequestPolice.configure do |c|
416
+ c.storage = DummyStorage.new
417
+ c.regex = nil
418
+
419
+ c.headers = [
420
+ 'HTTP_HEADER_1',
421
+ 'HTTP_HEADER_2',
422
+ 'HTTP_HEADER_3'
423
+ ]
424
+ end
425
+ end
426
+
427
+ it 'logs all declared headers' do
428
+ expect(Rack::RequestPolice.storage).to receive(:log_request)
429
+ .with({
430
+ "url" => "http://example.org/user",
431
+ "ip" => "127.0.0.1",
432
+ "method" => "get",
433
+ "time" => Time.now.to_i,
434
+ "headers" => { 'HTTP_HEADER_1' => "one", 'HTTP_HEADER_3' => 'two' }
435
+ })
436
+
437
+ get '/user', {}, { 'HTTP_HEADER_1' => 'one', 'HTTP_HEADER_3' => 'two' }
438
+
439
+ expect(last_response.status).to eq 200
440
+ end
441
+ end
442
+ end
216
443
  end
data/web/views/index.erb CHANGED
@@ -74,9 +74,23 @@
74
74
  <a href='#' data-details="details_<%= idx %>">toggle details</a></td>
75
75
  <tr class="details" id="details_<%= idx %>">
76
76
  <td colspan="5">
77
- <pre>
78
- <%= escape(parse_post_data(log.data)) %>
79
- </pre>
77
+ <% if log.data %>
78
+ <h4>Data:</h4>
79
+ <pre>
80
+ <%= escape(pretty_parse(log.data)) %>
81
+ </pre>
82
+ <% end %>
83
+
84
+ <% if log.headers %>
85
+ <h4>Headers:</h4>
86
+ <pre>
87
+ <%= escape(pretty_parse(log.headers)) %>
88
+ </pre>
89
+ <% end %>
90
+
91
+ <% if !log.data && !log.headers %>
92
+ <h4>No data / headers logged.</h4>
93
+ <% end %>
80
94
  </td>
81
95
  </tr>
82
96
  <% else %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-request_police
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4alpha
4
+ version: 0.1.0alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafał Wojsznis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-11 00:00:00.000000000 Z
11
+ date: 2015-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -161,6 +161,7 @@ files:
161
161
  - ".gitignore"
162
162
  - ".rspec"
163
163
  - ".travis.yml"
164
+ - CHANGELOG.md
164
165
  - Gemfile
165
166
  - LICENSE.txt
166
167
  - README.md
@@ -200,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
201
  version: 1.3.1
201
202
  requirements: []
202
203
  rubyforge_project:
203
- rubygems_version: 2.4.3
204
+ rubygems_version: 2.4.6
204
205
  signing_key:
205
206
  specification_version: 4
206
207
  summary: Rack middleware for logging selected request for further investigation /