koa 0.0.1
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 +7 -0
- data/lib/koa.rb +12 -0
- data/lib/koa/conf.rb +13 -0
- data/lib/koa/health-check.rb +27 -0
- data/lib/koa/kik.rb +87 -0
- data/lib/koa/leaderboard-client.rb +55 -0
- data/lib/koa/logger.rb +85 -0
- data/lib/koa/measurement.rb +45 -0
- data/lib/koa/query-time.rb +14 -0
- data/lib/koa/rack-request-timer.rb +13 -0
- data/lib/koa/request.rb +106 -0
- data/lib/koa/sequel-logger.rb +33 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc5945903a71ce2ab19ff84349e51f71b424168f
|
4
|
+
data.tar.gz: 0eb595a00c2ba2ea352c9d9e58efa822aa9b7005
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dd417b6f9690c978be64f172c73125cc81c682b557bcfa484285707f3426fe025d5c0e3276e90e0a047e3b03a542fbc5468d30d471b6b59f14480ffcdb7d2191
|
7
|
+
data.tar.gz: 1273a8f1a20bc5748ef0caf33e4510272fcaecc81afc2b3fafaedebbace0657c70d11b7ad096757da84bc6f0b85222403a0c475e056bcf12c983d569346efe8f
|
data/lib/koa.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Koa
|
2
|
+
end
|
3
|
+
|
4
|
+
require 'koa/conf'
|
5
|
+
require 'koa/health-check'
|
6
|
+
require 'koa/logger'
|
7
|
+
require 'koa/measurement'
|
8
|
+
require 'koa/kik'
|
9
|
+
require 'koa/rack-request-timer'
|
10
|
+
require 'koa/request'
|
11
|
+
require 'koa/sequel-logger'
|
12
|
+
require 'koa/query-time'
|
data/lib/koa/conf.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
class Koa::HealthCheck
|
5
|
+
def self.call(env)
|
6
|
+
begin
|
7
|
+
Timeout::timeout(2) do
|
8
|
+
db_check
|
9
|
+
end
|
10
|
+
rescue => e
|
11
|
+
return [500, {"Content-Type" => "text/json"},
|
12
|
+
[JSON.dump({error: e.message})]]
|
13
|
+
end
|
14
|
+
[200, {"Content-Type" => "text/json"}, [JSON.dump({info: Time.now})]]
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.db_check
|
18
|
+
query = "select * from information_schema.tables"
|
19
|
+
if defined?(ActiveRecord)
|
20
|
+
ActiveRecord::Base.connection.execute(query)
|
21
|
+
elsif defined?(Sequel)
|
22
|
+
c = Sequel.connect(ENV["DATABASE_URL"])
|
23
|
+
c.exec(query)
|
24
|
+
c.disconnect
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/koa/kik.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
require 'timeout'
|
4
|
+
|
5
|
+
module Koa::Kik
|
6
|
+
def self.purchase(signedData, username, host)
|
7
|
+
Koa::Logger.measure_block("kikpurchase") do
|
8
|
+
url = "https://purchase.kik.com/verification/v1/check?u=#{username}&d=#{host}"
|
9
|
+
response = request_with_retry(url, signedData, 5)
|
10
|
+
if response.nil? or response.code.to_i != 200
|
11
|
+
code = (response and response.code)
|
12
|
+
code ||= "nil"
|
13
|
+
Koa::Logger.count("kikpurchase.fail", 1,
|
14
|
+
{source: code, username: username, signed_data: signedData})
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
JSON.parse(response.body)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.push(token, message, data)
|
22
|
+
Koa::Logger.measure_block("kikpush") do
|
23
|
+
url = "https://api.kik.com/push/v1/send"
|
24
|
+
body = {
|
25
|
+
token: token,
|
26
|
+
ticker: message,
|
27
|
+
data: data
|
28
|
+
}
|
29
|
+
response = request_with_retry(url, body.to_json, 1, 1)
|
30
|
+
response_code = (response && response.code)
|
31
|
+
response_code ||= "nil"
|
32
|
+
PushResponse.new(response).tap do |pr|
|
33
|
+
if pr.success?
|
34
|
+
Koa::Logger.count("notification_sent", 1)
|
35
|
+
else
|
36
|
+
Koa::Logger.count("notification_fail", 1, {source: response_code})
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.verify(signedData, username, host)
|
43
|
+
Koa::Logger.measure_block("kikverify") do
|
44
|
+
url = "https://auth.kik.com/verification/v1/check?u=#{username}&d=#{host}"
|
45
|
+
request_with_retry(url, signedData, 5).body
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.request_with_retry(url, body, tries, seconds = 2)
|
50
|
+
tries.times do
|
51
|
+
begin
|
52
|
+
Timeout::timeout(seconds) do
|
53
|
+
return request(url, body)
|
54
|
+
end
|
55
|
+
rescue Timeout::Error
|
56
|
+
Koa::Logger.count("kiktimeout", 1)
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.request(url, body)
|
63
|
+
uri = URI.parse(url)
|
64
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
65
|
+
http.use_ssl = true
|
66
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
67
|
+
request.add_field('Content-Type', 'application/json')
|
68
|
+
request.add_field('Content-Length', body.size)
|
69
|
+
|
70
|
+
request.body = body
|
71
|
+
http.request(request)
|
72
|
+
end
|
73
|
+
|
74
|
+
class PushResponse
|
75
|
+
def initialize(response)
|
76
|
+
@response = response
|
77
|
+
end
|
78
|
+
|
79
|
+
def success?
|
80
|
+
@response and @response.code.to_i == 200
|
81
|
+
end
|
82
|
+
|
83
|
+
def bad_token?
|
84
|
+
@response and @response.code.to_i == 403
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module Koa
|
5
|
+
module LeaderboardClient
|
6
|
+
URL = Conf.env!('LEADERBOARD_URL')
|
7
|
+
GAME_ID = Conf.env!('KOA_GAME_ID')
|
8
|
+
|
9
|
+
def self.get_leaderboards(limit)
|
10
|
+
Logger.measure_block("leaderboard-client.get-leaderboards") do
|
11
|
+
response = Koa::Request.make(
|
12
|
+
type: :get,
|
13
|
+
url: URL + "/boards",
|
14
|
+
data: {limit: limit, game_id: GAME_ID}
|
15
|
+
)
|
16
|
+
if response.successful?
|
17
|
+
JSON.parse(response.body)
|
18
|
+
else
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get_user_scores(user_id)
|
25
|
+
Logger.measure_block("leaderboard-client.get-user-scores") do
|
26
|
+
response = Koa::Request.make(
|
27
|
+
type: :get,
|
28
|
+
url: URL + "/user_scores",
|
29
|
+
data: {game_id: GAME_ID, user_id: user_id}
|
30
|
+
)
|
31
|
+
if response.successful?
|
32
|
+
user_scores = JSON.parse(response.body)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.add_score(user_id, score)
|
38
|
+
Logger.measure_block("leaderboard-client.add-score") do
|
39
|
+
Koa::Request.make(
|
40
|
+
type: :put,
|
41
|
+
url: URL + "/score",
|
42
|
+
data: {
|
43
|
+
user_id: user_id,
|
44
|
+
score: score,
|
45
|
+
request_id: SecureRandom.uuid,
|
46
|
+
game_id: GAME_ID
|
47
|
+
},
|
48
|
+
tries: 3,
|
49
|
+
timeout: 5
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
data/lib/koa/logger.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
$stdout.sync = true
|
2
|
+
module Koa
|
3
|
+
class StringLogger
|
4
|
+
def initialize
|
5
|
+
@msgs = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def puts(msg)
|
9
|
+
@msgs << msg
|
10
|
+
end
|
11
|
+
|
12
|
+
def flush
|
13
|
+
@msgs.length.times.map {@msgs.pop}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module NullLogger
|
18
|
+
def self.puts(*args)
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.flush(*args)
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Logger
|
28
|
+
def self.appname
|
29
|
+
@prefix ||= ENV['APP_NAME'].downcase if ENV['APP_NAME']
|
30
|
+
if defined? ::Rails
|
31
|
+
@prefix ||= Rails.application.class.parent_name.downcase+"."
|
32
|
+
end
|
33
|
+
@prefix ||= ""
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.prefix(name)
|
37
|
+
appname+name
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.librato_log(type, name, val, data)
|
41
|
+
name = prefix(name)
|
42
|
+
data["#{type}##{name}"] = val
|
43
|
+
log(data)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.out=(o)
|
47
|
+
@out = o
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.out
|
51
|
+
@out || $stdout
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.log(data)
|
55
|
+
out.puts(stringify(data))
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.measure_block(name, data = {})
|
59
|
+
start = Time.now
|
60
|
+
result = yield
|
61
|
+
elapsed = (Time.now.to_f - start.to_f) * 1000
|
62
|
+
measure(name, elapsed.round.to_s+"ms")
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.count(name, val, data = {})
|
67
|
+
librato_log("count", name, val, data)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.measure(name, val, data = {})
|
71
|
+
librato_log("measure", name, val, data)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.sample(name, val, data = {})
|
75
|
+
librato_log("sample", name, val, data)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.stringify(data)
|
79
|
+
data.reduce(out=String.new) do |s, tup|
|
80
|
+
s << [tup.first, tup.last].join("=") << " "
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Koa
|
2
|
+
module Measurement
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def measure_methods(*args)
|
9
|
+
@methods_to_measure ||= []
|
10
|
+
@methods_to_measure += args
|
11
|
+
@methods_to_measure.map!(&:to_sym)
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_added(name)
|
15
|
+
return if @adding_measurers
|
16
|
+
@adding_measurers = true
|
17
|
+
if @methods_to_measure.include? name.to_sym
|
18
|
+
unmeasured_name = "unmeasured_#{name}"
|
19
|
+
alias_method unmeasured_name, name
|
20
|
+
define_method name do |*args, &block|
|
21
|
+
Koa::Logger.measure_block(name.to_s.gsub(/_/,"-")) do
|
22
|
+
send unmeasured_name, *args, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
@adding_measurers = nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def singleton_method_added(name)
|
30
|
+
return if @adding_measurers
|
31
|
+
@adding_measurers = true
|
32
|
+
if @methods_to_measure.include? name.to_sym
|
33
|
+
unmeasured_name = "unmeasured_#{name}"
|
34
|
+
self.singleton_class.send(:alias_method, unmeasured_name, name)
|
35
|
+
define_singleton_method name do |*args, &block|
|
36
|
+
Koa::Logger.measure_block(name.to_s.gsub(/_/,"-")) do
|
37
|
+
send unmeasured_name, *args, &block
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@adding_measurers = nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Koa
|
2
|
+
module QueryTime
|
3
|
+
def self.enable_log
|
4
|
+
if Rails.env.production?
|
5
|
+
ActiveSupport::Notifications.subscribe "sql.active_record" do |name, start, finish, id, payload|
|
6
|
+
if payload[:name] == "SQL"
|
7
|
+
duration = (finish - start) * 1000
|
8
|
+
Koa::Logger.measure("pg.query", duration.round.to_s+"ms")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Koa::RackRequestTimer
|
2
|
+
def initialize(app)
|
3
|
+
@app = app
|
4
|
+
end
|
5
|
+
|
6
|
+
def call(env)
|
7
|
+
start_request = Time.now
|
8
|
+
status, headers, body = @app.call(env)
|
9
|
+
elapsed = (Time.now - start_request) * 1000
|
10
|
+
$stdout.puts("request-id=#{env['HTTP_HEROKU_REQUEST_ID']} measure#rack-request=#{elapsed.round}ms")
|
11
|
+
[status, headers, body]
|
12
|
+
end
|
13
|
+
end
|
data/lib/koa/request.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'delegate'
|
3
|
+
require 'json'
|
4
|
+
require 'net/http'
|
5
|
+
require 'timeout'
|
6
|
+
require 'uri'
|
7
|
+
|
8
|
+
module Koa
|
9
|
+
class TimeoutResponse
|
10
|
+
|
11
|
+
def successful?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def code
|
16
|
+
'0'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.body_permitted?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def body
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class ResponseDecorator < SimpleDelegator
|
29
|
+
def successful?
|
30
|
+
code.to_i / 100 == 2
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Request
|
35
|
+
def self.make(opts)
|
36
|
+
opts[:tries] ||= 1
|
37
|
+
opts[:timeout] ||= 10
|
38
|
+
|
39
|
+
opts[:url] = build_url(opts)
|
40
|
+
opts[:body] = build_body(opts)
|
41
|
+
|
42
|
+
execute(opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def self.execute(opts)
|
48
|
+
opts[:tries].times do
|
49
|
+
|
50
|
+
begin
|
51
|
+
::Timeout::timeout(opts[:timeout]) do
|
52
|
+
return ResponseDecorator.new(net_request(opts))
|
53
|
+
end
|
54
|
+
rescue ::Timeout::Error
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
Koa::TimeoutResponse.new
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.net_request(opts)
|
62
|
+
uri = ::URI.parse(opts[:url])
|
63
|
+
http = ::Net::HTTP.new(uri.host, uri.port)
|
64
|
+
http.use_ssl = uri.scheme == "https"
|
65
|
+
request = request_factory(opts[:type]).new(uri.request_uri)
|
66
|
+
|
67
|
+
request.basic_auth uri.user, uri.password if uri.user
|
68
|
+
request.basic_auth opts[:auth][:user], opts[:auth][:pass] if opts[:auth]
|
69
|
+
|
70
|
+
if opts[:type] == :post || opts[:type] == :put
|
71
|
+
request.add_field('Content-Type', 'application/json')
|
72
|
+
request.add_field('Content-Length', opts[:body].size)
|
73
|
+
request.body = opts[:body]
|
74
|
+
end
|
75
|
+
|
76
|
+
http.request(request)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.request_factory(type)
|
80
|
+
return ::Net::HTTP::Post if type == :post
|
81
|
+
return ::Net::HTTP::Put if type == :put
|
82
|
+
return ::Net::HTTP::Delete if type == :delete
|
83
|
+
return ::Net::HTTP::Get if type == :get
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.build_body(opts)
|
87
|
+
body = ""
|
88
|
+
if opts[:type] == :post || opts[:type] == :put
|
89
|
+
body = ::JSON.dump(opts[:data]) if opts[:data]
|
90
|
+
end
|
91
|
+
body
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.build_url(opts)
|
95
|
+
url = opts[:url]
|
96
|
+
if opts[:type] == :get || opts[:type] == :delete
|
97
|
+
url += "?"+hash_to_query(opts[:data]) if opts[:data]
|
98
|
+
end
|
99
|
+
url
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.hash_to_query(hash)
|
103
|
+
hash.map{|k,v| "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"}.join("&")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Koa
|
2
|
+
if defined?(::Sequel)
|
3
|
+
module ::Sequel
|
4
|
+
class Database
|
5
|
+
def log_yield(sql, args=nil)
|
6
|
+
sql = "#{sql}; #{args.inspect}" if args
|
7
|
+
t0 = Time.now
|
8
|
+
begin
|
9
|
+
yield
|
10
|
+
rescue => e
|
11
|
+
log_exception(e, sql)
|
12
|
+
raise
|
13
|
+
ensure
|
14
|
+
t1 = Time.now
|
15
|
+
log_duration(Integer((t1-t0)*1000), sql) unless e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def log_duration(t, sql)
|
20
|
+
Logger.measure("sequel-latency", t, source: action(sql))
|
21
|
+
end
|
22
|
+
|
23
|
+
def log_exception(e, sql)
|
24
|
+
Logger.log(error: "sequel-exception", class: e.class)
|
25
|
+
end
|
26
|
+
|
27
|
+
def action(sql)
|
28
|
+
sql[/(\w+){1}/].downcase
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: koa
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Rykwalder
|
8
|
+
- Ryan Smith
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-01-02 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Gem of tools for Koala
|
15
|
+
email: eric@koa.la
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/koa/conf.rb
|
21
|
+
- lib/koa/health-check.rb
|
22
|
+
- lib/koa/kik.rb
|
23
|
+
- lib/koa/leaderboard-client.rb
|
24
|
+
- lib/koa/logger.rb
|
25
|
+
- lib/koa/measurement.rb
|
26
|
+
- lib/koa/query-time.rb
|
27
|
+
- lib/koa/rack-request-timer.rb
|
28
|
+
- lib/koa/request.rb
|
29
|
+
- lib/koa/sequel-logger.rb
|
30
|
+
- lib/koa.rb
|
31
|
+
homepage: http://koa.la
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 2.0.6
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: KOA tools
|
55
|
+
test_files: []
|