rack-request_police 0.0.1alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b0e36c2e22df4b342dbc2b60f234de136cab4e5b
4
+ data.tar.gz: 534a58f32183ad3319a029efd94e59bf89877558
5
+ SHA512:
6
+ metadata.gz: 11fd8b55d68d44d1dff25a1062472f39b1b9cfc96fe0646835c578dd49157c2615630c0ebb01d107c3fca361d9b7349bf1c4c2c15854411f593c03bf78e1014d
7
+ data.tar.gz: 949d05dda8d405ebc2227463a3aadfefd6ba34d1ee95e16d8928488f075b36fc7f369e93048931c0f8bc0f1054457eec73b62dfe7988851360d184b17d41e4a8
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ sudo: false
3
+ cache: bundler
4
+ services:
5
+ - redis-server
6
+ rvm:
7
+ - 2.0.0
8
+ - 2.1
9
+ - 2.2
10
+ - rbx-2
11
+ notifications:
12
+ email: false
13
+
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: rbx-2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-request_police.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Rafal Wojsznis
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # Rack::RequestPolice
2
+
3
+ [![Code Climate](https://codeclimate.com/github/emq/rack-request_police/badges/gpa.svg)](https://codeclimate.com/github/emq/rack-request_police)
4
+ [![Build Status](https://travis-ci.org/emq/rack-request_police.svg)](https://travis-ci.org/emq/rack-request_police)
5
+ [![Coverage Status](https://coveralls.io/repos/emq/rack-request_police/badge.svg)](https://coveralls.io/r/emq/rack-request_police)
6
+ [![Dependency Status](https://gemnasium.com/emq/rack-request_police.svg)](https://gemnasium.com/emq/rack-request_police)
7
+
8
+ Rack middleware for logging selected request for further investigation / analyze.
9
+
10
+ Features:
11
+
12
+ - filter requests by method (get/post/patch/delete) and/or regular expression
13
+ - log requests into storage of your choice (at the moment redis supported)
14
+
15
+ Work in progress.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ ```ruby
22
+ gem 'rack-request_police'
23
+ ```
24
+
25
+ And then execute:
26
+
27
+ $ bundle
28
+
29
+ Or install it yourself as:
30
+
31
+ $ gem install rack-request_police
32
+
33
+ ## Usage
34
+
35
+ ### Rails
36
+
37
+ Add do your `application.rb` / environment config:
38
+
39
+ ``` ruby
40
+ Application.configure do
41
+ # ...
42
+ config.middleware.use Rack::RequestPolice::Middleware
43
+ end
44
+ ```
45
+
46
+ Configure middleware using initializer (eg. `config/initializers/request_police.rb`).
47
+
48
+ ``` ruby
49
+ Rack::RequestPolice.configure do |config|
50
+ # For the time being only redis storage if provided, but you can hook up
51
+ # any storage of your choice as long it responds to log_request and page methods
52
+ # see Storage::Base and Storage::Redis for more references
53
+ config.storage = Rack::RequestPolice::Storage::Redis.new(host: 'localhost', port: 6379)
54
+
55
+ # Regular expression that will be matched against request uri
56
+ # Nil by default (logs all requests)
57
+ config.regex = /some-url/
58
+
59
+ # array of methods that should be logged (all four by default)
60
+ # requests not included in this list will be ignored
61
+ config.method = [:get, :post, :delete, :patch]
62
+ end
63
+ ```
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it ( https://github.com/emq/rack-request_police/fork )
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ task default: :spec
@@ -0,0 +1,36 @@
1
+ module Rack
2
+ module RequestPolice
3
+ class Middleware
4
+ class NoStorageFound < StandardError; end
5
+
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ if ::Rack::RequestPolice.method.include?(env['REQUEST_METHOD'].downcase.to_sym)
12
+ full_url = ''
13
+ full_url << (env['HTTPS'] == 'on' ? 'https://' : 'http://')
14
+ full_url << env['HTTP_HOST'] << env['PATH_INFO']
15
+ full_url << '?' << env['QUERY_STRING'] unless env['QUERY_STRING'].empty?
16
+
17
+ if !::Rack::RequestPolice.regex || full_url =~ ::Rack::RequestPolice.regex
18
+ request_params = {
19
+ 'url' => full_url,
20
+ 'ip' => env['REMOTE_ADDR'],
21
+ 'method' => env['REQUEST_METHOD'].downcase,
22
+ 'time' => Time.now.to_i
23
+ }
24
+
25
+ if %w(POST PATCH DELETE).include?(env['REQUEST_METHOD'])
26
+ request_params.merge!('data' => env['rack.input'].gets)
27
+ end
28
+ ::Rack::RequestPolice.storage.log_request(request_params)
29
+ end
30
+ end
31
+
32
+ @app.call(env)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ module Rack
2
+ module RequestPolice
3
+ module Storage
4
+ class Base
5
+ def log_request(request_params)
6
+ raise NotImplementedError, "Please implement `log_request` method"
7
+ end
8
+
9
+ def page(pageidx = 1, page_size = 25)
10
+ raise NotImplementedError, "Please implement `page` method that will return
11
+ [current_page_number, total_amount_of_logged_requests, array_of_paginated <Unit> objects]"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ module Rack
2
+ module RequestPolice
3
+ module Storage
4
+ class Redis < Base
5
+ REDIS_KEY = 'rack:request:police'.freeze
6
+
7
+ attr_reader :redis, :parser
8
+
9
+ def initialize(hash_of_options, json_parser: JSON)
10
+ @redis = ::Redis.new(hash_of_options)
11
+ @parser = json_parser
12
+ end
13
+
14
+ def log_request(request_params)
15
+ redis.lpush(REDIS_KEY, parser.dump(request_params))
16
+ end
17
+
18
+ def page(pageidx = 1, page_size = 25)
19
+ current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
20
+ pageidx = current_page - 1
21
+ total_size = 0
22
+ items = []
23
+ starting = pageidx * page_size
24
+ ending = starting + page_size - 1
25
+
26
+ total_size = redis.llen(REDIS_KEY)
27
+ items = redis.lrange(REDIS_KEY, starting, ending).map do |json|
28
+ hash = parser.load(json)
29
+ Unit.new(hash['method'], hash['ip'], hash['url'], Time.at(hash['time']), hash['data'])
30
+ end
31
+
32
+ [current_page, total_size, items]
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,8 @@
1
+ module Rack
2
+ module RequestPolice
3
+ module Storage
4
+ class Unit < Struct.new(:method, :ip, :url, :time, :data)
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module RequestPolice
3
+ VERSION = "0.0.1alpha"
4
+ end
5
+ end
@@ -0,0 +1,20 @@
1
+ require 'rack/request_police/web_helpers'
2
+ require 'sinatra/base'
3
+ require 'erb'
4
+
5
+ module Rack
6
+ module RequestPolice
7
+ class Web < Sinatra::Base
8
+ helpers WebHelpers
9
+
10
+ set :root, ::File.expand_path(::File.dirname(__FILE__) + "/../../../web")
11
+
12
+ get '/' do
13
+ @count = (params[:count] || 25).to_i
14
+ (@current_page, @total_size, @logs) = Rack::RequestPolice.storage.page(params[:page], @count)
15
+
16
+ erb :index
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ module Rack
2
+ module RequestPolice
3
+ module WebHelpers
4
+ def method_class(method)
5
+ case method
6
+ when 'get'
7
+ 'primary'
8
+ when 'post'
9
+ 'info'
10
+ when 'patch'
11
+ 'warning'
12
+ else
13
+ 'danger'
14
+ end
15
+ end
16
+
17
+ def environment_name
18
+ environment = ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
19
+ "[#{environment.upcase}]"
20
+ end
21
+
22
+ def qparams(options)
23
+ options = options.stringify_keys
24
+ params.merge(options).map do |key, value|
25
+ "#{key}=#{value}"
26
+ end.join("&")
27
+ end
28
+
29
+ def root_path
30
+ "#{env['SCRIPT_NAME']}/"
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,44 @@
1
+ require "json"
2
+ require "rack/request_police/version"
3
+ require "rack/request_police/storage/base"
4
+ require "rack/request_police/storage/redis"
5
+ require "rack/request_police/storage/unit"
6
+ require "rack/request_police/middleware"
7
+
8
+ module Rack
9
+ module RequestPolice
10
+ class NoStorageFound < StandardError; end
11
+
12
+ @@storage = nil
13
+ @@method = [:get, :post, :delete, :patch]
14
+ @@regex = nil
15
+
16
+ def self.configure
17
+ yield self
18
+ end
19
+
20
+ def self.storage=(obj)
21
+ @@storage = obj
22
+ end
23
+
24
+ def self.storage
25
+ @@storage || fail(NoStorageFound)
26
+ end
27
+
28
+ def self.method
29
+ @@method
30
+ end
31
+
32
+ def self.method=(array)
33
+ @@method = array
34
+ end
35
+
36
+ def self.regex
37
+ @@regex
38
+ end
39
+
40
+ def self.regex=(regular_expression)
41
+ @@regex = regular_expression
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack/request_police/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rack-request_police"
8
+ spec.version = Rack::RequestPolice::VERSION
9
+ spec.authors = ["Rafał Wojsznis"]
10
+ spec.email = ["rafal.wojsznis@gmail.com"]
11
+ spec.summary = spec.description = "Rack middleware for logging selected request for further investigation / analyze."
12
+ spec.homepage = "https://github.com/emq/rack-request_police"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.required_ruby_version = '>= 2.0.0'
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.7"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.1.0"
25
+ spec.add_development_dependency 'sinatra', '~> 1.4.5'
26
+ spec.add_development_dependency 'rack-test', '~> 0.6.3'
27
+ spec.add_development_dependency 'timecop', '~> 0.7.1'
28
+ spec.add_development_dependency 'redis', '~> 3.2.0'
29
+ spec.add_development_dependency 'oj', '~> 2.11.4'
30
+ spec.add_development_dependency 'coveralls', '~> 0.7.8'
31
+ spec.add_development_dependency 'rack', '1.5.2' # show useful sinatra errors
32
+ end
data/spec/bench.rb ADDED
@@ -0,0 +1,125 @@
1
+ require 'benchmark'
2
+
3
+ describe "Simple benchmark", type: :request do
4
+ let(:repeat) { 10_000 }
5
+ let(:app){
6
+ Sinatra.new do
7
+ use(Rack::RequestPolice::Middleware)
8
+ get '/' do
9
+ end
10
+ post '/' do
11
+ end
12
+ delete '/' do
13
+ end
14
+ patch '/' do
15
+ end
16
+ end
17
+ }
18
+
19
+ before do
20
+ Rack::RequestPolice.configure do |c|
21
+ c.storage = DummyStorage.new
22
+ c.regex = nil
23
+ c.method = [:get, :post, :delete, :patch]
24
+ end
25
+ end
26
+
27
+ context "with middleware (defaults)" do
28
+ it "benchmarks it" do
29
+ puts "with middleware (defaults)"
30
+ Benchmark.bm(7) do |x|
31
+ x.report("get") { repeat.times { get '/' } }
32
+ x.report("post") { repeat.times { post '/' } }
33
+ x.report("delete") { repeat.times { delete '/' } }
34
+ x.report("patch") { repeat.times { patch '/' } }
35
+ end
36
+ end
37
+ end
38
+
39
+ context "with middleware (customized)" do
40
+ Rack::RequestPolice.configure do |c|
41
+ c.storage = DummyStorage.new
42
+ c.regex = /.*/
43
+ c.method = [:get, :post, :delete]
44
+ end
45
+
46
+ it "benchmarks it" do
47
+ puts "with middleware (customized)"
48
+ Benchmark.bm(7) do |x|
49
+ x.report("get") { repeat.times { get '/' } }
50
+ x.report("post") { repeat.times { post '/' } }
51
+ x.report("delete") { repeat.times { delete '/' } }
52
+ x.report("patch") { repeat.times { patch '/' } }
53
+ end
54
+ end
55
+ end
56
+
57
+ context "with middleware (customized, redis storage)" do
58
+ after { REDIS.flushdb }
59
+ before do
60
+ REDIS.flushdb
61
+ Rack::RequestPolice.configure do |c|
62
+ c.storage = Rack::RequestPolice::Storage::Redis.new(REDIS_OPTIONS)
63
+ c.regex = /.*/
64
+ c.method = [:get, :post, :delete]
65
+ end
66
+ end
67
+
68
+ it "benchmarks it" do
69
+ puts "with middleware (customized, redis storage)"
70
+ Benchmark.bm(7) do |x|
71
+ x.report("get") { repeat.times { get '/' } }
72
+ x.report("post") { repeat.times { post '/' } }
73
+ x.report("delete") { repeat.times { delete '/' } }
74
+ x.report("patch") { repeat.times { patch '/' } }
75
+ end
76
+ end
77
+ end
78
+
79
+ context "with middleware (customized, redis storage, OJ json parser)" do
80
+ after { REDIS.flushdb }
81
+ before do
82
+ REDIS.flushdb
83
+ Rack::RequestPolice.configure do |c|
84
+ c.storage = Rack::RequestPolice::Storage::Redis.new(REDIS_OPTIONS, json_parser: Oj)
85
+ c.regex = /.*/
86
+ c.method = [:get, :post, :delete]
87
+ end
88
+ end
89
+
90
+ it "benchmarks it" do
91
+ puts "with middleware (customized, redis storage, OJ parser)"
92
+ Benchmark.bm(7) do |x|
93
+ x.report("get") { repeat.times { get '/' } }
94
+ x.report("post") { repeat.times { post '/' } }
95
+ x.report("delete") { repeat.times { delete '/' } }
96
+ x.report("patch") { repeat.times { patch '/' } }
97
+ end
98
+ end
99
+ end
100
+
101
+ context "without middleware" do
102
+ let(:app){
103
+ Sinatra.new do
104
+ get '/' do
105
+ end
106
+ post '/' do
107
+ end
108
+ delete '/' do
109
+ end
110
+ patch '/' do
111
+ end
112
+ end
113
+ }
114
+
115
+ it "benchmarks it" do
116
+ puts "without middleware"
117
+ Benchmark.bm(7) do |x|
118
+ x.report("get") { repeat.times { get '/' } }
119
+ x.report("post") { repeat.times { post '/' } }
120
+ x.report("delete") { repeat.times { delete '/' } }
121
+ x.report("patch") { repeat.times { patch '/' } }
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+
3
+ describe "My Middleware", type: :request do
4
+ before do
5
+ Timecop.freeze
6
+
7
+ Rack::RequestPolice.configure do |c|
8
+ c.storage = DummyStorage.new
9
+ c.regex = nil
10
+ c.method = [:get, :post, :delete, :patch]
11
+ end
12
+ end
13
+ after { Timecop.return }
14
+
15
+ context "logging all requests" do
16
+ let(:app){
17
+ Sinatra.new do
18
+ use(Rack::RequestPolice::Middleware)
19
+ get '/' do
20
+ end
21
+ end
22
+ }
23
+
24
+ it "logs request without query params" do
25
+ expect_any_instance_of(DummyStorage).to receive(:log_request)
26
+ .with('url' => "http://example.org/", 'ip' => "127.0.0.1", 'method' => "get", 'time' => Time.now.to_i)
27
+
28
+ get '/'
29
+
30
+ expect(last_response.status).to eq 200
31
+ end
32
+
33
+ it "logs request with query params" do
34
+ expect_any_instance_of(DummyStorage).to receive(:log_request)
35
+ .with('url' => "http://example.org/?what-the&hell=", 'ip' => "127.0.0.1", 'method' => "get", 'time' => Time.now.to_i)
36
+
37
+ get '/?what-the&hell='
38
+
39
+ expect(last_response.status).to eq 200
40
+ end
41
+ end
42
+
43
+ context "logging only POST requests" do
44
+ before do
45
+ Rack::RequestPolice.configure do |c|
46
+ c.method = [:post]
47
+ end
48
+ end
49
+
50
+ let(:app){
51
+ Sinatra.new do
52
+ use(Rack::RequestPolice::Middleware)
53
+ get '/' do
54
+ end
55
+ post '/form' do
56
+ end
57
+ end
58
+ }
59
+
60
+ it "ignores get requests" do
61
+ expect_any_instance_of(DummyStorage).not_to receive(:log_request)
62
+ get '/'
63
+ expect(last_response.status).to eq 200
64
+ end
65
+
66
+ it "logs post request with request data" do
67
+ expect_any_instance_of(DummyStorage).to receive(:log_request)
68
+ .with('url' => "http://example.org/form", 'ip' => "127.0.0.1", 'method' => "post", 'time' => Time.now.to_i, 'data' => 'user[name]=john&user[email]=john%40test.com')
69
+
70
+ post '/form', { user: { name: 'john', email: 'john@test.com' } }
71
+
72
+ expect(last_response.status).to eq 200
73
+ end
74
+ end
75
+
76
+ context "logging PATCH requests" do
77
+ before do
78
+ Rack::RequestPolice.configure do |c|
79
+ c.method = [:patch]
80
+ end
81
+ end
82
+
83
+ let(:app){
84
+ Sinatra.new do
85
+ use(Rack::RequestPolice::Middleware)
86
+ patch '/update' do
87
+ end
88
+ end
89
+ }
90
+
91
+ it "logs patch request with request data" do
92
+ expect_any_instance_of(DummyStorage).to receive(:log_request)
93
+ .with('url' => "http://example.org/update", 'ip' => "127.0.0.1", 'method' => "patch", 'time' => Time.now.to_i, 'data' => 'user[name]=john')
94
+
95
+ patch '/update', { user: { name: 'john' } }
96
+
97
+ expect(last_response.status).to eq 200
98
+ end
99
+ end
100
+
101
+ context "logging DELETE requests" do
102
+ before do
103
+ Rack::RequestPolice.configure do |c|
104
+ c.method = [:delete]
105
+ end
106
+ end
107
+
108
+ let(:app){
109
+ Sinatra.new do
110
+ use(Rack::RequestPolice::Middleware)
111
+ delete '/destroy' do
112
+ end
113
+ end
114
+ }
115
+
116
+ it "logs delete request with request data" do
117
+ expect_any_instance_of(DummyStorage).to receive(:log_request)
118
+ .with('url' => "http://example.org/destroy", 'ip' => "127.0.0.1", 'method' => "delete", 'time' => Time.now.to_i, 'data' => 'user[id]=1')
119
+
120
+ delete '/destroy', { user: { id: 1 } }
121
+
122
+ expect(last_response.status).to eq 200
123
+ end
124
+ end
125
+
126
+ context "logging requests via regex expression" do
127
+ before do
128
+ Rack::RequestPolice.configure do |c|
129
+ c.regex = /user/
130
+ end
131
+ end
132
+
133
+ let(:app){
134
+ Sinatra.new do
135
+ use(Rack::RequestPolice::Middleware)
136
+ get '/user' do
137
+ end
138
+ get '/account' do
139
+ end
140
+ end
141
+ }
142
+
143
+ it "ignores queries that does not match given regex" do
144
+ expect_any_instance_of(DummyStorage).not_to receive(:log_request)
145
+ get '/account'
146
+ expect(last_response.status).to eq 200
147
+ end
148
+
149
+ it "logs matching queries" do
150
+ expect_any_instance_of(DummyStorage).to receive(:log_request)
151
+ .with('url' => "http://example.org/user", 'ip' => "127.0.0.1", 'method' => "get", 'time' => Time.now.to_i)
152
+
153
+ get '/user'
154
+
155
+ expect(last_response.status).to eq 200
156
+ end
157
+ end
158
+
159
+ context "logging request via regex expression (with params)" do
160
+ before do
161
+ Rack::RequestPolice.configure do |c|
162
+ c.regex = /user\?id=1/
163
+ end
164
+ end
165
+
166
+ let(:app){
167
+ Sinatra.new do
168
+ use(Rack::RequestPolice::Middleware)
169
+ get '/user' do
170
+ end
171
+ end
172
+ }
173
+
174
+ it "ignores queries that does not match given regex" do
175
+ expect_any_instance_of(DummyStorage).not_to receive(:log_request)
176
+ get '/user?id=2'
177
+ expect(last_response.status).to eq 200
178
+ end
179
+
180
+ it "logs matching queries" do
181
+ expect_any_instance_of(DummyStorage).to receive(:log_request)
182
+ .with('url' => "http://example.org/user?id=1", 'ip' => "127.0.0.1", 'method' => "get", 'time' => Time.now.to_i)
183
+
184
+ get '/user?id=1'
185
+
186
+ expect(last_response.status).to eq 200
187
+ end
188
+ end
189
+
190
+ context "logging without storage" do
191
+ before do
192
+ Rack::RequestPolice.configure do |c|
193
+ c.storage = nil
194
+ end
195
+ end
196
+
197
+ let(:app){
198
+ Sinatra.new do
199
+ use(Rack::RequestPolice::Middleware)
200
+ end
201
+ }
202
+
203
+ it 'raises an error' do
204
+ expect { get '/' }.to raise_error(Rack::RequestPolice::NoStorageFound)
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,32 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ require_relative '../lib/rack/request_police'
5
+ require_relative '../lib/rack/request_police/web'
6
+ require 'rack/test'
7
+ require 'timecop'
8
+ require 'redis'
9
+ require 'oj'
10
+
11
+ RSpec.configure do |config|
12
+ config.include Rack::Test::Methods
13
+
14
+ config.expect_with :rspec do |expectations|
15
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
16
+ end
17
+
18
+ config.mock_with :rspec do |mocks|
19
+ mocks.verify_partial_doubles = true
20
+ end
21
+
22
+ config.warnings = true
23
+ config.order = :random
24
+ Kernel.srand config.seed
25
+ end
26
+
27
+ class DummyStorage < Rack::RequestPolice::Storage::Base
28
+ def log_request(hash); end
29
+ end
30
+
31
+ REDIS_OPTIONS = { url: "redis://localhost/15", namespace: "rack-request-police" }
32
+ REDIS = Redis.new(REDIS_OPTIONS)
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Redis storage' do
4
+ after { REDIS.flushdb }
5
+ before { REDIS.flushdb }
6
+
7
+ describe '#log_request' do
8
+ it 'pushes serialized requests params to redis list' do
9
+ storage = Rack::RequestPolice::Storage::Redis.new(REDIS_OPTIONS)
10
+
11
+ expect { storage.log_request({'test' => 'me'}) }
12
+ .to change{ REDIS.llen('rack:request:police')}.by(1)
13
+ end
14
+
15
+ it 'can serialize using different JSON library' do
16
+ storage = Rack::RequestPolice::Storage::Redis.new(REDIS_OPTIONS, json_parser: Oj)
17
+
18
+ expect(Oj).to receive(:dump).with({'test' => 'me'})
19
+ storage.log_request({'test' => 'me'})
20
+ end
21
+ end
22
+
23
+ end
data/spec/web_spec.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Web interface", type: :request do
4
+ before { Rack::RequestPolice.storage = Rack::RequestPolice::Storage::Redis.new(REDIS_OPTIONS) }
5
+ let!(:app){ Rack::RequestPolice::Web }
6
+
7
+ context "when there are logged requests" do
8
+ before do
9
+ Rack::RequestPolice.storage.log_request(method: 'get', ip: '127.0.0.1', url: 'example.com', time: Time.now.to_i)
10
+ get '/'
11
+ end
12
+
13
+ after { REDIS.flushdb }
14
+
15
+ it "is successful" do
16
+ expect(last_response.status).to eq 200
17
+ end
18
+
19
+ it "displays logged request" do
20
+ expect(last_response.body).to match(/127\.0\.0\.1.*example\.com/m)
21
+ end
22
+ end
23
+
24
+ context "when there is none logged requests" do
25
+ before { get '/' }
26
+
27
+ it "is successful" do
28
+ expect(last_response.status).to eq 200
29
+ end
30
+
31
+ it "displays proper information" do
32
+ expect(last_response.body).to match(/No requests logged/)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,122 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title><%= environment_name %> RequestPolice</title>
5
+ <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
6
+ <style type="text/css">
7
+ tr.details {
8
+ display: none;
9
+ }
10
+ td.time, td.ip {
11
+ font-size: 80%;
12
+ }
13
+ td.method {
14
+ text-align: center;
15
+ }
16
+
17
+ </style>
18
+ <script type="text/javascript">
19
+ function ready(fn){
20
+ if (document.readyState != 'loading'){
21
+ fn();
22
+ } else {
23
+ document.addEventListener('DOMContentLoaded', fn);
24
+ }
25
+ }
26
+
27
+ ready(function(){
28
+ var elements = document.querySelectorAll('a');
29
+ Array.prototype.forEach.call(elements, function(el){
30
+ el.addEventListener('click', function(e){
31
+ var row = document.getElementById(this.dataset.details);
32
+ if (row.style.display == 'table-row'){
33
+ row.style.display = 'none';
34
+ } else {
35
+ row.style.display = 'table-row';
36
+ }
37
+ e.preventDefault();
38
+ });
39
+ });
40
+ });
41
+ </script>
42
+ </head>
43
+ <body>
44
+ <nav class="navbar navbar-default">
45
+ <div class="container">
46
+ <div class="navbar-header">
47
+ <a href="/" class="navbar-brand"><%= environment_name %> Rack::RequestPolice v<%= Rack::RequestPolice::VERSION %></a>
48
+ </div>
49
+ </div>
50
+ </nav>
51
+ <div class="container">
52
+ <div class="row">
53
+ <div class="col-md-12">
54
+ <% if @logs.any? %>
55
+ <table class="table table-striped table-hover">
56
+ <thead>
57
+ <tr>
58
+ <th class="col-md-1">Method</th>
59
+ <th class="col-md-2">Time</th>
60
+ <th class="col-md-1">IP</th>
61
+ <th class="col-md-5">URL</th>
62
+ <th class="col-md-4">Data</th>
63
+ </tr>
64
+ </thead>
65
+ <tbody>
66
+ <% @logs.each_with_index do |log, idx| %>
67
+ <tr>
68
+ <td class="method"><span class="label label-<%= method_class log.method %>"><%= log.method %></span></td>
69
+ <td class="time"><%= log.time %></td>
70
+ <td class="ip"><%= log.ip %></td>
71
+ <td><a href="<%= log.url %>" target="_blank"><%= log.url %></a></td>
72
+ <td>
73
+ <% if log.data %>
74
+ <a href='#' data-details="details_<%= idx %>">toggle details</a></td>
75
+ <tr class="details" id="details_<%= idx %>">
76
+ <td colspan="5">
77
+ <pre>
78
+ <%= log.data.inspect %>
79
+ </pre>
80
+ </td>
81
+ </tr>
82
+ <% else %>
83
+ </td>
84
+ <% end %>
85
+ </tr>
86
+ <% end %>
87
+ </tbody>
88
+ </table>
89
+
90
+ <% if @total_size > @count %>
91
+ <ul class="pagination pull-right">
92
+ <li class="<%= 'disabled' if @current_page == 1 %>">
93
+ <a href="<%= root_path %>?page=1">&laquo;</a>
94
+ </li>
95
+ <% if @current_page > 1 %>
96
+ <li>
97
+ <a href="<%= root_path %>?<%= qparams(page: @current_page - 1) %>"><%= @current_page - 1 %></a>
98
+ </li>
99
+ <% end %>
100
+ <li class="disabled">
101
+ <a href="<%= root_path %>?<%= qparams(page: @current_page) %>"><%= @current_page %></a>
102
+ </li>
103
+ <% if @total_size > @current_page * @count %>
104
+ <li>
105
+ <a href="<%= root_path %>?<%= qparams(page: @current_page + 1) %>"><%= @current_page + 1 %></a>
106
+ </li>
107
+ <% end %>
108
+ <li class="<%= 'disabled' if @total_size <= @current_page * @count %>">
109
+ <a href="<%= root_path %>?<%= qparams(page: (@total_size.to_f / @count).ceil) %>">&raquo;</a>
110
+ </li>
111
+ </ul>
112
+ <% end %>
113
+ <% else %>
114
+ <div class="alert alert-info">No requests logged</div>
115
+ <% end %>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </body>
120
+ </html>
121
+
122
+
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-request_police
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1alpha
5
+ platform: ruby
6
+ authors:
7
+ - Rafał Wojsznis
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 1.4.5
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 1.4.5
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.6.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.6.3
83
+ - !ruby/object:Gem::Dependency
84
+ name: timecop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.7.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.7.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: redis
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 3.2.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 3.2.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: oj
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 2.11.4
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 2.11.4
125
+ - !ruby/object:Gem::Dependency
126
+ name: coveralls
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.7.8
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.7.8
139
+ - !ruby/object:Gem::Dependency
140
+ name: rack
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '='
144
+ - !ruby/object:Gem::Version
145
+ version: 1.5.2
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '='
151
+ - !ruby/object:Gem::Version
152
+ version: 1.5.2
153
+ description: Rack middleware for logging selected request for further investigation
154
+ / analyze.
155
+ email:
156
+ - rafal.wojsznis@gmail.com
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files: []
160
+ files:
161
+ - ".gitignore"
162
+ - ".rspec"
163
+ - ".travis.yml"
164
+ - Gemfile
165
+ - LICENSE.txt
166
+ - README.md
167
+ - Rakefile
168
+ - lib/rack/request_police.rb
169
+ - lib/rack/request_police/middleware.rb
170
+ - lib/rack/request_police/storage/base.rb
171
+ - lib/rack/request_police/storage/redis.rb
172
+ - lib/rack/request_police/storage/unit.rb
173
+ - lib/rack/request_police/version.rb
174
+ - lib/rack/request_police/web.rb
175
+ - lib/rack/request_police/web_helpers.rb
176
+ - rack-request_police.gemspec
177
+ - spec/bench.rb
178
+ - spec/middleware_spec.rb
179
+ - spec/spec_helper.rb
180
+ - spec/storage/redis_spec.rb
181
+ - spec/web_spec.rb
182
+ - web/views/index.erb
183
+ homepage: https://github.com/emq/rack-request_police
184
+ licenses:
185
+ - MIT
186
+ metadata: {}
187
+ post_install_message:
188
+ rdoc_options: []
189
+ require_paths:
190
+ - lib
191
+ required_ruby_version: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: 2.0.0
196
+ required_rubygems_version: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">"
199
+ - !ruby/object:Gem::Version
200
+ version: 1.3.1
201
+ requirements: []
202
+ rubyforge_project:
203
+ rubygems_version: 2.4.5
204
+ signing_key:
205
+ specification_version: 4
206
+ summary: Rack middleware for logging selected request for further investigation /
207
+ analyze.
208
+ test_files:
209
+ - spec/bench.rb
210
+ - spec/middleware_spec.rb
211
+ - spec/spec_helper.rb
212
+ - spec/storage/redis_spec.rb
213
+ - spec/web_spec.rb