udp_rest 0.9.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: 5c547a1a4aeafe5240118f42f1d060b861bba377
4
+ data.tar.gz: 1f67cc942b89d5d8da7b12066d019b96eaa54cf1
5
+ SHA512:
6
+ metadata.gz: 637dbc37f4e52a049b500f09aa60dcf167f27434fcfc9c2ea5c5fe8b442fc128e1d8cccf6b35c14ea42f3596c135cd1df3b03a1a5dea8f34f663dc8d56ebac33
7
+ data.tar.gz: e592fdfff3610df362d4a558d15bda20dde2b61302f4bafdb1ddbaebca62053291e71f101e349308ea8eb849725ae21e3cb6050ba5bcaaaa44fdb0b5d780ac0e
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in udp_rest.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ udp_rest (0.9.0)
5
+ colorize
6
+ trollop
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ colorize (0.8.1)
12
+ rake (10.5.0)
13
+ trollop (2.1.2)
14
+
15
+ PLATFORMS
16
+ ruby
17
+
18
+ DEPENDENCIES
19
+ bundler (~> 1.12)
20
+ rake (~> 10.0)
21
+ udp_rest!
22
+
23
+ BUNDLED WITH
24
+ 1.12.5
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Nathan Reed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "udp_rest"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'time'
5
+ require 'udp_rest'
6
+
7
+ req_count = 0
8
+ port = (ARGV.last || 7890).to_i
9
+ puts "listening on 0.0.0.0:#{port}..."
10
+
11
+ UDPRest::Server.new(:port => port) do |s|
12
+ s.get '/' do
13
+ "Hello, World!\nVisit http://github.com/reednj/udp_rest for more info"
14
+ end
15
+
16
+ s.get '/hello' do
17
+ 'hello'
18
+ end
19
+
20
+ s.post '/time' do
21
+ Time.now.to_s
22
+ end
23
+
24
+ s.get '/time/unix' do
25
+ Time.now.to_i
26
+ end
27
+
28
+ s.get '/time/iso' do
29
+ Time.now.iso8601
30
+ end
31
+
32
+ s.get '/count' do
33
+ req_count += 1
34
+ req_count.to_s
35
+ end
36
+
37
+ s.get '/too_long' do
38
+ 'a' * 600
39
+ end
40
+
41
+ end
data/exe/udp-rest ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rest_client'
3
+ App.new.main
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+ require 'colorize'
5
+ require 'trollop'
6
+ require 'udp_rest'
7
+
8
+ class App
9
+ def main
10
+ valid_methods = ['GET', 'PUT', 'POST', 'DELETE']
11
+ @opts = Trollop::options do
12
+ version "UDP RestClient (c) 2016 @reednj"
13
+ banner "Usage: udp-rest [options] <url>"
14
+ opt :method, "HTTP Method (GET, POST etc)", :type => :string, :default => 'GET'
15
+ opt :headers, "Show the response headers", :default => false
16
+ end
17
+
18
+ Trollop::educate if ARGV.empty?
19
+ url = ARGV.last
20
+ url = "uhttp://" + url unless url.start_with? 'uhttp://'
21
+
22
+ begin
23
+ if !valid_methods.include? @opts[:method].upcase
24
+ raise "Invalid REST method '#{@opts[:method]}'"
25
+ end
26
+
27
+ r = UDPRest::Client.uhttp(@opts[:method], url)
28
+ print_response(r)
29
+ rescue => e
30
+ puts e
31
+ end
32
+ end
33
+
34
+ def print_response(r)
35
+ if @opts[:headers]
36
+ puts r.ok? ? r.status_line.green : r.status_line.red
37
+ puts ''
38
+ end
39
+
40
+ puts r.text
41
+ end
42
+ end
data/lib/udp_rest.rb ADDED
@@ -0,0 +1,139 @@
1
+ require 'uri'
2
+ require 'socket'
3
+
4
+ require 'worker_thread'
5
+ require "udp_rest/version"
6
+ require "udp_rest/udp"
7
+ require "udp_rest/uhttp"
8
+
9
+ module UDPRest
10
+ class Server
11
+ def initialize(options = {})
12
+ @udp = UDPServer.new
13
+ @routes = {}
14
+
15
+ if block_given?
16
+ yield(self)
17
+ port = options[:port] || 80
18
+ options[:host] = options[:host] || '0.0.0.0'
19
+ self.listen(port, options)
20
+ end
21
+ end
22
+
23
+ def udp_server
24
+ @udp
25
+ end
26
+
27
+ def post(path, &block)
28
+ add_route('POST', path, &block)
29
+ end
30
+
31
+ def get(path, &block)
32
+ add_route('GET', path, &block)
33
+ end
34
+
35
+ def add_route(req_method, path, &block)
36
+ key = "#{req_method.upcase} #{path}"
37
+ @routes[key] = Proc.new &block
38
+ return key
39
+ end
40
+
41
+ def route_request(request)
42
+ key = "#{request.req_method.upcase} #{request.path}"
43
+ puts key
44
+
45
+ block = @routes[key]
46
+ return respond(404, 'Not Found') if block.nil?
47
+
48
+ # handle the response. No matter what gets returned
49
+ # we want to try and make it into something useful
50
+ result = block.call(request, self)
51
+ return result if result.is_a? UHTTPResponse
52
+ return respond(200, result.to_s) if result.respond_to? :to_s
53
+ return respond(200, 'ok')
54
+ end
55
+
56
+ def listen(port, options = {})
57
+ port = port || 80
58
+
59
+ @udp.listen(port, options) do |packet|
60
+ response = nil
61
+
62
+ if response.nil?
63
+ begin
64
+ request = UHTTPRequest.from_packet packet
65
+ rescue => e
66
+ puts "400 BAD REQUEST: #{e}"
67
+ response = respond(400, 'Bad Request')
68
+ end
69
+ end
70
+
71
+ if response.nil?
72
+ begin
73
+ response = route_request(request)
74
+
75
+ if response.to_s.bytesize > udp_server.max_packet_size
76
+ raise "response too long (#{response.to_s.bytesize} bytes)"
77
+ end
78
+ rescue => e
79
+ puts "500 APPLICATION ERROR: #{e}"
80
+ response = respond(500, 'Application Error')
81
+ end
82
+ end
83
+
84
+ @udp.send(response.to_s, packet.src_addr, packet.src_port)
85
+ end
86
+ end
87
+
88
+ def respond(code, text)
89
+ UHTTPResponse.new(code, :text => text)
90
+ end
91
+ end
92
+
93
+ class Client
94
+ attr_accessor :host
95
+ attr_accessor :port
96
+ attr_accessor :socket
97
+ attr_accessor :timeout
98
+
99
+ def initialize(host, port)
100
+ @max_packet_size = 512
101
+
102
+ self.host = host
103
+ self.port = port
104
+ self.socket = UDPSocket.new
105
+ self.timeout = 5.0
106
+ end
107
+
108
+ def send_text(text)
109
+ thread = WorkerThread.new.start :timeout => self.timeout do
110
+ self.socket.send(text, 0, self.host, self.port)
111
+ response_data = self.socket.recvfrom(@max_packet_size)
112
+ UDPPacket.new(response_data)
113
+ end
114
+
115
+ thread.join
116
+ packet = thread.value
117
+ raise "Request Timeout (#{host}:#{port})" if packet.nil?
118
+ return packet
119
+ end
120
+
121
+ def self.uhttp(req_method, url)
122
+ uri = URI(url)
123
+ client = self.new(uri.host, uri.port || 80)
124
+
125
+ req = UHTTPRequest.new
126
+ req.req_method = req_method
127
+ req.path = uri.path
128
+
129
+ packet = client.send_text(req.to_s)
130
+ UHTTPResponse.parse(packet.text)
131
+ end
132
+
133
+ def self.get(url)
134
+ self.uhttp('GET', url)
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -0,0 +1,71 @@
1
+ require 'uri'
2
+ require 'socket'
3
+
4
+ module UDPRest
5
+
6
+ class UDPRest::UDPServer
7
+ attr_accessor :socket
8
+
9
+ def initialize
10
+ @max_packet_size = 512
11
+ self.socket = UDPSocket.new
12
+ end
13
+
14
+ def max_packet_size
15
+ @max_packet_size
16
+ end
17
+
18
+ def listen(port, options = {})
19
+ @port = port.to_i
20
+ @host = options[:host] || '0.0.0.0'
21
+ self.socket.bind(@host, @port)
22
+
23
+ loop do
24
+ response = self.receive()
25
+ yield(response)
26
+ end
27
+ end
28
+
29
+ def receive
30
+ data = self.socket.recvfrom(@max_packet_size)
31
+ UDPPacket.new(data)
32
+ end
33
+
34
+ def send(text, host, port)
35
+ raise "message too long (max is #{@max_packet_size}b, was #{text.bytesize})" if text.bytesize > @max_packet_size
36
+ self.socket.send(text, 0, host, port)
37
+ end
38
+
39
+ def host
40
+ @host
41
+ end
42
+
43
+ def port
44
+ @port
45
+ end
46
+
47
+ end
48
+
49
+ class UDPRest::UDPPacket
50
+ attr_accessor :text
51
+ attr_accessor :addr_family
52
+ attr_accessor :src_port
53
+ attr_accessor :src_addr
54
+
55
+ def initialize(data = nil)
56
+ # assume this was initialized with the standard data structure
57
+ # that is returned by UDPSocket
58
+ if !data.nil?
59
+ self.text = data[0]
60
+ self.addr_family = data[1][0]
61
+ self.src_port = data[1][1]
62
+ self.src_addr = data[1][2]
63
+ end
64
+ end
65
+
66
+ def to_s
67
+ self.text
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,72 @@
1
+ require 'uri'
2
+ require 'socket'
3
+
4
+ module UDPRest
5
+
6
+ class UDPRest::UHTTPRequest
7
+ attr_accessor :req_method
8
+ attr_accessor :path
9
+ attr_accessor :protocol
10
+
11
+ def initialize
12
+ self.req_method = 'GET'
13
+ self.protocol = 'UHTTP/1.0'
14
+ self.path ='/'
15
+ end
16
+
17
+ def self.from_packet(p)
18
+ text = p
19
+ text = p.text if text.is_a? UDPPacket
20
+ data = text.split(' ')
21
+
22
+ raise 'invalid request' if data.length != 3
23
+ req = self.new
24
+ req.req_method = data[0]
25
+ req.path = data[1]
26
+ req.protocol = data[2]
27
+ return req
28
+ end
29
+
30
+ def to_s
31
+ self.path = '/' if path.nil? || path.empty?
32
+ "#{req_method} #{path} #{protocol}\n"
33
+ end
34
+ end
35
+
36
+ class UDPRest::UHTTPResponse
37
+ attr_accessor :code
38
+ attr_accessor :protocol
39
+ attr_accessor :text
40
+
41
+ def initialize(code, options = {})
42
+ self.code = code.to_i
43
+ self.protocol = options[:protocol] || 'UHTTP/1.0'
44
+ self.text = options[:text] || ''
45
+ end
46
+
47
+ def self.parse(s)
48
+ data = s.split("\n\n")
49
+ status = data[0].split(' ')
50
+ text = data[1] if data.length > 1
51
+ self.new(status[1], :text => text || '')
52
+ end
53
+
54
+ def ok?
55
+ code == 200
56
+ end
57
+
58
+ def status_text
59
+ return 'OK' if ok?
60
+ return 'FAILED'
61
+ end
62
+
63
+ def status_line
64
+ "#{protocol} #{code} #{status_text}"
65
+ end
66
+
67
+ def to_s
68
+ "#{status_line}\n\n#{text}"
69
+ end
70
+ end
71
+
72
+ end
@@ -0,0 +1,3 @@
1
+ module UDPRest
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,42 @@
1
+ # Helper for running threads in the background, with a timeout
2
+ # and error logging.
3
+ #
4
+ # Example:
5
+ #
6
+ # WorkerThread.new.start :timeout => 5.minutes do
7
+ # # long running task here...
8
+ # sleep 10.0
9
+ # end
10
+ #
11
+ class WorkerThread
12
+
13
+ def start(options = nil)
14
+ raise 'background_task needs a block' unless block_given?
15
+
16
+ options ||= {}
17
+
18
+ worker = Thread.new do
19
+ begin
20
+ yield
21
+ rescue => e
22
+ $stderr.puts "#{Time.now}\t#{e.class.to_s}\t#{e.message}\n"
23
+ raise e
24
+ end
25
+ end
26
+
27
+ # if the user set a timeout then we need a thread to monitor
28
+ # the worker to make sure it doesn't run too long
29
+ if !options[:timeout].nil?
30
+ Thread.new do
31
+ sleep options[:timeout].to_f
32
+
33
+ if worker.status != false
34
+ #$stderr.puts "#{Time.now}\tbackground_task thread timeout\n"
35
+ worker.kill
36
+ end
37
+ end
38
+ end
39
+
40
+ worker
41
+ end
42
+ end
data/readme.md ADDED
@@ -0,0 +1,35 @@
1
+ # REST over UDP
2
+
3
+ - Most REST requets are very small
4
+ - it should be possible to implement a simple version of HTTP that can run over UDP in order to make REST requets
5
+ - we have implemented that
6
+ - the max packet size is 512 bytes
7
+ - This gem makes it easy to create servers and clients for udp-rest
8
+ - it also contains a curl-like command line app
9
+ - obviously this is not going to work from any browser, but it can be useful for simple command line apps.
10
+
11
+ ## Try it out
12
+
13
+ There is a udp rest server running on uhttp.reednj.com. You can make requests to it by installing the gem.
14
+
15
+ gem install udp_rest
16
+ udp-rest uhttp.reednj.com
17
+
18
+ <SCREEN SHOT OF CONSOLE>
19
+
20
+ ## Server
21
+
22
+ Use this gem to create sinatra style servers to respond to requests.
23
+
24
+ <CODE>
25
+
26
+ ## Client
27
+
28
+ ## Benchmarks
29
+
30
+ - do some testing of the latency here, see if it really is faster. Pick a few different servers
31
+
32
+ ## Other Points
33
+
34
+ - encoding is always UTF-8
35
+ - the max request and response size is 512 bytes
data/udp_rest.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'udp_rest/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "udp_rest"
8
+ spec.version = UDPRest::VERSION
9
+ spec.authors = ["Nathan Reed"]
10
+ spec.email = ["reednj@gmail.com"]
11
+
12
+ spec.summary = %q{Client and server modules to allow making REST HTTP requests over UDP}
13
+ spec.homepage = "https://github.com/reednj/udp_rest"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = '>=1.9.3'
22
+ spec.add_development_dependency "bundler", "~> 1.12"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+
25
+ spec.add_dependency 'colorize'
26
+ spec.add_dependency 'trollop'
27
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: udp_rest
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Nathan Reed
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-12-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
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: trollop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - reednj@gmail.com
72
+ executables:
73
+ - udp-rest
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - LICENSE.txt
81
+ - Rakefile
82
+ - bin/console
83
+ - bin/setup
84
+ - demo/simple_server.rb
85
+ - exe/udp-rest
86
+ - lib/rest_client.rb
87
+ - lib/udp_rest.rb
88
+ - lib/udp_rest/udp.rb
89
+ - lib/udp_rest/uhttp.rb
90
+ - lib/udp_rest/version.rb
91
+ - lib/worker_thread.rb
92
+ - readme.md
93
+ - udp_rest.gemspec
94
+ homepage: https://github.com/reednj/udp_rest
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: 1.9.3
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.6.6
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: Client and server modules to allow making REST HTTP requests over UDP
118
+ test_files: []