rackdis 0.12.pre.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 75c6e66a488ac41277d59ce05e77de10be798f4e
4
+ data.tar.gz: 492d897a8e9593501e80b27d2d149be707b104ed
5
+ SHA512:
6
+ metadata.gz: 60a14fb0a27ba95a2e006b540d4e58590be7650729b849e33c3ca12763edef12d1785e537f3671095b226fe4aaae512cdd094e2d99001c84fe6139ed4d873927
7
+ data.tar.gz: 921643b6b898c0e72b4000233eeaa1312227201745c9bbf3042d90135394670aef9d329daed3717582ae17fba8d0aa7dd20bc7267a48350b23082b267a932da2
data/.gitignore ADDED
@@ -0,0 +1,14 @@
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
data/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ ## 0.12-beta
4
+
5
+ * refactored the API routes to dedup code, support POST commands
6
+ * added arg parser and response builder
7
+ * added a batch mode through a post request
8
+ * validated commands that come through the redis facade
9
+ * allowed running unsafe commands
10
+ * allowed force enabling commands
11
+ * made the config be setup better
12
+ * added some basic specs
13
+ * rescued all errors to return 500 (temporary)
14
+ * added logging with support for multiple locations/levels
15
+ * fixed option merging bugs
16
+ * added CORS support
17
+ * successfully tested chunked encoded subscribe manually
18
+
19
+ ## 0.6-beta
20
+
21
+ * used the proper rack commands in the executable script
22
+ * allowed options to be loaded from a config file
23
+ * allowed options to be specified a the command line
24
+ * added support for most set commands (missing `SSCAN`)
25
+ * added support for most list commands (missing blocking commands)
26
+ * added support for `MGET`
27
+ * added support for `PUBLISH`
28
+ * added support for `SUBSCRIBE` over a streaming connection
29
+
30
+ ## 0.0.1
31
+
32
+ * got the server running
33
+ * added GET and SET commands
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rackdis.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Robert McLeod
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,198 @@
1
+ # Rackdis
2
+
3
+ ![](http://i.imgur.com/CDKmIl1.png)
4
+
5
+ A Ruby clone of the awesome [Webdis](http://webd.is) server, allowing HTTP access to a Redis server through an API.
6
+
7
+ Webdis is awesome, but it hasn't had much action lately and there are lots of feature requests and bug reports that haven't been addressed. I would love to contribute but I have no desire in learning C at this stage. I also think Ruby is awesome so I wanted to try cloning Webdis in Ruby. Most of the stuff is already written for me, so this project is mostly glue code:
8
+
9
+ * [Grape](https://github.com/intridea/grape) provides the micro framework necessary to build an API
10
+ * [Slop](https://github.com/leejarvis/slop) awesome command line options
11
+ * [Rack::Stream](https://github.com/intridea/rack-stream) provides the websockets and chunked encoding support
12
+ * [Rack::Cors](https://github.com/cyu/rack-cors) provides cross origin resource sharing
13
+ * [Redis-rb](https://github.com/redis/redis-rb) provides the interface to a redis server
14
+ * [Thin](https://github.com/macournoyer/thin/) serves it all through rack
15
+
16
+ Most commands should work, there are a few left unsupported. However this code is mostly untested, with specs forthcoming so stuff might not work quite right yet (hence the `-beta`). I still have work to do in the Argument Parser and Response Builder area so commands that differ from the classic `COMMAND key [arg]` pattern may not work yet. In this situation you would see a `NotImplementError` reported.
17
+
18
+ **Please see the issues for feature planning and a vauge roadmap.**
19
+
20
+ ## Features
21
+
22
+ * Most commands supported
23
+ * Unsafe commands disabled by default
24
+ * Pub/sub over streaming connections (chunked encoding/websockets)
25
+ * Batch processing of multiple redis commands (MULTI/EXEC anyone?)
26
+ * Logging to multiple locations and levels
27
+ * Cross Origin Resource Sharing
28
+ * Flexible command line operation
29
+ * Configuration priority: command line > config file > hard coded defaults
30
+
31
+ ## Todo
32
+
33
+ * more relevant HTTP codes
34
+ * more specs
35
+ * figure out what MULTI/EXEC is and how to support it
36
+ * a lower level batch mode
37
+
38
+ ## Installation
39
+
40
+ **Sorry I haven't actually pushed it to rubygems yet**
41
+
42
+ Add this line to your application's Gemfile:
43
+
44
+ ```ruby
45
+ gem 'rackdis'
46
+ ```
47
+
48
+ And then execute:
49
+
50
+ $ bundle
51
+
52
+ Or install it yourself as:
53
+
54
+ $ gem install rackdis
55
+
56
+ ## Usage
57
+
58
+ The help screen is a good place to start:
59
+
60
+ ```sh
61
+ $ rackdis --help
62
+ Usage: rackdis [options]
63
+ -r, --redis The redis server location (127.0.0.1, :6379, 127.0.0.1:6379)
64
+ -c, --config Config file to load options from
65
+ -p, --port Port for rackdis to bind to
66
+ -a, --address Address for rackdis to bind to
67
+ -d, --daemonize Put rackdis in the background
68
+ --log_level Log level (shutup, error, warn, debug, info)
69
+ -l, --log Log location (- or stdout for stdout, stderr)
70
+ --db The redis db to use (a number 0 through 16)
71
+ --allow_unsafe Enable unsafe commands (things like flushdb) !CAREFUL!
72
+ --force_enable Comma separated list of commands to enable !CAREFUL!
73
+ --allow_batching Allows batching of commands through a POST request
74
+ -h, --help Display this help message.
75
+ ```
76
+
77
+ All of these commands are optional. The hard coded configuration has sane defaults. Specifying a configuration file would override those defaults. Specifying a command line options from above overrides everything. Speaking of configuration files, here's what one looks like:
78
+
79
+ ```yml
80
+ ---
81
+ :port: 7380
82
+ :address: 127.0.0.1
83
+ :daemonize: false
84
+ :db: 0
85
+ :log: stdout
86
+ :log_level: info
87
+ :allow_unsafe: false
88
+ :force_enable: false
89
+ :allow_batching: false
90
+ :redis: 127.0.0.1:6379
91
+ ```
92
+
93
+ ### Running it
94
+
95
+ Fully fledged example:
96
+
97
+ ```sh
98
+ rackdis -p 7379 -a 127.0.0.1 -d --allow_batching --allow_unsafe -l stderr --log_level debug --redis :6379 --db 4 --force_enable flushdb,move,migrate,select
99
+ ```
100
+
101
+ Simply load from a config file:
102
+
103
+ ```sh
104
+ rackdis -c config.yml
105
+ ```
106
+
107
+ ### Call and response
108
+
109
+ The pattern looks like this: `http://localhost:7379/:version/:command/*args`
110
+
111
+ * **version** is just the API version (v1)
112
+ * **command** is the redis command to run
113
+ * everything after that is arguments to the redis command
114
+
115
+ The response is in JSON and provides some basic information about the request and including the result (of course).
116
+
117
+ So if I want to get a range from a list: `http://localhost:7380/v1/lrange/shopping_list/4/10`
118
+
119
+ ```json
120
+ {
121
+ "success":true,
122
+ "command":"LRANGE",
123
+ "key":"shopping_list",
124
+ "result":[ "milk", "chicken abortions", "prophylactics"]
125
+ }
126
+ ```
127
+
128
+ Add some members to a set: `http://localhost:7380/v1/sadd/cars/toyota/nissan/dodge`
129
+
130
+ ```json
131
+ {
132
+ "success":true,
133
+ "command":"SADD",
134
+ "key":"cars",
135
+ "result":"OK"
136
+ }
137
+ ```
138
+
139
+ Or just get a value: `http://localhost:7380/v1/get/readings:ph`
140
+
141
+ ```json
142
+ {
143
+ "success":true,
144
+ "command":"GET",
145
+ "key":"readings:ph",
146
+ "result":"7.4"
147
+ }
148
+ ```
149
+
150
+ You get the gist.
151
+
152
+ ### Batching
153
+
154
+ Batching allows you to get redis to execute a bunch of commands one after the other
155
+
156
+ ```javascript
157
+ $.post(
158
+ "http://localhost:7380/v1/batch",
159
+ {
160
+ commands: [
161
+ "SET this that",
162
+ "INCR rickrolls",
163
+ "SUNIONSTORE anothermans:treasure onemans:trash anothermans:treasure"
164
+ ]
165
+ }
166
+ )
167
+ ```
168
+
169
+ That will return this JSON:
170
+
171
+ ```json
172
+ ["OK", 45, "OK"]
173
+ ```
174
+
175
+ You will need to enable batching explicitly at command line or in the config file.
176
+
177
+ ### Pub/Sub
178
+
179
+ Publish/Subscribe is supported over chunked encoding and websockets.
180
+
181
+ More details forthcoming.
182
+
183
+ Subscribe: `http://localhost:7380/v1/subscribe/messages`
184
+
185
+ Publish: `http://localhost:7380/v1/publish/messages/hi`
186
+
187
+ ## Changelog
188
+
189
+ Please see the file `CHANGELOG.md`
190
+
191
+ ## Contributing
192
+
193
+ 1. Fork it ( https://github.com/[my-github-username]/rackdis/fork )
194
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
195
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
196
+ 4. Push to the branch (`git push origin my-new-feature`)
197
+ 5. Create a new Pull Request
198
+ 6. If it's all good I'll merge it
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rackdis"
3
+
4
+ task :routes do
5
+ puts Rackdis::API::routes
6
+ end
data/bin/rackdis ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rackdis'
4
+ require 'rack'
5
+ require 'slop'
6
+
7
+ opts = Slop.new help: true, strict: true, indent: 2 do
8
+ banner "Usage: rackdis [options]"
9
+
10
+ on :r, :redis=, "The redis server location (127.0.0.1, :6379, 127.0.0.1:6379)"
11
+ on :c, :config=, "Config file to load options from"
12
+ on :p, :port=, "Port for rackdis to bind to"
13
+ on :a, :address=, "Address for rackdis to bind to"
14
+ on :d, :daemonize, "Put rackdis in the background"
15
+ on :log_level=, "Log level (shutup, error, warn, debug, info)"
16
+ on :l, :log=, "Log location (- or stdout for stdout, stderr)"
17
+ on :db=, "The redis db to use (a number 0 through 16)"
18
+ on :allow_unsafe, "Enable unsafe commands (things like flushdb) !CAREFUL!"
19
+ on :force_enable=, "Comma separated list of commands to enable !CAREFUL!", as: Array
20
+ on :allow_batching, "Allows batching of commands through a POST request"
21
+ end
22
+
23
+ # Show any errors with the given options
24
+ begin
25
+ opts.parse!
26
+ Rackdis.configure(opts.to_hash)
27
+ rescue Slop::Error, ArgumentError => e
28
+ puts "ERROR: #{e.message}"
29
+ puts opts
30
+ abort
31
+ end
32
+
33
+ app = Rack::Builder.new do
34
+ use Rack::Stream
35
+ use Rack::Cors do
36
+ allow do
37
+ origins '*'
38
+ resource '*', headers: :any, methods: :get
39
+ end
40
+ end
41
+ run Rackdis::API.new
42
+ end
43
+
44
+ Rack::Handler::Thin.run(app, Rackdis.rack_options)
data/etc/config.yml ADDED
@@ -0,0 +1,11 @@
1
+ ---
2
+ :port: 9342
3
+ :address: 127.0.0.1
4
+ :daemonize: false
5
+ :db: 0
6
+ :log: /dev/null
7
+ :log_level: shutup
8
+ :allow_unsafe: false
9
+ :force_enable:
10
+ :allow_batching: false
11
+ :redis: "127.0.0.1:6379"
@@ -0,0 +1,62 @@
1
+ module Rackdis
2
+ class API < Grape::API
3
+
4
+
5
+ rescue_from :all do |e|
6
+ Rackdis.logger.error("#{e.class.name}: #{e.message}")
7
+ Rack::Response.new({ success: false, error: e.message }.to_json, 500)
8
+ end
9
+
10
+ version 'v1'
11
+ format :json
12
+
13
+ helpers do
14
+ include Rack::Stream::DSL
15
+
16
+ def redis
17
+ @redis ||= RedisFacade.new(Rackdis.redis_client, Rackdis.config, Rackdis.logger)
18
+ end
19
+
20
+ def do_subscribe
21
+ after_open do
22
+ Rackdis.logger.debug "Someone subscribed to #{params[:channel]}"
23
+ redis.redis.subscribe params[:channel] do |on|
24
+ on.message do |channel, msg|
25
+ Rackdis.logger.debug "Pushing: #{msg}"
26
+ chunk msg
27
+ end
28
+ end
29
+ end
30
+
31
+ status 200
32
+ header 'Content-Type', 'application/json'
33
+ ""
34
+ end
35
+
36
+ def args
37
+ params[:args].split("/")
38
+ end
39
+ end
40
+
41
+ # Subscribe
42
+ get 'subscribe/:channel' do
43
+ do_subscribe
44
+ end
45
+
46
+ get 'SUBSCRIBE/:channel' do
47
+ do_subscribe
48
+ end
49
+
50
+ post 'batch' do
51
+ redis.batch params[:commands]
52
+ end
53
+
54
+ get ':command/*args' do
55
+ redis.call params[:command], args
56
+ end
57
+
58
+ post ':command/*args' do
59
+ redis.call params[:command], args
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,29 @@
1
+ module Rackdis
2
+ class ArgumentParser
3
+ def initialize(command)
4
+ @command = command.to_sym
5
+ end
6
+
7
+ def process(args)
8
+ case @command
9
+ when :lpush, :rpush, :sadd, :srem, :sunionstore, :sinterstore, :sdiffstore
10
+ [
11
+ args[0],
12
+ args[1..-1]
13
+ ]
14
+ when :mget, :sunion, :sinter, :sdiff
15
+ [
16
+ args[0..-1]
17
+ ]
18
+ when :publish
19
+ [
20
+ args[0],
21
+ args[1..-1].join("/")
22
+ ]
23
+ else
24
+ args
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,88 @@
1
+ module Rackdis
2
+ class Config
3
+
4
+ def self.defaults
5
+ {
6
+ port: 7380,
7
+ address: "0.0.0.0",
8
+ daemonize: false,
9
+ log: STDOUT,
10
+ log_level: "info",
11
+ db: 0,
12
+ allow_unsafe: false,
13
+ force_enable: [],
14
+ allow_batching: false,
15
+ redis: "127.0.0.1:6379"
16
+ }
17
+ end
18
+
19
+ def initialize(opts)
20
+ @config = Config.defaults
21
+ process_options opts
22
+ post_process
23
+ end
24
+
25
+ def [](key)
26
+ @config[key]
27
+ end
28
+
29
+ private
30
+
31
+ def process_options(opts)
32
+ load_from_file opts[:config]
33
+
34
+ # We want to merge the command line opts
35
+ # overtop of the config file options but
36
+ # only if they are set because when they
37
+ # are not set they are nil which ends up
38
+ # wiping out all of the config
39
+ opts.each do |key, value|
40
+ next if value.nil?
41
+ @config[key] = value
42
+ end
43
+ end
44
+
45
+ def load_from_file(file)
46
+ return false if file.nil?
47
+ raise ArgumentError, "Invalid config file: #{file}" unless File.exist? file
48
+ @config.merge!(YAML.load_file(file))
49
+ end
50
+
51
+ def post_process
52
+ process_log
53
+ process_redis
54
+ end
55
+
56
+ def process_log
57
+ @config[:log] = case @config[:log]
58
+ when nil, "", " ", "no", "none"
59
+ "/dev/null"
60
+ when "stdout", "-", "STDOUT"
61
+ STDOUT
62
+ when "stderr", "STDERR"
63
+ STDERR
64
+ else
65
+ @config[:log]
66
+ end
67
+
68
+ @config[:log] = "/dev/null" if @config[:log_level] == "shutup"
69
+ end
70
+
71
+ def process_redis
72
+ if @config[:redis].include? ":"
73
+ parts = @config[:redis].split(":")
74
+ port = parts.last
75
+ host = parts.first
76
+ else
77
+ host = @config[:redis]
78
+ end
79
+
80
+ host = "127.0.0.1" if host.nil? or host.empty?
81
+ port = 6379 if port.nil? or port.empty?
82
+
83
+ @config[:redis_port] = port
84
+ @config[:redis_host] = host
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,118 @@
1
+ module Rackdis
2
+ class RedisFacade
3
+ attr_reader :redis
4
+
5
+ def initialize(redis, config, log)
6
+ @redis = redis
7
+ @config = config
8
+ @log = log
9
+ end
10
+
11
+ def call(command, args)
12
+ command.downcase!
13
+ valid_command! command
14
+
15
+ @log.info("redis> #{command} #{args.join(' ')}")
16
+ args = Rackdis::ArgumentParser.new(command).process(args)
17
+
18
+ @log.debug("API => REDIS: "+{command: command, args: args}.inspect)
19
+
20
+ begin
21
+ result = @redis.send(command, *args)
22
+ rescue ArgumentError
23
+ raise NotImplementedError, "Oops sorry - too beta - this needs fixing. A bug report at https://github.com/penguinpowernz/rackdis/issues would be nice :)"
24
+ end
25
+
26
+ return Rackdis::ResponseBuilder.new(command).process(args, result)
27
+ end
28
+
29
+ def batch(commands)
30
+ raise ArgumentError, "Batching is disabled" unless @config[:allow_batching]
31
+
32
+ # Try to extract individual commands
33
+ begin
34
+ commands = JSON.parse(commands)
35
+ rescue JSON::ParserError
36
+ raise ArgumentError, "Invalid batch commands" unless commands.is_a? String
37
+ commands = commands.split("\n")
38
+ end
39
+
40
+ raise ArgumentError, "Invalid batch commands" unless commands.is_a? Array
41
+
42
+ # Check each command and their arguments
43
+ calls = []
44
+ commands.each do |line|
45
+ args = line.split(" ")
46
+ cmd = args.shift.downcase.to_sym
47
+
48
+ # bail out if any of the commands or args are bad
49
+ valid_command!(cmd)
50
+ args = Rackdis::ArgumentParser.new(cmd).process(args)
51
+
52
+ calls << [cmd, args]
53
+ end
54
+
55
+ # Everything passed to run all the commands now
56
+ calls.collect do |call|
57
+ result = @redis.send(*call)
58
+ Rackdis::ResponseBuilder.new(cmd).process(args, result)
59
+ end
60
+ end
61
+
62
+ # Pub/Sub
63
+
64
+ def publish(channel, message)
65
+ @redis.publish(channel, message)
66
+ { success: true, command: :PUBLISH, channel: channel }
67
+ end
68
+
69
+ private
70
+
71
+ def valid_command!(cmd)
72
+ raise ArgumentError, "Unsupported command: #{command}" unless valid_command?
73
+ end
74
+
75
+ def valid_command?(cmd)
76
+ safe_commands.include?(cmd) or
77
+ (@config[:allow_unsafe] and unsafe_commands.include?(cmd)) or
78
+ (@config[:force_enable] and @config[:force_enable].include?(cmd))
79
+ end
80
+
81
+ def safe_commands
82
+ [
83
+ :ping, :echo, :dbsize, :time,
84
+ :persist, :expire, :expireat, :ttl, :pexpire, :pexpireat, :pttl, :dump, :restore,
85
+ :set, :setex, :psetex, :setnx, :mset, :msetnx, :setrange, :append,
86
+ :get, :getset, :mget, :getrange,
87
+ :getbit, :setbit, :bitcount, :bitop, :bitpos,
88
+ :incr, :incrby, :incrbyfloat, :decr, :decrby, :del, :exists, :keys, :randomkey, :rename, :renamex, :sort, :type, :strlen, :scan,
89
+ :lpush, :lpushx, :rpush, :rpushx, :lrange, :lindex, :linsert, :llen, :lpop, :rpop, :rpoplpush, :lrem, :lset, :ltrim,
90
+ :sadd, :scard, :smembers, :sismember, :srem, :sinter, :sinterstore, :sdiff, :sdiffstore, :sunion, :sunionstore, :spop, :srandmember, :smove, :sscan,
91
+ :zcard, :zadd, :zincrby, :zrem, :zscore, :zrange, :zrevrange, :zrank, :zrevrank, :zremrangebyrank, :zrangebylex, :zrangebyscore, :zrevrangebyscore, :zremrangebyscore, :zcount, :zinterstore, :zunionstore, :zsan,
92
+ :hlen, :hset, :hsetnx, :hmset, :hget, :hmget, :hdel, :hexists, :hincrby, :hincrbyfloat, :hkeys, :hvals, :hgetall, :hscan,
93
+ :publish,
94
+ :pfadd, :pfcount, :pfmerge
95
+ ]
96
+ end
97
+
98
+ def unsafe_commands
99
+ [
100
+ :auth, :info, :slowlog,
101
+ :bgrewriteaof, :bgsave, :lastsave, :save,
102
+ :config, :flushall, :flushdb,
103
+ :move, :migrate, :select, :slaveof,
104
+ :script, :eval, :evalsha
105
+ ]
106
+ end
107
+
108
+ # TODO: investigate and support these
109
+ # def unsupported_commmands
110
+ # [
111
+ # :monitor,
112
+ # :unsubscribe, :psubscribe, :punsubscribe,
113
+ # :brpop, :blpop, :brpoplpush
114
+ # ]
115
+ # end
116
+
117
+ end
118
+ end
@@ -0,0 +1,22 @@
1
+ module Rackdis
2
+ class ResponseBuilder
3
+ def initialize(command)
4
+ @command = command.to_sym
5
+ end
6
+
7
+ def process(args, result)
8
+ response_hash = {
9
+ command: @command.to_s.upcase,
10
+ result: result
11
+ }
12
+
13
+ case @command
14
+ when :sinterstore
15
+ else
16
+ response_hash[:key] = args[0]
17
+ end
18
+
19
+ response_hash
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module Rackdis
2
+ VERSION = "0.12-beta"
3
+ end
data/lib/rackdis.rb ADDED
@@ -0,0 +1,76 @@
1
+ require "logger"
2
+ require "yaml"
3
+ require "json"
4
+ require "redis"
5
+ require "grape"
6
+ require "redis/connection/synchrony"
7
+ require "rack/stream"
8
+ require "rack/cors"
9
+
10
+ require "rackdis/version"
11
+ require "rackdis/api"
12
+ require "rackdis/redis_facade"
13
+ require "rackdis/config"
14
+ require "rackdis/argument_parser"
15
+ require "rackdis/response_builder"
16
+
17
+ module Rackdis
18
+ class << self
19
+ attr_reader :config
20
+
21
+ def configure(**opts)
22
+ @config = Rackdis::Config.new(opts)
23
+ end
24
+
25
+ def rack_options
26
+ {
27
+ Port: @config[:port],
28
+ Host: @config[:address],
29
+ daemonize: @config[:daemonize]
30
+ }
31
+ end
32
+
33
+ def redis_options
34
+ {
35
+ host: @config[:redis_host],
36
+ port: @config[:redis_port],
37
+ db: @config[:db] || 0
38
+ }
39
+ end
40
+
41
+ def redis_client
42
+ Redis.new redis_options
43
+ end
44
+
45
+ def allow_unsafe_commands?
46
+ @config[:unsafe] || false
47
+ end
48
+
49
+ def logger
50
+ @logger ||= create_logger
51
+ end
52
+
53
+ def create_logger
54
+ logger = Logger.new @config[:log] || STDOUT
55
+
56
+ if @config
57
+ logger.level = case @config[:log_level]
58
+ when "debug"
59
+ Logger::DEBUG
60
+ when "info"
61
+ Logger::INFO
62
+ when "error"
63
+ Logger::ERROR
64
+ when "warn"
65
+ Logger::WARN
66
+ else
67
+ Logger::UNKNOWN # shutup!
68
+ end
69
+ else
70
+ logger.log_level = Logger::UNKNOWN
71
+ end
72
+
73
+ logger
74
+ end
75
+ end
76
+ end
data/rackdis.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rackdis/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rackdis"
8
+ spec.version = Rackdis::VERSION
9
+ spec.authors = ["Robert McLeod"]
10
+ spec.email = ["robert@penguinpower.co.nz"]
11
+ spec.summary = %q{Ruby clone of Webdis Redis web API}
12
+ spec.description = %q{A Ruby clone of the awesome Webdis Redis web API}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "rack"
22
+ spec.add_dependency "hiredis"
23
+ spec.add_dependency "redis"
24
+ spec.add_dependency "grape"
25
+ spec.add_dependency "rack-stream"
26
+ spec.add_dependency "em-synchrony"
27
+ spec.add_dependency "slop"
28
+ spec.add_dependency "thin"
29
+ spec.add_dependency "rack-cors"
30
+
31
+ spec.add_development_dependency "bundler", "~> 1.7"
32
+ spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.add_development_dependency "rspec"
34
+ spec.add_development_dependency "rack-test"
35
+ spec.add_development_dependency "pry"
36
+ end
data/spec/api_spec.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rackdis::API do
4
+ include Rack::Test::Methods
5
+
6
+ subject(:response) { j2o last_response.body }
7
+ let(:r) { redis_connection }
8
+ let(:key) { "test:hello" }
9
+ after(:each) { r.del key }
10
+
11
+ describe "Key operations" do
12
+
13
+ describe "GET" do
14
+ it "should give null for unset values" do
15
+ get "/v1/get/#{key}"
16
+ expect(response.result).to eq nil
17
+ end
18
+
19
+ it "should get a value that is set" do
20
+ r.set(key, "world")
21
+ # pry
22
+ get "/v1/get/#{key}"
23
+ expect(response.result).to eq "world"
24
+ end
25
+
26
+ end
27
+
28
+ describe "SET" do
29
+ it "should set a value" do
30
+ get "/v1/set/#{key}/world"
31
+ expect(r.get(key)).to eq "world"
32
+ end
33
+
34
+ it "should override a key" do
35
+ get "/v1/set/#{key}/world"
36
+ get "/v1/set/#{key}/bob"
37
+ expect(r.get(key)).to eq "bob"
38
+ end
39
+ end
40
+
41
+ describe "INCR" do
42
+ it "should increment a value" do
43
+ get "/v1/incr/#{key}"
44
+ get "/v1/incr/#{key}"
45
+ get "/v1/incr/#{key}"
46
+ expect(r.get key).to eq "3"
47
+ end
48
+
49
+ it "should return the incremented value" do
50
+ get "/v1/incr/#{key}"
51
+ get "/v1/incr/#{key}"
52
+ get "/v1/incr/#{key}"
53
+ expect(response.result).to eq 3
54
+ end
55
+ end
56
+ end
57
+
58
+ describe "Sets operations" do
59
+
60
+ describe "SMEMBERS" do
61
+
62
+ context "when there are no memebers" do
63
+ it "should return an empty array" do
64
+ get "/v1/smembers/#{key}"
65
+ expect(response.result).to eq []
66
+ end
67
+ end
68
+
69
+ context "when there are members" do
70
+ it "should return them" do
71
+ r.sadd key, "world"
72
+ get "/v1/smembers/#{key}"
73
+ expect(response.result).to eq ["world"]
74
+ end
75
+ end
76
+
77
+ end
78
+
79
+ describe "SADD" do
80
+
81
+ it "should add single member" do
82
+ expect { get "/v1/sadd/#{key}/world" }.to change { r.scard key }.by(1)
83
+ end
84
+
85
+ it "should add multiple members" do
86
+ expect { get "/v1/sadd/#{key}/tom/dick/harry" }.to change { r.scard key }.by(3)
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rackdis::ArgumentParser do
4
+ subject(:parser) { Rackdis::ArgumentParser.new(command) }
5
+
6
+
7
+ describe "PUBLISH" do
8
+ let(:command) { "publish" }
9
+
10
+ it "should not modify the correct number of args" do
11
+ inn = ["channel", "hi there"]
12
+ out = parser.process(inn)
13
+ expect(out).to eq inn
14
+ end
15
+
16
+ it "should join the message args together" do
17
+ inn = ["channel", "she was about 18", "19"]
18
+ out = parser.process(inn)
19
+ expect(out.size).to eq 2
20
+ expect(out[1]).to eq "she was about 18/19"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'rack/test'
2
+ require 'pry'
3
+ require 'ostruct'
4
+ require 'bundler/setup'
5
+ Bundler.setup
6
+
7
+ require 'rackdis'
8
+
9
+ # Set test options
10
+ Rackdis.configure(db: 13)
11
+
12
+ # give a seperate redis connection to the same
13
+ # database but use the hiredis driver to stop
14
+ # errors about the evma server
15
+ def redis_connection
16
+ Redis.new driver: :hiredis, db: 13
17
+ end
18
+
19
+ # Helpers for testing the action api
20
+
21
+ def app
22
+ Rackdis::API
23
+ end
24
+
25
+ def j2o(json)
26
+ OpenStruct.new(JSON.parse(json))
27
+ end
metadata ADDED
@@ -0,0 +1,263 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rackdis
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.12.pre.beta
5
+ platform: ruby
6
+ authors:
7
+ - Robert McLeod
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hiredis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: redis
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: grape
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-stream
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: em-synchrony
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: slop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: thin
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rack-cors
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: bundler
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.7'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.7'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rake
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '10.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '10.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rspec
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: rack-test
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: pry
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
+ description: A Ruby clone of the awesome Webdis Redis web API
210
+ email:
211
+ - robert@penguinpower.co.nz
212
+ executables:
213
+ - rackdis
214
+ extensions: []
215
+ extra_rdoc_files: []
216
+ files:
217
+ - ".gitignore"
218
+ - CHANGELOG.md
219
+ - Gemfile
220
+ - LICENSE.txt
221
+ - README.md
222
+ - Rakefile
223
+ - bin/rackdis
224
+ - etc/config.yml
225
+ - lib/rackdis.rb
226
+ - lib/rackdis/api.rb
227
+ - lib/rackdis/argument_parser.rb
228
+ - lib/rackdis/config.rb
229
+ - lib/rackdis/redis_facade.rb
230
+ - lib/rackdis/response_builder.rb
231
+ - lib/rackdis/version.rb
232
+ - rackdis.gemspec
233
+ - spec/api_spec.rb
234
+ - spec/lib/rackdis/argument_parser_spec.rb
235
+ - spec/spec_helper.rb
236
+ homepage: ''
237
+ licenses:
238
+ - MIT
239
+ metadata: {}
240
+ post_install_message:
241
+ rdoc_options: []
242
+ require_paths:
243
+ - lib
244
+ required_ruby_version: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - ">="
247
+ - !ruby/object:Gem::Version
248
+ version: '0'
249
+ required_rubygems_version: !ruby/object:Gem::Requirement
250
+ requirements:
251
+ - - ">"
252
+ - !ruby/object:Gem::Version
253
+ version: 1.3.1
254
+ requirements: []
255
+ rubyforge_project:
256
+ rubygems_version: 2.2.2
257
+ signing_key:
258
+ specification_version: 4
259
+ summary: Ruby clone of Webdis Redis web API
260
+ test_files:
261
+ - spec/api_spec.rb
262
+ - spec/lib/rackdis/argument_parser_spec.rb
263
+ - spec/spec_helper.rb