ultrahook 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +6 -0
- data/bin/ultrahook +6 -0
- data/lib/ultrahook.rb +2 -0
- data/lib/ultrahook/client.rb +170 -0
- data/lib/ultrahook/version.rb +3 -0
- metadata +70 -0
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
|
+
|
data/bin/ultrahook
ADDED
data/lib/ultrahook.rb
ADDED
@@ -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
|
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: []
|