reject 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +16 -0
- data/README.md +94 -0
- data/lib/rack/reject.rb +10 -0
- data/lib/rack/reject/rejector.rb +65 -0
- data/lib/rack/reject/version.rb +6 -0
- data/reject.gemspec +16 -0
- metadata +53 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -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
|
+
|
data/lib/rack/reject.rb
ADDED
@@ -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
|
data/reject.gemspec
ADDED
@@ -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: []
|