nanoserve 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: '019575a37a4493c4816afbe726f0c2a0495049ea'
4
+ data.tar.gz: bf54d9e145418838ce211764c5555ffaa68ae97d
5
+ SHA512:
6
+ metadata.gz: 5576fec9ea2afa3d053996248045c8df2d418a8eab235354727b983b7457f2aaebe0f88a847f745b5534c499bc58c081d3c1689283df38fb4118c7cf66a0b06f
7
+ data.tar.gz: 18c903426feae6ec6eb8c1d27e693f8cda25553635cff9087da1d71908245116e3d9573b21bba04d277264729accf8bddb4d4f3e660f2d410eba28b71f791741
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,29 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nanoserve (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
+ pry (0.10.4)
13
+ coderay (~> 1.1.0)
14
+ method_source (~> 0.8.1)
15
+ slop (~> 3.4)
16
+ rake (12.0.0)
17
+ slop (3.6.0)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ minitest (~> 5.10)
24
+ nanoserve!
25
+ pry
26
+ rake (~> 12.0)
27
+
28
+ BUNDLED WITH
29
+ 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/lib/nanoserve.rb ADDED
@@ -0,0 +1,212 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'logger'
5
+ require 'time'
6
+
7
+ module NanoServe
8
+ class TCPResponder
9
+ def initialize(host, port, &block)
10
+ @host = host
11
+ @port = port
12
+ @block = block
13
+ @thr = nil
14
+ end
15
+
16
+ def start(y)
17
+ server = TCPServer.new(@port)
18
+
19
+ @thr = Thread.new do
20
+ Thread.abort_on_exception = true
21
+ conn = server.accept
22
+ port, host = conn.peeraddr[1, 2]
23
+ client = "#{host}:#{port}"
24
+ logger.debug "#{client}: connected"
25
+
26
+ begin
27
+ @block.call(conn, y)
28
+ rescue EOFError
29
+ logger.debug "#{client}: disconnected"
30
+ ensure
31
+ logger.debug "#{client}: closed"
32
+ conn.close
33
+ end
34
+ end
35
+
36
+ return unless block_given?
37
+
38
+ yield
39
+
40
+ @thr.join
41
+ server.close
42
+
43
+ y
44
+ end
45
+
46
+ def stop
47
+ @thr.kill
48
+ end
49
+
50
+ def logger
51
+ @logger ||= Logger.new(STDOUT).tap { |l| l.level = Logger::INFO }
52
+ end
53
+ end
54
+
55
+ class HTTPResponder < TCPResponder
56
+ def initialize(host, port)
57
+ super(host, port) do |conn, y|
58
+ req = Request.new
59
+ buf = +''
60
+ loop do
61
+ line = conn.readline
62
+ break if line.chomp == ''
63
+ req << line
64
+ buf << line
65
+ end
66
+ logger.debug "request:\n" + buf.gsub(/^/, ' ')
67
+
68
+ res = Response.new
69
+ logger.debug 'calling'
70
+ yield(res, req, y)
71
+ logger.debug "response:\n" + res.to_s.gsub(/^/, ' ')
72
+ conn.write(res.to_s)
73
+ end
74
+ end
75
+
76
+ class RequestError < StandardError; end
77
+
78
+ class Request
79
+ def initialize
80
+ @method = nil
81
+ @uri = nil
82
+ @http_version = nil
83
+ @sep = nil
84
+ @headers = {}
85
+ end
86
+
87
+ def params
88
+ Hash[*@uri.query.split('&').map { |kv| kv.split('=') }.flatten]
89
+ end
90
+
91
+ def <<(line)
92
+ if @method.nil?
93
+ parse_request(line.chomp)
94
+ elsif @sep.nil?
95
+ parse_header(line.chomp)
96
+ else
97
+ @body << line
98
+ end
99
+ end
100
+
101
+ REQ_RE = %r{(?<method>[A-Z]+)\s+(?<path>\S+)\s+(?<version>HTTP/\d+.\d+)$}
102
+
103
+ def parse_request(str)
104
+ unless (m = str.match(REQ_RE))
105
+ raise RequestError, "cannot parse request: '#{str}'"
106
+ end
107
+
108
+ @method = parse_method(m[:method])
109
+ @uri = parse_path(m[:path])
110
+ @http_version = parse_version(m[:version])
111
+ end
112
+
113
+ def parse_method(str)
114
+ str
115
+ end
116
+
117
+ def parse_path(str)
118
+ URI(str)
119
+ end
120
+
121
+ def parse_version(str)
122
+ str
123
+ end
124
+
125
+ def parse_header(str)
126
+ (@sep = '' && return) if str == ''
127
+
128
+ unless (m = str.match(/(?<header>[A-Z][-A-Za-z]*):\s+(?<value>.+)$/))
129
+ raise RequestError, "cannot parse header: '#{str}'"
130
+ end
131
+
132
+ @headers[m[:header]] = m[:value]
133
+ end
134
+ end
135
+
136
+ class Response
137
+ def headers
138
+ {
139
+ 'Date' => date,
140
+ 'Content-Type' => content_type,
141
+ 'Content-Length' => content_length,
142
+ 'Last-Modified' => last_modified,
143
+ 'Server' => server,
144
+ 'ETag' => etag,
145
+ 'Connection' => connection,
146
+ }
147
+ end
148
+
149
+ def body
150
+ @body ||= ''
151
+ end
152
+
153
+ def body=(value)
154
+ @body = value.tap { @content_length = body.bytes.count.to_s }
155
+ end
156
+
157
+ def to_s
158
+ (status_line + header_block + body_block).encode('ASCII-8BIT')
159
+ end
160
+
161
+ private
162
+
163
+ def status_code
164
+ 200
165
+ end
166
+
167
+ def status_string
168
+ 'OK'
169
+ end
170
+
171
+ def status_line
172
+ "HTTP/1.1 #{status_code} #{status_string}\r\n"
173
+ end
174
+
175
+ def header_block
176
+ headers.map { |k, v| [k, v].join(': ') }.join("\r\n") + "\r\n\r\n"
177
+ end
178
+
179
+ def body_block
180
+ body
181
+ end
182
+
183
+ def content_length
184
+ @content_length ||= body.bytes.count.to_s
185
+ end
186
+
187
+ def content_type
188
+ @content_type ||= 'text/html; charset=UTF-8'
189
+ end
190
+
191
+ def date
192
+ @date ||= Time.now.httpdate
193
+ end
194
+
195
+ def last_modified
196
+ @last_modified ||= date
197
+ end
198
+
199
+ def etag
200
+ @etag ||= SecureRandom.uuid
201
+ end
202
+
203
+ def server
204
+ 'NanoServe'
205
+ end
206
+
207
+ def connection
208
+ 'close'
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'minitest/autorun'
4
+ require 'nanoserve'
5
+
6
+ require 'securerandom'
7
+ require 'uri'
8
+ require 'net/http'
9
+ require 'pry'
10
+ require 'pathname'
11
+
12
+ class TestNanoServe < MiniTest::Test
13
+ def test_tcp_responder
14
+ uuid = SecureRandom.uuid.encode('UTF-8')
15
+ uri = URI('tcp://localhost:2000')
16
+
17
+ r = NanoServe::TCPResponder.new(uri.host, uri.port) do |conn, buf|
18
+ buf << conn.readpartial(1024)
19
+ end
20
+
21
+ buf = r.start(+'') do
22
+ s = TCPSocket.new(uri.host, uri.port)
23
+ s.write(uuid)
24
+ s.close
25
+ end
26
+
27
+ assert_equal(uuid, buf)
28
+ end
29
+
30
+ def test_http_responder
31
+ uuid = SecureRandom.uuid.encode('UTF-8')
32
+ uri = URI('http://localhost:2000')
33
+
34
+ r = NanoServe::HTTPResponder.new(uri.host, uri.port) do |res, req, y|
35
+ y << req
36
+
37
+ res.body = <<-EOS.gsub(/^ {8}/, '')
38
+ <html>
39
+ <head>
40
+ <title>An Example Page</title>
41
+ </head>
42
+ <body>
43
+ Hello World, this is a very simple HTML document.
44
+ </body>
45
+ </html>
46
+ EOS
47
+ end
48
+
49
+ req = r.start([]) do
50
+ Net::HTTP.get(uri + "test?uuid=#{uuid}")
51
+ end
52
+
53
+ assert_equal(uuid, req.first.params['uuid'])
54
+ end
55
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nanoserve
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-18 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: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ description: " NanoServe allows you to wait for an external call and act on it.\n"
56
+ email: loic.nageleisen@gmail.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - Gemfile
62
+ - Gemfile.lock
63
+ - LICENSE
64
+ - Rakefile
65
+ - lib/nanoserve.rb
66
+ - test/test_nanoserve.rb
67
+ homepage:
68
+ licenses:
69
+ - 3BSD
70
+ metadata: {}
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '2.3'
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 2.6.13
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Listen to one-shot connections
91
+ test_files: []