shellacrb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +123 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/shellac +7 -0
- data/lib/shellac/application.rb +85 -0
- data/lib/shellac/config/task.rb +35 -0
- data/lib/shellac/config/tasklist.rb +6 -0
- data/lib/shellac/config.rb +241 -0
- data/lib/shellac/core.rb +6 -0
- data/lib/shellac/storage_engine/hash.rb +64 -0
- data/lib/shellac/storage_engine/roma.rb +54 -0
- data/lib/shellac/storage_engine.rb +65 -0
- data/lib/shellac/version.rb +3 -0
- data/lib/shellac.rb +4 -0
- data/shellacrb.gemspec +31 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 20bc93cc9afb84671752476bcdb5a5ed9714da7b
|
4
|
+
data.tar.gz: bea16b6892d3191b2d584950342497143ccdf220
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 80171b685c351d84e9f592dc34110a865ecc0262eaafbd00f7de1aaaf7fadc1ddbf02c0254aa208141192417a7a2f82bf187606e7877ba58394a02762adf0088
|
7
|
+
data.tar.gz: ce091833fc6f9e7268816500b4cf5a420b97675fe1fe47854db3eaee6cf8a306e97c1123a63bc487657e9d37d0d4319cffb2c3302ce3a117639b6689a143b0e0
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Kirk Haines
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Kirk Haines
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# Shellac
|
2
|
+
|
3
|
+
Shellac is a simple caching reverse proxy. In it's current state, it is a
|
4
|
+
nominally usable proof of concept. It was built on top of Puma and Rack, and
|
5
|
+
it supports pluggable backend storage modules.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
$ gem install shellac
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Use the `-h` flag to get help from `shellac`. If a storage engine is specified
|
14
|
+
with `-s`, then any command line flags and arguments supported by that storage
|
15
|
+
engine will also be listed.
|
16
|
+
|
17
|
+
```
|
18
|
+
$ shellac -s hash -h
|
19
|
+
shellac [OPTIONS]
|
20
|
+
|
21
|
+
shellac is a simple caching proxy server.
|
22
|
+
|
23
|
+
-h, --help:
|
24
|
+
Show this help.
|
25
|
+
|
26
|
+
-b HOSTNAME[:PORT], --bind HOSTNAME[:PORT]:
|
27
|
+
The hostname/IP and optionally the port to bind to. This defaults to 127.0.0.1:80 if it is not provided.
|
28
|
+
|
29
|
+
-c FILENAME, --config FILENAME:
|
30
|
+
The configuration file to load.
|
31
|
+
|
32
|
+
-r ROUTESPEC, --route ROUTESPEC:
|
33
|
+
Provides a routing specification for the proxy. A route spec is one or more
|
34
|
+
host names or IPs, comma seperated, to match requests from, a regular
|
35
|
+
expression to match against, and a target to proxy to:
|
36
|
+
|
37
|
+
-r 'foo.bar.com::\?(w+)$::https://github.com/'
|
38
|
+
|
39
|
+
This can be specified multiple times. For complex route specs, it is better
|
40
|
+
to use a configuration file.
|
41
|
+
|
42
|
+
-s ENGINE, --storageengine ENGINE:
|
43
|
+
The storage engine to use for storing cached content.
|
44
|
+
|
45
|
+
-t MIN:MAX, --threads MIN:MAX:
|
46
|
+
The minimum and maximum number of threads to run. Defaults to 0:10
|
47
|
+
|
48
|
+
-w COUNT, --workers COUNT:
|
49
|
+
The number of worker processes to start.
|
50
|
+
|
51
|
+
-v, --version:
|
52
|
+
Show the version of shellac.
|
53
|
+
|
54
|
+
--cache-trim-interval INTERVAL:
|
55
|
+
The wait time in seconds between sweeps of the cache to ensure it isn't too large.
|
56
|
+
|
57
|
+
--max-cache-elements LENGTH:
|
58
|
+
The maximum number of elements to store in the cache.
|
59
|
+
|
60
|
+
--max-cache-size SIZE:
|
61
|
+
The maximum size, in bytes, of the cache.
|
62
|
+
|
63
|
+
```
|
64
|
+
|
65
|
+
For simple usage, routing rules can be specified right on the command line.
|
66
|
+
They are given in the format of:
|
67
|
+
|
68
|
+
```
|
69
|
+
DOMAIN[,DOMAIN2,DOMAINn]::MATCHRULE::DESTINATION
|
70
|
+
```
|
71
|
+
|
72
|
+
The DOMAIN section is a comma delimited list of one or more host names or IPs
|
73
|
+
which will match this rule.
|
74
|
+
|
75
|
+
THe MATCHRULE itself is simply a regular expression. The first rule that
|
76
|
+
matches stops checking of any remaining rules.
|
77
|
+
|
78
|
+
The DESTINATION can be either a simple string which will be evaulated with
|
79
|
+
normal Ruby string interpolation rules, allowing matched portions of the
|
80
|
+
MATCHRULE to be inserted, or it can be a chunk of ruby code to execute, the
|
81
|
+
return value of which should be a URL to proxy to.
|
82
|
+
|
83
|
+
If providing actual Ruby code, the `DESTINATION` should be prefixed with
|
84
|
+
`lambda:`. It is suggested that if a DESTINATION is to be determined by any
|
85
|
+
non-trivial code, that a configuration file be used instead of a command line
|
86
|
+
argument.
|
87
|
+
|
88
|
+
An example:
|
89
|
+
|
90
|
+
```
|
91
|
+
shellac -s hash -r '127.0.0.1::\?(.*)$::https://github.com/#{$1}' -t 2:16 -w 1
|
92
|
+
```
|
93
|
+
|
94
|
+
This will use the built in Ruby in memory Hash based storage engine (which is
|
95
|
+
also the default if `-s` is not specified. It is looking for requests on the
|
96
|
+
localhost IP, with a query string specified. If it finds a match, it uses the
|
97
|
+
query string to construct a github.com URL, and proxies that. It will run with
|
98
|
+
a minimum of two, and a maximum of 16 threads, and with a single worker
|
99
|
+
process.
|
100
|
+
|
101
|
+
## Development
|
102
|
+
|
103
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
104
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
105
|
+
prompt that will allow you to experiment.
|
106
|
+
|
107
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To
|
108
|
+
release a new version, update the version number in `version.rb`, and then run
|
109
|
+
`bundle exec rake release`, which will create a git tag for the version, push
|
110
|
+
git commits and tags, and push the `.gem` file to
|
111
|
+
[rubygems.org](https://rubygems.org).
|
112
|
+
|
113
|
+
## Contributing
|
114
|
+
|
115
|
+
Bug reports and pull requests are welcome on GitHub at
|
116
|
+
https://github.com/wyhaines/shellac.
|
117
|
+
|
118
|
+
|
119
|
+
## License
|
120
|
+
|
121
|
+
The gem is available as open source under the terms of the
|
122
|
+
[MIT License](http://opensource.org/licenses/MIT).
|
123
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "shellac"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/bin/shellac
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
module Shellac
|
2
|
+
class Launcher
|
3
|
+
class << self
|
4
|
+
def run
|
5
|
+
puma_config = Puma::Configuration.new do |pconf|
|
6
|
+
pconf.threads Config[:minimum_threads], Config[:maximum_threads]
|
7
|
+
pconf.workers Config[:worker_count]
|
8
|
+
Config[:bind].each {|b| pconf.bind b}
|
9
|
+
pconf.app Application
|
10
|
+
end
|
11
|
+
|
12
|
+
Puma::Launcher.new(puma_config, events: Puma::Events.stdio).run
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class ApplicationHelpers
|
18
|
+
class << self
|
19
|
+
def run
|
20
|
+
puma_config = Puma::Configuration.new do |pconf|
|
21
|
+
pconf.threads Config[:minimum_threads], Config[:maximum_threads]
|
22
|
+
pconf.workers Config[:worker_count]
|
23
|
+
pconf.app Application
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def request_headers( env )
|
28
|
+
Hash[*env.select {|k,v| k.start_with? 'HTTP_'}
|
29
|
+
.collect {|k,v| [k.sub(/^HTTP_/, ''), v]}
|
30
|
+
.collect {|k,v| [k.split('_').collect(&:capitalize).join('-'), v]}
|
31
|
+
.flatten]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Application = lambda { |env|
|
37
|
+
hdrs = Shellac::ApplicationHelpers.request_headers( env )
|
38
|
+
hdrs.delete("Host")
|
39
|
+
response = nil
|
40
|
+
|
41
|
+
original_url = ::Rack::Request.new( env ).url
|
42
|
+
|
43
|
+
if Shellac::Config[:routes].has_key?( env["SERVER_NAME"] )
|
44
|
+
Shellac::Config[:routes][ env["SERVER_NAME"] ].each do |route|
|
45
|
+
if cached_response = Shellac::Config[:storageengine]["response:#{original_url}"]
|
46
|
+
response = cached_response
|
47
|
+
elsif to_url = route[:func].call(original_url, route[:regexp])
|
48
|
+
fetched_response = HTTP.request(
|
49
|
+
env["REQUEST_METHOD"].downcase.intern,
|
50
|
+
to_url,
|
51
|
+
HTTP.default_options.with_headers( hdrs )
|
52
|
+
)
|
53
|
+
fetched_body = fetched_response.body
|
54
|
+
body = []
|
55
|
+
if fetched_response.chunked?
|
56
|
+
while partial = fetched_body.readpartial
|
57
|
+
body << ( partial.bytesize.to_s(16) << "\r\n" << partial << "\r\n")
|
58
|
+
end
|
59
|
+
body << "0\r\n\r\n"
|
60
|
+
else
|
61
|
+
while partial = fetched_body.readpartial
|
62
|
+
body << partial
|
63
|
+
end
|
64
|
+
end
|
65
|
+
# Make this sexy and make it so that the exact caching key can be
|
66
|
+
# specified instead of just crude path based caching.
|
67
|
+
|
68
|
+
response = [
|
69
|
+
fetched_response.status.code,
|
70
|
+
fetched_response.headers.to_h,
|
71
|
+
body
|
72
|
+
]
|
73
|
+
Shellac::Config[:storageengine]["response:#{original_url}"] = response
|
74
|
+
break
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if response
|
80
|
+
response
|
81
|
+
else
|
82
|
+
[200, {}, ["undefined"]]
|
83
|
+
end
|
84
|
+
}
|
85
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Shellac
|
2
|
+
class ConfigClass
|
3
|
+
class Task
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
attr_accessor :order, :task
|
7
|
+
|
8
|
+
def initialize(order = 0, &task)
|
9
|
+
@order = order
|
10
|
+
@task = task
|
11
|
+
end
|
12
|
+
|
13
|
+
def <=>(another_task)
|
14
|
+
if @order < another_task.order
|
15
|
+
-1
|
16
|
+
elsif @order > another_task.order
|
17
|
+
1
|
18
|
+
else
|
19
|
+
if @task.to_s < another_task.task.to_s
|
20
|
+
-1
|
21
|
+
elsif @task.to_s > another_task.task.to_s
|
22
|
+
1
|
23
|
+
else
|
24
|
+
0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(*args)
|
30
|
+
@task.call(*args) if @task
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'shellac/storage_engine'
|
3
|
+
require 'shellac/config/task'
|
4
|
+
require 'shellac/config/tasklist'
|
5
|
+
|
6
|
+
module Shellac
|
7
|
+
class ConfigClass
|
8
|
+
def initialize
|
9
|
+
@configuration = {}
|
10
|
+
@configuration[:bind] = []
|
11
|
+
@configuration[:minimum_threads] = 1
|
12
|
+
@configuration[:maximum_threads] = 10
|
13
|
+
@configuration[:worker_count] = 1
|
14
|
+
@configuration[:routes] = Hash.new { |h, k| h[k] = [] }
|
15
|
+
|
16
|
+
@meta_configuration = {}
|
17
|
+
@meta_configuration[:helptext] = ''
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](val)
|
21
|
+
@configuration.has_key?(val) ? @configuration[val] : @meta_configuration[val]
|
22
|
+
end
|
23
|
+
|
24
|
+
def config
|
25
|
+
@configuration
|
26
|
+
end
|
27
|
+
|
28
|
+
def meta
|
29
|
+
@meta_configuration
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse(parse_cl = true, additional_config = {}, additional_meta_config = {}, additional_tasks = nil)
|
33
|
+
@configuration.merge! additional_config
|
34
|
+
@meta_configuration.merge! additional_meta_config
|
35
|
+
|
36
|
+
tasklist = parse_command_line if parse_cl
|
37
|
+
|
38
|
+
tasklist = merge_task_lists(tasklist, additional_tasks) if additional_tasks
|
39
|
+
|
40
|
+
run_task_list tasklist
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_task_list( tasks )
|
44
|
+
tasks = tasks.sort
|
45
|
+
|
46
|
+
result = nil
|
47
|
+
while tasks.any? do
|
48
|
+
new_task = tasks.shift
|
49
|
+
result = new_task.call # If any task returns a task list, fall out of execution
|
50
|
+
break if TaskList === result
|
51
|
+
end
|
52
|
+
|
53
|
+
tasks = merge_task_lists(tasks, result) if TaskList === result # merge any new tasks into the remaining tasks
|
54
|
+
|
55
|
+
run_task_list( tasks ) if tasks.any? # run any remaining tasks
|
56
|
+
end
|
57
|
+
|
58
|
+
def merge_task_lists(old_list, new_list)
|
59
|
+
( old_list + new_list ).sort
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse_command_line
|
63
|
+
call_list = TaskList.new
|
64
|
+
|
65
|
+
options = OptionParser.new do |opts|
|
66
|
+
opts.on( '-h', '--help' ) do
|
67
|
+
exe = File.basename( $PROGRAM_NAME )
|
68
|
+
@meta_configuration[:helptext] << <<-EHELP
|
69
|
+
#{exe} [OPTIONS]
|
70
|
+
|
71
|
+
#{exe} is a simple caching proxy server.
|
72
|
+
|
73
|
+
-h, --help:
|
74
|
+
Show this help.
|
75
|
+
|
76
|
+
-b HOSTNAME[:PORT], --bind HOSTNAME[:PORT]:
|
77
|
+
The hostname/IP and optionally the port to bind to. This defaults to 127.0.0.1:80 if it is not provided.
|
78
|
+
|
79
|
+
-c FILENAME, --config FILENAME:
|
80
|
+
The configuration file to load.
|
81
|
+
|
82
|
+
-r ROUTESPEC, --route ROUTESPEC:
|
83
|
+
Provides a routing specification for the proxy. A route spec is one or more
|
84
|
+
host names or IPs, comma seperated, to match requests from, a regular
|
85
|
+
expression to match against, and a target to proxy to:
|
86
|
+
|
87
|
+
-r 'foo.bar.com::\?(\w+)$::https://github.com/\#{$1}'
|
88
|
+
|
89
|
+
This can be specified multiple times. For complex route specs, it is better
|
90
|
+
to use a configuration file.
|
91
|
+
|
92
|
+
-s ENGINE, --storageengine ENGINE:
|
93
|
+
The storage engine to use for storing cached content.
|
94
|
+
|
95
|
+
-t MIN:MAX, --threads MIN:MAX:
|
96
|
+
The minimum and maximum number of threads to run. Defaults to 0:10
|
97
|
+
|
98
|
+
-w COUNT, --workers COUNT:
|
99
|
+
The number of worker processes to start.
|
100
|
+
|
101
|
+
-v, --version:
|
102
|
+
Show the version of #{exe}.
|
103
|
+
EHELP
|
104
|
+
call_list << Task.new(9999) { puts @meta_configuration[:helptext]; exit 0 }
|
105
|
+
end
|
106
|
+
|
107
|
+
opts.on( '-v', '--version') do
|
108
|
+
exe = File.basename( $PROGRAM_NAME )
|
109
|
+
@meta_configuration[:version] = "#{exe} v. #{Shellac::VERSION}"
|
110
|
+
call_list << Task.new(9999) { puts @meta_configuration[:version]; exit 0 }
|
111
|
+
end
|
112
|
+
|
113
|
+
opts.on( '-c', '--config FILENAME' ) do |configfile|
|
114
|
+
require 'yaml'
|
115
|
+
call_list << Task.new(0) do
|
116
|
+
parsed_config = YAML.load( File.read( File.expand_path( configfile ) ) )
|
117
|
+
@configuration = parsed_config.merge( @configuration ) if Hash === parsed_config
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
opts.on( '-s', '--storageengine ENGINE' ) do |storageengine|
|
122
|
+
@configuration[:storageengine] = storageengine
|
123
|
+
call_list << Task.new(1) do
|
124
|
+
libname = "shellac/storage_engine/#{@configuration[:storageengine]}"
|
125
|
+
setup_engine(:storageengine, libname)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on( '-t', '--threads THREADSPEC' ) do |threadspec|
|
130
|
+
call_list << Task.new(9000) do
|
131
|
+
min = 1
|
132
|
+
max = 10
|
133
|
+
if threadspec =~ /\s*(\d+)\s*:\s*(\d+)/
|
134
|
+
min,max = [ $1.to_i > 0 ? $1.to_i : 1, $2.to_i > 0 ? $2.to_i : 10 ]
|
135
|
+
else
|
136
|
+
n = Integer( threadspec.to_i )
|
137
|
+
max = n > 0 ? n : 10
|
138
|
+
end
|
139
|
+
@configuration[:minimum_threads] = min
|
140
|
+
@configuration[:maximum_threads] = max
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
opts.on( '-w', '--workers COUNT' ) do |worker_count|
|
145
|
+
call_list << Task.new(9000) do
|
146
|
+
count = Integer( worker_count.to_i )
|
147
|
+
count = count > 0 ? count : 1
|
148
|
+
@configuration[:worker_count] = count
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
opts.on( '-r', '--route ROUTESPEC' ) do |routespec|
|
153
|
+
# -r 'foo.bar.com::?(\w+)::https://github.com/#{$1}'
|
154
|
+
#
|
155
|
+
hosts,regexp,matchfunc = routespec.split(/::/,3)
|
156
|
+
|
157
|
+
hosts = hosts.split(/,/).collect {|h| h.strip}
|
158
|
+
|
159
|
+
regexp = Regexp.new( regexp )
|
160
|
+
|
161
|
+
if matchfunc =~ /^lambda:(.*)$/m
|
162
|
+
code = "#{$1}"
|
163
|
+
else
|
164
|
+
code = "\"#{matchfunc}\""
|
165
|
+
end
|
166
|
+
|
167
|
+
matchfunc = <<~ECODE
|
168
|
+
lambda {|s,r|
|
169
|
+
if s =~ r
|
170
|
+
#{code}
|
171
|
+
else
|
172
|
+
nil
|
173
|
+
end
|
174
|
+
}
|
175
|
+
ECODE
|
176
|
+
matchfunc = Object.new.instance_eval(matchfunc)
|
177
|
+
|
178
|
+
hosts.each do |h|
|
179
|
+
@configuration[:routes][h] << {
|
180
|
+
regexp: regexp,
|
181
|
+
func: matchfunc
|
182
|
+
}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
opts.on( '-b', '--bind HOST') do |host_and_port|
|
187
|
+
if host_and_port =~ /^(\w+:\/\/)/
|
188
|
+
protocol = $1
|
189
|
+
host_and_port.gsub!(/^\w+:\/\//,'')
|
190
|
+
else
|
191
|
+
protocol = 'tcp://'
|
192
|
+
end
|
193
|
+
h,p = host_and_port.split(/:/,2)
|
194
|
+
h = '127.0.0.1' if h.empty?
|
195
|
+
p = '80' if p.empty?
|
196
|
+
call_list << Task.new(9000) { @configuration[:bind] << "#{protocol}#{h}:#{p}" }
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
leftover_argv = []
|
201
|
+
|
202
|
+
begin
|
203
|
+
options.parse!(ARGV)
|
204
|
+
rescue OptionParser::InvalidOption => e
|
205
|
+
e.recover ARGV
|
206
|
+
leftover_argv << ARGV.shift
|
207
|
+
leftover_argv << ARGV.shift if ARGV.any? && ( ARGV.first[0..0] != '-' )
|
208
|
+
retry
|
209
|
+
ensure
|
210
|
+
puts "adding default storage engine"
|
211
|
+
unless @configuration[:storageengine]
|
212
|
+
@configuration[:storageengine] = 'hash'
|
213
|
+
call_list << Task.new(100) do
|
214
|
+
libname = "shellac/storage_engine/#{@configuration[:storageengine]}"
|
215
|
+
setup_engine(:storageengine, libname)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
ARGV.replace( leftover_argv ) if leftover_argv.any?
|
221
|
+
|
222
|
+
call_list
|
223
|
+
end
|
224
|
+
|
225
|
+
def classname(klass)
|
226
|
+
parts = Array === klass ? klass : klass.split(/::/)
|
227
|
+
parts.inject(::Object) {|o,n| o.const_get n}
|
228
|
+
end
|
229
|
+
|
230
|
+
def setup_engine(key, libname)
|
231
|
+
require libname
|
232
|
+
klass = classname( libname.split(/\//).collect {|s| s.capitalize} )
|
233
|
+
@configuration[key] = klass.new
|
234
|
+
@configuration[key].class.parse_command_line(@configuration, @meta_configuration) if @configuration[key].class.respond_to? :parse_command_line
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
Config = ConfigClass.new
|
240
|
+
Config.parse
|
241
|
+
end
|
data/lib/shellac/core.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'shellac/storage_engine'
|
2
|
+
|
3
|
+
module Shellac
|
4
|
+
class Storage_engine
|
5
|
+
class Hash < Shellac::Storage_engine::Base
|
6
|
+
|
7
|
+
def self.parse_command_line(configuration, meta_configuration)
|
8
|
+
call_list = Shellac::ConfigClass::TaskList.new
|
9
|
+
|
10
|
+
meta_configuration[:helptext] << <<-EHELP
|
11
|
+
|
12
|
+
--cache-trim-interval INTERVAL:
|
13
|
+
The wait time in seconds between sweeps of the cache to ensure it isn't too large.
|
14
|
+
|
15
|
+
--max-cache-elements LENGTH:
|
16
|
+
The maximum number of elements to store in the cache.
|
17
|
+
|
18
|
+
--max-cache-size SIZE:
|
19
|
+
The maximum size, in bytes, of the cache.
|
20
|
+
|
21
|
+
EHELP
|
22
|
+
|
23
|
+
options = OptionParser.new do |opts|
|
24
|
+
opts.on( '--cache-trim-interval INTERVAL' ) do |interval|
|
25
|
+
call_list << Shellac::ConfigClass::Task.new(9000) do
|
26
|
+
n = Integer( interval.to_i )
|
27
|
+
configuration[:cache_trim_interval] = n
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on( '--max-cache-elements LENGTH' ) do |len|
|
32
|
+
call_list << Shellac::ConfigClass::Task.new(9000) do
|
33
|
+
n = Integer( len.to_i )
|
34
|
+
configuration[:cache_max_elements] = n
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on( '--max-cache-size SIZE' ) do |size|
|
39
|
+
call_list << Shellac::ConfigClass::Task.new(9000) do
|
40
|
+
n = Integer( size.to_i )
|
41
|
+
n = n > 0 ? n : 1024 * 1024 * 20
|
42
|
+
configuration[:cache_max_size] = n
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
leftover_argv = []
|
48
|
+
|
49
|
+
begin
|
50
|
+
options.parse!(ARGV)
|
51
|
+
rescue OptionParser::InvalidOption => e
|
52
|
+
e.recover ARGV
|
53
|
+
leftover_argv << ARGV.shift
|
54
|
+
leftover_argv << ARGV.shift if ARGV.any? && ( ARGV.first[0..0] != '-' )
|
55
|
+
retry
|
56
|
+
end
|
57
|
+
|
58
|
+
ARGV.replace( leftover_argv ) if leftover_argv.any?
|
59
|
+
|
60
|
+
call_list
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Shellac
|
2
|
+
class StorageEngine
|
3
|
+
class Roma << Shellac::StorageEngine
|
4
|
+
def self.parse_command_line(configuration, meta_configuration)
|
5
|
+
call_list = Shellac::Config::TaskList.new
|
6
|
+
|
7
|
+
meta_configuration[:helptext] << <<-EHELP
|
8
|
+
--processes COUNT:
|
9
|
+
The number of processes to fork. Defaults to 1.
|
10
|
+
|
11
|
+
-s SIZE|MINSIZE,MAXSIZE, --pool-size SIZE|MINSIZE,MAXSIZE:
|
12
|
+
The size of the thread pool to create. If unset, Scrawls will spawn a thread for each request. If given two comman separated numbers, those numbers will be interpreted to be the minimum and maximum size of the thread pool. Scrawls will spawn new threads as needed, to the maximum number, if all threads are busy, and will later reduce the size of the thread pool back down toward the minimum size if the threads become idle.
|
13
|
+
|
14
|
+
EHELP
|
15
|
+
|
16
|
+
options = OptionParser.new do |opts|
|
17
|
+
opts.on( '--processes COUNT' ) do |count|
|
18
|
+
call_list << SimpleRubyWebServer::Config::Task.new(9000) { n = Integer( count.to_i ); n = n > 0 ? n : 1; configuration[:processes] = n }
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on( '--s', '--pool-size SIZE' ) do |size|
|
22
|
+
call_list << SimpleRubyWebServer::Config::Task.new(9000) do
|
23
|
+
n = nil
|
24
|
+
|
25
|
+
if size =~ /\s*(\d+)\s*,\s*(\d+)/
|
26
|
+
n = [ $1.to_i, $2.to_i > 0 ? $2.to_i : 1 ]
|
27
|
+
else
|
28
|
+
n = Integer( size.to_i )
|
29
|
+
n = n > 0 ? [ n ] : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
configuration[:thread_pool] = n
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
leftover_argv = []
|
38
|
+
|
39
|
+
begin
|
40
|
+
options.parse!(ARGV)
|
41
|
+
rescue OptionParser::InvalidOption => e
|
42
|
+
e.recover ARGV
|
43
|
+
leftover_argv << ARGV.shift
|
44
|
+
leftover_argv << ARGV.shift if ARGV.any? && ( ARGV.first[0..0] != '-' )
|
45
|
+
retry
|
46
|
+
end
|
47
|
+
|
48
|
+
ARGV.replace( leftover_argv ) if leftover_argv.any?
|
49
|
+
|
50
|
+
call_list
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Shellac
|
2
|
+
class Storage_engine
|
3
|
+
class Base
|
4
|
+
def initialize( args = {} )
|
5
|
+
@config = _default_args.merge( args )
|
6
|
+
@cache_control_thread = new_cache_control_thread
|
7
|
+
@cache_size = 0
|
8
|
+
@cache = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def _default_args
|
12
|
+
{
|
13
|
+
preload: {},
|
14
|
+
length_limit: 1000,
|
15
|
+
size_limit: 1024 * 1024 * 20,
|
16
|
+
trim_interval: 30
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def new_cache_control_thread
|
21
|
+
Thread.new do
|
22
|
+
sleep( @config[ :trim_interval ] )
|
23
|
+
|
24
|
+
while @cache_size > @config[ :size_limit ]
|
25
|
+
# Trim Cache -- stupid algorithm just randomly deletes things
|
26
|
+
# until it is small enough
|
27
|
+
sz = @cache.delete( @cache.keys[ rand( @cache.length ) ] ).to_s.length
|
28
|
+
@cache_size -= sz
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def []( k )
|
34
|
+
@cache[ k ]
|
35
|
+
end
|
36
|
+
|
37
|
+
def get( k )
|
38
|
+
self[ k ]
|
39
|
+
end
|
40
|
+
|
41
|
+
def []=( k, v )
|
42
|
+
@cache_size += v.to_s.length
|
43
|
+
@cache[ k ] = v
|
44
|
+
end
|
45
|
+
|
46
|
+
def set( k, v )
|
47
|
+
self[ k ] = v
|
48
|
+
end
|
49
|
+
|
50
|
+
def keys
|
51
|
+
@cache.keys
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete( k )
|
55
|
+
@cache.delete( k )
|
56
|
+
end
|
57
|
+
|
58
|
+
def length
|
59
|
+
@cache.length
|
60
|
+
end
|
61
|
+
alias_method :size, :length
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/shellac.rb
ADDED
data/shellacrb.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'shellac/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "shellacrb"
|
8
|
+
spec.version = Shellac::VERSION
|
9
|
+
spec.authors = ["Kirk Haines"]
|
10
|
+
spec.email = ["kirk-haines@cookpad.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A simple caching proxy, written in Ruby.}
|
13
|
+
spec.description = %q{A simple caching proxy, like Varnish, but...not. And written in Ruby.}
|
14
|
+
spec.homepage = "https://github.com/wyhaines/shellac"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
27
|
+
|
28
|
+
spec.add_runtime_dependency "puma", "~> 3.11"
|
29
|
+
spec.add_runtime_dependency "http", "~> 3.3"
|
30
|
+
spec.add_runtime_dependency "rack", "~> 2.0"
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shellacrb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kirk Haines
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-28 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.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
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: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: puma
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.11'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.11'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: http
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rack
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.0'
|
97
|
+
description: A simple caching proxy, like Varnish, but...not. And written in Ruby.
|
98
|
+
email:
|
99
|
+
- kirk-haines@cookpad.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".travis.yml"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE
|
108
|
+
- LICENSE.txt
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- bin/shellac
|
114
|
+
- lib/shellac.rb
|
115
|
+
- lib/shellac/application.rb
|
116
|
+
- lib/shellac/config.rb
|
117
|
+
- lib/shellac/config/task.rb
|
118
|
+
- lib/shellac/config/tasklist.rb
|
119
|
+
- lib/shellac/core.rb
|
120
|
+
- lib/shellac/storage_engine.rb
|
121
|
+
- lib/shellac/storage_engine/hash.rb
|
122
|
+
- lib/shellac/storage_engine/roma.rb
|
123
|
+
- lib/shellac/version.rb
|
124
|
+
- shellacrb.gemspec
|
125
|
+
homepage: https://github.com/wyhaines/shellac
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata: {}
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
requirements: []
|
144
|
+
rubyforge_project:
|
145
|
+
rubygems_version: 2.6.4
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: A simple caching proxy, written in Ruby.
|
149
|
+
test_files: []
|