rest-ftp-daemon 0.30.1 → 0.41
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/Gemfile.lock +11 -4
- data/README.md +30 -6
- data/bin/rest-ftp-daemon +16 -9
- data/config.ru +9 -1
- data/lib/rest-ftp-daemon.rb +13 -1
- data/lib/rest-ftp-daemon/api/defaults.rb +58 -0
- data/lib/rest-ftp-daemon/api/jobs.rb +151 -0
- data/lib/rest-ftp-daemon/api/root.rb +79 -0
- data/lib/rest-ftp-daemon/common.rb +49 -0
- data/lib/rest-ftp-daemon/config.rb +9 -2
- data/lib/rest-ftp-daemon/exceptions.rb +6 -1
- data/lib/rest-ftp-daemon/job.rb +69 -23
- data/lib/rest-ftp-daemon/job_queue.rb +105 -0
- data/lib/rest-ftp-daemon/logger.rb +7 -0
- data/lib/rest-ftp-daemon/notification.rb +80 -0
- data/lib/rest-ftp-daemon/worker_pool.rb +63 -0
- data/lib/rest-ftp-daemon/www.rb +6 -0
- data/rest-ftp-daemon.gemspec +2 -2
- metadata +15 -7
- data/lib/rest-ftp-daemon/server.rb +0 -237
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MDQwNWFlNDE3ZGUwZGY2ZTc1N2FiMGE3OWNkODMxN2VhODdkNDNiMA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
MmM2YmRkMDkwMjM2OTM2YjZlOTcyNjU0NmNmODQwNmJhNjI0YzdlNQ==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YzEzZjdmYzEyNWJhZTRkODc0NmY4NjRjNTMxNmZiZWFmMWIwOTg5YmM3YzE5
|
10
|
+
NjIwYTNlMTgzNzhhYTljYTUzNTk2NTk3YzJmOTgzYmNjNzAwY2NiMjFlMDYz
|
11
|
+
MjU3ZmZmNjBhYTA0ZWUzOWJkY2MyNWI1ZmYyM2M5ZDVmYWNiNTA=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZTEyZmVkYjIwYzQ2NjVkZjEyNTUzOGFjY2NkNGJjMzkyZWIzZDQ4ZWJiOGNi
|
14
|
+
OGUxMGY5Nzk4YzUxZWEzMjE5Mjg3NDhkMDI1MzFjMWYyNjYxMDk2NmYzZDY2
|
15
|
+
ODQ0NzIwMWY1NGQwYWZjYTE1NjRiMjFhNGZiYzI1Y2U2YjEwMjI=
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rest-ftp-daemon (0.
|
4
|
+
rest-ftp-daemon (0.30.1)
|
5
5
|
grape
|
6
6
|
json
|
7
|
+
thin (~> 1.6)
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: http://rubygems.org/
|
@@ -21,10 +22,12 @@ GEM
|
|
21
22
|
builder (3.2.2)
|
22
23
|
coercible (1.0.0)
|
23
24
|
descendants_tracker (~> 0.0.1)
|
25
|
+
daemons (1.1.9)
|
24
26
|
descendants_tracker (0.0.4)
|
25
27
|
thread_safe (~> 0.3, >= 0.3.1)
|
26
28
|
equalizer (0.0.9)
|
27
|
-
|
29
|
+
eventmachine (1.0.3)
|
30
|
+
grape (0.9.0)
|
28
31
|
activesupport
|
29
32
|
builder
|
30
33
|
hashie (>= 2.1.0)
|
@@ -34,11 +37,11 @@ GEM
|
|
34
37
|
rack-accept
|
35
38
|
rack-mount
|
36
39
|
virtus (>= 1.0.0)
|
37
|
-
hashie (3.
|
40
|
+
hashie (3.3.1)
|
38
41
|
i18n (0.6.11)
|
39
42
|
ice_nine (0.11.0)
|
40
43
|
json (1.8.1)
|
41
|
-
minitest (5.4.
|
44
|
+
minitest (5.4.1)
|
42
45
|
multi_json (1.10.1)
|
43
46
|
multi_xml (0.5.5)
|
44
47
|
rack (1.5.2)
|
@@ -47,6 +50,10 @@ GEM
|
|
47
50
|
rack-mount (0.8.3)
|
48
51
|
rack (>= 1.0.0)
|
49
52
|
rake (10.3.2)
|
53
|
+
thin (1.6.2)
|
54
|
+
daemons (>= 1.0.9)
|
55
|
+
eventmachine (>= 1.0.0)
|
56
|
+
rack (>= 1.0.0)
|
50
57
|
thread_safe (0.3.4)
|
51
58
|
tzinfo (1.2.2)
|
52
59
|
thread_safe (~> 0.1)
|
data/README.md
CHANGED
@@ -5,10 +5,32 @@ This is a pretty simple FTP client daemon, controlled through a RESTfull API.
|
|
5
5
|
|
6
6
|
As of today, its main features are :
|
7
7
|
|
8
|
-
* Delegate a transfer job
|
8
|
+
* Delegate a transfer job by ``POST```'ing a simple JSON structure
|
9
9
|
* Spawn a dedicated thread to handle this job in its own context
|
10
|
-
* Report transfer status, progress and errors for each
|
10
|
+
* Report transfer status, progress and errors for each job in realtime
|
11
11
|
* Expose JSON status of workers on ```GET /jobs/``` for automated monitoring
|
12
|
+
* Parralelize jobs as soon as they arrive
|
13
|
+
|
14
|
+
Expected features in a short-time range :
|
15
|
+
|
16
|
+
* Handle job queues
|
17
|
+
* Handle job priorities
|
18
|
+
* Allow change of priorities or other attributes after a job has been started
|
19
|
+
* Provide RESTful notifications to the requesting client
|
20
|
+
* Offer a basic dashboard directly within the daemon HTTP interface
|
21
|
+
* Periodically send an update-notification with transfer status and progress
|
22
|
+
* Allow fallback file source when first file path is unavailable (failover)
|
23
|
+
* Some refactoring may be needed after thos steps
|
24
|
+
* Provide swagger-style API documentation
|
25
|
+
* Authenticate API clients
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
Documentation TODO
|
30
|
+
------------------------------------------------------------------------------------
|
31
|
+
overwrite: any non empty value allows overwriting
|
32
|
+
todo: queues
|
33
|
+
|
12
34
|
|
13
35
|
|
14
36
|
Installation
|
@@ -19,7 +41,7 @@ This project is available as a rubygem, requires on ruby >= 1.9.3 and rubygems i
|
|
19
41
|
Get and install the gem from rubygems.org:
|
20
42
|
|
21
43
|
```
|
22
|
-
# apt-get install
|
44
|
+
# apt-get install ruby1.9.3 ruby-dev rubygems gcc g++
|
23
45
|
gem install rest-ftp-daemon --no-ri --no-rdoc
|
24
46
|
```
|
25
47
|
|
@@ -41,6 +63,8 @@ For now, daemon logs to ```APP_LOGTO``` defined in ```lib/config.rb```
|
|
41
63
|
Usage examples
|
42
64
|
------------------------------------------------------------------------------------
|
43
65
|
|
66
|
+
Requesting notifications is achieved by passing a "notify" key in the request, with a callback URL. This URL will be called at some points, ``POST```'ing a generic JSON structure with progress information.
|
67
|
+
|
44
68
|
Start a job to transfer a file named "file.iso" to a local FTP server
|
45
69
|
|
46
70
|
```
|
@@ -48,17 +72,17 @@ curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
|
|
48
72
|
'{"source":"~/file.iso","target":"ftp://anonymous@localhost/incoming/dest2.iso"}' "http://localhost:3000/jobs"
|
49
73
|
```
|
50
74
|
|
51
|
-
Start a job to transfer a file
|
75
|
+
Start a job to transfer a file, and request notifications ``POST```'ed on "http://requestb.in/1321axg1"
|
52
76
|
|
53
77
|
```
|
54
78
|
curl -H "Content-Type: application/json" -X POST -D /dev/stdout -d \
|
55
|
-
'{"source":"~/file.dmg","target":"ftp://anonymous@localhost/incoming/dest4.dmg"}' "http://localhost:3000/jobs"
|
79
|
+
'{"source":"~/file.dmg","target":"ftp://anonymous@localhost/incoming/dest4.dmg","notify":"http://requestb.in/1321axg1"}' "http://localhost:3000/jobs"
|
56
80
|
```
|
57
81
|
|
58
82
|
Get status of a specific job based on its name
|
59
83
|
|
60
84
|
```
|
61
|
-
curl -H "Content-Type: application/json" -X
|
85
|
+
curl -H "Content-Type: application/json" -X GET -D /dev/stdout "http://localhost:3000/jobs/bob-45320-1"
|
62
86
|
```
|
63
87
|
|
64
88
|
Delete a specific job based on its name
|
data/bin/rest-ftp-daemon
CHANGED
@@ -1,23 +1,30 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
# Libs and init
|
4
|
-
require
|
4
|
+
require "thin"
|
5
5
|
|
6
6
|
# Initialize some local constants
|
7
7
|
APP_ROOT = File.dirname(__FILE__) + '/../'
|
8
|
-
|
8
|
+
rackup_file = File.expand_path "#{APP_ROOT}/config.ru"
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
APP_LOGTO = "/tmp/#{APP_NAME}.log"
|
10
|
+
# Include config file
|
11
|
+
require File.expand_path "#{APP_ROOT}/lib/rest-ftp-daemon/config.rb"
|
13
12
|
|
14
13
|
# Prepare thin
|
15
|
-
rackup_file = File.expand_path "#{APP_ROOT}/config.ru"
|
16
14
|
argv = ARGV
|
17
15
|
argv << ["-R", rackup_file] unless ARGV.include?("-R")
|
18
|
-
argv << ["-
|
16
|
+
argv << ["-l", "/tmp/thin.log"] unless ARGV.include?("-l")
|
17
|
+
argv << ["--daemonize"] unless ARGV.include?("--daemonize")
|
19
18
|
argv << ["-e", "production"] unless ARGV.include?("-e")
|
20
|
-
argv << ["
|
19
|
+
argv << ["-p", (RestFtpDaemon::PORT).to_s] unless ARGV.include?("-p")
|
21
20
|
#argv << ["--stats", "/stats"] unless ARGV.include?("--stats")
|
22
21
|
|
23
|
-
Thin
|
22
|
+
# Start Thin with this rackup configuration
|
23
|
+
puts "#{RestFtpDaemon::NAME}: using [#{argv.join (' ')}]"
|
24
|
+
begin
|
25
|
+
Thin::Runner.new(argv.flatten).run!
|
26
|
+
rescue Thin::PidFileNotFound
|
27
|
+
puts "#{RestFtpDaemon::NAME}: not running (Thin::PidFileNotFound)"
|
28
|
+
end
|
29
|
+
|
30
|
+
|
data/config.ru
CHANGED
@@ -2,5 +2,13 @@
|
|
2
2
|
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
|
3
3
|
require 'rest-ftp-daemon'
|
4
4
|
|
5
|
+
# Some extra constants
|
6
|
+
APP_STARTED = Time.now
|
7
|
+
|
8
|
+
# Create worker pool
|
9
|
+
$queue = RestFtpDaemon::JobQueue.new
|
10
|
+
$pool = RestFtpDaemon::WorkerPool.new(1)
|
11
|
+
|
5
12
|
# Start REST FTP Daemon
|
6
|
-
run RestFtpDaemon::API
|
13
|
+
#run Rack::Cascade.new [Rack::File.new("/public"), RestFtpDaemon::API::Root]
|
14
|
+
run Rack::Cascade.new [RestFtpDaemon::API::Root]
|
data/lib/rest-ftp-daemon.rb
CHANGED
@@ -2,9 +2,21 @@
|
|
2
2
|
require 'json'
|
3
3
|
require 'grape'
|
4
4
|
require 'net/ftp'
|
5
|
+
require 'net/http'
|
6
|
+
require 'securerandom'
|
7
|
+
# require 'celluloid/autostart'
|
5
8
|
|
6
9
|
# My libs
|
7
10
|
require 'rest-ftp-daemon/config'
|
8
11
|
require 'rest-ftp-daemon/exceptions'
|
12
|
+
require 'rest-ftp-daemon/common'
|
13
|
+
require 'rest-ftp-daemon/job_queue'
|
14
|
+
require 'rest-ftp-daemon/worker_pool'
|
15
|
+
require 'rest-ftp-daemon/logger'
|
9
16
|
require 'rest-ftp-daemon/job'
|
10
|
-
require 'rest-ftp-daemon/
|
17
|
+
require 'rest-ftp-daemon/notification'
|
18
|
+
require 'rest-ftp-daemon/api/defaults'
|
19
|
+
require 'rest-ftp-daemon/api/jobs'
|
20
|
+
require 'rest-ftp-daemon/api/root'
|
21
|
+
require 'rest-ftp-daemon/www'
|
22
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
module API
|
3
|
+
module Defaults
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
#version 'v1', using: :header, vendor: 'ftven'
|
8
|
+
format :json
|
9
|
+
do_not_route_head!
|
10
|
+
do_not_route_options!
|
11
|
+
|
12
|
+
# before do
|
13
|
+
# header['Access-Control-Allow-Origin'] = '*'
|
14
|
+
# header['Access-Control-Request-Method'] = '*'
|
15
|
+
# end
|
16
|
+
|
17
|
+
# Handle authentication
|
18
|
+
# http_basic do |username, password|
|
19
|
+
# User.authenticate!(username, password)
|
20
|
+
# end
|
21
|
+
|
22
|
+
# # global handler for simple not found case
|
23
|
+
# rescue_from ActiveRecord::RecordNotFound do |e|
|
24
|
+
# error_response(message: e.message, status: 404)
|
25
|
+
# end
|
26
|
+
|
27
|
+
# # global exception handler, used for error notifications
|
28
|
+
# rescue_from :all do |e|
|
29
|
+
# if Rails.env.development?
|
30
|
+
# raise e
|
31
|
+
# else
|
32
|
+
# Raven.capture_exception(e)
|
33
|
+
# error_response(message: "Internal server error", status: 500)
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
|
37
|
+
# # HTTP header based authentication
|
38
|
+
# before do
|
39
|
+
# error!('Unauthorized', 401) unless headers['Authorization'] == "some token"
|
40
|
+
# end
|
41
|
+
|
42
|
+
helpers do
|
43
|
+
def api_error exception
|
44
|
+
{
|
45
|
+
:error => exception.class,
|
46
|
+
:errmsg => exception.message,
|
47
|
+
:backtrace => exception.backtrace.first,
|
48
|
+
#:backtrace => exception.backtrace,
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
module API
|
3
|
+
|
4
|
+
class Jobs < Grape::API
|
5
|
+
include RestFtpDaemon::API::Defaults
|
6
|
+
logger ActiveSupport::Logger.new APP_LOGTO, 'daily'
|
7
|
+
|
8
|
+
params do
|
9
|
+
optional :overwrite, type: Integer, default: false
|
10
|
+
end
|
11
|
+
|
12
|
+
helpers do
|
13
|
+
|
14
|
+
def threads_with_id job_id
|
15
|
+
$threads.list.select do |thread|
|
16
|
+
next unless thread[:job].is_a? Job
|
17
|
+
thread[:job].id == job_id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def job_describe job_id
|
22
|
+
raise RestFtpDaemon::JobNotFound if ($queue.queued_size==0 && $queue.popped_size==0)
|
23
|
+
|
24
|
+
# Find job with this id
|
25
|
+
found = $queue.all.select { |job| job.id == job_id }.first
|
26
|
+
raise RestFtpDaemon::JobNotFound if found.nil?
|
27
|
+
raise RestFtpDaemon::JobNotFound unless found.is_a? Job
|
28
|
+
|
29
|
+
# Return job description
|
30
|
+
found.describe
|
31
|
+
end
|
32
|
+
|
33
|
+
# def job_delete job_id
|
34
|
+
# end
|
35
|
+
|
36
|
+
def job_list
|
37
|
+
$queue.all.map do |item|
|
38
|
+
next unless item.is_a? Job
|
39
|
+
item.describe
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Get information about a specific job"
|
46
|
+
params do
|
47
|
+
requires :id, type: Integer, desc: "job id"
|
48
|
+
end
|
49
|
+
get ':id' do
|
50
|
+
info "GET /jobs/#{params[:id]}"
|
51
|
+
begin
|
52
|
+
response = job_describe params[:id].to_i
|
53
|
+
rescue RestFtpDaemon::JobNotFound => exception
|
54
|
+
status 404
|
55
|
+
api_error exception
|
56
|
+
rescue RestFtpDaemonException => exception
|
57
|
+
status 500
|
58
|
+
api_error exception
|
59
|
+
rescue Exception => exception
|
60
|
+
status 501
|
61
|
+
api_error exception
|
62
|
+
else
|
63
|
+
status 200
|
64
|
+
response
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Delete jobs
|
69
|
+
desc "Kill and remove a specific job"
|
70
|
+
delete ':id' do
|
71
|
+
info "DELETE /jobs/#{params[:name]}"
|
72
|
+
status 501
|
73
|
+
# begin
|
74
|
+
# response = job_delete params[:id].to_i
|
75
|
+
# rescue RestFtpDaemon::JobNotFound => exception
|
76
|
+
# status 404
|
77
|
+
# api_error exception
|
78
|
+
# rescue RestFtpDaemonException => exception
|
79
|
+
# status 500
|
80
|
+
# api_error exception
|
81
|
+
# rescue Exception => exception
|
82
|
+
# status 501
|
83
|
+
# api_error exception
|
84
|
+
# else
|
85
|
+
# status 200
|
86
|
+
# response
|
87
|
+
# end
|
88
|
+
end
|
89
|
+
|
90
|
+
# List jobs
|
91
|
+
desc "Get a list of jobs"
|
92
|
+
get do
|
93
|
+
info "GET /jobs"
|
94
|
+
begin
|
95
|
+
response = job_list
|
96
|
+
rescue RestFtpDaemonException => exception
|
97
|
+
status 501
|
98
|
+
api_error exception
|
99
|
+
rescue Exception => exception
|
100
|
+
status 501
|
101
|
+
api_error exception
|
102
|
+
else
|
103
|
+
status 200
|
104
|
+
response
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
# Spawn a new thread for this new job
|
110
|
+
desc "Create a new job"
|
111
|
+
post do
|
112
|
+
info "POST /jobs: #{request.body.read}"
|
113
|
+
begin
|
114
|
+
# Extract params
|
115
|
+
request.body.rewind
|
116
|
+
params = JSON.parse request.body.read
|
117
|
+
|
118
|
+
# Create a new job
|
119
|
+
job_id = $last_worker_id += 1
|
120
|
+
job = Job.new(job_id, params)
|
121
|
+
|
122
|
+
# And psuh it to the queue
|
123
|
+
$queue.push job
|
124
|
+
|
125
|
+
# Later: start it asynchronously
|
126
|
+
#job.future.process
|
127
|
+
|
128
|
+
rescue JSON::ParserError => exception
|
129
|
+
status 406
|
130
|
+
api_error exception
|
131
|
+
rescue RestFtpDaemonException => exception
|
132
|
+
status 412
|
133
|
+
api_error exception
|
134
|
+
rescue Exception => exception
|
135
|
+
status 501
|
136
|
+
api_error exception
|
137
|
+
else
|
138
|
+
status 201
|
139
|
+
job.describe
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
def progname
|
146
|
+
"API::Jobs"
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module RestFtpDaemon
|
2
|
+
module API
|
3
|
+
|
4
|
+
class Root < Grape::API
|
5
|
+
include RestFtpDaemon::API::Defaults
|
6
|
+
logger ActiveSupport::Logger.new APP_LOGTO, 'daily'
|
7
|
+
#add_swagger_documentation
|
8
|
+
|
9
|
+
mount RestFtpDaemon::API::Jobs => '/jobs'
|
10
|
+
# mount RestFtpDaemon::API::Workers => '/workers'
|
11
|
+
|
12
|
+
helpers do
|
13
|
+
def info message, level = 0
|
14
|
+
Root.logger.add(Logger::INFO, "#{' '*level} #{message}", "API::Root")
|
15
|
+
end
|
16
|
+
|
17
|
+
def job_list_by_status
|
18
|
+
statuses = {}
|
19
|
+
alljobs = $queue.all.map do |item|
|
20
|
+
next unless item.is_a? Job
|
21
|
+
statuses[item.get_status] ||= 0
|
22
|
+
statuses[item.get_status] +=1
|
23
|
+
end
|
24
|
+
|
25
|
+
statuses
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
######################################################################
|
30
|
+
####### INIT
|
31
|
+
######################################################################
|
32
|
+
def initialize
|
33
|
+
$last_worker_id = 0
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
######################################################################
|
39
|
+
####### API DEFINITION
|
40
|
+
######################################################################
|
41
|
+
|
42
|
+
# Server global status
|
43
|
+
get '/' do
|
44
|
+
info "GET /"
|
45
|
+
status 200
|
46
|
+
return {
|
47
|
+
name: RestFtpDaemon::NAME,
|
48
|
+
hostname: `hostname`.chomp,
|
49
|
+
version: RestFtpDaemon::VERSION,
|
50
|
+
started: APP_STARTED,
|
51
|
+
uptime: (Time.now - APP_STARTED).round(1),
|
52
|
+
status: job_list_by_status,
|
53
|
+
queue_size: $queue.all_size,
|
54
|
+
jobs_queued: $queue.queued.collect(&:id),
|
55
|
+
jobs_popped: $queue.popped.collect(&:id),
|
56
|
+
routes: RestFtpDaemon::API::Root::routes,
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Server test
|
61
|
+
get '/debug' do
|
62
|
+
info "GET /debug/"
|
63
|
+
begin
|
64
|
+
raise RestFtpDaemon::DummyException
|
65
|
+
rescue RestFtpDaemon::RestFtpDaemonException => exception
|
66
|
+
status 501
|
67
|
+
api_error exception
|
68
|
+
rescue Exception => exception
|
69
|
+
status 501
|
70
|
+
api_error exception
|
71
|
+
else
|
72
|
+
status 200
|
73
|
+
{}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|