local_tunnel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f5300a92b5881f63621885ab10ce834c85d2e2f1
4
+ data.tar.gz: 894a2285865ce5a1e99f94c9263252bcb0d9b9bf
5
+ SHA512:
6
+ metadata.gz: 0cd55b30eea1aacb60990c1a632e7b38353c5dbad6b22b5bdeaec99ab038b6bdb7e874da1defd4f6aebe1e28faf82a7872fdf482d8cf49bfd85634427e844e59
7
+ data.tar.gz: dace353ede608e9113e0ee67ca59d2f950b2fed21f5f05864ed1e1ca4c4588af7c56dd8a1e2e8cd2868d5fee262c91ecdee110ff767d7015b5a1265f21b6b9cb
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ local_tunnel (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ coderay (1.1.1)
10
+ method_source (0.8.2)
11
+ minitest (5.10.3)
12
+ mustermann (1.0.1)
13
+ pry (0.10.4)
14
+ coderay (~> 1.1.0)
15
+ method_source (~> 0.8.1)
16
+ slop (~> 3.4)
17
+ rack (2.0.3)
18
+ rack-protection (2.0.0)
19
+ rack
20
+ rake (12.0.0)
21
+ sinatra (2.0.0)
22
+ mustermann (~> 1.0)
23
+ rack (~> 2.0)
24
+ rack-protection (= 2.0.0)
25
+ tilt (~> 2.0)
26
+ slop (3.6.0)
27
+ tilt (2.0.8)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ local_tunnel!
34
+ minitest (~> 5.10)
35
+ pry
36
+ rake (~> 12.0)
37
+ sinatra (~> 2.0)
38
+
39
+ BUNDLED WITH
40
+ 1.15.4
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2017, Loic Nageleisen
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ * Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+ require 'bundler'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ t.test_files = FileList['test/test_*.rb']
9
+ t.verbose = true
10
+ end
data/bin/local_tunnel ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'local_tunnel/cli'
5
+
6
+ LocalTunnel::CLI.start(ARGV)
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'local_tunnel'
4
+
5
+ class LocalTunnel::CLI
6
+ def initialize(port: 8000, verbose: false)
7
+ @options = {}
8
+ @options[:port] = port
9
+ @options[:verbose] = verbose
10
+ end
11
+
12
+ def option
13
+ @options
14
+ end
15
+
16
+ def start
17
+ LocalTunnel::Tunnel.new(debug: @options[:verbose]).start(option[:port]).wait
18
+ end
19
+
20
+ class << self
21
+ def start(argv)
22
+ options = {}
23
+
24
+ while argv.size > 0 && argv[0].start_with?('-') && argv[0] != '--'
25
+ case argv[0]
26
+ when '-v'
27
+ options[:verbose] = true
28
+ argv.shift
29
+ else
30
+ $stderr.write("#{program_name}: illegal option -- #{argv[0]}\n")
31
+ $stderr.write("#{usage}\n")
32
+ exit SysExits::USAGE
33
+ end
34
+ end
35
+
36
+ begin
37
+ options[:port] = Integer(argv[0]) if argv[0]
38
+ rescue TypeError, ArgumentError
39
+ $stderr.write("#{usage}\n")
40
+ exit SysExits::USAGE
41
+ end
42
+
43
+ new(options).start
44
+ end
45
+
46
+ def program_name
47
+ File.basename($PROGRAM_NAME)
48
+ end
49
+
50
+ def usage
51
+ "usage: #{program_name} [-v] [port]"
52
+ end
53
+ end
54
+
55
+ module SysExits
56
+ OK = 0
57
+ USAGE = 64
58
+ DATAERR = 65
59
+ NOINPUT = 66
60
+ NOUSER = 67
61
+ NOHOST = 68
62
+ UNAVAILABLE = 69
63
+ SOFTWARE = 70
64
+ OSERR = 71
65
+ OSFILE = 72
66
+ CANTCREAT = 73
67
+ IOERR = 74
68
+ TEMPFAIL = 75
69
+ PROTOCOL = 76
70
+ NOPERM = 77
71
+ CONFIG = 78
72
+ end
73
+ end
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LocalTunnel; end
4
+
5
+ require 'net/http'
6
+ require 'uri'
7
+ require 'json'
8
+ require 'logger'
9
+
10
+ module LocalTunnel
11
+ AssignedUrlInfo = Struct.new(
12
+ :id,
13
+ :url,
14
+ :port,
15
+ :max_conn_count,
16
+ )
17
+
18
+ SERVER = 'http://localtunnel.me/'
19
+
20
+ class TunnelConn
21
+ def initialize(rhost, rport, lport, id:, logger: nil)
22
+ @rhost = rhost
23
+ @rport = rport
24
+ @lhost = 'localhost'
25
+ @lport = lport
26
+ @lrcount = 0
27
+ @rlcount = 0
28
+ @id = id
29
+ @logger = logger || self.class.logger.dup
30
+ end
31
+
32
+ def start
33
+ @rconn = rconnect
34
+ @lconn = lconnect
35
+
36
+ @lrthr = Thread.new do
37
+ buf = String.new(capacity: 1024)
38
+ loop do
39
+ begin
40
+ logger.debug(format("%03d lr: attempting read", @id))
41
+ @lconn.readpartial(1024, buf)
42
+ rescue EOFError
43
+ logger.debug(format("%03d lr: read failed, reconnecting", @id))
44
+ @lconn.close
45
+ @lconn = lconnect
46
+ retry
47
+ end
48
+ logger.debug(format("%03d lr: read #{buf.size}", @id))
49
+
50
+ begin
51
+ logger.debug(format("%03d lr: attempting write", @id))
52
+ s = @rconn.write(buf)
53
+ rescue IOError
54
+ logger.debug(format("%03d lr: write failed, reconnecting", @id))
55
+ @rconn.close
56
+ @rconn = rconnect
57
+ retry
58
+ end
59
+ logger.debug(format("%03d lr: write #{s}", @id))
60
+
61
+ @lrcount += buf.size
62
+ logger.debug(format("%03d lr: total #{@lrcount}", @id))
63
+ buf.clear
64
+ end
65
+ end
66
+
67
+ @rlthr = Thread.new do
68
+ buf = String.new(capacity: 1024)
69
+ loop do
70
+ begin
71
+ logger.debug(format("%03d rl: attempting read", @id))
72
+ @rconn.readpartial(1024, buf)
73
+ rescue EOFError
74
+ logger.debug(format("%03d rl: read failed, reconnecting", @id))
75
+ @rconn.close
76
+ @rconn = rconnect
77
+ retry
78
+ end
79
+ logger.debug(format("%03d rl: read #{buf.size}", @id))
80
+
81
+ begin
82
+ logger.debug(format("%03d rl: attempting write", @id))
83
+ s = @lconn.write(buf)
84
+ rescue IOError
85
+ logger.debug(format("%03d rl: write failed, reconnecting", @id))
86
+ @lconn.close
87
+ @lconn = lconnect
88
+ retry
89
+ end
90
+ logger.debug(format("%03d rl: write #{s}", @id))
91
+
92
+ @rlcount += buf.size
93
+ logger.debug(format("%03d rl: total #{@rlcount}", @id))
94
+ buf.clear
95
+ end
96
+ end
97
+
98
+ self
99
+ end
100
+
101
+ def wait
102
+ [@lrthr, @rlthr].each(&:join)
103
+ self
104
+ end
105
+
106
+ def stop
107
+ [@lrthr, @rlthr].compact.each(&:kill)
108
+ self
109
+ end
110
+
111
+ def rconnect
112
+ TCPSocket.new(@rhost, @rport)
113
+ end
114
+
115
+ def lconnect
116
+ TCPSocket.new(@lhost, @lport)
117
+ end
118
+
119
+ private
120
+
121
+ def logger
122
+ @logger
123
+ end
124
+
125
+ class << self
126
+ def logger
127
+ @logger ||= LocalTunnel.logger.dup
128
+ end
129
+
130
+ def logger=(value)
131
+ @logger = value
132
+ end
133
+ end
134
+ end
135
+
136
+ class Tunnel
137
+ def initialize(domain: nil, debug: false)
138
+ @domain = domain
139
+ @logger = self.class.logger.dup
140
+ @logger.level = Logger::DEBUG if debug
141
+ end
142
+
143
+ def url
144
+ assign_url! unless defined?(@url)
145
+ @url
146
+ end
147
+
148
+ def port
149
+ assign_url! unless defined?(@port)
150
+ @port
151
+ end
152
+
153
+ def max_conn_count
154
+ assign_url! unless defined?(@max_conn_count)
155
+ @max_conn_count
156
+ end
157
+
158
+ def create(port)
159
+ assign_url!
160
+ logger.debug("#{url}, #{max_conn_count}")
161
+ @max_conn_count.times do |i|
162
+ @conns[i] = TunnelConn.new(URI(SERVER).host, @port, port, id: i)
163
+ @conns[i].start
164
+ end
165
+ end
166
+
167
+ def start(port)
168
+ create(port)
169
+ self
170
+ end
171
+
172
+ def wait
173
+ @conns.compact.each(&:wait)
174
+ end
175
+
176
+ def stop
177
+ @conns.compact.each(&:stop)
178
+ end
179
+
180
+ private
181
+
182
+ def logger
183
+ @logger
184
+ end
185
+
186
+ def assign_url!
187
+ info = LocalTunnel.get_assigned_url(@domain)
188
+ @url = info.url
189
+ @max_conn_count = info.max_conn_count
190
+ @port = info.port
191
+ @conns = Array.new(@max_conn_count)
192
+ info
193
+ end
194
+
195
+ class << self
196
+ def logger
197
+ @logger ||= LocalTunnel.logger.dup
198
+ end
199
+
200
+ def logger=(value)
201
+ @logger = value
202
+ end
203
+ end
204
+ end
205
+
206
+ class << self
207
+ def logger
208
+ @logger ||= Logger.new(STDOUT).tap { |l| l.level = Logger:: WARN }
209
+ end
210
+
211
+ def logger=(value)
212
+ @logger = value
213
+ end
214
+
215
+ def get_assigned_url(domain = nil)
216
+ domain = '?new' unless domain
217
+
218
+ Net::HTTP.start(URI(SERVER).hostname) do |http|
219
+ req = Net::HTTP::Get.new(URI(SERVER) + domain)
220
+ res = http.request(req)
221
+
222
+ case res
223
+ when Net::HTTPSuccess
224
+ j = JSON.parse(res.body)
225
+ AssignedUrlInfo.new(j['id'], j['url'], j['port'], j['max_conn_count'])
226
+ else
227
+ raise
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1 @@
1
+ b35c88e0-d652-4d91-b425-393427cdedba
@@ -0,0 +1,10 @@
1
+ [2017-09-12 12:36:57] INFO WEBrick 1.3.1
2
+ [2017-09-12 12:36:57] INFO ruby 2.4.1 (2017-03-22) [x86_64-darwin16]
3
+ == Sinatra (v2.0.0) has taken the stage on 8000 for development with backup from WEBrick
4
+ [2017-09-12 12:36:57] INFO WEBrick::HTTPServer#start: pid=35843 port=8000
5
+ 46.218.157.254 - - [12/Sep/2017:12:36:59 +0200] "GET /b35c88e0-d652-4d91-b425-393427cdedba HTTP/1.1" 200 36 0.0127
6
+ == Sinatra has ended his set (crowd applauds)
7
+ [2017-09-12 12:37:00] INFO going to shutdown ...
8
+ ::1 - - [12/Sep/2017:12:36:59 CEST] "GET /b35c88e0-d652-4d91-b425-393427cdedba HTTP/1.1" 200 36
9
+ - -> /b35c88e0-d652-4d91-b425-393427cdedba
10
+ [2017-09-12 12:37:00] INFO WEBrick::HTTPServer#start done.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'local_tunnel'
5
+
6
+ require 'securerandom'
7
+ require 'uri'
8
+ require 'net/http'
9
+ require 'pry'
10
+ require 'pathname'
11
+
12
+ class TestLocalTunnel < MiniTest::Test
13
+ def test_live_tunnel
14
+ uuid = SecureRandom.uuid.encode('UTF-8')
15
+ serve_dir = Pathname.new('test/serve')
16
+ (serve_dir + uuid).open('wb') { |f| f.write(uuid) }
17
+ LocalTunnel.logger = Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG }
18
+
19
+ begin
20
+ start_test_server(serve_dir, 8000, serve_dir + "#{uuid}.log")
21
+ sleep 1
22
+
23
+ t = LocalTunnel::Tunnel.new
24
+ t.start(8000)
25
+
26
+ res = Net::HTTP.get(URI(t.url) + uuid)
27
+ assert_equal(uuid, res)
28
+ ensure
29
+ t.stop if t
30
+ stop_test_server
31
+ end
32
+ end
33
+
34
+ def start_test_server(path, port, out)
35
+ @pid = Process.spawn(<<-EOS, [:out, :err] => out.to_s)
36
+ ruby -rsinatra -e'set :public_folder, "#{path}"; set :port, #{port}'
37
+ EOS
38
+ end
39
+
40
+ def stop_test_server
41
+ Process.kill('TERM', @pid) if @pid
42
+ end
43
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: local_tunnel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Loic Nageleisen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.10'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.10'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sinatra
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '12.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '12.0'
69
+ description: |2
70
+ Localtunnel allows you to easily share a web service on your local
71
+ development machine without messing with DNS and firewall settings.
72
+ email: loic.nageleisen@gmail.com
73
+ executables:
74
+ - local_tunnel
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - LICENSE
81
+ - Rakefile
82
+ - bin/local_tunnel
83
+ - lib/local_tunnel.rb
84
+ - lib/local_tunnel/cli.rb
85
+ - test/serve/b35c88e0-d652-4d91-b425-393427cdedba
86
+ - test/serve/b35c88e0-d652-4d91-b425-393427cdedba.log
87
+ - test/test_local_tunnel.rb
88
+ homepage:
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '2.3'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.6.13
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Expose yourself to the world from Ruby
112
+ test_files: []