gcpc 0.0.4

This diff has not been reviewed by any users.
Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []