gcpc 0.0.4

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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d26d83b3ad1b3c23b83aa62eb8742e522eaa14df00d608e33b639848354081db
4
+ data.tar.gz: 74366ee318ca77cb08baaaf0d99c3021f72e30b744cc38cd66d6fb0bc33dc6b6
5
+ SHA512:
6
+ metadata.gz: f2c90798e86df808662e2906e2ab5b073b102a7aacfb77803dd63f37b72fb559822e0b1b91d9afdd2f929fa6b02ed6f6714df7ca67fa17a74fd3491a2dd5840d
7
+ data.tar.gz: 4f659b9666d194f9317ab16cee630ba1698168557af6c9ea6bc15fa34e9ee399c3f31ae50fa6ace1db2511cb4192d487e7412ba37043aed590cd75cedd705f5a
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,19 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.2
7
+ before_install:
8
+ - gem install bundler -v 1.17.2
9
+ jobs:
10
+ include:
11
+ - name: 'test'
12
+ script:
13
+ - bundle exec rspec
14
+ - name: 'test with a emulator'
15
+ before_install:
16
+ # Start a Cloud Pub/Sub emulator. See https://cloud.google.com/pubsub/docs/emulator
17
+ - docker run -d -p 8085:8085 -it google/cloud-sdk:latest gcloud beta emulators pubsub start --host-port=0.0.0.0:8085
18
+ script:
19
+ - bundle exec rspec --tag emulator
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in gcpc.gemspec
6
+ gemspec
@@ -0,0 +1,95 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gcpc (0.0.4)
5
+ google-cloud-pubsub
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.6.0)
11
+ public_suffix (>= 2.0.2, < 4.0)
12
+ coderay (1.1.2)
13
+ concurrent-ruby (1.1.5)
14
+ diff-lcs (1.3)
15
+ faraday (0.15.4)
16
+ multipart-post (>= 1.2, < 3)
17
+ google-cloud-core (1.3.0)
18
+ google-cloud-env (~> 1.0)
19
+ google-cloud-env (1.1.0)
20
+ faraday (~> 0.11)
21
+ google-cloud-pubsub (0.36.0)
22
+ concurrent-ruby (~> 1.0)
23
+ google-cloud-core (~> 1.2)
24
+ google-gax (~> 1.3)
25
+ googleapis-common-protos (>= 1.3.9, < 2.0)
26
+ grpc-google-iam-v1 (~> 0.6.9)
27
+ google-gax (1.6.3)
28
+ google-protobuf (~> 3.2)
29
+ googleapis-common-protos (>= 1.3.5, < 2.0)
30
+ googleauth (>= 0.6.2, < 0.10.0)
31
+ grpc (>= 1.7.2, < 2.0)
32
+ rly (~> 0.2.3)
33
+ google-protobuf (3.8.0)
34
+ googleapis-common-protos (1.3.9)
35
+ google-protobuf (~> 3.0)
36
+ googleapis-common-protos-types (~> 1.0)
37
+ grpc (~> 1.0)
38
+ googleapis-common-protos-types (1.0.4)
39
+ google-protobuf (~> 3.0)
40
+ googleauth (0.8.1)
41
+ faraday (~> 0.12)
42
+ jwt (>= 1.4, < 3.0)
43
+ memoist (~> 0.16)
44
+ multi_json (~> 1.11)
45
+ os (>= 0.9, < 2.0)
46
+ signet (~> 0.7)
47
+ grpc (1.21.0)
48
+ google-protobuf (~> 3.7)
49
+ googleapis-common-protos-types (~> 1.0)
50
+ grpc-google-iam-v1 (0.6.9)
51
+ googleapis-common-protos (>= 1.3.1, < 2.0)
52
+ grpc (~> 1.0)
53
+ jwt (2.2.1)
54
+ memoist (0.16.0)
55
+ method_source (0.9.2)
56
+ multi_json (1.13.1)
57
+ multipart-post (2.1.1)
58
+ os (1.0.1)
59
+ pry (0.12.2)
60
+ coderay (~> 1.1.0)
61
+ method_source (~> 0.9.0)
62
+ public_suffix (3.1.0)
63
+ rake (10.5.0)
64
+ rly (0.2.3)
65
+ rspec (3.8.0)
66
+ rspec-core (~> 3.8.0)
67
+ rspec-expectations (~> 3.8.0)
68
+ rspec-mocks (~> 3.8.0)
69
+ rspec-core (3.8.0)
70
+ rspec-support (~> 3.8.0)
71
+ rspec-expectations (3.8.2)
72
+ diff-lcs (>= 1.2.0, < 2.0)
73
+ rspec-support (~> 3.8.0)
74
+ rspec-mocks (3.8.0)
75
+ diff-lcs (>= 1.2.0, < 2.0)
76
+ rspec-support (~> 3.8.0)
77
+ rspec-support (3.8.0)
78
+ signet (0.11.0)
79
+ addressable (~> 2.3)
80
+ faraday (~> 0.9)
81
+ jwt (>= 1.5, < 3.0)
82
+ multi_json (~> 1.10)
83
+
84
+ PLATFORMS
85
+ ruby
86
+
87
+ DEPENDENCIES
88
+ bundler (~> 1.17)
89
+ gcpc!
90
+ pry
91
+ rake (~> 10.0)
92
+ rspec (~> 3.2)
93
+
94
+ BUNDLED WITH
95
+ 1.17.2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Nao Minami
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,161 @@
1
+ # Gcpc
2
+
3
+ **G**oogle **C**loud **P**ub/Sub **C**lient for Ruby.
4
+
5
+ Gcpc provides the implementation of the publisher / subscriber for [Google Cloud Pub/Sub](https://cloud.google.com/pubsub/). You can add some functionality to the publisher / subscriber as interceptors.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'gcpc'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install gcpc
22
+
23
+ ## Usage
24
+
25
+ `gcpc` have publisher and subscriber implementation of Google Cloud Pub/Sub.
26
+
27
+ ### Publisher
28
+
29
+ To use `Gcpc::Publisher`, pleaese initialize `Gcpc::Publisher` with some configurations.
30
+
31
+ ```ruby
32
+ publisher = Gcpc::Publisher.new(
33
+ project_id: "<project id>",
34
+ topic: "<topic name>",
35
+ credentials: "/path/to/credentials",
36
+ )
37
+ ```
38
+
39
+ Then, simply call `Gcpc::Publisher#publish` to post a message!
40
+
41
+ ```ruby
42
+ publisher.publish("<message payload>")
43
+ ```
44
+
45
+ #### Interceptors
46
+
47
+ By using interceptors, you can add some functionality to the publisher.
48
+
49
+ For example, you can add logging functionality by adding `LogInterceptor` as shown below.
50
+
51
+ ```ruby
52
+ class LogInterceptor < Gcpc::Publisher::BaseInterceptor
53
+ MyLogger = Logger.new(STDOUT)
54
+
55
+ # @param [String] data
56
+ # @param [Hash] attributes
57
+ def publish(data, attributes)
58
+ MyLogger.info "[Interceptor Log] publish data: \"#{data}\", attributes: #{attributes}"
59
+ yield data, attributes
60
+ end
61
+ end
62
+
63
+ publisher = Gcpc::Publisher.new(
64
+ project_id: "<project id>",
65
+ topic: "<topic name>",
66
+ interceptors: [LogInterceptor],
67
+ credentials: "/path/to/credentials",
68
+ )
69
+
70
+ publisher.publish("<message payload>")
71
+ ```
72
+
73
+ #### Publisher Example
74
+ A full example code is in [publisher-example](./examples/publisher-example). Please see it.
75
+
76
+ ### Subscriber
77
+
78
+ To use `Gcpc::Subscriber`, pleaese initialize `Gcpc::Subscriber` with some configurations.
79
+
80
+ ```ruby
81
+ subscriber = Gcpc::Subscriber.new(
82
+ project_id: "<project id>",
83
+ subscription: "<subscription name>",
84
+ credentials: "/path/to/credentials",
85
+ )
86
+ ```
87
+
88
+ Then, call `Gcpc::Subscriber#handle` to register a handler. A registered handler executes `#handle` callback for each published message.
89
+
90
+ ```ruby
91
+ class NopHandler < Gcpc::Subscriber::BaseHandler
92
+ # @param [String] data
93
+ # @param [Hash] attributes
94
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
95
+ def handle(data, attributes, message)
96
+ # Do nothing. Consume only.
97
+ end
98
+ end
99
+
100
+ subscriber.handle(NopHandler)
101
+ ```
102
+
103
+ To start subscribing, please call `Gcpc::Subscriber#start`. It does not return, and run subscribing loops in it.
104
+
105
+ ```ruby
106
+ subscriber.run
107
+ ```
108
+
109
+ #### Signal Handling
110
+
111
+ By default, you can stop a subscriber process by sending `SIGINT`, `SIGTERM`, or `SIGKILL` signals.
112
+
113
+ If you want to use other signals, please pass signals to `#run`.
114
+
115
+ ```ruby
116
+ subscriber.run(['SIGINT', 'SIGTERM', 'SIGSTOP', 'SIGTSTP'])
117
+ ```
118
+
119
+ #### Interceptors
120
+
121
+ By using interceptors, you can add some functionality to the subscriber.
122
+
123
+ For example, you can add logging functionality by adding `LogInterceptor` as shown below.
124
+
125
+ ```ruby
126
+ class LogInterceptor < Gcpc::Subscriber::BaseInterceptor
127
+ MyLogger = Logger.new(STDOUT)
128
+
129
+ # @param [String] data
130
+ # @param [Hash] attributes
131
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
132
+ def handle(data, attributes, message)
133
+ MyLogger.info "[Interceptor Log] subscribed a message: #{message}"
134
+ yield data, attributes, message
135
+ end
136
+ end
137
+
138
+ subscriber = Gcpc::Subscriber.new(
139
+ project_id: "<project id>",
140
+ subscription: "<subscription name>",
141
+ interceptors: [LogInterceptor],
142
+ credentials: "/path/to/credentials",
143
+ )
144
+ ```
145
+
146
+ #### Subscriber Example
147
+ A full example code is in [subscriber-example](./examples/subscriber-example). Please see it.
148
+
149
+ ## Development
150
+
151
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
152
+
153
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
154
+
155
+ ## Contributing
156
+
157
+ Bug reports and pull requests are welcome on GitHub at https://github.com/south37/gcpc.
158
+
159
+ ## License
160
+
161
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gcpc"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "pry"
10
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,11 @@
1
+ ## publisher example
2
+
3
+ An example code of `Gcpc::Publisher`.
4
+
5
+ If you want to try `Gcpc`, please execute commands below in the root of this repository.
6
+
7
+ ```
8
+ $ gcloud beta emulators pubsub start # Please install Cloud Pub/Sub emulator from https://cloud.google.com/pubsub/docs/emulator for executing this.
9
+ $ bundle exec ruby examples/subscriber-example/subscriber-example.rb
10
+ $ bundle exec ruby examples/publisher-example/publisher-example.rb
11
+ ```
@@ -0,0 +1,35 @@
1
+ require "gcpc"
2
+
3
+ # Please execute commands below.
4
+ #
5
+ # ```
6
+ # $ gcloud beta emulators pubsub start
7
+ # $ bundle exec ruby examples/subscriber-example/subscriber-example.rb
8
+ # $ bundle exec ruby examples/publisher-example/publisher-example.rb
9
+ # ```
10
+
11
+ PROJECT_ID = "project-example-1"
12
+ TOPIC_NAME = "topic-example-1"
13
+
14
+ class LogInterceptor < Gcpc::Publisher::BaseInterceptor
15
+ MyLogger = Logger.new(STDOUT)
16
+
17
+ def publish(data, attributes)
18
+ MyLogger.info "[Interceptor Log] publish data: \"#{data}\", attributes: #{attributes}"
19
+ yield data, attributes
20
+ end
21
+ end
22
+
23
+ def main
24
+ publisher = Gcpc::Publisher.new(
25
+ project_id: PROJECT_ID,
26
+ topic: TOPIC_NAME,
27
+ interceptors: [LogInterceptor],
28
+ emulator_host: "localhost:8085",
29
+ )
30
+ data = ARGV[0] || "message payload"
31
+ attributes = { publisher: "publisher-example" }
32
+ publisher.publish(data, attributes)
33
+ end
34
+
35
+ main
@@ -0,0 +1,11 @@
1
+ ## subscriber example
2
+
3
+ An example code of `Gcpc::Subscriber`.
4
+
5
+ If you want to try `Gcpc`, please execute commands below in the root of this repository.
6
+
7
+ ```
8
+ $ gcloud beta emulators pubsub start # Please install Cloud Pub/Sub emulator from https://cloud.google.com/pubsub/docs/emulator for executing this.
9
+ $ bundle exec ruby examples/subscriber-example/subscriber-example.rb
10
+ $ bundle exec ruby examples/publisher-example/publisher-example.rb
11
+ ```
@@ -0,0 +1,73 @@
1
+ require "gcpc"
2
+
3
+ # Please execute commands below.
4
+ #
5
+ # ```
6
+ # $ gcloud beta emulators pubsub start
7
+ # $ bundle exec ruby examples/subscriber-example/subscriber-example.rb
8
+ # $ bundle exec ruby examples/publisher-example/publisher-example.rb
9
+ # ```
10
+
11
+ PROJECT_ID = "project-example-1"
12
+ TOPIC_NAME = "topic-example-1"
13
+ SUBSCRIPTION_NAME = "subscription-example-1"
14
+
15
+ MyLogger = Logger.new(STDOUT)
16
+
17
+ class LogInterceptor < Gcpc::Subscriber::BaseInterceptor
18
+ def handle(data, attributes, message)
19
+ MyLogger.info "[Interceptor Log] #{message.inspect}"
20
+ MyLogger.info "[Interceptor Log] data: #{data}"
21
+ MyLogger.info "[Interceptor Log] attributes: #{attributes}"
22
+ yield data, attributes, message
23
+ end
24
+ end
25
+
26
+ class LogHandler < Gcpc::Subscriber::BaseHandler
27
+ def handle(data, attributes, message)
28
+ MyLogger.info "[Handler Log] #{message.inspect}"
29
+ MyLogger.info "[Handler Log] data: #{data}"
30
+ MyLogger.info "[Handler Log] attributes: #{attributes}"
31
+ end
32
+ end
33
+
34
+ # We create topic and subscription only for demonstration.
35
+ def with_setup_subscription(&block)
36
+ project = Google::Cloud::Pubsub.new(
37
+ project_id: PROJECT_ID,
38
+ emulator_host: "localhost:8085",
39
+ )
40
+ if (topic = project.topic(TOPIC_NAME)).nil?
41
+ # Create a topic if necessary
42
+ topic = project.create_topic(TOPIC_NAME)
43
+ end
44
+ if (subscription = topic.subscription(SUBSCRIPTION_NAME)).nil?
45
+ # Create a subscription if necessary
46
+ subscription = topic.create_subscription(SUBSCRIPTION_NAME)
47
+ end
48
+
49
+ yield
50
+
51
+ ensure
52
+ topic.delete
53
+ subscription.delete
54
+ end
55
+
56
+ def run
57
+ subscriber = Gcpc::Subscriber.new(
58
+ project_id: PROJECT_ID,
59
+ subscription: SUBSCRIPTION_NAME,
60
+ interceptors: [LogInterceptor],
61
+ emulator_host: "localhost:8085",
62
+ )
63
+ subscriber.handle(LogHandler)
64
+ subscriber.run
65
+ end
66
+
67
+ def main
68
+ with_setup_subscription do
69
+ run
70
+ end
71
+ end
72
+
73
+ main
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "gcpc/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gcpc"
8
+ spec.version = Gcpc::VERSION
9
+ spec.authors = ["Nao Minami"]
10
+ spec.email = ["south37777@gmail.com"]
11
+
12
+ spec.summary = %q{Google Cloud Pub/Sub Client}
13
+ spec.description = %q{Google Cloud Pub/Sub Client}
14
+ spec.homepage = "https://github.com/south37/gcpc"
15
+ spec.license = "MIT"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.17"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.2"
29
+ spec.add_development_dependency "pry"
30
+ spec.add_runtime_dependency "google-cloud-pubsub"
31
+ end
@@ -0,0 +1,6 @@
1
+ require "gcpc/version"
2
+ require "gcpc/publisher"
3
+ require "gcpc/subscriber"
4
+
5
+ module Gcpc
6
+ end
@@ -0,0 +1,42 @@
1
+ require "gcpc/publisher/base_interceptor"
2
+ require "gcpc/publisher/engine"
3
+ require "gcpc/publisher/topic_client"
4
+
5
+ module Gcpc
6
+ class Publisher
7
+ # @param [String] project_id
8
+ # @param [String] topic
9
+ # @param [String, Google::Cloud::Pubsub::Credentials, nil] credentials Path
10
+ # of keyfile or Google::Cloud::Pubsub::Credentials or nil.
11
+ # @param [String, nil] emulator_host Emulator's host or nil.
12
+ # @param [<#publish>] interceptors
13
+ def initialize(
14
+ project_id:,
15
+ topic:,
16
+ credentials: nil,
17
+ emulator_host: nil,
18
+ interceptors: []
19
+ )
20
+ topic_client = TopicClient.new(
21
+ project_id: project_id,
22
+ topic_name: topic,
23
+ credentials: credentials,
24
+ emulator_host: emulator_host,
25
+ )
26
+
27
+ t = topic_client.get
28
+ if t.nil?
29
+ raise "Getting topic \"#{topic}\" from project \"#{project_id}\" failed! The topic \"#{topic}\" does not exist!"
30
+ end
31
+
32
+ @engine = Engine.new(
33
+ topic: t,
34
+ interceptors: interceptors,
35
+ )
36
+ end
37
+
38
+ extend Forwardable
39
+
40
+ def_delegators :@engine, :publish
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ # Interceptor must implement #publish. Gcpc::Publisher::BaseHandler is a base
2
+ # class to implement such a class. You don't have to inherit this, this is only
3
+ # for indicating interface.
4
+ module Gcpc
5
+ class Publisher
6
+ class BaseInterceptor
7
+ # @param [String] data
8
+ # @param [Hash] attributes
9
+ def publish(data, attributes, &block)
10
+ yield data, attributes
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,48 @@
1
+ module Gcpc
2
+ class Publisher
3
+ class Engine
4
+ # @param [Google::Cloud::Pubsub::Topic] topic
5
+ # @param [<#publish>] interceptors
6
+ def initialize(topic:, interceptors:)
7
+ @topic = topic
8
+ @interceptors = interceptors.map { |i| (i.class == Class) ? i.new : i }
9
+ end
10
+
11
+ # @param [String] data
12
+ # @param [Hash] attributes
13
+ def publish(data, attributes = {})
14
+ d = data.dup
15
+ a = attributes.dup
16
+
17
+ intercept!(@interceptors, d, a) do |dd, aa|
18
+ publish_message(dd, aa)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # @param [<#publish>] interceptors
25
+ # @param [String] data
26
+ # @param [Hash] attributes
27
+ # @param [Proc] block
28
+ def intercept!(interceptors, data, attributes, &block)
29
+ if interceptors.size == 0
30
+ return yield(data, attributes)
31
+ end
32
+
33
+ i = interceptors.first
34
+ rest = interceptors[1..-1]
35
+
36
+ i.publish(data, attributes) do |d, a|
37
+ intercept!(rest, d, a, &block)
38
+ end
39
+ end
40
+
41
+ # @param [String] data
42
+ # @param [Hash] attributes
43
+ def publish_message(data, attributes)
44
+ @topic.publish(data, attributes)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ module Gcpc
2
+ class Publisher
3
+ class TopicClient
4
+ DEFAULT_CONNECT_TIMEOUT = 5
5
+
6
+ # @param [String] project_id
7
+ # @param [String] topic_name
8
+ # @param [Google::Cloud::Pubsub::Credentials, String, nil]
9
+ # @param [String, nil] emulator_host
10
+ def initialize(project_id:, topic_name:, credentials:, emulator_host:, connect_timeout: DEFAULT_CONNECT_TIMEOUT)
11
+ project = Google::Cloud::Pubsub.new(
12
+ project_id: project_id,
13
+ credentials: credentials,
14
+ emulator_host: emulator_host,
15
+ )
16
+ @project = project
17
+ @topic_name = topic_name
18
+ @connect_timeout = connect_timeout
19
+ end
20
+
21
+ # @return [Google::Cloud::Pubsub::Topic]
22
+ def get
23
+ t = nil
24
+ Timeout.timeout(@connect_timeout) do
25
+ t = @project.topic(@topic_name)
26
+ end
27
+ t
28
+ rescue Timeout::Error => e
29
+ raise "Getting topic \"#{@topic_name}\" from project \"#{@project.project_id}\" timed out!"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,50 @@
1
+ require "gcpc/subscriber/base_handler"
2
+ require "gcpc/subscriber/base_interceptor"
3
+ require "gcpc/subscriber/default_logger"
4
+ require "gcpc/subscriber/engine"
5
+ require "gcpc/subscriber/subscription_client"
6
+
7
+ module Gcpc
8
+ class Subscriber
9
+ # @param [String] project_id
10
+ # @param [String] subscription
11
+ # @param [String, Google::Cloud::Pubsub::Credentials, nil] credentials Path
12
+ # of keyfile or Google::Cloud::Pubsub::Credentials or nil.
13
+ # @param [String, nil] emulator_host Emulator's host or nil.
14
+ # @param [<#handle, #on_error>] interceptors
15
+ # @param [bool] ack_immediately
16
+ # @param [Logger] logger
17
+ def initialize(
18
+ project_id:,
19
+ subscription:,
20
+ credentials: nil,
21
+ emulator_host: nil,
22
+ interceptors: [],
23
+ ack_immediately: false,
24
+ logger: DefaultLogger
25
+ )
26
+ subscription_client = SubscriptionClient.new(
27
+ project_id: project_id,
28
+ subscription_name: subscription,
29
+ credentials: credentials,
30
+ emulator_host: emulator_host,
31
+ )
32
+
33
+ s = subscription_client.get
34
+ if s.nil?
35
+ raise "Getting subscription \"#{subscription}\" from project \"#{project_id}\" failed! The subscription \"#{subscription}\" does not exist!"
36
+ end
37
+
38
+ @engine = Engine.new(
39
+ subscription: s,
40
+ interceptors: interceptors,
41
+ ack_immediately: ack_immediately,
42
+ logger: logger,
43
+ )
44
+ end
45
+
46
+ extend Forwardable
47
+
48
+ def_delegators :@engine, :handle, :run, :stop
49
+ end
50
+ end
@@ -0,0 +1,21 @@
1
+ # Handler must implement #handle and can implement #on_error.
2
+ # Gcpc::Subscriber::BaseHandler is a base class to implement such a class.
3
+ # You don't have to inherit this, this is only for indicating interface.
4
+ module Gcpc
5
+ class Subscriber
6
+ class BaseHandler
7
+ # @param [String] data
8
+ # @param [Hash] attributes
9
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
10
+ def handle(data, attributes, message)
11
+ raise NotImplementedError.new("You must implement #{self.class}##{__method__}")
12
+ end
13
+
14
+ # You don't need to implement #on_error if it is not necessary.
15
+ # @param [Exception] error
16
+ # def on_error(error)
17
+ # raise NotImplementedError.new("You must implement #{self.class}##{__method__}")
18
+ # end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # Interceptor must implement #handle and can implement #on_error.
2
+ # Gcpc::Subscriber::BaseHandler is a base class to implement such a class.
3
+ # You don't have to inherit this, this is only for indicating interface.
4
+ module Gcpc
5
+ class Subscriber
6
+ class BaseInterceptor
7
+ # @param [String] data
8
+ # @param [Hash] attributes
9
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
10
+ # @param [Proc] block
11
+ def handle(data, attributes, message, &block)
12
+ yield data, attributes, message
13
+ end
14
+
15
+ # You don't need to implement #on_error is it is not necessary.
16
+ # @param [Exception] error
17
+ # @param [Plock] block
18
+ # def on_error(error, &block)
19
+ # yield error
20
+ # end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ require 'logger'
2
+
3
+ module Gcpc
4
+ class Subscriber
5
+ DefaultLogger = Logger.new(STDOUT)
6
+ end
7
+ end
@@ -0,0 +1,144 @@
1
+ require "gcpc/subscriber/handle_engine"
2
+
3
+ module Gcpc
4
+ class Subscriber
5
+ class Engine
6
+ WAIT_INTERVAL = 1
7
+
8
+ # @param [Google::Cloud::Pubsub::Subscription] subscription
9
+ # @param [<#handle, #on_error>] interceptors
10
+ # @param [bool] ack_immediately
11
+ # @param [Logger] logger
12
+ def initialize(
13
+ subscription:,
14
+ interceptors: [],
15
+ ack_immediately: false,
16
+ logger: DefaultLogger
17
+ )
18
+
19
+ @subscription = subscription
20
+ @interceptors = interceptors
21
+ @ack_immediately = ack_immediately
22
+ @logger = logger
23
+
24
+ @subscriber = nil # @subscriber is created by calling `#run`
25
+ @handler = nil # @handler must be registered by `#handle`
26
+
27
+ @stopped_mutex = Mutex.new
28
+ @stopped = false
29
+ end
30
+
31
+ # @param [<String>] signals Signals which are used to shutdown subscriber
32
+ # gracefully.
33
+ def run(signals = ['SIGTERM', 'SIGINT'])
34
+ if @handler.nil?
35
+ raise "You must register handler by #handle before calling #run"
36
+ end
37
+
38
+ @logger.info("Starting to subscribe a subscription \"#{@subscription.name}\", will wait for background threads to start...")
39
+
40
+ @subscriber = @subscription.listen do |message|
41
+ handle_message(message)
42
+ end
43
+ @subscriber.on_error do |err|
44
+ handle_error(err)
45
+ end
46
+ @subscriber.start
47
+
48
+ @logger.info("Started")
49
+
50
+ loop_until_receiving_signals(signals)
51
+ end
52
+
53
+ def stop
54
+ if @subscriber.nil?
55
+ raise "You must call #run before stopping"
56
+ end
57
+
58
+ @stopped_mutex.synchronize do
59
+ # `#stop` may be called multiple times. Only first call can proceed.
60
+ return if @stopped
61
+ @stopped = true
62
+ end
63
+
64
+ @logger.info('Stopping, will wait for background threads to exit')
65
+
66
+ @subscriber.stop
67
+ @subscriber.wait!
68
+
69
+ @logger.info('Stopped, background threads are shutdown')
70
+ end
71
+
72
+ # We support registrion of only one handler
73
+ # @param [#handle, #on_error, Class] handler
74
+ def handle(handler)
75
+ @handler = HandleEngine.new(
76
+ handler: handler,
77
+ interceptors: @interceptors,
78
+ )
79
+ end
80
+
81
+ private
82
+
83
+ def loop_until_receiving_signals(signals)
84
+ signal_received = false
85
+ signals.each do |signal|
86
+ Signal.trap(signal) { signal_received = true }
87
+ end
88
+ while !(signal_received || stopped?)
89
+ sleep WAIT_INTERVAL
90
+ end
91
+
92
+ stop unless stopped?
93
+ end
94
+
95
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
96
+ def handle_message(message)
97
+ ack(message) if @ack_immediately
98
+
99
+ begin
100
+ worker_info("Started hanlding message")
101
+ @handler.handle(message)
102
+ worker_info("Finished hanlding message successfully")
103
+ rescue => e
104
+ nack(message) if !@ack_immediately
105
+ raise e # exception is handled in `#handle_error`
106
+ end
107
+
108
+ ack(message) if !@ack_immediately
109
+ end
110
+
111
+ def ack(message)
112
+ message.ack!
113
+ worker_info("Acked message")
114
+ end
115
+
116
+ def nack(message)
117
+ message.nack!
118
+ worker_info("Nacked message")
119
+ end
120
+
121
+ # @param [Exception] error
122
+ def handle_error(error)
123
+ worker_error(error)
124
+ @handler.on_error(error)
125
+ end
126
+
127
+ # @param [String] message
128
+ def worker_info(message)
129
+ @logger.info("[Worker #{Thread.current.object_id}] #{message}")
130
+ end
131
+
132
+ # @param [Exception] error
133
+ def worker_error(error)
134
+ e_str = "#{error.message}"
135
+ e_str += "\n#{error.backtrace.join("\n")}" if error.backtrace
136
+ @logger.error("[Worker #{Thread.current.object_id}] #{e_str}")
137
+ end
138
+
139
+ def stopped?
140
+ @stopped_mutex.synchronize { @stopped }
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,83 @@
1
+ module Gcpc
2
+ class Subscriber
3
+ # HandleEngine handle messages and exceptions with interceptors.
4
+ class HandleEngine
5
+ # @param [#handle, #on_error, Class] handler
6
+ # @param [<#handle, #on_error, Class>] interceptors
7
+ def initialize(handler:, interceptors:)
8
+ @handler = (handler.class == Class) ? handler.new : handler
9
+ @interceptors = interceptors.map { |i| (i.class == Class) ? i.new : i }
10
+ end
11
+
12
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
13
+ def handle(message)
14
+ d = message.data.dup
15
+ a = message.attributes.dup
16
+
17
+ intercept_message!(@interceptors, d, a, message) do |dd, aa, m|
18
+ handle_message(dd, aa, m)
19
+ end
20
+ end
21
+
22
+ # @param [Exception] error
23
+ def on_error(error)
24
+ intercept_error!(@interceptors, error) do |e|
25
+ handle_on_error(e)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # @param [<#handle>] interceptors
32
+ # @param [String] data
33
+ # @param [Hash] attributes
34
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
35
+ # @param [Proc] block
36
+ def intercept_message!(interceptors, data, attributes, message, &block)
37
+ if interceptors.size == 0
38
+ return yield(data, attributes, message)
39
+ end
40
+
41
+ i = interceptors.first
42
+ rest = interceptors[1..-1]
43
+
44
+ i.handle(data, attributes, message) do |d, a, m|
45
+ intercept_message!(rest, d, a, m, &block)
46
+ end
47
+ end
48
+
49
+ # @param [<#on_error>] interceptors
50
+ # @param [Exception] error
51
+ # @param [Proc] block
52
+ def intercept_error!(interceptors, error, &block)
53
+ return yield(error) if interceptors.size == 0
54
+
55
+ i = interceptors.first
56
+ rest = interceptors[1..-1]
57
+
58
+ if !i.respond_to?(:on_error)
59
+ # If #on_error is not implemented in the interceptor, it is simply
60
+ # skipped.
61
+ return intercept_error!(rest, error, &block)
62
+ end
63
+
64
+ i.on_error(error) do |e|
65
+ intercept_error!(rest, e, &block)
66
+ end
67
+ end
68
+
69
+ # @param [String] data
70
+ # @param [Hash] attributes
71
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
72
+ def handle_message(data, attributes, message)
73
+ @handler.handle(data, attributes, message)
74
+ end
75
+
76
+ # @param [Exception] error
77
+ def handle_on_error(error)
78
+ return if !@handler.respond_to?(:on_error)
79
+ @handler.on_error(error)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,35 @@
1
+ require "google/cloud/pubsub"
2
+
3
+ module Gcpc
4
+ class Subscriber
5
+ class SubscriptionClient
6
+ DEFAULT_CONNECT_TIMEOUT = 5
7
+
8
+ # @param [String] project_id
9
+ # @param [String] subscription_name
10
+ # @param [Google::Cloud::Pubsub::Credentials, String, nil]
11
+ # @param [String, nil] emulator_host
12
+ def initialize(project_id:, subscription_name:, credentials:, emulator_host:, connect_timeout: DEFAULT_CONNECT_TIMEOUT)
13
+ project = Google::Cloud::Pubsub.new(
14
+ project_id: project_id,
15
+ credentials: credentials,
16
+ emulator_host: emulator_host,
17
+ )
18
+ @project = project
19
+ @subscription_name = subscription_name
20
+ @connect_timeout = connect_timeout
21
+ end
22
+
23
+ # @return [Google::Cloud::Pubsub::Subscription]
24
+ def get
25
+ t = nil
26
+ Timeout.timeout(@connect_timeout) do
27
+ t = @project.subscription(@subscription_name)
28
+ end
29
+ t
30
+ rescue Timeout::Error => e
31
+ raise "Getting subscription \"#{@subscription_name}\" from project \"#{@project.project_id}\" timed out!"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Gcpc
2
+ VERSION = "0.0.4"
3
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gcpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Nao Minami
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: google-cloud-pubsub
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Google Cloud Pub/Sub Client
84
+ email:
85
+ - south37777@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - bin/console
99
+ - bin/setup
100
+ - examples/publisher-example/README.md
101
+ - examples/publisher-example/publisher-example.rb
102
+ - examples/subscriber-example/README.md
103
+ - examples/subscriber-example/subscriber-example.rb
104
+ - gcpc.gemspec
105
+ - lib/gcpc.rb
106
+ - lib/gcpc/publisher.rb
107
+ - lib/gcpc/publisher/base_interceptor.rb
108
+ - lib/gcpc/publisher/engine.rb
109
+ - lib/gcpc/publisher/topic_client.rb
110
+ - lib/gcpc/subscriber.rb
111
+ - lib/gcpc/subscriber/base_handler.rb
112
+ - lib/gcpc/subscriber/base_interceptor.rb
113
+ - lib/gcpc/subscriber/default_logger.rb
114
+ - lib/gcpc/subscriber/engine.rb
115
+ - lib/gcpc/subscriber/handle_engine.rb
116
+ - lib/gcpc/subscriber/subscription_client.rb
117
+ - lib/gcpc/version.rb
118
+ homepage: https://github.com/south37/gcpc
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubygems_version: 3.0.3
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Google Cloud Pub/Sub Client
141
+ test_files: []