ultrahook 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,6 @@
1
+ Copyright (c) 2013 Senvee Inc.
2
+
3
+ Senvee Inc hereby grants you a non-exclusive commercial license to use and distribute the provided software. You may not create derivative works of the provided software.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
6
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require 'ultrahook'
5
+
6
+ UltraHook::Client.new.start(*ARGV)
@@ -0,0 +1,2 @@
1
+ require 'ultrahook/client'
2
+ require 'ultrahook/version'
@@ -0,0 +1,170 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'base64'
5
+ require 'optparse'
6
+
7
+ module UltraHook
8
+ class Client
9
+
10
+ def start(*args)
11
+ @options = {}
12
+
13
+ optparse = OptionParser.new do |o|
14
+ o.banner = "Usage: ultrahook [options] <subdomain> <destination>"
15
+
16
+ o.on("-k", "--key <api key>", String, "API Key") do |key|
17
+ @options["key"] =key
18
+ end
19
+
20
+ o.on_tail("-h", "--help", "Display this help") do
21
+ puts o
22
+ exit
23
+ end
24
+
25
+ o.on("-V", "--version", "Display client version") do
26
+ puts "UltraHook client version: #{UltraHook::VERSION}"
27
+ exit
28
+ end
29
+ end
30
+ optparse.parse!(args)
31
+
32
+ if @options["key"].nil? || @options["key"] == ""
33
+ @options["key"] = ENV["ULTRAHOOK_API_KEY"]
34
+ end
35
+
36
+ if @options["key"].nil? || @options["key"] == ""
37
+ path = "#{Dir.home}/.ultrahook"
38
+ if File.readable?(path)
39
+ if matchdata = /api_key:\s*([^\s]+)/.match(IO.read(path))
40
+ @options["key"] = matchdata.captures[0]
41
+ end
42
+ end
43
+ end
44
+
45
+ if @options["key"].nil? || @options["key"] == ""
46
+ error "An API key must be provided. Get one from http://www.ultrahook.com"
47
+ end
48
+
49
+ if args.size != 2
50
+ puts optparse
51
+ exit
52
+ end
53
+
54
+ @options["host"] = ARGV[0].downcase
55
+ error "Subdomain can only contain alphanumeric characters and hyphen (-)" unless @options["host"] =~ /^[[:alnum:]\-]+$/
56
+ error "Subdomain cannot start or end with hyphens" if @options["host"] =~ /^\-/ || @options["host"] =~ /\-$/
57
+ error "Subdomain cannot contain consecutive hyphens" if @options["host"] =~ /\-\-/
58
+
59
+ @options["destination"] = parse_destination(ARGV[1])
60
+
61
+ retrieve_config
62
+ init_stream
63
+ end
64
+
65
+ def parse_destination(dest)
66
+ if dest =~ /^\d+/
67
+ "http://localhost:#{dest}"
68
+ elsif dest =~ /^[[:alnum:]\.\-]+:\d+$/
69
+ "http://#{dest}"
70
+ elsif dest =~ /^[[:alnum:]\.\-]+$/
71
+ "http://#{dest}"
72
+ elsif dest =~ /^http:\/\//
73
+ dest
74
+ else
75
+ error "Cannot parse destination url"
76
+ end
77
+ end
78
+
79
+ def process(msg)
80
+ begin
81
+ payload = JSON.parse(Base64.decode64(msg))
82
+ rescue
83
+ error "Cannot communicate with server."
84
+ end
85
+
86
+ method_name = "process_#{payload["type"]}"
87
+ send(method_name, payload) if respond_to?(method_name)
88
+ end
89
+
90
+ def process_init(payload)
91
+ puts "Forwarding activated..."
92
+ puts "http://#{@options["host"]}.#{@namespace}.ultrahook.com -> #{@options["destination"]}"
93
+ end
94
+
95
+ def process_error(payload)
96
+ error payload["message"]
97
+ end
98
+
99
+ def process_warning(payload)
100
+ puts "Warning: "+payload["message"]
101
+ end
102
+
103
+ def full_path(uri)
104
+ return "/" if uri.path == "" && uri.query == ""
105
+ return "/?#{uri.query}" if uri.path == "" && uri.query != ""
106
+ return "#{uri.path}" if uri.path != "" && uri.query == ""
107
+ return "#{uri.path}?#{uri.query}" if uri.path != "" && uri.query != ""
108
+ raise "huh? this is impossible!"
109
+ end
110
+
111
+ def process_request(payload)
112
+ uri = URI("#{@options["destination"]}#{payload["path"]}?#{payload["query"]}")
113
+ response = http_post(uri, payload["body"], payload["headers"])
114
+
115
+ puts "[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] POST http://#{uri.host}:#{uri.port}#{full_path(uri)} - #{response.code}"
116
+ end
117
+
118
+ def http_post(uri, data, headers=nil)
119
+ Net::HTTP.new(uri.host, uri.port).post(full_path(uri), data, headers)
120
+ end
121
+
122
+ def retrieve_config
123
+ response = http_post(URI("http://www.ultrahook.com/init"), "key=#{@options["key"]}&host=#{@options["host"]}&version=#{UltraHook::VERSION}")
124
+
125
+ error "Could not retrieve config from server: #{response.code}" if response.code.to_i != 200
126
+
127
+ payload = JSON.parse(response.body)
128
+ if payload["success"] == true
129
+ @stream_url = payload["url"]
130
+ @namespace = payload["namespace"]
131
+
132
+ puts "Authenticated as #{@namespace}"
133
+ else
134
+ error payload["error"]
135
+ end
136
+ end
137
+
138
+ def init_stream
139
+ uri = URI(@stream_url)
140
+ Net::HTTP.start(uri.host, uri.port) do |http|
141
+ begin
142
+ request = Net::HTTP::Get.new "#{uri.path}?#{uri.query}"
143
+
144
+ http.read_timeout = 3660 # give server an extra 60 seconds to send disconnect
145
+ http.request request do |response|
146
+ io = ""
147
+ response.read_body do |chunk|
148
+ io += chunk
149
+
150
+ if idx = io.index("\n\n")
151
+ msg = io.slice!(0, idx+2)
152
+ process(msg)
153
+ end
154
+ end
155
+ end
156
+
157
+ error "Lost connection to UltraHook server"
158
+ rescue Interrupt
159
+ http.finish
160
+ end
161
+ end
162
+ end
163
+
164
+ def error(msg)
165
+ puts "Error: #{msg}"
166
+ exit -1
167
+ end
168
+
169
+ end
170
+ end
@@ -0,0 +1,3 @@
1
+ module UltraHook
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ultrahook
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Vinay Sahni
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-07-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.8.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.8.0
30
+ description: UltraHook is a simple command line tool that connects public endpoints
31
+ (provided by the ultrahook.com service) to private endpoints accessible from your
32
+ computer. It relays any HTTP POST requests sent to the public endpoint.
33
+ email: vinay@sahni.org
34
+ executables:
35
+ - ultrahook
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - lib/ultrahook/client.rb
40
+ - lib/ultrahook/version.rb
41
+ - lib/ultrahook.rb
42
+ - LICENSE
43
+ - bin/ultrahook
44
+ homepage: http://www.ultrahook.com
45
+ licenses:
46
+ - Commercial
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.23
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: A simple command line tool that enables you to receive webhooks while behind
69
+ firewalls
70
+ test_files: []