activehook 0.1.0 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ ActiveHook.mode = :server
2
+
3
+ require 'redis'
4
+ require 'json'
5
+ require 'uri'
6
+ require 'net/http'
7
+ require 'connection_pool'
8
+ require 'activehook/config'
9
+ require 'activehook/redis'
10
+ require 'activehook/errors'
11
+ require 'activehook/hook'
12
+ require 'activehook/log'
13
+ require 'activehook/server/config'
14
+ require 'activehook/server/launcher'
15
+ require 'activehook/server/manager'
16
+ require 'activehook/server/queue'
17
+ require 'activehook/server/retry'
18
+ require 'activehook/server/send'
19
+ require 'activehook/server/worker'
@@ -0,0 +1,32 @@
1
+ module ActiveHook
2
+ module Server
3
+ class Config < ActiveHook::BaseConfig
4
+ OTHER_DEFAULTS = {
5
+ workers: 2,
6
+ queue_threads: 2,
7
+ retry_threads: 1
8
+ }.freeze
9
+
10
+ attr_accessor :workers, :queue_threads, :retry_threads
11
+
12
+ def initialize
13
+ super
14
+ OTHER_DEFAULTS.each { |key, value| send("#{key}=", value) }
15
+ end
16
+
17
+ def worker_options
18
+ {
19
+ queue_threads: queue_threads,
20
+ retry_threads: retry_threads
21
+ }
22
+ end
23
+
24
+ def manager_options
25
+ {
26
+ workers: workers,
27
+ options: worker_options
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveHook
2
+ module Server
3
+ # Handles the start of the ActiveHook server via command line
4
+ #
5
+ class Launcher
6
+ def initialize(argv)
7
+ @argv = argv
8
+ end
9
+
10
+ # Parses commmand line options and starts the Manager object
11
+ #
12
+ def start
13
+ start_message
14
+ setup_options
15
+ boot_manager
16
+ end
17
+
18
+ private
19
+
20
+ def start_message
21
+ ActiveHook.log.info('ActiveHook Server starting!')
22
+ ActiveHook.log.info("* Version #{VERSION}, codename: #{CODENAME}")
23
+ end
24
+
25
+ # Parses the arguments passed through the command line.
26
+ #
27
+ def setup_options
28
+ parser = OptionParser.new do |o|
29
+ o.banner = 'Usage: bundle exec bin/activehook [options]'
30
+
31
+ o.on('-c', '--config PATH', 'Load PATH for config file') do |arg|
32
+ load(arg)
33
+ ActiveHook.log.info("* Server config: #{arg}")
34
+ end
35
+
36
+ o.on('-h', '--help', 'Prints this help') { puts o && exit }
37
+ end
38
+ parser.parse!(@argv)
39
+ end
40
+
41
+ def boot_manager
42
+ manager = ActiveHook::Server::Manager.new(ActiveHook.config.manager_options)
43
+ manager.start
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,64 @@
1
+ module ActiveHook
2
+ module Server
3
+ # The Manager controls our Worker processes. We use it to instruct each
4
+ # of them to start and shutdown.
5
+ #
6
+ class Manager
7
+ attr_accessor :workers, :options
8
+ attr_reader :forks
9
+
10
+ def initialize(options = {})
11
+ options.each { |key, value| send("#{key}=", value) }
12
+ @master = Process.pid
13
+ at_exit { shutdown }
14
+ end
15
+
16
+ # Instantiates new Worker objects, setting them with our options. We
17
+ # follow up by booting each of our Workers. Our Manager is then put to
18
+ # sleep so that our Workers can do their thing.
19
+ #
20
+ def start
21
+ validate!
22
+ start_messages
23
+ create_workers
24
+ sleep
25
+ end
26
+
27
+ # Shutsdown our Worker processes.
28
+ #
29
+ def shutdown
30
+ @forks.each { |w| Process.kill('SIGTERM', w[:pid].to_i) }
31
+ Process.kill('SIGTERM', @master)
32
+ exit
33
+ end
34
+
35
+ private
36
+
37
+ # Create the specified number of workers and starts them
38
+ #
39
+ def create_workers
40
+ @forks = []
41
+ @workers.times do |id|
42
+ pid = fork { Worker.new(@options.merge(id: id)).start }
43
+ @forks << { id: id, pid: pid }
44
+ end
45
+ end
46
+
47
+ # Information about the start process
48
+ #
49
+ def start_messages
50
+ ActiveHook.log.info("* Workers: #{@workers}")
51
+ ActiveHook.log.info("* Threads: #{@options[:queue_threads]} queue, #{@options[:retry_threads]} retry")
52
+ end
53
+
54
+ # Validates our data before starting our Workers. Also instantiates our
55
+ # connection pool by pinging Redis.
56
+ #
57
+ def validate!
58
+ raise Errors::Server, 'Cound not connect to Redis.' unless ActiveHook.redis.with { |c| c.ping && c.quit }
59
+ raise Errors::Server, 'Workers must be an Integer.' unless @workers.is_a?(Integer)
60
+ raise Errors::Server, 'Options must be a Hash.' unless @options.is_a?(Hash)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,10 +1,15 @@
1
- require 'activehook/workers/base'
2
- require 'activehook/post'
3
-
4
-
5
1
  module ActiveHook
6
- module Workers
7
- class Queue < Base
2
+ module Server
3
+ # The Queue object processes any hooks that are queued into our Redis server.
4
+ # It will perform a 'blocking pop' on our hook list until one is added.
5
+ #
6
+ class Queue
7
+ def initialize
8
+ @done = false
9
+ end
10
+
11
+ # Starts our queue process. This will run until instructed to stop.
12
+ #
8
13
  def start
9
14
  until @done
10
15
  json = retrieve_hook
@@ -12,18 +17,26 @@ module ActiveHook
12
17
  end
13
18
  end
14
19
 
20
+ # Shutsdown our queue process.
21
+ #
22
+ def shutdown
23
+ @done = true
24
+ end
25
+
15
26
  private
16
27
 
28
+ # Performs a 'blocking pop' on our redis queue list.
29
+ #
17
30
  def retrieve_hook
18
- json = ActiveHook.redis.with { |c| c.brpop('ah:queue', @time) }
31
+ json = ActiveHook.redis.with { |c| c.brpop('ah:queue') }
19
32
  json.last if json
20
33
  end
21
34
  end
22
35
 
23
36
  class HookRunner
24
37
  def initialize(json)
25
- @hook = ActiveHook::Hook.new(JSON.parse(json))
26
- @post = ActiveHook::POST.new(uri: @hook.uri, payload: @hook.payload)
38
+ @hook = Hook.new(JSON.parse(json))
39
+ @post = Send.new(uri: @hook.uri, payload: @hook.secure_payload)
27
40
  start
28
41
  end
29
42
 
@@ -1,8 +1,10 @@
1
- require 'activehook/workers/base'
2
-
3
1
  module ActiveHook
4
- module Workers
5
- class Retry < Base
2
+ module Server
3
+ class Retry
4
+ def initialize
5
+ @done = false
6
+ end
7
+
6
8
  def start
7
9
  until @done
8
10
  retries = retrieve_retries
@@ -11,6 +13,10 @@ module ActiveHook
11
13
  end
12
14
  end
13
15
 
16
+ def shutdown
17
+ @done = true
18
+ end
19
+
14
20
  private
15
21
 
16
22
  def retrieve_retries
@@ -33,12 +39,11 @@ module ActiveHook
33
39
  class RetryRunner
34
40
  def initialize(json)
35
41
  @json = json
36
- @hook = ActiveHook::Hook.new(JSON.parse(@json))
42
+ @hook = Hook.new(JSON.parse(@json))
37
43
  start
38
44
  end
39
45
 
40
46
  def start
41
- @hook.bump_retry
42
47
  @hook.perform
43
48
  end
44
49
  end
@@ -0,0 +1,70 @@
1
+ module ActiveHook
2
+ REQUEST_HEADERS = {
3
+ "Content-Type" => "application/json",
4
+ "Accept" => "application/json",
5
+ "User-Agent" => "ActiveHook/#{ActiveHook::VERSION}"
6
+ }.freeze
7
+
8
+ module Server
9
+ class Send
10
+ attr_accessor :payload
11
+ attr_reader :response_time, :uri, :status, :response
12
+
13
+ def initialize(options = {})
14
+ options.each { |key, value| send("#{key}=", value) }
15
+ end
16
+
17
+ def start
18
+ @status = post_hook
19
+ log_status
20
+ end
21
+
22
+ def uri=(uri)
23
+ @uri = URI.parse(uri)
24
+ end
25
+
26
+ def success?
27
+ @status == :success
28
+ end
29
+
30
+ private
31
+
32
+ def post_hook
33
+ http = Net::HTTP.new(uri.host, uri.port)
34
+ measure_response_time do
35
+ @response = http.post(uri.path, payload, REQUEST_HEADERS)
36
+ end
37
+ response_status(@response)
38
+ rescue
39
+ :error
40
+ end
41
+
42
+ def measure_response_time
43
+ start = Time.now
44
+ yield
45
+ finish = Time.now
46
+ @response_time = "| #{((finish - start) * 1000.0).round(3)} ms"
47
+ end
48
+
49
+ def response_status(response)
50
+ case response.code.to_i
51
+ when (200..204)
52
+ :success
53
+ when (400..499)
54
+ :bad_request
55
+ when (500..599)
56
+ :server_problems
57
+ end
58
+ end
59
+
60
+ def log_status
61
+ msg = "POST | #{uri} | #{status.upcase} #{response_time}"
62
+ if status == :success
63
+ ActiveHook.log.info(msg)
64
+ else
65
+ ActiveHook.log.err(msg)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,78 @@
1
+ module ActiveHook
2
+ module Server
3
+ # The Worker manages our two main processes - Queue and Retry. Each of these
4
+ # processes is alloted a number of threads. These threads are then forked.
5
+ # Each worker object maintains control of these threads through the aptly
6
+ # named start and shutdown methods.
7
+ #
8
+ class Worker
9
+ attr_accessor :queue_threads, :retry_threads, :id
10
+
11
+ def initialize(options = {})
12
+ options.each { |key, value| send("#{key}=", value) }
13
+ @pid = Process.pid
14
+ @threads = []
15
+ @_threads_real = []
16
+ at_exit { shutdown }
17
+ end
18
+
19
+ # Starts our new worker.
20
+ #
21
+ def start
22
+ validate!
23
+ start_message
24
+ build_threads
25
+ start_threads
26
+ end
27
+
28
+ # Shutsdown our worker as well as its threads.
29
+ #
30
+ def shutdown
31
+ shutdown_message
32
+ @threads.each(&:shutdown)
33
+ @_threads_real.each(&:exit)
34
+ end
35
+
36
+ private
37
+
38
+ # Forks the worker and creates the actual threads (@_threads_real) for
39
+ # our Queue and Retry objects. We then start them and join them to the
40
+ # main process.
41
+ #
42
+ def start_threads
43
+ @threads.each do |thread|
44
+ @_threads_real << Thread.new { thread.start }
45
+ end
46
+ @_threads_real.map(&:join)
47
+ end
48
+
49
+ # Instantiates our Queue and Retry objects based on the number of threads
50
+ # specified for each process type. We store these objects as an array in
51
+ # @threads.
52
+ #
53
+ def build_threads
54
+ @queue_threads.times { @threads << Queue.new }
55
+ @retry_threads.times { @threads << Retry.new }
56
+ end
57
+
58
+ # Information about the start process
59
+ #
60
+ def start_message
61
+ ActiveHook.log.info("* Worker #{@id} started, pid: #{@pid}")
62
+ end
63
+
64
+ # Information about the shutdown process
65
+ #
66
+ def shutdown_message
67
+ ActiveHook.log.info("* Worker #{@id} shutdown, pid: #{@pid}")
68
+ end
69
+
70
+ # Validates our data before starting the worker.
71
+ #
72
+ def validate!
73
+ raise Errors::Worker, 'Queue threads must be an Integer.' unless @queue_threads.is_a?(Integer)
74
+ raise Errors::Worker, 'Retry threads must be an Integer.' unless @retry_threads.is_a?(Integer)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,29 @@
1
+ module ActiveHook
2
+ class Validate
3
+ attr_accessor :id, :key
4
+
5
+ def initialize(options = {})
6
+ options.each { |key, value| send("#{key}=", value) }
7
+ end
8
+
9
+ def perform
10
+ validate!
11
+ @key == find_key
12
+ rescue
13
+ false
14
+ end
15
+
16
+ private
17
+
18
+ def find_key
19
+ ActiveHook.redis.with do |conn|
20
+ conn.zrangebyscore('ah:validation', @id.to_i, @id.to_i).first
21
+ end
22
+ end
23
+
24
+ def validate!
25
+ raise Errors::Validation, 'ID must be an integer.' unless @id.is_a?(Integer)
26
+ raise Errors::Validation, 'Key must be a a string.' unless @key.is_a?(String) && @key.length > 6
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,4 @@
1
1
  module ActiveHook
2
- VERSION = "0.1.0"
2
+ VERSION = '0.1.3'
3
+ CODENAME = 'Fat Sparrow'
3
4
  end
data/lib/activehook.rb CHANGED
@@ -1,9 +1,8 @@
1
- require 'connection_pool'
2
- require 'json'
3
- require 'uri'
4
- require 'activehook/config'
5
- require 'activehook/errors'
6
- require 'activehook/hook'
7
- require 'activehook/log'
8
- require 'activehook/redis'
9
1
  require 'activehook/version'
2
+ require 'byebug'
3
+
4
+ module ActiveHook
5
+ class << self
6
+ attr_accessor :mode
7
+ end
8
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activehook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nicholas Sweeting
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-20 00:00:00.000000000 Z
11
+ date: 2016-06-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: puma
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.4'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
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'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: bundler
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -112,7 +140,8 @@ description: Fast and simple webhook microservice for Ruby.
112
140
  email:
113
141
  - nsweeting@gmail.com
114
142
  executables:
115
- - activehook
143
+ - activehook-server
144
+ - activehook-app
116
145
  extensions: []
117
146
  extra_rdoc_files: []
118
147
  files:
@@ -121,26 +150,38 @@ files:
121
150
  - CODE_OF_CONDUCT.md
122
151
  - Gemfile
123
152
  - LICENSE.txt
153
+ - Procfile
124
154
  - README.md
125
155
  - Rakefile
126
156
  - activehook.gemspec
127
- - bin/activehook
157
+ - bin/activehook-app
158
+ - bin/activehook-server
128
159
  - bin/console
129
160
  - bin/setup
130
161
  - lib/activehook.rb
131
- - lib/activehook/cli.rb
162
+ - lib/activehook/app/base.rb
163
+ - lib/activehook/app/config.rb
164
+ - lib/activehook/app/config.ru
165
+ - lib/activehook/app/launcher.rb
166
+ - lib/activehook/app/middleware.rb
167
+ - lib/activehook/client/base.rb
168
+ - lib/activehook/client/config.rb
169
+ - lib/activehook/client/recieve.rb
132
170
  - lib/activehook/config.rb
133
171
  - lib/activehook/errors.rb
134
172
  - lib/activehook/hook.rb
135
173
  - lib/activehook/log.rb
136
- - lib/activehook/post.rb
137
174
  - lib/activehook/redis.rb
138
- - lib/activehook/server.rb
175
+ - lib/activehook/server/base.rb
176
+ - lib/activehook/server/config.rb
177
+ - lib/activehook/server/launcher.rb
178
+ - lib/activehook/server/manager.rb
179
+ - lib/activehook/server/queue.rb
180
+ - lib/activehook/server/retry.rb
181
+ - lib/activehook/server/send.rb
182
+ - lib/activehook/server/worker.rb
183
+ - lib/activehook/validate.rb
139
184
  - lib/activehook/version.rb
140
- - lib/activehook/workers/base.rb
141
- - lib/activehook/workers/manager.rb
142
- - lib/activehook/workers/queue.rb
143
- - lib/activehook/workers/retry.rb
144
185
  homepage: https://github.com/nsweeting/activehook
145
186
  licenses:
146
187
  - MIT
data/bin/activehook DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'activehook'
4
- require 'activehook/cli'
5
-
6
- cli = ActiveHook::CLI.new(ARGV)
7
- cli.start
@@ -1,32 +0,0 @@
1
- require 'activehook/server'
2
-
3
- module ActiveHook
4
- class CLI
5
- def initialize(argv)
6
- setup_options(argv)
7
- end
8
-
9
- def start
10
- server = ActiveHook::Server.new
11
- server.start
12
- end
13
-
14
- private
15
-
16
- def setup_options(argv)
17
- parser = OptionParser.new do |o|
18
- o.banner = 'Usage: bundle exec bin/activehook [options]'
19
-
20
- o.on('-c', '--config PATH', 'Load PATH for config file') do |arg|
21
- load(arg)
22
- ActiveHook.log.info("Loaded configuration from #{arg}")
23
- end
24
-
25
- o.on('-h', '--help', 'Prints this help') do
26
- puts o && exit
27
- end
28
- end
29
- parser.parse!(argv)
30
- end
31
- end
32
- end
@@ -1,71 +0,0 @@
1
- require 'net/http'
2
- require 'uri'
3
-
4
- module ActiveHook
5
- class POST
6
- HEADERS = {
7
- "Content-Type" => "application/json",
8
- "Accept" => "application/json",
9
- "User-Agent" => "ActiveHook/#{ActiveHook::VERSION}"
10
- }.freeze
11
-
12
- attr_accessor :payload
13
- attr_reader :response_time, :uri, :status, :response
14
-
15
- def initialize(options = {})
16
- options.each { |key, value| send("#{key}=", value) }
17
- end
18
-
19
- def start
20
- @status = post_hook
21
- log_status
22
- end
23
-
24
- def uri=(uri)
25
- @uri = URI.parse(uri)
26
- end
27
-
28
- def success?
29
- status == :success
30
- end
31
-
32
- private
33
-
34
- def post_hook
35
- http = Net::HTTP.new(uri.host, uri.port)
36
- measure_response_time do
37
- @response = http.post(uri.path, payload.to_json, HEADERS)
38
- end
39
- response_status(@response)
40
- rescue
41
- :error
42
- end
43
-
44
- def measure_response_time
45
- start = Time.now
46
- yield
47
- finish = Time.now
48
- @response_time = "| #{((finish - start) * 1000.0).round(3)} ms"
49
- end
50
-
51
- def response_status(response)
52
- case response.code.to_i
53
- when (200..204)
54
- :success
55
- when (400..499)
56
- :bad_request
57
- when (500..599)
58
- :server_problems
59
- end
60
- end
61
-
62
- def log_status
63
- msg = "POST | #{uri} | #{status.upcase} #{response_time}"
64
- if status == :success
65
- ActiveHook.log.info(msg)
66
- else
67
- ActiveHook.log.err(msg)
68
- end
69
- end
70
- end
71
- end
@@ -1,19 +0,0 @@
1
- require 'activehook/workers/manager'
2
-
3
- module ActiveHook
4
- class Server
5
- def initialize
6
- at_exit { shutdown }
7
- end
8
-
9
- def start
10
- @manager = Workers::Manager.new(ActiveHook.config.worker_options)
11
- @manager.start
12
- sleep
13
- end
14
-
15
- def shutdown
16
- @manager.shutdown
17
- end
18
- end
19
- end