local_tunnel 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []