reject 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+ *.rbc
2
+ *.sassc
3
+ .sass-cache
4
+ capybara-*.html
5
+ .rspec
6
+ /.bundle
7
+ /vendor/bundle
8
+ /log/*
9
+ /tmp/*
10
+ /db/*.sqlite3
11
+ /public/system/*
12
+ /coverage/
13
+ /spec/tmp/*
14
+ **.orig
15
+ rerun.txt
16
+ pickle-email-*.html
@@ -0,0 +1,94 @@
1
+ Reject
2
+ ======
3
+
4
+ Rack Module to reject unwanted requests.
5
+
6
+ Sometimes you don't want to reject incoming requests. Use cases could be:
7
+
8
+ - block page for maintenance reasons
9
+ - blacklist resources or clients
10
+ - enforce usage limits (you might want to use [datagraph/rack-throttle](https://github.com/datagraph/rack-throttle) for that)
11
+
12
+ Usage with Rails
13
+ ----------------
14
+
15
+ **Add to Gemfile**:
16
+
17
+ gem 'reject', :github => 'tobiasfinknet/reject'
18
+
19
+ I still have to check that rubygems stuff, maybe i'll have to rename the gem to avoid annoying :require parameters in the Gemfile
20
+
21
+ **Add to <environment>.rb**
22
+
23
+ Basically you still have to write the conditions for rejection on your own. The rest is handled by this gem. This example would deny access to all clients with IPs other than 127.0.0.1:
24
+
25
+ config.middleware.use "Rack::Reject::Rejector" do |request, opts|
26
+ request.ip != "127.0.0.1"
27
+ end
28
+
29
+ Parameters and defaults
30
+ -----------------------
31
+
32
+ default_options = {
33
+ :code => 503, # HTTP STATUS CODE
34
+ :msg => "503 SERVICE UNAVAILIBLE", # Text that is displayed as response unless param :file is used
35
+ :headers => {}, # overwrite headers
36
+ :retry_after => nil, # send retry after header
37
+ :html_file => nil # Path to html file to return, e.g. Rails.root.join('public', '500.html')
38
+ }
39
+
40
+ For the retry_after param see [the rfc](http://webee.technion.ac.il/labs/comnet/netcourse/CIE/RFC/2068/201.htm) for examples.
41
+
42
+ You can set all options in the middleware command and override them on the opts-hash that is passed to your rejection-block.
43
+
44
+ Examples
45
+ --------
46
+ **Deliver funny random error page on every 5th request (rails)**
47
+
48
+ config.middleware.use "Rack::Reject::Rejector", :html_file => Rails.root.join('public', 'iis.html'), :code => 500 do |request, opts|
49
+ (0...5).to_a.sample == 0
50
+ end
51
+
52
+ Use only on april-fools day. Don't forget to create iis.html.
53
+
54
+ **Limit the number of incoming requests per ip (rails)**
55
+
56
+ config.middleware.insert_before 'Rack::Lock',"Rack::Reject::Rejector", :code => 403 do |request, opts|
57
+ allowed_requests_per_second = 0.5
58
+ max_lockout_seconds = 100
59
+ threshold = 20
60
+ now = Time.now
61
+
62
+ client_info = Rails.cache.read(request.ip) || [now, 0.0]
63
+ last_visit = client_info[0]
64
+ activity = client_info[1]
65
+
66
+ activity = [activity - (now - last_visit) * allowed_requests_per_second, 0.0].max + 1.0
67
+ activity = [activity, (max_lockout_seconds * allowed_requests_per_second) + threshold].min
68
+
69
+
70
+ opts[:retry_after] = ((activity - threshold) / allowed_requests_per_second).ceil.to_s
71
+ opts[:msg] = "Your last request was only #{(now - last_visit)} seconds ago. \nCome back in #{opts[:retry_after]} seconds."
72
+
73
+ Rails.cache.write(request.ip, [now, activity])
74
+
75
+ activity > threshold
76
+ end
77
+
78
+ Make sure to use a fast cross-instance rails cache for this, e.g. [memcache store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemCacheStore.html).
79
+ Make also sure to configure the number of allowed requests per second and threshold to your needs, that depends strongly on if assets are delivered through rails or through an external webserver.
80
+ Also there can be lots of non-malicious ajax requests which should not be blocked.
81
+
82
+ With config.middleware.insert_before, the rejection code is called at the very beginning of the request handling.
83
+ This will lower the used resources to a minimum.
84
+
85
+ ToDo
86
+ ----
87
+ - Write lots of awesome tests
88
+
89
+ Acknowledgements
90
+ -----------------
91
+ - [datagraph/rack-throttle](https://github.com/datagraph/rack-throttle) was the inspiration to limit requests, although it didn't fit our needs
92
+ - [ASCIIcasts](http://asciicasts.com/episodes/151-rack-middleware) for explaining how to write a rack module :-)
93
+ - A strange customer whos performance test consists of a bunch of testers pressing F5 on the slowest pages (that normally wouldn't be requested more often than 5 times a minute)
94
+
@@ -0,0 +1,10 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rack'
4
+
5
+ module Rack
6
+ module Reject
7
+ autoload :Rejector, 'rack/reject/rejector'
8
+ autoload :VERSION, 'rack/reject/version'
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ module Rack
2
+ module Reject
3
+ class Rejector
4
+ ##
5
+ # Reject Request if block.call(request) returns true
6
+ #
7
+ def initialize(app, options = {}, &block)
8
+ default_options = {
9
+ :code => 503, # HTTP STATUS CODE
10
+ :msg => "503 SERVICE UNAVAILIBLE", # Text that is displayed a response unless param :file is used
11
+ :headers => {}, # overwrite headers
12
+ :retry_after => nil, # send retry after header, see http://webee.technion.ac.il/labs/comnet/netcourse/CIE/RFC/2068/201.htm
13
+ :html_file => nil # Path to html file to return, e.g. Rails.root.join('public', '500.html')
14
+ }
15
+
16
+ @app, @options, @block = app, default_options.merge(options), block
17
+ end
18
+
19
+ def call(env)
20
+ request = Rack::Request.new(env)
21
+ opts = @options.clone
22
+ reject?(request, opts) ? reject!(request, opts) : @app.call(env)
23
+ end
24
+
25
+ ##
26
+ # Check whether the request is to be rejected
27
+ def reject? request, opts
28
+ @block.call(request, opts)
29
+ end
30
+
31
+ ##
32
+ # Reject the request
33
+ def reject! request, opts
34
+ [status(opts), headers(opts), response(opts)]
35
+ end
36
+
37
+ def headers opts
38
+ headers = {}
39
+ if opts[:html_file].nil?
40
+ headers['Content-Type'] = 'text/plain; charset=utf-8'
41
+ else
42
+ headers['Content-Type'] = 'text/html; charset=utf-8'
43
+ headers['Content-Disposition'] = "inline; filename='reject.html'"
44
+ headers['Content-Transfer-Encoding'] = 'binary'
45
+ headers['Cache-Control'] = 'private'
46
+ end
47
+ headers['Retry-After'] = opts[:retry_after] unless opts[:retry_after].nil?
48
+
49
+ headers.merge(opts[:headers])
50
+ end
51
+
52
+ def status opts
53
+ opts[:code]
54
+ end
55
+
56
+ def response opts
57
+ if opts[:html_file].nil?
58
+ Array.wrap(opts[:msg])
59
+ else
60
+ ::File.open(opts[:html_file], "rb")
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ module Rack
3
+ module Reject
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/reject/version"
4
+ require "rack/reject/rejector"
5
+ Gem::Specification.new do |s|
6
+ s.name = 'reject'
7
+ s.version = Rack::Reject::VERSION
8
+ s.authors = ["Tobias Fink"]
9
+ s.email = ["code@tobias-fink.net"]
10
+ s.homepage = "https://github.com/tobiasfinknet/reject"
11
+ s.summary = "Rack Module to reject unwanted requests."
12
+ s.description = "Rack Module to reject unwanted requests."
13
+ s.licenses = ["MIT", "GPLv3"]
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: reject
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tobias Fink
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Rack Module to reject unwanted requests.
15
+ email:
16
+ - code@tobias-fink.net
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - README.md
23
+ - lib/rack/reject.rb
24
+ - lib/rack/reject/rejector.rb
25
+ - lib/rack/reject/version.rb
26
+ - reject.gemspec
27
+ homepage: https://github.com/tobiasfinknet/reject
28
+ licenses:
29
+ - MIT
30
+ - GPLv3
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.24
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Rack Module to reject unwanted requests.
53
+ test_files: []