waithook 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2d3624c5bebd573d8f2e33f66d96d19b93acc3a9
4
- data.tar.gz: 25d37ca7b8c923b24c04425ce35f75ed997d9033
3
+ metadata.gz: a16ae52a0e661142f71a645d2eacf259bab70230
4
+ data.tar.gz: ff1efd58740c07f102bfc9f54957526c4c13f37c
5
5
  SHA512:
6
- metadata.gz: 32a2181e21aa618a589c64d7dbd07132ebdc1942c4169bfe26019b8061d05ea18a494d0ffb8e2da343accba7307427c9c69742ed3a05477897ff3d91dd3de847
7
- data.tar.gz: 3a96f332074cbd7a5e8364bcebbc42de580ca8575f39ad17c19c28d0f012320c09cd36e7df2d18ad664bd43a3852a682ad79bb608fd3ca7796fb4cf090f930b3
6
+ metadata.gz: 5f5b498540935c8e52788837cff9b5dbbceeec68bd9a6b6810522112344f0b36efefa067d4ec1ee7047bcd9b6b1332b4c2c78347ed8bbe2135ee041ba8ffc517
7
+ data.tar.gz: 28446b7fefec07045ad9ce56b3326f599aa66fa59c470c35cc6b87b55ef7a9b9694564422ea4c407a7a62e995095fb7ce92a905efff412409c92fff6d44dc802
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
- *.gem
1
+ *.gem
2
+ doc
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- waithook (0.1)
4
+ waithook (0.2)
5
5
  websocket (~> 1.2)
6
6
 
7
7
  GEM
@@ -10,16 +10,16 @@ GEM
10
10
  addressable (2.5.0)
11
11
  public_suffix (~> 2.0, >= 2.0.2)
12
12
  ansi (1.5.0)
13
- builder (3.2.2)
13
+ builder (3.2.3)
14
14
  excon (0.54.0)
15
- minitest (5.9.1)
16
- minitest-reporters (1.1.12)
15
+ minitest (5.10.1)
16
+ minitest-reporters (1.1.14)
17
17
  ansi
18
18
  builder
19
19
  minitest (>= 5.0)
20
20
  ruby-progressbar
21
- public_suffix (2.0.4)
22
- rake (11.3.0)
21
+ public_suffix (2.0.5)
22
+ rake (12.0.0)
23
23
  ruby-progressbar (1.8.1)
24
24
  websocket (1.2.3)
25
25
 
@@ -34,4 +34,4 @@ DEPENDENCIES
34
34
  waithook!
35
35
 
36
36
  BUNDLED WITH
37
- 1.13.6
37
+ 1.14.2
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Waithook ruby client
2
+
3
+ A ruby client [Waithook](http://waithook.com).
4
+ Wiathook is a service to transmit HTTP requests over websocket connection.
5
+ It's kinda Pub/Sub system for HTTP notifications, built for recieving webhook notifications behind proxy or when public IP is unknown (such as cloud CI server).
6
+
7
+
8
+ To recieve notifications you should add `http://waithook.com/some_random_string` as notification URL and start listening websocket messages at `wss://waithook.com/some_random_string`.
9
+
10
+ ### Command line usage
11
+
12
+ Install:
13
+ ```sh
14
+ gem install waithook
15
+ ```
16
+
17
+ Subscribe and print incoming requests:
18
+ ````sh
19
+ waithook waithook.com/my_path
20
+ ```
21
+
22
+ Subscribe and forvard to other web server:
23
+ ````sh
24
+ waithook waithook.com/my_path --forward http://localhost:3000/notify
25
+ ```
26
+
27
+ ### Ruby API
28
+
29
+ ```ruby
30
+ waithook = Waithook.subscribe(timeout: 60, raise_on_timeout: true) do |webhook|
31
+ webhook.json_body['order_id'] == order_id
32
+ end
33
+
34
+ waithook.send_to("http://localhost:3000/notify")
35
+ ```
36
+
37
+ ### Usage examples
38
+
39
+ * Testing integration with payment gateway
40
+ * Testing github webhooks
41
+ * Testing incoming email processing
42
+ * Testing slack bots
43
+ * Testing facebook webhooks
44
+
45
+ So waithook just help to deliver webhook to your application when public IP is unknown or not available. It can help when multiple developers testing integration with other service on localhost or your automated tests running in CI.
46
+
data/Rakefile CHANGED
@@ -2,3 +2,7 @@
2
2
  task :test do
3
3
  Dir.glob('./tests/**/*_test.rb').each { |file| require file }
4
4
  end
5
+
6
+ task :doc do
7
+ `rdoc -f hanna lib/* -m README.md`
8
+ end
data/bin/waithook CHANGED
@@ -9,8 +9,8 @@ def help_message
9
9
  "Waithook command line client.",
10
10
  "",
11
11
  "Example usage:",
12
- " waithook waithook.herokuapp.com/my_path",
13
- " waithook waithook.herokuapp.com/my_path --forward http://localhost:3000/notify"
12
+ " waithook waithook.com/my_path",
13
+ " waithook waithook.com/my_path --forward http://localhost:3000/notify"
14
14
  ].join("\n")
15
15
  end
16
16
 
data/example/example.rb CHANGED
@@ -5,7 +5,7 @@ require './websocket-client'
5
5
 
6
6
  HOST = 'localhost'
7
7
  PORT = 3012
8
- #HOST = 'waithook.herokuapp.com'
8
+ #HOST = 'waithook.com'
9
9
  #PORT = 80
10
10
 
11
11
  client = WebsocketClient.new(host: HOST, port: PORT, path: 'test-ruby')
@@ -42,7 +42,7 @@ end
42
42
 
43
43
  #socket = TCPSocket.open(hostname, port)
44
44
  #
45
- #request = WebSocket::Handshake::Client.new(url: 'ws://waithook.herokuapp.com/test-ruby')
45
+ #request = WebSocket::Handshake::Client.new(url: 'ws://waithook.com/test-ruby')
46
46
  #puts request
47
47
  #
48
48
  #socket.print(request)
@@ -7,7 +7,7 @@ require './websocket-client'
7
7
 
8
8
  HOST = 'localhost'
9
9
  PORT = 3012
10
- #HOST = 'waithook.herokuapp.com'
10
+ #HOST = 'waithook.com'
11
11
  #PORT = 80
12
12
 
13
13
  threads_num = ENV['THREADS'] ? ENV['THREADS'].to_i : 10
@@ -5,8 +5,8 @@ require './websocket-client'
5
5
 
6
6
  #HOST = 'localhost'
7
7
  #PORT = 3012
8
- HOST = 'waithook.herokuapp.com'
9
- PORT = 443
8
+ HOST = 'waithook.com'
9
+ PORT = 80
10
10
 
11
11
  client = WebsocketClient.new(host: HOST, port: PORT, path: 'test-ruby').connect!.wait_handshake!
12
12
 
data/lib/waithook.rb CHANGED
@@ -10,9 +10,22 @@ require_relative 'waithook/cli'
10
10
 
11
11
  class Waithook
12
12
 
13
- SERVER_HOST = "waithook.herokuapp.com"
14
- SERVER_PORT = 443
15
-
13
+ # Default server host
14
+ SERVER_HOST = "waithook.com"
15
+ # Default server port
16
+ SERVER_PORT = 80
17
+
18
+ # Connect to server and start filter incoming messages (optionally)
19
+ #
20
+ # Usage:
21
+ # Waithook.default_path = 'my-notification-endpoint'
22
+ # waithook = Waithook.subscribe(timeout: 10) do |message|
23
+ # message.json_body['user_name'] == 'John Doe' # will return messages only passing this criteria
24
+ # end
25
+ # waithook.forward_to('http://localhost:4567/webhook', raise_on_timeout: true)
26
+ #
27
+ # <tt>options</tt> argument is will be passed to #initialize
28
+ #
16
29
  def self.subscribe(options = {}, &block)
17
30
  instance = new(options)
18
31
  if block
@@ -22,19 +35,35 @@ class Waithook
22
35
  instance
23
36
  end
24
37
 
25
- def self.default_path
26
- @default_path
27
- end
28
-
38
+ # Default path that it will be subscribed to
29
39
  def self.default_path=(value)
30
40
  @default_path = value
31
41
  end
32
42
 
43
+ # Accessor for @default_path
44
+ def self.default_path
45
+ @default_path
46
+ end
47
+
48
+ # Filter (Proc), can be used to to filter incoming messages
33
49
  attr_accessor :filter
50
+ # Array of all recieved messages
34
51
  attr_accessor :messages
52
+ # Websocket client, instance of Waithook::WebsocketClient
35
53
  attr_reader :client
54
+ # Connection options, hash
36
55
  attr_reader :options
37
56
 
57
+ # Available options:
58
+ # * :host
59
+ # * :port
60
+ # * :auto_connect - whenever connect to server automatically when instance is created (default is true)
61
+ # * :path
62
+ # * :timeout
63
+ # * :logger_level - logger level, default :info
64
+ # * :output - output stream for default logger. If value == false then it will be silent, default is $stdout
65
+ # * :logger - custom logger object
66
+ #
38
67
  def initialize(options = {})
39
68
  @options = {
40
69
  host: SERVER_HOST,
@@ -47,6 +76,7 @@ class Waithook
47
76
  raise ArgumentError, ":path is missing. Please add :path to options argument or set Waithook.default_path = 'foo'"
48
77
  end
49
78
 
79
+ # TODO: add SSL options
50
80
  @client = WebsocketClient.new(
51
81
  path: @options[:path],
52
82
  host: @options[:host],
@@ -63,6 +93,7 @@ class Waithook
63
93
  connect! if @options[:auto_connect]
64
94
  end
65
95
 
96
+ # Logger object
66
97
  def logger
67
98
  @logger ||= LoggerWithTrace.new(@options[:logger] ? $stdout : StringIO.new).setup(
68
99
  progname: self.class.name,
@@ -70,6 +101,7 @@ class Waithook
70
101
  )
71
102
  end
72
103
 
104
+ # Start connection to waithook server
73
105
  def connect!
74
106
  raise "Waithook connection already started" if @started
75
107
  @started = true
@@ -77,15 +109,21 @@ class Waithook
77
109
  self
78
110
  end
79
111
 
112
+ # Whenever connected to server or not
80
113
  def started?
81
114
  !!@started
82
115
  end
83
116
 
117
+ # Send all incoming webhook requests to running HTTP server
118
+ #
119
+ # webhook = Waithook.subscribe(path: 'my-webhoooks').forward_to('http://localhost:3000/notification')
120
+ #
84
121
  def forward_to(url, options = {})
85
122
  webhook = wait_message(options)
86
123
  webhook.send_to(url) unless webhook.nil?
87
124
  end
88
125
 
126
+ # Wait incoming request information (wait message on websocket connection)
89
127
  def wait_message(options = {})
90
128
  raise_timeout_error = options.has_key?(:raise_on_timeout) ? options[:raise_on_timeout] : true
91
129
  timeout = options[:timeout] || @options[:timeout] || 0
@@ -108,16 +146,24 @@ class Waithook
108
146
  return nil
109
147
  end
110
148
 
149
+ # Close connection to server
111
150
  def close!
112
151
  @client.close!
113
152
  @started = false
114
153
  end
115
154
 
155
+ # Represent incoming request to waithook,
156
+ # that was send to client as JSON via websocket connection
116
157
  class Webhook
158
+ # Original request URL, e.g. '/my-notification-endpoint?aaa=bbb'
117
159
  attr_reader :url
160
+ # Hash of headers
118
161
  attr_reader :headers
162
+ # Request body (for POST, PATCH, PUT)
119
163
  attr_reader :body
164
+ # Request method ("GET", "POST", "PATCH", etc)
120
165
  attr_reader :method
166
+ # Raw message from waithook server
121
167
  attr_reader :message
122
168
 
123
169
  def initialize(payload)
@@ -129,12 +175,19 @@ class Waithook
129
175
  @method = data['method']
130
176
  end
131
177
 
178
+ # Returns Hash.
179
+ # Access request body encoded as JSON (for POST, PATCH, PUT)
132
180
  def json_body
133
181
  if @body
134
182
  @json_body ||= JSON.parse(@body)
135
183
  end
136
184
  end
137
185
 
186
+ # Send webhook information to other HTTP server
187
+ #
188
+ # webhook = Waithook.subscribe(path: 'my-webhoooks').wait_message
189
+ # webhook.send_to('http://localhost:3000/notification')
190
+ #
138
191
  def send_to(url)
139
192
  uri = URI.parse(url)
140
193
  response = nil
data/lib/waithook/cli.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  class Waithook
2
- module WithColor
2
+ module WithColor #:nodoc:
3
3
  extend self
4
4
  def black(s); "\033[30m#{s}\033[0m" end
5
5
  def red(s); "\033[31m#{s}\033[0m" end
@@ -13,8 +13,8 @@ class Waithook
13
13
  def bold(s); "\e[1m#{s}\e[m" end
14
14
  end
15
15
 
16
- module CLI
17
- class ArgError < ArgumentError; end
16
+ module CLI #:nodoc:
17
+ class ArgError < ArgumentError; end #:nodoc:
18
18
 
19
19
  extend self
20
20
 
@@ -1,8 +1,8 @@
1
1
  require 'logger'
2
2
 
3
3
  # Just add trace level
4
- class LoggerWithTrace < ::Logger
5
- module Severity
4
+ class LoggerWithTrace < ::Logger #:nodoc:
5
+ module Severity #:nodoc:
6
6
  include ::Logger::Severity
7
7
  TRACE = -1
8
8
  end
@@ -1,3 +1,3 @@
1
1
  class Waithook
2
- VERSION = "0.2"
2
+ VERSION = "0.3"
3
3
  end
@@ -5,21 +5,48 @@ require 'stringio'
5
5
  require_relative 'logger_with_trace'
6
6
 
7
7
  class Waithook
8
+ # Simple websocket client, internally use websocket gem to construct and parse websocket messages
9
+ #
10
+ # Usage:
11
+ #
12
+ # client = WebsocketClient.new(host: HOST, port: PORT, path: 'test-ruby')
13
+ # client.send_ping!
14
+ # client.send_message!("Hello, server")
15
+ # type, data = client.wait_message # => type == :text, data is message content as a string
16
+ #
8
17
  class WebsocketClient
9
18
 
10
19
  attr_accessor :logger
11
20
 
21
+ # Waiter object, blocking current thread. Better abstraction for ruby's Queue utility class
22
+ #
23
+ # message_waiter = Waiter.new
24
+ # Thread.new { sleep 5; message_waiter.notify('Done!!!') }
25
+ # message_waiter.wait # => return 'Done!!!' after 5 seconds pause
26
+ #
12
27
  class Waiter
28
+ # Waiting for someone to call #notify
13
29
  def wait
14
30
  @queue = Queue.new
15
31
  @queue.pop(false)
16
32
  end
17
33
 
34
+ # Notify waiting side
18
35
  def notify(data)
19
36
  @queue.push(data)
20
37
  end
21
38
  end
22
39
 
40
+ # Available options:
41
+ # * :host - hostname
42
+ # * :port - server port (default 80)
43
+ # * :path - HTTP path
44
+ # * :ssl - true/false (will detect base on port if blank)
45
+ # * :ssl_version
46
+ # * :verify_mode
47
+ # * :logger_level - logger level, default :info
48
+ # * :output - output stream for default logger. If value == false then it will be silent, default is $stdout
49
+ # * :logger - custom logger object
23
50
  def initialize(options = {})
24
51
  # required: :host, :path
25
52
 
@@ -52,6 +79,7 @@ class Waithook
52
79
  end
53
80
  end
54
81
 
82
+ # Estabilish connection to server and send initial handshake
55
83
  def connect!
56
84
  logger.info "Connecting to #{@host} #{@port}"
57
85
 
@@ -72,7 +100,7 @@ class Waithook
72
100
  end
73
101
 
74
102
  @is_open = true
75
- @handshake = WebSocket::Handshake::Client.new(url: "ws://#{@host}/#{@path}")
103
+ @handshake = WebSocket::Handshake::Client.new(url: "ws://#{@host}/#{@path.to_s.sub(/^\//, '')}")
76
104
 
77
105
  logger.trace "Sending handshake:\n#{@handshake}"
78
106
 
@@ -82,6 +110,7 @@ class Waithook
82
110
  self
83
111
  end
84
112
 
113
+ # Return true/false
85
114
  def connected?
86
115
  !!@is_open
87
116
  end
@@ -106,41 +135,46 @@ class Waithook
106
135
  end
107
136
  end
108
137
 
138
+ # Send <tt>:ping</tt> message to server
109
139
  def send_ping!
110
140
  _send_frame(:ping)
111
141
  end
112
142
 
143
+ # Send <tt>:pong</tt> message to server, usually as a response to :ping message
113
144
  def send_pong!
114
145
  _send_frame(:pong)
115
146
  end
116
147
 
148
+ # Send message to server (type <tt>:text</tt>)
117
149
  def send_message!(payload)
118
150
  _send_frame(:text, payload)
119
151
  end
120
152
 
121
- def wait_handshake!
122
- while !@handshake_received
123
- sleep 0.001
124
- end
125
- self
126
- end
127
-
153
+ # Wait for new message (thread safe, all waiting threads will recieve a message)
154
+ # Message format [type, data], e.g. <tt>[:text, "hello world"]</tt>
128
155
  def wait_new_message
129
156
  waiter = Waiter.new
130
157
  @waiters << waiter
131
158
  waiter.wait
132
159
  end
133
160
 
161
+ # Synchroniously waiting for new message. Not thread safe, only one thread will receive message
162
+ # Message format [type, data], e.g. <tt>[:text, "hello world"]</tt>
134
163
  def wait_message
135
164
  @messages.pop
136
165
  end
137
166
 
138
- def wait_connected
167
+ # Wait until server handshake recieved.
168
+ # Once it's recieved - we can are listening for new messages
169
+ # and connection is ready for sending data
170
+ def wait_handshake!
139
171
  return true if @handshake_received
140
172
  waiter = Waiter.new
141
173
  @connect_waiters << waiter
142
174
  waiter.wait
175
+ self
143
176
  end
177
+ alias_method :wait_connected, :wait_handshake!
144
178
 
145
179
  def _handshake_recieved!
146
180
  @handshake_received = true
@@ -195,6 +229,7 @@ class Waithook
195
229
  data.join("")
196
230
  end
197
231
 
232
+ # Send <tt>:close</tt> message to socket and close socket connection
198
233
  def close!(options = {send_close: true})
199
234
  unless @is_open
200
235
  logger.info "Already closed"
data/tests/server_test.rb CHANGED
@@ -128,4 +128,32 @@ describe "Server" do
128
128
  assert_equal("test-data", message['body'])
129
129
  assert_equal("PUT", message['method'])
130
130
  end
131
+
132
+ it "should response with challenge value for slack type" do
133
+ @client = Waithook::WebsocketClient.new(host: HOST, port: PORT, path: PATH + "?type=slack", output: StringIO.new)
134
+ .connect!.wait_handshake!
135
+
136
+ message = {
137
+ token: "mJbhjRozbVxkrQmthcxndV0X",
138
+ challenge: "x39xHMiljI4m42XPO9jxui47fRvi0SMZhOlYI0SbFSzNSbQJItus",
139
+ type: "url_verification"
140
+ }
141
+ response = POST(PATH + "?type=slack", JSON.generate(message))
142
+ assert_equal(response.body, message[:challenge])
143
+ end
144
+
145
+ it "should response with OK for slack when not a json" do
146
+ response = POST(PATH + "?type=slack", "AAA")
147
+ assert_equal(response.body, "OK\n")
148
+ end
149
+
150
+ it "should response with OK for slack without challenge" do
151
+ response = POST(PATH + "?type=slack", "{}")
152
+ assert_equal(response.body, "OK\n")
153
+ end
154
+
155
+ it "should response with OK for slack when challenge isn't a string" do
156
+ response = POST(PATH + "?type=slack", '{"challenge": []}')
157
+ assert_equal(response.body, "OK\n")
158
+ end
131
159
  end
data/waithook.gemspec CHANGED
@@ -7,8 +7,9 @@ Gem::Specification.new do |s|
7
7
  s.email = ["pavel.evst@gmail.com"]
8
8
  s.homepage = "https://github.com/paxa/waithook-ruby"
9
9
  s.summary = %q{HTTP to WebSocket transmitting client}
10
- s.description = "Waithook gem is client lib for waithook service https://waithook.heroku.com"
10
+ s.description = "Waithook gem is client lib for waithook service http://waithook.com"
11
11
  s.license = 'MIT'
12
+ s.required_ruby_version = '~> 2.0'
12
13
 
13
14
  s.files = `git ls-files`.split("\n")
14
15
  s.test_files = []
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waithook
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.2'
4
+ version: '0.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pavel Evstigneev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-29 00:00:00.000000000 Z
11
+ date: 2017-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: websocket
@@ -24,7 +24,7 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.2'
27
- description: Waithook gem is client lib for waithook service https://waithook.heroku.com
27
+ description: Waithook gem is client lib for waithook service http://waithook.com
28
28
  email:
29
29
  - pavel.evst@gmail.com
30
30
  executables:
@@ -35,6 +35,7 @@ files:
35
35
  - ".gitignore"
36
36
  - Gemfile
37
37
  - Gemfile.lock
38
+ - README.md
38
39
  - Rakefile
39
40
  - bin/waithook
40
41
  - example/example.rb
@@ -60,9 +61,9 @@ require_paths:
60
61
  - lib
61
62
  required_ruby_version: !ruby/object:Gem::Requirement
62
63
  requirements:
63
- - - ">="
64
+ - - "~>"
64
65
  - !ruby/object:Gem::Version
65
- version: '0'
66
+ version: '2.0'
66
67
  required_rubygems_version: !ruby/object:Gem::Requirement
67
68
  requirements:
68
69
  - - ">="