Skip to content

Instantly share code, notes, and snippets.

@joaodrp
Created January 6, 2012 19:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save joaodrp/1571976 to your computer and use it in GitHub Desktop.
Save joaodrp/1571976 to your computer and use it in GitHub Desktop.
# Simple web service that looks for an image metadata record on a MySQL db given its ID and sends it back in JSON
#
# Ruby 1.9.3
# All gems updated to the most recent version
# MBP i5 8gb Lion
#
# See source code above, combo is:
# Sinatra + Thin:
# - mysql_db.rb # mysql2 api class
# - sinatra_thin_server.rb
#
# Goliath:
# - em_mysql_db.rb # em-mysql2 api class
# - goliath_server.rb
#
# Database setup:
#
$ mysql -u root
CREATE DATABASE mydb;
CREATE USER 'mydb'@'localhost' IDENTIFIED BY 'mydb';
SET PASSWORD FOR 'mydb'@'localhost' = PASSWORD('passwd');
GRANT ALL PRIVILEGES ON mydb.* TO 'mydb'@'localhost';
# create table
CREATE TABLE IF NOT EXISTS `mydb`.`images` (
`_id` VARCHAR(36) NOT NULL ,
`name` VARCHAR(45) NOT NULL ,
`access` VARCHAR(45) NOT NULL ,
`type` VARCHAR(45) NULL ,
`format` VARCHAR(45) NULL ,
`owner` VARCHAR(45) NULL ,
`status` VARCHAR(45) NULL ,
`size` INT NULL ,
`created_at` DATETIME NULL ,
`accessed_at` DATETIME NULL ,
`access_count` INT NULL DEFAULT 0 ,
`checksum` VARCHAR(255) NULL ,
`others` VARCHAR(255) NULL,
PRIMARY KEY (`_id`) )
ENGINE = InnoDB;
# insert sample record a couple of times, make sure to change some character in _id string at each insert:
INSERT INTO `mydb`.`images` (_id, name, access, type, format, owner, status, size, created_at, accessed_at, access_count, checksum, others)
VALUES ('13e2d8ca-cd9e-4ca9-8366-0f953fd0f43f', 'hihi', 'public', 'picture', 'png', 'someone', 'locked', 1024, '2012-01-06 22:11:15 +0000', '2012-01-07 13:14:15 +0000', 1000, null, null)
# DONE!
# Sample JSON document retrieved with #get_image at each request:
#
{
"image": {
"_id": "13e2d8ca-cd9e-4ca9-8366-0f953fd0f43f",
"name": "hihi",
"access": "public",
"type": null,
"format": null,
"owner": null,
"status": "locked",
"size": 1024,
"created_at": "2012-01-06 22:11:15 +0000",
"accessed_at": "2012-01-07 13:14:15 +0000",
"access_count": 10002,
"checksum": null,
"others": null
}
}
#
# Start servers
#
$ be ruby goliath_server.rb -sv -p 4568 -e production
$ be ruby sinatra_thin_server.rb #will autostart in 4567 and production
#
# Results:
#
# Grab some _id record from db to do this tests
#
# --------------- Goliath ----------------------
$ ab -n1000 -c100 "http://0.0.0.0:4568/images/13e2d8ca-cd9e-4ca9-8366-0f953fd0f43f"
This is ApacheBench, Version 2.3 <$Revision: 1139530 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 0.0.0.0 (be patient)
...
Finished 1000 requests
Server Software: PostRank
Server Hostname: 0.0.0.0
Server Port: 4568
Document Path: /images/53b75e9a-b17a-4bea-976a-1eba70f6ba70
Document Length: 312 bytes
Concurrency Level: 100
Time taken for tests: 2.971 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 495000 bytes
HTML transferred: 312000 bytes
Requests per second: 336.64 [#/sec] (mean)
Time per request: 297.055 [ms] (mean)
Time per request: 2.971 [ms] (mean, across all concurrent requests)
Transfer rate: 162.73 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.5 1 7
Processing: 84 288 58.0 283 415
Waiting: 68 265 55.8 269 392
Total: 84 290 57.6 287 416
# --------------- Sinatra ----------------------
$ ab -n1000 -c100 "http://0.0.0.0:4567/images/13e2d8ca-cd9e-4ca9-8366-0f953fd0f43f"
This is ApacheBench, Version 2.3 <$Revision: 1139530 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 0.0.0.0 (be patient)
...
Finished 1000 requests
Server Software: thin
Server Hostname: 0.0.0.0
Server Port: 4567
Document Path: /images/ce9a45b5-bf83-4cc5-8648-c590cce4e7e0
Document Length: 223 bytes
Concurrency Level: 100
Time taken for tests: 1.338 seconds
Complete requests: 1000
Failed requests: 0
Write errors: 0
Total transferred: 373000 bytes
HTML transferred: 223000 bytes
Requests per second: 747.64 [#/sec] (mean)
Time per request: 133.754 [ms] (mean)
Time per request: 1.338 [ms] (mean, across all concurrent requests)
Transfer rate: 272.33 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.2 0 6
Processing: 62 128 16.1 129 154
Waiting: 22 115 17.9 118 143
Total: 68 129 15.2 130 154
require "em-synchrony"
require "em-synchrony/mysql2"
require 'json'
require 'uri'
module SomeModule
module MyExceptions
class NotFound < StandardError; end
end
end
module MyModule
class EmMySQL
include SomeModule::MyExceptions
def self.connect(opts = {})
uri = URI.parse(opts[:uri])
opts[:host] = uri.host=='' ? '127.0.0.1' : uri.host
opts[:port] = uri.port=='' ? 3306 : uri.port
opts[:db] = uri.path=='' ? 'my_db' : uri.path.gsub('/', '')
opts[:user] = uri.user=='' ? 'my_db' : uri.user
opts[:password] = uri.password=='' ? 'passwd' : uri.password
self.new opts
end
def initialize(opts)
@host = opts[:host]
@port = opts[:port]
@db = opts[:db]
@user = opts[:user]
@password = opts[:password]
EventMachine.synchrony do
conn = connection
conn.query %[
CREATE TABLE IF NOT EXISTS `#{@db}`.`images` (
`_id` VARCHAR(36) NOT NULL ,
`name` VARCHAR(45) NOT NULL ,
`access` VARCHAR(45) NOT NULL ,
`type` VARCHAR(45) NULL ,
`format` VARCHAR(45) NULL ,
`owner` VARCHAR(45) NULL ,
`status` VARCHAR(45) NULL ,
`size` INT NULL ,
`created_at` DATETIME NULL ,
`accessed_at` DATETIME NULL ,
`access_count` INT NULL DEFAULT 0 ,
PRIMARY KEY (`_id`) )
ENGINE = InnoDB;
]
conn.close
EventMachine.stop
end
end
def connection
Mysql2::EM::Client.new(host: @host, port: @port, database: @db,
username: @user, password: @password)
end
def get_image(id)
conn = connection
meta = conn.query("SELECT * FROM images WHERE _id='#{id}'", symbolize_keys: true).first
raise NotFound, "No image found with id '#{id}'." if meta.nil?
set_protected_get(id, conn)
conn.close
meta
end
def set_protected_get(id, conn)
conn.query "UPDATE images SET accessed_at='#{Time.now}', access_count=access_count+1 WHERE _id='#{id}'"
end
end
end
require 'goliath'
require 'json'
require File.join(File.dirname(__FILE__) , 'em_mysql_db')
DB = MyModule::EmMySQL.connect uri: 'mysql://visor:passwd@127.0.0.1:3306/visor'
module SomeModule
module MyExceptions
class NotFound < StandardError; end
end
end
module MyModule
class GetImage < Goliath::API
include SomeModule::MyExceptions
def response(env)
begin
image = DB.get_image(params[:id])
[200, {}, {image: image}.to_json]
rescue NotFound => e
[404, {}, {code: 404, message: e.message}.to_json]
end
end
end
class GoliathServer < Goliath::API
use Goliath::Rack::Heartbeat
use Goliath::Rack::Params
use Goliath::Rack::Render
use Goliath::Rack::DefaultMimeType
use Goliath::Rack::Validation::RequestMethod, %w(GET POST PUT DELETE)
get '/images/:id', GetImage
# other routes not touched...
end
end
require "mysql2"
require 'json'
require 'uri'
module SomeModule
module MyExceptions
class NotFound < StandardError; end
end
end
module MyModule
class MySQL
include SomeModule::MyExceptions
def self.connect(opts = {})
uri = URI.parse(opts[:uri])
opts[:host] = uri.host=='' ? '127.0.0.1' : uri.host
opts[:port] = uri.port=='' ? 3306 : uri.port
opts[:db] = uri.path=='' ? 'mydb' : uri.path.gsub('/', '')
opts[:user] = uri.user=='' ? 'mydb' : uri.user
opts[:password] = uri.password=='' ? 'passwd' : uri.password
self.new opts
end
def initialize(opts)
@host = opts[:host]
@port = opts[:port]
@db = opts[:db]
@user = opts[:user]
@password = opts[:password]
conn = connection
conn.query %[
CREATE TABLE IF NOT EXISTS `#{@db}`.`images` (
`_id` VARCHAR(36) NOT NULL ,
`name` VARCHAR(45) NOT NULL ,
`access` VARCHAR(45) NOT NULL ,
`type` VARCHAR(45) NULL ,
`format` VARCHAR(45) NULL ,
`owner` VARCHAR(45) NULL ,
`status` VARCHAR(45) NULL ,
`size` INT NULL ,
`created_at` DATETIME NULL ,
`accessed_at` DATETIME NULL ,
`access_count` INT NULL DEFAULT 0 ,
`checksum` VARCHAR(255) NULL ,
`others` VARCHAR(255) NULL,
PRIMARY KEY (`_id`) )
ENGINE = InnoDB;
]
conn.close
end
def connection
Mysql2::Client.new(host: @host, port: @port, database: @db,
username: @user, password: @password)
end
def get_image(id)
conn = connection
meta = conn.query("SELECT * FROM images WHERE _id='#{id}'", symbolize_keys: true).first
raise NotFound, "No image found with id '#{id}'." if meta.nil?
set_protected_get(id, conn)
conn.close
meta
end
def set_protected_get(id, conn)
conn.query "UPDATE images SET accessed_at='#{Time.now}', access_count=access_count+1 WHERE _id='#{id}'"
end
end
end
require 'sinatra/base'
require 'json'
require File.join(File.dirname(__FILE__) , 'mysql_db')
module SomeModule
module MyExceptions
class NotFound < StandardError; end
end
end
module MyModule
class SinatraThinServer < Sinatra::Base
include SomeModule::MyExceptions
configure do
DB = MyModule::MySQL.connect uri: 'mysql://mydb:passwd@127.0.0.1:3306/mydb'
use Rack::CommonLogger, STDOUT
end
get '/images/:id' do |id|
content_type :json
begin
image = DB.get_image(id)
{image: image}.to_json
rescue NotFound => e
error 404, {code: 404, message: e.message}.to_json
end
end
end
end
MyModule::SinatraThinServer.run! port: 4567, environment: :production
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment