rack-request_police 0.0.4alpha → 0.1.0alpha

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 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 /