alephant-broker 3.9.2 → 3.10.0

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
2
  SHA1:
3
- metadata.gz: 3551ebe253cb1eb4939a53c0175a486e018aa640
4
- data.tar.gz: 112525df499493ea48bbaf9c7c7d7a5aa99c65b9
3
+ metadata.gz: 272a9c7f2d5df921c3188da9cf79398f5cf5c8f7
4
+ data.tar.gz: a3f1f91801351092cf9f8da3e1bc533c69178875
5
5
  SHA512:
6
- metadata.gz: e99f9ffabc1aeead26a0ee7d8cab8e363b2d8e7274a3b544e0aca9916ecb5157c3aed074c5989523fd97dbf49959a3c442a8276fd53fb03eee0dbdd9d5e3bff6
7
- data.tar.gz: 7d70165c6a1fdc5a31ca876a744b0d55b3bc62fe5d542fd8d5ad6b63a7f24fb131896958ee2d47bb578816c883d8b643dbf6dbe5faebdd0cf7dc398fa51eed51
6
+ metadata.gz: 512e9abbe4ad8299257d01e816268992876ff91ba5abcade3e4d54bf10fb2621ce5ce091f9c6b9ffe9df1e586589e1e099379dfa43d3f188a0f72ce99f3c3d77
7
+ data.tar.gz: 54e34ee9a11283eb9e7a44f1781564537419036f81a6a81a43f23a5cad48d2bc6c0a0f009bc99cfaae7a6f83f4ec1702b730943050b933159dcd40dd8ba88ff4
@@ -36,7 +36,6 @@ Gem::Specification.new do |spec|
36
36
  spec.add_runtime_dependency "alephant-logger", "1.2.0"
37
37
  spec.add_runtime_dependency 'alephant-sequencer'
38
38
  spec.add_runtime_dependency "dalli-elasticache"
39
- spec.add_runtime_dependency "pmap"
40
39
  spec.add_runtime_dependency "faraday"
41
40
  spec.add_runtime_dependency "crimp"
42
41
  end
@@ -11,19 +11,19 @@ module Alephant
11
11
 
12
12
  def initialize
13
13
  unless config_endpoint.nil?
14
- @@elasticache ||= ::Dalli::ElastiCache.new(config_endpoint, { :expires_in => ttl })
15
- @@client ||= @@elasticache.client
14
+ @elasticache ||= ::Dalli::ElastiCache.new(config_endpoint, { :expires_in => ttl })
15
+ @client ||= @elasticache.client
16
16
  else
17
17
  logger.debug "Broker::Cache::Client#initialize: No config endpoint, NullClient used"
18
18
  logger.metric "NoConfigEndpoint"
19
- @@client = NullClient.new
19
+ @client = NullClient.new
20
20
  end
21
21
  end
22
22
 
23
23
  def get(key, &block)
24
24
  begin
25
25
  versioned_key = versioned key
26
- result = @@client.get versioned_key
26
+ result = @client.get versioned_key
27
27
  logger.info "Broker::Cache::Client#get key: #{versioned_key} - #{result ? 'hit' : 'miss'}"
28
28
  logger.metric "GetKeyMiss" unless result
29
29
  result ? result : set(key, block.call)
@@ -33,7 +33,7 @@ module Alephant
33
33
  end
34
34
 
35
35
  def set(key, value, ttl = nil)
36
- value.tap { |o| @@client.set(versioned(key), o, ttl) }
36
+ value.tap { |o| @client.set(versioned(key), o, ttl) }
37
37
  end
38
38
 
39
39
  private
@@ -56,7 +56,9 @@ module Alephant
56
56
  end
57
57
 
58
58
  class NullClient
59
- def get(key); end
59
+ def get(key, &block)
60
+ yield
61
+ end
60
62
 
61
63
  def set(key, value, ttl = nil)
62
64
  value
@@ -32,6 +32,14 @@ module Alephant
32
32
  settings['PATH_INFO']
33
33
  end
34
34
 
35
+ def if_none_match
36
+ settings["IF_NONE_MATCH"]
37
+ end
38
+
39
+ def if_modified_since
40
+ settings["IF_MODIFIED_SINCE"]
41
+ end
42
+
35
43
  def data
36
44
  parse(rack_input) if post?
37
45
  end
@@ -1,6 +1,5 @@
1
- require 'alephant/logger'
2
- require 'alephant/broker/component'
3
- require 'pmap'
1
+ require "alephant/logger"
2
+ require "alephant/broker/component"
4
3
 
5
4
  module Alephant
6
5
  module Broker
@@ -13,7 +12,7 @@ module Alephant
13
12
  def initialize(component_factory, env)
14
13
  logger.info "Request::Batch#initialize: id: #{env.data['batch_id']}"
15
14
 
16
- @batch_id = env.data['batch_id']
15
+ @batch_id = env.data["batch_id"]
17
16
  @component_factory = component_factory
18
17
  @components = components_for env
19
18
  end
@@ -21,11 +20,11 @@ module Alephant
21
20
  private
22
21
 
23
22
  def components_for(env)
24
- env.data['components'].pmap do |c|
23
+ env.data["components"].map do |c|
25
24
  @component_factory.create(
26
- c['component'],
25
+ c["component"],
27
26
  batch_id,
28
- c['options']
27
+ c["options"]
29
28
  )
30
29
  end
31
30
  end
@@ -12,16 +12,16 @@ module Alephant
12
12
  class Handler
13
13
  extend Logger
14
14
 
15
- def self.request_for(load_strategy, env)
16
- Request::Factory.request_for(load_strategy, env)
15
+ def self.request_for(load_strategy, request_env)
16
+ Request::Factory.request_for(load_strategy, request_env)
17
17
  end
18
18
 
19
- def self.response_for(request)
20
- Response::Factory.response_for request
19
+ def self.response_for(request, request_env)
20
+ Response::Factory.response_for(request, request_env)
21
21
  end
22
22
 
23
- def self.process(load_strategy, env)
24
- response_for request_for(load_strategy, env)
23
+ def self.process(load_strategy, request_env)
24
+ response_for(request_for(load_strategy, request_env), request_env)
25
25
  end
26
26
  end
27
27
  end
@@ -6,13 +6,17 @@ module Alephant
6
6
  class Asset < Base
7
7
  include Logger
8
8
 
9
- def initialize(component)
9
+ def initialize(component, request_env)
10
10
  @component = component
11
- super component.status
11
+
12
+ @status = component_not_modified(@component.headers, request_env) ? 304 : component.status
13
+
14
+ super @status
15
+
16
+ @headers.merge!(@component.headers)
12
17
  end
13
18
 
14
19
  def setup
15
- @headers.merge!(@component.headers)
16
20
  @content = @component.content
17
21
  log if @component.is_a? Component
18
22
  end
@@ -14,6 +14,7 @@ module Alephant
14
14
 
15
15
  STATUS_CODE_MAPPING = {
16
16
  200 => "ok",
17
+ 304 => "",
17
18
  404 => "Not found",
18
19
  500 => "Error retrieving content"
19
20
  }
@@ -24,8 +25,8 @@ module Alephant
24
25
  @headers.merge!(Broker.config[:headers]) if Broker.config.has_key?(:headers)
25
26
  @status = status
26
27
 
27
- add_no_cache_headers if status != 200
28
- setup
28
+ add_no_cache_headers if should_add_no_cache_headers?(status)
29
+ setup if status == 200
29
30
  end
30
31
 
31
32
  protected
@@ -34,6 +35,10 @@ module Alephant
34
35
 
35
36
  private
36
37
 
38
+ def should_add_no_cache_headers?(status)
39
+ status != 200 && status != 304
40
+ end
41
+
37
42
  def add_no_cache_headers
38
43
  headers.merge!(
39
44
  "Cache-Control" => "no-cache, must-revalidate",
@@ -43,6 +48,12 @@ module Alephant
43
48
  log
44
49
  end
45
50
 
51
+ def component_not_modified(headers, request_env)
52
+ return false if headers["Last-Modified"].nil? && headers["ETag"].nil?
53
+
54
+ headers["Last-Modified"] == request_env.if_modified_since || headers["ETag"] == request_env.if_none_match
55
+ end
56
+
46
57
  def log
47
58
  logger.metric "BrokerNon200Response#{status}"
48
59
  end
@@ -9,11 +9,14 @@ module Alephant
9
9
 
10
10
  attr_reader :components, :batch_id
11
11
 
12
- def initialize(components, batch_id)
12
+ def initialize(components, batch_id, request_env)
13
13
  @components = components
14
14
  @batch_id = batch_id
15
+ @status = component_not_modified(batch_response_headers, request_env) ? 304 : 200
15
16
 
16
- super(200, "application/json")
17
+ super(@status, "application/json")
18
+
19
+ @headers.merge!(batch_response_headers)
17
20
  end
18
21
 
19
22
  def setup
@@ -21,8 +24,6 @@ module Alephant
21
24
  'batch_id' => batch_id,
22
25
  'components' => json
23
26
  })
24
-
25
- @headers.merge!(batch_response_headers)
26
27
  end
27
28
 
28
29
  private
@@ -53,7 +54,7 @@ module Alephant
53
54
  def batch_response_etag
54
55
  etags = components.map do |component|
55
56
  component.headers["ETag"]
56
- end.compact
57
+ end.compact.sort
57
58
 
58
59
  Crimp.signature(etags)
59
60
  end
@@ -4,12 +4,12 @@ module Alephant
4
4
  module Broker
5
5
  module Response
6
6
  class Factory
7
- def self.response_for(request)
7
+ def self.response_for(request, request_env)
8
8
  case request
9
9
  when Request::Asset
10
- Asset.new(request.component)
10
+ Asset.new(request.component, request_env)
11
11
  when Request::Batch
12
- Batch.new(request.components, request.batch_id)
12
+ Batch.new(request.components, request.batch_id, request_env)
13
13
  when Request::Status
14
14
  Status.new
15
15
  else
@@ -1,5 +1,5 @@
1
1
  module Alephant
2
2
  module Broker
3
- VERSION = "3.9.2"
3
+ VERSION = "3.10.0"
4
4
  end
5
5
  end
@@ -1,7 +1,9 @@
1
1
  require_relative "spec_helper"
2
+ require "alephant/broker"
2
3
 
3
4
  describe Alephant::Broker::Application do
4
5
  include Rack::Test::Methods
6
+
5
7
  let(:options) do
6
8
  {
7
9
  :lookup_table_name => "test_table",
@@ -16,16 +18,18 @@ describe Alephant::Broker::Application do
16
18
  options
17
19
  )
18
20
  end
21
+
19
22
  let(:content) do
20
23
  AWS::Core::Data.new(
21
24
  :content_type => "test/content",
22
25
  :content => "Test",
23
26
  :meta => {
24
- :head_ETag => "123",
25
- :"head_Last-Modified" => "Mon, 11 Apr 2016 10:39:57 GMT"
27
+ "head_ETag" => "123",
28
+ "head_Last-Modified" => "Mon, 11 Apr 2016 10:39:57 GMT"
26
29
  }
27
30
  )
28
31
  end
32
+
29
33
  let(:sequencer_double) do
30
34
  instance_double(
31
35
  "Alephant::Sequencer::Sequencer",
@@ -90,6 +94,27 @@ describe Alephant::Broker::Application do
90
94
  end
91
95
  end
92
96
 
97
+ describe "single component not modified response" do
98
+ before do
99
+ allow(Alephant::Storage).to receive(:new) { s3_double }
100
+ get(
101
+ "/component/test_component",
102
+ {},
103
+ {
104
+ "IF_MODIFIED_SINCE" => "Mon, 11 Apr 2016 10:39:57 GMT"
105
+ }
106
+ )
107
+ end
108
+
109
+ specify { expect(last_response.status).to eql 304 }
110
+ specify { expect(last_response.body).to eql "" }
111
+ specify { expect(last_response.headers).to_not include("Cache-Control") }
112
+ specify { expect(last_response.headers).to_not include("Pragma") }
113
+ specify { expect(last_response.headers).to_not include("Expires") }
114
+ specify { expect(last_response.headers["ETag"]).to eq("123") }
115
+ specify { expect(last_response.headers["Last-Modified"]).to eq("Mon, 11 Apr 2016 10:39:57 GMT") }
116
+ end
117
+
93
118
  describe "Components endpoint '/components'" do
94
119
  let(:fixture_path) { "#{File.dirname(__FILE__)}/../fixtures/json" }
95
120
  let(:batch_json) do
@@ -98,9 +123,10 @@ describe Alephant::Broker::Application do
98
123
  let(:batch_compiled_json) do
99
124
  IO.read("#{fixture_path}/batch_compiled.json").strip
100
125
  end
126
+ let(:s3_double_batch) { instance_double("Alephant::Storage") }
101
127
 
102
128
  before do
103
- allow(s3_double).to receive(:get).and_return(
129
+ allow(s3_double_batch).to receive(:get).and_return(
104
130
  content,
105
131
  AWS::Core::Data.new(
106
132
  :content_type => "test/content",
@@ -112,7 +138,7 @@ describe Alephant::Broker::Application do
112
138
  )
113
139
  )
114
140
 
115
- allow(Alephant::Storage).to receive(:new) { s3_double }
141
+ allow(Alephant::Storage).to receive(:new) { s3_double_batch }
116
142
  end
117
143
 
118
144
  context "when using valid batch asset data" do
@@ -141,6 +167,78 @@ describe Alephant::Broker::Application do
141
167
  end
142
168
  end
143
169
 
170
+ describe "Components unmodified '/components' response" do
171
+ let(:fixture_path) { "#{File.dirname(__FILE__)}/../fixtures/json" }
172
+ let(:batch_json) { IO.read("#{fixture_path}/batch.json").strip }
173
+ let(:batch_compiled_json) { IO.read("#{fixture_path}/batch_compiled.json").strip }
174
+ let(:s3_double_with_etag) { instance_double("Alephant::Storage") }
175
+ let(:lookup_location_double_for_options_request) do
176
+ instance_double("Alephant::Lookup::Location", :location => "test/location/with/options")
177
+ end
178
+
179
+ before do
180
+ allow(lookup_helper_double).to receive(:read)
181
+ .with("ni_council_results_table", { :foo => "bar" }, "111")
182
+ .and_return(lookup_location_double_for_options_request)
183
+
184
+ allow(s3_double_with_etag).to receive(:get)
185
+ .with("test/location")
186
+ .and_return(
187
+ content
188
+ )
189
+
190
+ allow(s3_double_with_etag).to receive(:get)
191
+ .with("test/location/with/options")
192
+ .and_return(
193
+ AWS::Core::Data.new(
194
+ :content_type => "test/content",
195
+ :content => "Test",
196
+ :meta => {
197
+ "head_ETag" => "abc",
198
+ "head_Last-Modified" => "Mon, 11 Apr 2016 09:39:57 GMT"
199
+ }
200
+ )
201
+ )
202
+
203
+ allow(Alephant::Storage).to receive(:new) { s3_double_with_etag }
204
+ end
205
+
206
+ context "when requesting an unmodified response" do
207
+ let(:path) { "/components/batch" }
208
+ let(:content_type) { "application/json" }
209
+ let(:etag) { "34774567db979628363e6e865127623f" }
210
+
211
+ before do
212
+ post(path, batch_json,
213
+ "CONTENT_TYPE" => content_type,
214
+ "IF_NONE_MATCH" => etag)
215
+ end
216
+
217
+ specify { expect(last_response.status).to eql 304 }
218
+ specify { expect(last_response.body).to eq "" }
219
+
220
+ describe "response should have headers" do
221
+ it "should NOT have a Content-Type header" do
222
+ expect(last_response.headers).to_not include("Content-Type")
223
+ end
224
+
225
+ it "should have ETag cache header" do
226
+ expect(last_response.headers["ETag"]).to eq(etag)
227
+ end
228
+
229
+ it "should have most recent Last-Modified header" do
230
+ expect(last_response.headers["Last-Modified"]).to eq("Mon, 11 Apr 2016 10:39:57 GMT")
231
+ end
232
+
233
+ it "shoud not have no cache headers" do
234
+ expect(last_response.headers).to_not include("Cache-Control")
235
+ expect(last_response.headers).to_not include("Pragma")
236
+ expect(last_response.headers).to_not include("Expires")
237
+ end
238
+ end
239
+ end
240
+ end
241
+
144
242
  describe "S3 headers" do
145
243
  let(:content) do
146
244
  AWS::Core::Data.new(
data/spec/spec_helper.rb CHANGED
@@ -9,6 +9,10 @@ require "alephant/broker/load_strategy/http"
9
9
  require "alephant/broker/cache"
10
10
  require "alephant/broker/errors/content_not_found"
11
11
 
12
- require 'rack/test'
12
+ require "rack/test"
13
13
 
14
- ENV['RACK_ENV'] = 'test'
14
+ ENV['RACK_ENV'] = "test"
15
+
16
+ RSpec.configure do |config|
17
+ config.order = "random"
18
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alephant-broker
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.9.2
4
+ version: 3.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BBC News
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-14 00:00:00.000000000 Z
11
+ date: 2016-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -234,20 +234,6 @@ dependencies:
234
234
  - - '>='
235
235
  - !ruby/object:Gem::Version
236
236
  version: '0'
237
- - !ruby/object:Gem::Dependency
238
- requirement: !ruby/object:Gem::Requirement
239
- requirements:
240
- - - '>='
241
- - !ruby/object:Gem::Version
242
- version: '0'
243
- name: pmap
244
- prerelease: false
245
- type: :runtime
246
- version_requirements: !ruby/object:Gem::Requirement
247
- requirements:
248
- - - '>='
249
- - !ruby/object:Gem::Version
250
- version: '0'
251
237
  - !ruby/object:Gem::Dependency
252
238
  requirement: !ruby/object:Gem::Requirement
253
239
  requirements: