asyncapi-client 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +148 -0
- data/Rakefile +26 -0
- data/app/controllers/asyncapi/client/v1/jobs_controller.rb +28 -0
- data/app/models/asyncapi/client/callback_runner.rb +30 -0
- data/app/models/asyncapi/client/job.rb +95 -0
- data/app/services/asyncapi/client/update_job.rb +39 -0
- data/app/workers/asyncapi/client/cleaner_worker.rb +16 -0
- data/app/workers/asyncapi/client/job_cleaner_worker.rb +26 -0
- data/app/workers/asyncapi/client/job_post_worker.rb +50 -0
- data/app/workers/asyncapi/client/job_status_worker.rb +28 -0
- data/app/workers/asyncapi/client/job_time_out_worker.rb +25 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20141104030959_create_asyncapi_client_jobs.rb +17 -0
- data/db/migrate/20141119011011_add_callback_params_to_asyncapi_client_jobs.rb +5 -0
- data/db/migrate/20141212064041_add_secret_to_asyncapi_client_job.rb +5 -0
- data/db/migrate/20150202062211_add_expired_at_to_asyncapi_client_job.rb +5 -0
- data/db/migrate/20150202062320_populate_asyncapi_client_job_expired_at.rb +7 -0
- data/db/migrate/20150610053320_add_on_time_out_to_asyncapi_client_job.rb +5 -0
- data/db/migrate/20150612082965_add_time_out_index_to_asyncapi_client_job.rb +5 -0
- data/lib/asyncapi-client.rb +1 -0
- data/lib/asyncapi/client.rb +25 -0
- data/lib/asyncapi/client/engine.rb +18 -0
- data/lib/asyncapi/client/factories.rb +2 -0
- data/lib/asyncapi/client/version.rb +5 -0
- data/lib/generators/asyncapi/client/config_generator.rb +17 -0
- data/spec/factories/job.rb +7 -0
- metadata +285 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 13ebb832003a17b09de81bdfb57189642d7fcadc
|
4
|
+
data.tar.gz: 138494b7d2eb080eaab93682140874b57b3737b6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ea09a451882fb31a7d9c2ed01f1f111b853bf2bc61e1a513266767ea0ce9d35ab242e08168d771b3690696ac572641660b2bbaefff5ac60598d94b7aa0531bc8
|
7
|
+
data.tar.gz: ead656b31c998c890eb930133aa27f50cdc457c35b6b0ba50c115ecb4c113bdff2b5077809b53c3978ec4d87c7185f39592f5853779d62272d64443a7dff752b
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 G5Search
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
# Asyncapi::Client
|
2
|
+
|
3
|
+
Asyncapi::Client is a Rails engine that easily allows asynchronous communication with a [Asyncapi::Server](https://github.com/G5/asyncapi-server)-based API server.
|
4
|
+
|
5
|
+
This avoids tying up your web servers executing long running processes. Scaling typically requires more workers.
|
6
|
+
|
7
|
+
# Usage
|
8
|
+
|
9
|
+
To communicate with the server (in the spirit of running things in the background, call the following from a worker):
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Asyncapi::Client::Job.post(
|
13
|
+
"http://server.com/long/running/process",
|
14
|
+
# Options below are optional
|
15
|
+
body: {info: "i want to send"},
|
16
|
+
headers: { AUTHORIZATION: "Bearer XYZ" },
|
17
|
+
on_success: DoOnSuccess,
|
18
|
+
on_error: DoOnError,
|
19
|
+
on_queue: DoOnQueue,
|
20
|
+
on_time_out: DoOnTimeOut,
|
21
|
+
time_out: 2.minutes # Defaults to nil (never time out)
|
22
|
+
)
|
23
|
+
```
|
24
|
+
|
25
|
+
*Jobs that should be timed out are marked as `timed_out` approximately every minute.*
|
26
|
+
|
27
|
+
Each of the `on_*` classes will get executed. For example, when the job is queued on the server, `DoOnQueue#call` will get called with the `Asyncapi::Client::Job` instance passed in. Example:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class DoOnQueue < Asyncapi::Client::CallbackRunner
|
31
|
+
def call
|
32
|
+
# you have access to: job, callback_params
|
33
|
+
Rails.logger.info "Job##{job.id} successfully queued with body: #{job.body}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class DoOnSuccess < Asyncapi::Client::CallbackRunner
|
38
|
+
def self.call
|
39
|
+
Rails.logger.info "Job##{job.id} is done. The server's response: #{job.message}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class DoOnError < Asyncapi::Client::CallbackRunner
|
44
|
+
def call
|
45
|
+
# you have access to: job and it fields: callback_params, :body, :headers, :message
|
46
|
+
Rails.logger.info "Job##{job.id} failed. The server's response: #{job.message}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
In the callback classes, you have access to the `job` and its fields (directly):
|
52
|
+
|
53
|
+
- `callback_params`
|
54
|
+
- `body`
|
55
|
+
- `headers`
|
56
|
+
- `message`
|
57
|
+
|
58
|
+
Currently, this Engine only works with [Sidekiq](http://sidekiq.org), [typhoeus](https://github.com/typhoeus/typhoeus), and [kaminari](https://github.com/amatsuda/kaminari). Customizing these can be introduced as needed.
|
59
|
+
|
60
|
+
To run the application, you need to have the Sidekiq workers running as well.
|
61
|
+
|
62
|
+
There is a feed of all jobs that can be accessed via `/asyncapi/client/v1/jobs.json`. You can pass `per_page` and `page` to paginate through the records. Pagination is done by [api-pagination](https://github.com/davidcelis/api-pagination) via kaminari.
|
63
|
+
|
64
|
+
## Expiry
|
65
|
+
|
66
|
+
To make space in the database, old jobs must be deleted. By default, jobs older than 10 days will be deleted in both the Asyncapi Client and Asyncapi Server. Asyncapi Client is responsible for deleting the jobs it no longer needs a response from on the server.
|
67
|
+
|
68
|
+
By default, jobs 10 days old and older will be deleted. You can change this setting by putting this in an initializer:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
Asyncapi::Client.expiry_threshold = 5.days
|
72
|
+
```
|
73
|
+
|
74
|
+
If you don't ever want the jobs to get delete, set the threshold to `nil`.
|
75
|
+
|
76
|
+
# Installation
|
77
|
+
|
78
|
+
**Note**: if you're using the `protected_attributes`, also see the "Optional" section below.
|
79
|
+
|
80
|
+
## Required
|
81
|
+
|
82
|
+
Add the gem to the Gemfile:
|
83
|
+
|
84
|
+
```
|
85
|
+
gem "asyncapi-client"
|
86
|
+
```
|
87
|
+
|
88
|
+
From your Rails app:
|
89
|
+
|
90
|
+
```
|
91
|
+
rails g asyncapi:client:config
|
92
|
+
rake asyncapi_client:install:migrations
|
93
|
+
rake db:migrate
|
94
|
+
```
|
95
|
+
|
96
|
+
Make sure you have `:host` in the `default_url_options` set up for your server. Example `config/initializers/default_url_options.rb`:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
Rails.application.routes.default_url_options ||= {}
|
100
|
+
Rails.application.routes.default_url_options[:host] = ENV["HOST"]
|
101
|
+
```
|
102
|
+
|
103
|
+
## Optional:
|
104
|
+
|
105
|
+
If you want to change the controller that the engine's controllers inherit from, from an initializer:
|
106
|
+
|
107
|
+
```
|
108
|
+
Asyncapi::Client.configure do |c|
|
109
|
+
c.parent_controller = Api::BaseController
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
If you use `protected_attributes`, you need to whitelist attributes. In an initializer:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
Asyncapi::Client::Job.attr_accessible(
|
117
|
+
:follow_up_at,
|
118
|
+
:time_out_at,
|
119
|
+
:on_queue,
|
120
|
+
:on_success,
|
121
|
+
:on_error,
|
122
|
+
:headers,
|
123
|
+
:body,
|
124
|
+
:status,
|
125
|
+
)
|
126
|
+
```
|
127
|
+
|
128
|
+
If you use FactoryGirl, you can require the factories for easy testing and stubbing:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
require "asyncapi/client/factories"
|
132
|
+
```
|
133
|
+
|
134
|
+
## Security
|
135
|
+
|
136
|
+
`asyncapi-client` generates a secret for the job using `SecureRandom.uuid`, and sends this to the app using `asyncapi-server`. The server app, using the latest compatible gem, will reply with the secret in the params. `asyncapi-client` will make sure the job id matches with the given secret.
|
137
|
+
|
138
|
+
## License
|
139
|
+
|
140
|
+
Copyright (c) 2014 G5
|
141
|
+
|
142
|
+
MIT License
|
143
|
+
|
144
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
145
|
+
|
146
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
147
|
+
|
148
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'AsyncapiClient'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rspec/core/rake_task'
|
25
|
+
task :default => :spec
|
26
|
+
RSpec::Core::RakeTask.new
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Asyncapi::Client
|
2
|
+
module V1
|
3
|
+
class JobsController < Asyncapi::Client.parent_controller
|
4
|
+
respond_to :json
|
5
|
+
|
6
|
+
def index
|
7
|
+
jobs = Job.all
|
8
|
+
paginate json: jobs
|
9
|
+
end
|
10
|
+
|
11
|
+
def update
|
12
|
+
if job = Job.find_by(id: params[:id], secret: params[:job][:secret])
|
13
|
+
UpdateJob.execute(job: job, params: job_params)
|
14
|
+
respond_with job
|
15
|
+
else
|
16
|
+
render nothing: true, status: 403
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def job_params
|
23
|
+
params.require(:job).permit(:status, :message)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Asyncapi::Client
|
2
|
+
class CallbackRunner
|
3
|
+
|
4
|
+
attr_accessor :job_id
|
5
|
+
DELEGATED_ATTRS = [
|
6
|
+
:callback_params,
|
7
|
+
:body,
|
8
|
+
:headers,
|
9
|
+
:message,
|
10
|
+
]
|
11
|
+
delegate *DELEGATED_ATTRS, to: :job
|
12
|
+
|
13
|
+
def self.call(job_id)
|
14
|
+
self.new(job_id).call
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(job_id)
|
18
|
+
self.job_id = job_id
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
fail StandardError, "over-ride me"
|
23
|
+
end
|
24
|
+
|
25
|
+
def job
|
26
|
+
@job ||= Job.find(job_id)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Asyncapi::Client
|
2
|
+
class Job < ActiveRecord::Base
|
3
|
+
|
4
|
+
after_initialize :generate_secret
|
5
|
+
before_create :set_expired_at
|
6
|
+
|
7
|
+
enum status: %i[queued success error timed_out fresh]
|
8
|
+
serialize :headers, Hash
|
9
|
+
serialize :callback_params, Hash
|
10
|
+
|
11
|
+
include AASM
|
12
|
+
aasm column: :status, enum: true do
|
13
|
+
state :fresh, initial: true
|
14
|
+
state :queued
|
15
|
+
state :success
|
16
|
+
state :error
|
17
|
+
state :timed_out
|
18
|
+
|
19
|
+
event :enqueue do
|
20
|
+
transitions from: :fresh, to: :queued
|
21
|
+
end
|
22
|
+
|
23
|
+
event :succeed do
|
24
|
+
transitions from: :queued, to: :success
|
25
|
+
end
|
26
|
+
|
27
|
+
event :fail do
|
28
|
+
transitions from: :queued, to: :error
|
29
|
+
end
|
30
|
+
|
31
|
+
event :time_out do
|
32
|
+
transitions from: [:fresh, :queued], to: :timed_out
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
scope :expired, -> { where(arel_table[:expired_at].lt(Time.now)) }
|
37
|
+
scope :with_time_out, -> { where(arel_table[:time_out_at].not_eq(nil)) }
|
38
|
+
scope :for_time_out, -> do
|
39
|
+
with_time_out.where(arel_table[:time_out_at].lt(Time.now))
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.post(url,
|
43
|
+
headers: nil,
|
44
|
+
body: nil,
|
45
|
+
on_queue: nil,
|
46
|
+
on_success: nil,
|
47
|
+
on_error: nil,
|
48
|
+
on_time_out: nil,
|
49
|
+
callback_params: {},
|
50
|
+
follow_up: 5.minutes,
|
51
|
+
time_out: nil)
|
52
|
+
|
53
|
+
args = {
|
54
|
+
follow_up_at: follow_up.from_now,
|
55
|
+
on_queue: on_queue,
|
56
|
+
on_success: on_success,
|
57
|
+
on_error: on_error,
|
58
|
+
on_time_out: on_time_out,
|
59
|
+
callback_params: callback_params,
|
60
|
+
headers: headers,
|
61
|
+
body: body,
|
62
|
+
}
|
63
|
+
args[:time_out_at] = time_out.from_now if time_out
|
64
|
+
job = create(args)
|
65
|
+
JobPostWorker.perform_async(job.id, url)
|
66
|
+
CleanerWorker.perform_async
|
67
|
+
end
|
68
|
+
|
69
|
+
def url
|
70
|
+
Asyncapi::Client::Engine.routes.url_helpers.v1_job_url(self)
|
71
|
+
end
|
72
|
+
|
73
|
+
def body=(body)
|
74
|
+
json = body.is_a?(Hash) ? body.to_json : body
|
75
|
+
write_attribute :body, json
|
76
|
+
end
|
77
|
+
|
78
|
+
[:on_success, :on_error, :on_queue, :on_time_out].each do |attr|
|
79
|
+
define_method("#{attr}=") do |klass|
|
80
|
+
write_attribute attr, klass.to_s
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def set_expired_at
|
87
|
+
self.expired_at ||= Asyncapi::Client.expiry_threshold.from_now
|
88
|
+
end
|
89
|
+
|
90
|
+
def generate_secret
|
91
|
+
self.secret ||= SecureRandom.uuid
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Asyncapi::Client
|
2
|
+
class UpdateJob
|
3
|
+
|
4
|
+
def self.execute(job: job, params: params)
|
5
|
+
sanitized_params = params.reject { |key, value| key.to_sym == :status }
|
6
|
+
job.assign_attributes(sanitized_params)
|
7
|
+
status = params[:status]
|
8
|
+
|
9
|
+
if may_transition?(job, to: status)
|
10
|
+
transition(job, to: status)
|
11
|
+
if job.status_changed? && job.save
|
12
|
+
JobStatusWorker.perform_async(job.id)
|
13
|
+
else
|
14
|
+
job.save
|
15
|
+
end
|
16
|
+
elsif !may_transition?(job, to: status) && job.status == status
|
17
|
+
job.save
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def self.event_for(status)
|
24
|
+
case status
|
25
|
+
when "success"; :succeed
|
26
|
+
when "error"; :fail
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.may_transition?(job, to:)
|
31
|
+
job.send(:"may_#{event_for(to)}?")
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.transition(job, to:)
|
35
|
+
job.send(event_for(to)) if may_transition?(job, to: to)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Asyncapi
|
2
|
+
module Client
|
3
|
+
class JobCleanerWorker
|
4
|
+
|
5
|
+
include Sidekiq::Worker
|
6
|
+
sidekiq_options retry: false
|
7
|
+
|
8
|
+
def perform(job_id)
|
9
|
+
if job = Job.find_by(id: job_id)
|
10
|
+
destroy_remote job
|
11
|
+
job.destroy
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def destroy_remote(job)
|
18
|
+
Typhoeus.delete(job.server_job_url, {
|
19
|
+
params: { secret: job.secret },
|
20
|
+
headers: job.headers,
|
21
|
+
})
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Asyncapi::Client
|
2
|
+
class JobPostWorker
|
3
|
+
|
4
|
+
include Sidekiq::Worker
|
5
|
+
sidekiq_options retry: false
|
6
|
+
|
7
|
+
def perform(job_id, server_url)
|
8
|
+
job = Job.find(job_id)
|
9
|
+
server_params = server_params_from(job, job.body)
|
10
|
+
response = Typhoeus.post(server_url, {
|
11
|
+
body: server_params,
|
12
|
+
headers: job.headers,
|
13
|
+
})
|
14
|
+
process response, job
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def process(response, job)
|
20
|
+
if response.success?
|
21
|
+
job.assign_attributes(job_params_from(response))
|
22
|
+
job.enqueue
|
23
|
+
if job.save!
|
24
|
+
JobStatusWorker.perform_async(job.id)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
if job.update_attributes(status: :error, message: response.body)
|
28
|
+
JobStatusWorker.perform_async(job.id)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def job_params_from(response)
|
34
|
+
response_body = JSON.parse(response.body).with_indifferent_access
|
35
|
+
server_job_params = response_body[:job]
|
36
|
+
{server_job_url: server_job_params[:url]}
|
37
|
+
end
|
38
|
+
|
39
|
+
def server_params_from(job, params)
|
40
|
+
{
|
41
|
+
job: {
|
42
|
+
callback_url: job.url,
|
43
|
+
params: params,
|
44
|
+
secret: job.secret,
|
45
|
+
}
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Asyncapi::Client
|
2
|
+
class JobStatusWorker
|
3
|
+
|
4
|
+
include Sidekiq::Worker
|
5
|
+
sidekiq_options retry: false
|
6
|
+
|
7
|
+
STATUS_CALLBACK_MAP = {
|
8
|
+
queued: :on_queue,
|
9
|
+
success: :on_success,
|
10
|
+
error: :on_error,
|
11
|
+
timed_out: :on_time_out,
|
12
|
+
}.with_indifferent_access
|
13
|
+
|
14
|
+
def perform(job_id)
|
15
|
+
job = Job.find(job_id)
|
16
|
+
callback_method = STATUS_CALLBACK_MAP[job.status]
|
17
|
+
return unless job.respond_to?(callback_method)
|
18
|
+
class_name = job.send(callback_method)
|
19
|
+
begin
|
20
|
+
callback_class = class_name.constantize
|
21
|
+
rescue NameError
|
22
|
+
return
|
23
|
+
end
|
24
|
+
callback_class.call(job.id)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Asyncapi::Client
|
2
|
+
class JobTimeOutWorker
|
3
|
+
|
4
|
+
include Sidekiq::Worker
|
5
|
+
include Sidetiq::Schedulable
|
6
|
+
|
7
|
+
sidekiq_options retry: false
|
8
|
+
|
9
|
+
recurrence { minutely }
|
10
|
+
|
11
|
+
def perform
|
12
|
+
Job.for_time_out.find_each do |job|
|
13
|
+
time_out_job(job) if job.may_time_out?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def time_out_job(job)
|
20
|
+
job.update_attributes(status: :timed_out)
|
21
|
+
JobStatusWorker.perform_async(job.id)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateAsyncapiClientJobs < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :asyncapi_client_jobs do |t|
|
4
|
+
t.string "server_job_url"
|
5
|
+
t.integer "status"
|
6
|
+
t.text "message"
|
7
|
+
t.datetime "follow_up_at"
|
8
|
+
t.datetime "time_out_at"
|
9
|
+
t.string "on_queue"
|
10
|
+
t.string "on_success"
|
11
|
+
t.string "on_error"
|
12
|
+
t.text "body"
|
13
|
+
t.text "headers"
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "asyncapi/client"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "sidekiq"
|
2
|
+
require "sidetiq"
|
3
|
+
require "api-pagination"
|
4
|
+
require "typhoeus"
|
5
|
+
require 'aasm'
|
6
|
+
require "asyncapi/client/engine"
|
7
|
+
require "securerandom"
|
8
|
+
|
9
|
+
module Asyncapi
|
10
|
+
module Client
|
11
|
+
|
12
|
+
CONFIG_ATTRS = [:parent_controller, :expiry_threshold]
|
13
|
+
mattr_accessor(*CONFIG_ATTRS)
|
14
|
+
self.expiry_threshold = 10.days
|
15
|
+
|
16
|
+
def self.configure
|
17
|
+
yield self
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parent_controller
|
21
|
+
@@parent_controller || ActionController::Base
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Asyncapi
|
2
|
+
module Client
|
3
|
+
class Engine < ::Rails::Engine
|
4
|
+
isolate_namespace Asyncapi::Client
|
5
|
+
engine_name "asyncapi_client"
|
6
|
+
|
7
|
+
config.to_prepare do
|
8
|
+
Dir.glob(Engine.root + "app/workers/**/*.rb").each do |c|
|
9
|
+
require_dependency(c)
|
10
|
+
end
|
11
|
+
|
12
|
+
Engine.routes.default_url_options =
|
13
|
+
Rails.application.routes.default_url_options
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "asyncapi/client/engine"
|
2
|
+
|
3
|
+
module Asyncapi::Client
|
4
|
+
class ConfigGenerator < Rails::Generators::Base
|
5
|
+
|
6
|
+
desc "Inserts routes code for Asyncapi::Client"
|
7
|
+
|
8
|
+
def mount_on_routes
|
9
|
+
inject_into_file(
|
10
|
+
"config/routes.rb",
|
11
|
+
%Q( mount Asyncapi::Client::Engine, at: "/asyncapi/client"\n),
|
12
|
+
before: /^end/
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asyncapi-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- G5
|
8
|
+
- Marc Ignacio
|
9
|
+
- Ramon Tayag
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2015-06-29 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 4.1.5
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 4.1.5
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: sidekiq
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '0'
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: sidetiq
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :runtime
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
- !ruby/object:Gem::Dependency
|
58
|
+
name: kaminari
|
59
|
+
requirement: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: api-pagination
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: typhoeus
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
type: :runtime
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: aasm
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '4.0'
|
106
|
+
type: :runtime
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '4.0'
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: sqlite3
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
type: :development
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: rspec-rails
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: rspec-its
|
143
|
+
requirement: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
type: :development
|
149
|
+
prerelease: false
|
150
|
+
version_requirements: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
name: pry
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
type: :development
|
163
|
+
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
- !ruby/object:Gem::Dependency
|
170
|
+
name: factory_girl
|
171
|
+
requirement: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
type: :development
|
177
|
+
prerelease: false
|
178
|
+
version_requirements: !ruby/object:Gem::Requirement
|
179
|
+
requirements:
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '0'
|
183
|
+
- !ruby/object:Gem::Dependency
|
184
|
+
name: database_cleaner
|
185
|
+
requirement: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
type: :development
|
191
|
+
prerelease: false
|
192
|
+
version_requirements: !ruby/object:Gem::Requirement
|
193
|
+
requirements:
|
194
|
+
- - ">="
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
197
|
+
- !ruby/object:Gem::Dependency
|
198
|
+
name: rspec-sidekiq
|
199
|
+
requirement: !ruby/object:Gem::Requirement
|
200
|
+
requirements:
|
201
|
+
- - ">="
|
202
|
+
- !ruby/object:Gem::Version
|
203
|
+
version: '0'
|
204
|
+
type: :development
|
205
|
+
prerelease: false
|
206
|
+
version_requirements: !ruby/object:Gem::Requirement
|
207
|
+
requirements:
|
208
|
+
- - ">="
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
version: '0'
|
211
|
+
- !ruby/object:Gem::Dependency
|
212
|
+
name: timecop
|
213
|
+
requirement: !ruby/object:Gem::Requirement
|
214
|
+
requirements:
|
215
|
+
- - ">="
|
216
|
+
- !ruby/object:Gem::Version
|
217
|
+
version: '0'
|
218
|
+
type: :development
|
219
|
+
prerelease: false
|
220
|
+
version_requirements: !ruby/object:Gem::Requirement
|
221
|
+
requirements:
|
222
|
+
- - ">="
|
223
|
+
- !ruby/object:Gem::Version
|
224
|
+
version: '0'
|
225
|
+
description: Asynchronous API communication
|
226
|
+
email:
|
227
|
+
- lateam@getg5.com
|
228
|
+
- marcrendlignacio@gmail.com
|
229
|
+
- ramon.tayag@gmail.com
|
230
|
+
executables: []
|
231
|
+
extensions: []
|
232
|
+
extra_rdoc_files: []
|
233
|
+
files:
|
234
|
+
- MIT-LICENSE
|
235
|
+
- README.md
|
236
|
+
- Rakefile
|
237
|
+
- app/controllers/asyncapi/client/v1/jobs_controller.rb
|
238
|
+
- app/models/asyncapi/client/callback_runner.rb
|
239
|
+
- app/models/asyncapi/client/job.rb
|
240
|
+
- app/services/asyncapi/client/update_job.rb
|
241
|
+
- app/workers/asyncapi/client/cleaner_worker.rb
|
242
|
+
- app/workers/asyncapi/client/job_cleaner_worker.rb
|
243
|
+
- app/workers/asyncapi/client/job_post_worker.rb
|
244
|
+
- app/workers/asyncapi/client/job_status_worker.rb
|
245
|
+
- app/workers/asyncapi/client/job_time_out_worker.rb
|
246
|
+
- config/routes.rb
|
247
|
+
- db/migrate/20141104030959_create_asyncapi_client_jobs.rb
|
248
|
+
- db/migrate/20141119011011_add_callback_params_to_asyncapi_client_jobs.rb
|
249
|
+
- db/migrate/20141212064041_add_secret_to_asyncapi_client_job.rb
|
250
|
+
- db/migrate/20150202062211_add_expired_at_to_asyncapi_client_job.rb
|
251
|
+
- db/migrate/20150202062320_populate_asyncapi_client_job_expired_at.rb
|
252
|
+
- db/migrate/20150610053320_add_on_time_out_to_asyncapi_client_job.rb
|
253
|
+
- db/migrate/20150612082965_add_time_out_index_to_asyncapi_client_job.rb
|
254
|
+
- lib/asyncapi-client.rb
|
255
|
+
- lib/asyncapi/client.rb
|
256
|
+
- lib/asyncapi/client/engine.rb
|
257
|
+
- lib/asyncapi/client/factories.rb
|
258
|
+
- lib/asyncapi/client/version.rb
|
259
|
+
- lib/generators/asyncapi/client/config_generator.rb
|
260
|
+
- spec/factories/job.rb
|
261
|
+
homepage: https://github.com/G5/asyncapi-client
|
262
|
+
licenses:
|
263
|
+
- MIT
|
264
|
+
metadata: {}
|
265
|
+
post_install_message:
|
266
|
+
rdoc_options: []
|
267
|
+
require_paths:
|
268
|
+
- lib
|
269
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
270
|
+
requirements:
|
271
|
+
- - ">="
|
272
|
+
- !ruby/object:Gem::Version
|
273
|
+
version: '0'
|
274
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
275
|
+
requirements:
|
276
|
+
- - ">="
|
277
|
+
- !ruby/object:Gem::Version
|
278
|
+
version: '0'
|
279
|
+
requirements: []
|
280
|
+
rubyforge_project:
|
281
|
+
rubygems_version: 2.4.5
|
282
|
+
signing_key:
|
283
|
+
specification_version: 4
|
284
|
+
summary: Asynchronous API communication
|
285
|
+
test_files: []
|