rackdis 0.12.pre.beta

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: 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