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 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,13 @@
1
+ module Koa
2
+ module Conf
3
+
4
+ def self.env(k)
5
+ ENV[k]
6
+ end
7
+
8
+ def self.env!(k)
9
+ env(k) || raise("Must set #{k}")
10
+ end
11
+
12
+ end
13
+ end
@@ -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
@@ -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: []