Skip to content

Instantly share code, notes, and snippets.

@Keoven

Keoven/README.md Secret

Created August 30, 2011 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Keoven/315290e1e3f7deb0e90f to your computer and use it in GitHub Desktop.
Save Keoven/315290e1e3f7deb0e90f to your computer and use it in GitHub Desktop.
Key/value stores Codebrawl Entry: Ruby Evented Database

RuEDB (Ruby Evented Database)

Mainly based it out of redis. This basically supports basic key/value store that is accessible through through a REST API. It also stores it as yaml and has the option to encrypt the database given a key. (Although that needs manually changing the file atm.)

Environment:

  • ruby-1.9.2-p290
  • eventmachine
  • yajl
  • gibberish

Sample Usage:

# Run Server
$ ruby server.rb
Server started, Ruedb version 0.1.0
The server is now ready to accept connections on port 6187

# In another terminal
$ curl http://127.0.0.1:6187/values/name
$ curl http://127.0.0.1:6187/values/name -d "Some Value Here"
$ curl http://127.0.0.1:6187/values/name
$ curl http://127.0.0.1:6187/keys
$ curl -X DELETE http://127.0.0.1:6187/values/name

# I'm unsure though why curl returns "curl: (52) Empty reply from server" 
# when value is just one word. Value expected returns fine in the rest console I use
# in Google Chrome
require 'singleton'
require 'gibberish'
require 'yajl'
module Ruedb
class Database
include Singleton
class << self
alias_method :connection, :instance
def use(database = :default, key = nil)
dump_database! if @database
@database = get_path(database)
@key = key
@@value_store, @@key_store = decrypt(key, @database)
end
def decrypt(key, database)
return [Hash.new, Array.new] unless(File.exist?(database))
file = File.open(database)
raw_data = key ? Gibberish::AES.new(key).dec(file.read) : file.read
file.close
Yajl::Parser.new.parse(raw_data)
end
def dump_database!
string = Yajl::Encoder.encode([@@value_store, @@key_store])
encrypted_data = @key ? Gibberish::AES.new(@key).enc(string) : string
file = File.open(@database, 'w+')
file.write(encrypted_data)
file.close
end
def get_path(database)
return File.join('/', 'tmp', '.ruedb_store') if database.eql?(:default)
# Check Ruedb::Config
#return database
end
end
def initialize
# Initialize Values
end
def key_store ; @@key_store ; end
def value_store; @@value_store; end
def set(key, value)
key_store.push(key)
length, library_key, unique_key = setup_values(key)
setup_length(length)
setup_library_key(length, library_key)
value_store[length][library_key][unique_key] = value.to_s
end
def get(key)
length, library_key, unique_key = setup_values(key)
value_store[length][library_key][unique_key] || 'Key Not Found'
rescue
'Key Not Found'
end
def delete(key)
key_store.delete(key)
length, library_key, unique_key = setup_values(key)
value = value_store[length][library_key].delete(unique_key)
value ? {key => value} : 'Key Not Found'
rescue
'Key Not Found'
end
def keys
key_store
end
def clear
# TODO
end
private
def setup_values(key)
key, length = key.to_s, key.length
mid_length = length / 2
library_key = generate_library_key(mid_length, key)
unique_key = generate_unique_key(mid_length, length, key)
[length.to_s, library_key.to_s, unique_key.to_s]
end
def generate_library_key(mid_length, key)
key.slice(0, mid_length)
end
def generate_unique_key(mid_length, length, key)
unique_key = key.slice(mid_length, length);
end
def setup_length(length)
value_store[length] ||= Hash.new
end
def setup_library_key(length, library_key)
value_store[length][library_key] ||= Hash.new
end
end
end
$:.push File.expand_path("../", __FILE__)
require 'eventmachine'
require 'socket'
require 'database'
require 'version'
module Ruedb
module Server
def post_init
@port, @ip = Socket.unpack_sockaddr_in(get_peername)
puts "Client from #{@ip}:#{@port} connected (currently serving #{EM.connection_count} clients)"
end
def receive_data(data)
EM.defer(proc {
match_data = data.match(/^(PUT|GET|POST|DELETE)\s([^\s]*).*\r\n\r\n(.*)/mx)
raw_data, http_method, path, body = match_data.to_a
route_path(http_method, path, body)
}, proc { |result|
puts "Result: #{result.inspect}"
send_data(result)
close_connection_after_writing
})
end
def unbind
puts "Client from #{@ip}:#{@port} disconnected"
end
def route_path(http_method, path, body)
case path
when /^\/values\/([^\/]*)\/?$/
execute(http_method, $1, body)
when /^\/keys\/?$/
Ruedb::Database.connection.keys
end
end
def execute(http_method, key, value = nil)
case http_method
when 'POST'
puts "Store value #{value} into key #{key}"
Ruedb::Database.connection.set(key, value)
when 'DELETE'
puts "Delete store with #{key}"
Ruedb::Database.connection.delete(key)
when 'GET'
puts "Get value with key #{key}"
Ruedb::Database.connection.get(key)
end
end
end
end
begin
Ruedb::Database.use(:default)
EventMachine.run do
port = 6187
EventMachine::add_periodic_timer(5) do
Ruedb::Database.dump_database!
end
EventMachine.start_server '127.0.0.1', port, Ruedb::Server
puts "Server started, Ruedb version #{Ruedb::VERSION}"
puts "The server is now ready to accept connections on port #{port}"
end
rescue SystemExit, Interrupt
puts "# User requested shutdown..."
puts "* Saving database snapshot before exiting."
Ruedb::Database.dump_database!
puts "* DB saved on disk"
puts "RuEDB is now ready to exit, thanks!"
rescue Yajl::ParseError
puts "Invalid Key to Access Database!"
end
@dunedain289
Copy link

  • points for periodic save, - lots of points for hand-parsing http (Sinatra + Thin can run under EventMachine)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment