shellacrb 0.1.0

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: 20bc93cc9afb84671752476bcdb5a5ed9714da7b
4
+ data.tar.gz: bea16b6892d3191b2d584950342497143ccdf220
5
+ SHA512:
6
+ metadata.gz: 80171b685c351d84e9f592dc34110a865ecc0262eaafbd00f7de1aaaf7fadc1ddbf02c0254aa208141192417a7a2f82bf187606e7877ba58394a02762adf0088
7
+ data.tar.gz: ce091833fc6f9e7268816500b4cf5a420b97675fe1fe47854db3eaee6cf8a306e97c1123a63bc487657e9d37d0d4319cffb2c3302ce3a117639b6689a143b0e0
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.3
5
+ before_install: gem install bundler -v 1.14.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in shellac.gemspec
4
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/shellac ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'shellac'
4
+
5
+ puts "Leftover: #{ARGV.inspect}"
6
+
7
+ Shellac::Launcher.run
@@ -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,6 @@
1
+ module Shellac
2
+ class ConfigClass
3
+ class TaskList < Array
4
+ end
5
+ end
6
+ 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
@@ -0,0 +1,6 @@
1
+ require 'http'
2
+ require 'puma/cli'
3
+ require 'rack'
4
+ require 'shellac/version'
5
+ require 'shellac/application'
6
+ require 'shellac/config'
@@ -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
@@ -0,0 +1,3 @@
1
+ module Shellac
2
+ VERSION = "0.1.0"
3
+ end
data/lib/shellac.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'shellac/core'
2
+
3
+ module Shellac
4
+ end
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: []