ferto 0.0.4 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9fe993b148492a811c70a78c299c483b2d23ef5e
4
- data.tar.gz: '0180f279e8372d85c5e908060cb3c43e50467d62'
2
+ SHA256:
3
+ metadata.gz: c80bb0d54c8592f31f6ea01de91ea12d83f546ab8bf097b130a5a197161705d3
4
+ data.tar.gz: 169b822ff83c13a9be069436c3ae45a62af82eda65fdb7c8b57b3ecccc44ca62
5
5
  SHA512:
6
- metadata.gz: '09ca432db72f3bb2f2c6456672f95a075c58ce32530fe0fc4fdd52849918d013f361157d2626e2899bc9b8f8912979cfbce46bbdf32cc5750a900d6b6cdb6d4a'
7
- data.tar.gz: 6c1384ff84dcb8ce3afbafed63e61562b58743194a1ccf6c50f24be0624d95cd70165a543235b6f2e016fbaf5f3879b94be577da560883504b5cf5f5276302e1
6
+ metadata.gz: 0cd88c6be7c4bf31a98c9cf45b90271ad011411f4136940fd7011548e938e3be0919d12cddcd8389b401b95e26d63d631df9be7aea8f218c8f6a8ce29ef83cc3
7
+ data.tar.gz: 7a94067053ecebe87069bd3a63a5effc0a98748c030981f7d98edb1350fb0cb1310a737420c60900a949028c5517124c47bc96bd2c6ff9bbe90481d7b2857ce9
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ fail-fast: false
15
+ matrix:
16
+ ruby-version: ['2.7', '3.2', '3.4', '3.5']
17
+
18
+ steps:
19
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
20
+ - name: Install dependencies
21
+ run: |
22
+ sudo apt update
23
+ sudo apt install -y libcurl4-openssl-dev
24
+ - name: Set up Ruby ${{ matrix.ruby-version }}
25
+ uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
26
+ with:
27
+ ruby-version: ${{ matrix.ruby-version }}
28
+ - name: Install dependencies
29
+ run: bundle install
30
+ - name: Run tests
31
+ run: bundle exec rspec
data/CHANGELOG.md ADDED
@@ -0,0 +1,71 @@
1
+ # Changelog
2
+
3
+ Breaking changes are prefixed with a "[BREAKING]" label.
4
+
5
+ ## master (unreleased)
6
+
7
+ ## 0.1.1 (2026-07-11)
8
+
9
+ - Manage a per-thread `Curl::Easy` handle in `Client#download` instead of
10
+ relying on `Curl.post`, whose per-thread handle cache was removed in curb
11
+ 1.3.6. Restores keep-alive connection reuse towards the downloader and
12
+ prevents open-connection pile-up in long-lived workers.
13
+ Note: like every setup before curb 1.3.6, the long-lived cached handle is
14
+ only safe under GC compaction with curb >= 1.3.7 (or a build carrying the
15
+ `curl_easy_mark` pin fix). The gemspec intentionally leaves curb
16
+ unconstrained — the consuming app owns the curb version.
17
+ - [BREAKING] `Ferto::Response` is now a plain snapshot object instead of a
18
+ `SimpleDelegator` around the `Curl::Easy` handle: `response_code` and `body`
19
+ (aliased as `body_str`) are captured when the response is built, so a
20
+ response held across `download` calls keeps its own data instead of
21
+ exposing the reused handle's next request. Other `Curl::Easy` methods are
22
+ no longer forwarded.
23
+ - [BREAKING] `Ferto::ResponseError#response` now returns a `Ferto::Response`
24
+ snapshot instead of the live `Curl::Easy` handle, which a subsequent
25
+ `download` on the same thread would have reset.
26
+ - Add compatibility for Ruby 3.4 and 3.5
27
+
28
+ ## 0.1.0 (2023-06-16)
29
+
30
+ - Add compatibility for Ruby 3
31
+ - Unpin curb version from gemspec
32
+ - Unpin faker version
33
+ - specs: Pass params as kwargs instead of hash
34
+
35
+ ## 0.0.9 (2022-11-14)
36
+
37
+ ### Added
38
+
39
+ - Support for different callbacks when a job fails [[#13](https://github.com/skroutz/ferto/pull/13)]
40
+
41
+ ## 0.0.8 (2022-08-16)
42
+
43
+ ### Added
44
+
45
+ - Support for setting subpath in download requests [[#12](https://github.com/skroutz/ferto/pull/12)]
46
+
47
+ ## 0.0.6 (2019-07-09)
48
+
49
+ ### Added
50
+
51
+ - Support for setting request headers in download requests [[#10](https://github.com/skroutz/ferto/pull/10)]
52
+
53
+ ## 0.0.7 (2022-07-21)
54
+
55
+ ### Added
56
+
57
+ - Support for setting AWS S3 bucket as filestorage solution [[#11](https://github.com/skroutz/ferto/pull/11)]
58
+
59
+ ## 0.0.5 (2019-05-16)
60
+
61
+ ### Added
62
+
63
+ - [BREAKING] `Ferto::ResponseError` exception raising when 40X or 50X response is returned [[#9](https://github.com/skroutz/ferto/pull/9)]
64
+
65
+ ## 0.0.4 (2019-04-18)
66
+
67
+ ### Added
68
+
69
+ - Support setting a job download timeout [[#7](https://github.com/skroutz/ferto/pull/7)]
70
+ - Support setting an HTTP proxy for use in download requests [[#7](https://github.com/skroutz/ferto/pull/7)]
71
+ - Support setting the User-Agent header in download requests [[#7](https://github.com/skroutz/ferto/pull/7)]
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ferto
2
2
 
3
- [![Build Status](https://travis-ci.org/skroutz/ferto.svg?branch=master)](https://travis-ci.org/skroutz/ferto)
3
+ ![Build Status](https://github.com/skroutz/ferto/actions/workflows/CI.yml/badge.svg?branch=master)
4
4
  [![Gem Version](https://badge.fury.io/rb/ferto.svg)](https://badge.fury.io/rb/ferto)
5
5
  [![Documentation](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/skroutz/ferto)
6
6
 
@@ -50,7 +50,8 @@ dl_resp = client.download(aggr_id: 'bucket1',
50
50
  mime_type: 'text/html',
51
51
  callback_type: 'http',
52
52
  callback_dst: 'http://myservice.com/downloader_callback',
53
- extra: { some_extra_info: 'info' })
53
+ extra: { some_extra_info: 'info' },
54
+ request_headers: { "Accept" => "application/html,application/xhtml+html" })
54
55
  ```
55
56
 
56
57
  In order for a service to consume downloader's result, it *must* accept the HTTP
@@ -65,7 +66,8 @@ dl_resp = client.download(aggr_id: 'bucket1',
65
66
  mime_type: 'text/html',
66
67
  callback_type: 'kafka',
67
68
  callback_dst: 'my-kafka-topic',
68
- extra: { some_extra_info: 'info' })
69
+ extra: { some_extra_info: 'info' },
70
+ request_headers: { "Accept" => "application/html,application/xhtml+html" })
69
71
  ```
70
72
 
71
73
  To consume the downloader's result, you can use your favorite Kafka library and
@@ -77,6 +79,10 @@ If the connection with the `downloader` API was successful, the aforementioned
77
79
  object. If the client failed to connect, a
78
80
  [`Ferto::ConnectionError`](https://github.com/skroutz/ferto/blob/master/lib/ferto.rb#L18)
79
81
  exception is raised.
82
+ Also if the download call, results to a response with code
83
+ either `40X` or `50X` then a [`Ferto::ResponseError`](https://github.com/skroutz/ferto/blob/master/lib/ferto.rb#L21)
84
+ is raised with the response object encapsulated in the raised exception in order
85
+ to be further handled by the end user.
80
86
 
81
87
  To handle the actual callback message, e.g. from inside a Rails controller:
82
88
 
@@ -103,6 +109,17 @@ end
103
109
  > parameters](https://github.com/skroutz/downloader#endpoints), [callback
104
110
  > payload](https://github.com/skroutz/downloader/tree/kafka-backend#usage)).
105
111
 
112
+
113
+ #### A Note on User-Agent
114
+
115
+ We continue to expose the `user_agent` field as tools like `curl` and `wget` do.
116
+ Along with that we will follow their paradigm where if both a `user-agent` flag
117
+ and a `User-Agent` in the request headers are provided then the user-agent in
118
+ the request headers is preferred.
119
+
120
+ Also if the `user_agent` is provided but the request headers do not
121
+ contain a `User-Agent` key, then the `user_agent` is copied to the headers
122
+
106
123
  ## Contributing
107
124
 
108
125
  Bug reports and pull requests are welcome on GitHub at
data/ferto.gemspec CHANGED
@@ -23,12 +23,13 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.add_dependency 'curb', "~> 0.9"
26
+ spec.add_dependency 'curb'
27
27
 
28
- spec.add_development_dependency "bundler", "~> 1.13"
29
28
  spec.add_development_dependency "rake", "~> 10.0"
30
29
  spec.add_development_dependency "rspec", "~> 3.0"
31
30
  spec.add_development_dependency "webmock", "~> 3.5"
32
31
  spec.add_development_dependency "factory_bot", "~> 4.10"
33
- spec.add_development_dependency "faker", "~> 1.9"
32
+ spec.add_development_dependency "faker"
33
+ spec.add_development_dependency "observer"
34
+ spec.add_development_dependency "ostruct"
34
35
  end
data/lib/ferto/client.rb CHANGED
@@ -50,6 +50,8 @@ module Ferto
50
50
  # @param url [String] the resource to be downloaded
51
51
  # @param callback_type [String]
52
52
  # @param callback_dst [String] the callback destination
53
+ # @param callback_error_type [String]
54
+ # @param callback_error_dst [String] the callback destination in case the job fails
53
55
  # @param mime_type [String] (default: "") accepted MIME types for the
54
56
  # resource
55
57
  # @param aggr_id [String] aggregation identifier
@@ -62,6 +64,11 @@ module Ferto
62
64
  # @param user_agent [String] the User-Agent string to use for
63
65
  # downloading the resource, by default it uses the User-Agent string
64
66
  # set in the downloader's configuration
67
+ # @param request_headers [Hash] the request headers that will be used
68
+ # in downloader when performing the actual request in order to fetch
69
+ # the desired resource
70
+ # @param subpath [String] the subfolder(s) that the jobs will be stored
71
+ # under the top level directory of storage backend
65
72
  #
66
73
  # @example
67
74
  # client.download(
@@ -73,38 +80,61 @@ module Ferto
73
80
  # aggr_proxy: 'http://myproxy.com/',
74
81
  # user_agent: 'my-useragent',
75
82
  # mime_type: "image/jpeg",
83
+ # request_headers: { "Accept" => "image/*,*/*;q=0.8" },
76
84
  # extra: { something: 'someone' }
77
85
  # )
78
86
  #
79
87
  # @raise [Ferto::ConnectionError] if there was an error scheduling the
80
- # job to downloader
88
+ # job to downloader with respect to the fact that a Curl ConnectionFailedError occured
89
+ # @raise [Ferto::ResponseError] if a response code of 40X or 50X is received
81
90
  #
82
91
  # @return [Ferto::Response]
83
92
  #
84
93
  # @see https://github.com/skroutz/downloader/#post-download
85
94
  def download(aggr_id:, aggr_limit: @aggr_limit, url:,
86
95
  aggr_proxy: nil, download_timeout: nil, user_agent: nil,
87
- callback_url: "", callback_dst: "",
88
- callback_type: "", mime_type: "", extra: {})
96
+ callback_url: "", callback_dst: "", callback_type: "",
97
+ callback_error_type: "", callback_error_dst: "",
98
+ mime_type: "", extra: {},
99
+ request_headers: {},
100
+ s3_bucket: nil, s3_region: nil, subpath: nil)
89
101
  uri = URI::HTTP.build(
90
102
  scheme: scheme, host: host, port: port, path: path
91
103
  )
92
104
  body = build_body(
93
- aggr_id, aggr_limit, url, callback_url, callback_type, callback_dst,
94
- aggr_proxy, download_timeout, user_agent, mime_type, extra
105
+ aggr_id, aggr_limit, url,
106
+ callback_url, callback_type, callback_dst,
107
+ callback_error_type, callback_error_dst,
108
+ aggr_proxy, download_timeout, user_agent,
109
+ mime_type, extra, request_headers,
110
+ s3_bucket, s3_region, subpath
95
111
  )
96
- # Curl.post reuses the same handler
97
112
  begin
98
- res = Curl.post(uri.to_s, body.to_json) do |handle|
99
- handle.headers = build_header(aggr_id)
100
- handle.connect_timeout = connect_timeout
101
- handle.timeout = timeout
113
+ # Inlines what the Curl.post shortcut did internally (url, post_body,
114
+ # perform) plus the options previously passed in its block, on a
115
+ # handle we manage ourselves.
116
+ handle = curl_handle
117
+ handle.url = uri.to_s
118
+ handle.headers = build_header(aggr_id)
119
+ handle.connect_timeout = connect_timeout
120
+ handle.timeout = timeout
121
+ handle.post_body = body.to_json
122
+ handle.http(:POST)
123
+
124
+ response = Ferto::Response.new(handle)
125
+
126
+ case response.response_code
127
+ when 400..599
128
+ error_msg = ("An error occured during the download call. " \
129
+ "Received a #{response.response_code} response code and body " \
130
+ "#{response.body}")
131
+ raise Ferto::ResponseError.new(error_msg, response)
102
132
  end
103
133
  rescue Curl::Err::ConnectionFailedError => e
104
134
  raise Ferto::ConnectionError.new(e)
105
135
  end
106
136
 
107
- Ferto::Response.new res
137
+ response
108
138
  end
109
139
 
110
140
  private
@@ -116,15 +146,39 @@ module Ferto
116
146
  }
117
147
  end
118
148
 
149
+ # One handle per thread: curl_easy_reset keeps live connections, so the
150
+ # keep-alive connection to the downloader is reused across calls.
151
+ # Deliberately fiber-local (Thread.current[]): sharing a handle across
152
+ # fibers could let two fibers of one thread enter the same handle
153
+ # mid-transfer under a fiber scheduler; less reuse beats corruption.
154
+ def curl_handle
155
+ handle = Thread.current[:ferto_curl] ||= Curl::Easy.new
156
+ handle.reset
157
+ handle
158
+ end
159
+
119
160
  def build_body(aggr_id, aggr_limit, url, callback_url, callback_type,
120
- callback_dst, aggr_proxy, download_timeout, user_agent,
121
- mime_type, extra)
161
+ callback_dst, callback_error_type, callback_error_dst,
162
+ aggr_proxy, download_timeout, user_agent,
163
+ mime_type, extra, request_headers,
164
+ s3_bucket, s3_region, subpath)
122
165
  body = {
123
166
  aggr_id: aggr_id,
124
167
  aggr_limit: aggr_limit,
125
168
  url: url
126
169
  }
127
170
 
171
+ if s3_bucket && s3_region
172
+ body[:s3_bucket] = s3_bucket
173
+ body[:s3_region] = s3_region
174
+ end
175
+
176
+ if !s3_bucket && s3_region
177
+ raise ArgumentError, "s3_region provided without an s3_bucket"
178
+ elsif !s3_region && s3_bucket
179
+ raise ArgumentError, "s3_bucket provided without an s3_region"
180
+ end
181
+
128
182
  if callback_url.empty?
129
183
  body[:callback_type] = callback_type
130
184
  body[:callback_dst] = callback_dst
@@ -132,10 +186,14 @@ module Ferto
132
186
  body[:callback_url] = callback_url
133
187
  end
134
188
 
189
+ body[:callback_error_type] = callback_error_type unless callback_error_type.to_s.empty?
190
+ body[:callback_error_dst] = callback_error_dst unless callback_error_dst.to_s.empty?
191
+
135
192
  if !mime_type.empty?
136
193
  body[:mime_type] = mime_type
137
194
  end
138
195
 
196
+ body[:subpath] = subpath if subpath
139
197
  body[:aggr_proxy] = aggr_proxy if aggr_proxy
140
198
  body[:download_timeout] = download_timeout if download_timeout
141
199
  body[:user_agent] = user_agent if user_agent
@@ -144,6 +202,19 @@ module Ferto
144
202
  body[:extra] = extra.is_a?(Hash) ? extra.to_json : extra.to_s
145
203
  end
146
204
 
205
+ # We will continue to expose the user_agent field just like tools
206
+ # like curl and wget do. Along with that we will follow their paradigm
207
+ # where if both a user-agent flag and a `User-Agent` in the request headers
208
+ # are provided then the user agent in the request headers is preferred.
209
+ #
210
+ # Also if the `user_agent` is provided but the request headers do not
211
+ # contain a `User-Agent` key, then the `user_agent` is copied to the headers
212
+ if user_agent && !request_headers.key?("User-Agent")
213
+ request_headers["User-Agent"] = user_agent
214
+ end
215
+
216
+ body[:request_headers] = request_headers
217
+
147
218
  body
148
219
  end
149
220
  end
@@ -1,5 +1,17 @@
1
1
  module Ferto
2
- class Response < SimpleDelegator
2
+ # A point-in-time snapshot of a download response. The underlying
3
+ # Curl::Easy handle is reused by the thread's next download, so every
4
+ # field is captured eagerly here and the handle is not retained.
5
+ class Response
6
+ attr_reader :response_code, :body
7
+
8
+ alias body_str body
9
+
10
+ def initialize(handle)
11
+ @response_code = handle.response_code
12
+ @body = handle.body
13
+ end
14
+
3
15
  def job_id
4
16
  @job_id ||= body.nil? ? nil : JSON.parse(body)['id']
5
17
  end
data/lib/ferto/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ferto
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/ferto.rb CHANGED
@@ -16,4 +16,25 @@ module Ferto
16
16
  }.freeze
17
17
 
18
18
  class ConnectionError < StandardError; end
19
+
20
+ # A custom error class for 40X and 50X responses
21
+ class ResponseError < StandardError
22
+
23
+ # Initialize a Ferto::ResponseError
24
+ #
25
+ # @param [String] err A string describing the error occured
26
+ # @param [Ferto::Response | nil] response a snapshot of the
27
+ # response returned by the download method.
28
+ # Default: nil
29
+ def initialize(err, response=nil)
30
+ super(err)
31
+ @response = response
32
+ end
33
+
34
+ # response is set, during the download in case of
35
+ # 40X or 50X responses are returned, so that it
36
+ # can be used in case of debugging but it is also
37
+ # included for reasons of completeness.
38
+ attr_reader :response
39
+ end
19
40
  end
metadata CHANGED
@@ -1,43 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ferto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aggelos Avgerinos
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2019-04-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: curb
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '0.9'
18
+ version: '0'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
- - - "~>"
23
+ - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '0.9'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.13'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.13'
25
+ version: '0'
41
26
  - !ruby/object:Gem::Dependency
42
27
  name: rake
43
28
  requirement: !ruby/object:Gem::Requirement
@@ -98,16 +83,44 @@ dependencies:
98
83
  name: faker
99
84
  requirement: !ruby/object:Gem::Requirement
100
85
  requirements:
101
- - - "~>"
86
+ - - ">="
102
87
  - !ruby/object:Gem::Version
103
- version: '1.9'
88
+ version: '0'
104
89
  type: :development
105
90
  prerelease: false
106
91
  version_requirements: !ruby/object:Gem::Requirement
107
92
  requirements:
108
- - - "~>"
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: observer
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: ostruct
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
109
122
  - !ruby/object:Gem::Version
110
- version: '1.9'
123
+ version: '0'
111
124
  description: Ruby API client for Downloader service
112
125
  email:
113
126
  - avgerinos@skroutz.gr
@@ -115,8 +128,9 @@ executables: []
115
128
  extensions: []
116
129
  extra_rdoc_files: []
117
130
  files:
131
+ - ".github/workflows/CI.yml"
118
132
  - ".gitignore"
119
- - ".travis.yml"
133
+ - CHANGELOG.md
120
134
  - Gemfile
121
135
  - LICENSE.txt
122
136
  - README.md
@@ -134,7 +148,6 @@ homepage: https://github.com/skroutz/ferto
134
148
  licenses:
135
149
  - GPL-3.0
136
150
  metadata: {}
137
- post_install_message:
138
151
  rdoc_options: []
139
152
  require_paths:
140
153
  - lib
@@ -149,9 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
149
162
  - !ruby/object:Gem::Version
150
163
  version: '0'
151
164
  requirements: []
152
- rubyforge_project:
153
- rubygems_version: 2.5.2
154
- signing_key:
165
+ rubygems_version: 4.0.3
155
166
  specification_version: 4
156
167
  summary: Ruby API client for Downloader
157
168
  test_files: []
data/.travis.yml DELETED
@@ -1,5 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- rvm:
4
- - 2.3
5
- - 2.4