activehook 0.1.0 → 0.1.3

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.
@@ -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