net-ptth 0.0.5 → 0.0.6

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.
data/Gemfile.lock CHANGED
@@ -1,9 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- net-ptth (0.0.5)
4
+ net-ptth (0.0.6.pre14)
5
5
  celluloid-io (~> 0.12.1)
6
- http-parser-lite (~> 0.6.0)
6
+ http_parser.rb (~> 0.5.3)
7
7
  rack (~> 1.4.4)
8
8
 
9
9
  GEM
@@ -25,8 +25,8 @@ GEM
25
25
  debugger-linecache (1.1.2)
26
26
  debugger-ruby_core_source (>= 1.1.1)
27
27
  debugger-ruby_core_source (1.1.3)
28
- facter (1.6.17)
29
- http-parser-lite (0.6.0)
28
+ facter (1.6.18)
29
+ http_parser.rb (0.5.3)
30
30
  minitest (4.4.0)
31
31
  nio4r (0.4.3)
32
32
  rack (1.4.4)
@@ -38,7 +38,7 @@ GEM
38
38
  rack-protection (~> 1.2)
39
39
  tilt (~> 1.3, >= 1.3.3)
40
40
  tilt (1.3.3)
41
- timers (1.0.2)
41
+ timers (1.1.0)
42
42
 
43
43
  PLATFORMS
44
44
  ruby
data/SPEC.md ADDED
@@ -0,0 +1,6 @@
1
+ host = "http://127.0.0.1:7000"
2
+ ptth = Net::PTTH.new(host)
3
+
4
+ request = Net::HTTP::Post.new("/reverse")
5
+ ptth.app = Cuba
6
+ response = ptth.request(request)
@@ -0,0 +1,16 @@
1
+ class Net::PTTH
2
+ IncomingRequest = Struct.new(:method, :path, :headers, :body) do
3
+ def to_env
4
+ env = {
5
+ "PATH_INFO" => path,
6
+ "SCRIPT_NAME" => "",
7
+ "rack.input" => StringIO.new(body || ""),
8
+ "REQUEST_METHOD" => method,
9
+ }
10
+
11
+ env.tap { |h| h["CONTENT_LENGTH"] = body.length if !body.nil? }
12
+
13
+ env.merge!(headers) if headers
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ class Net::PTTH
2
+ OutgoingRequest = Struct.new(:req) do
3
+ def to_s
4
+ package = "#{req.method} #{req.path} HTTP/1.1#{Net::PTTH::CRLF}"
5
+
6
+ req["Content-Length"] ||= req.body ? req.body.size : 0
7
+
8
+ req.each_header do |header, value|
9
+ header_parts = header.split("-").map(&:capitalize)
10
+ package += "#{header_parts.join("-")}: #{value}#{Net::PTTH::CRLF}"
11
+ end
12
+
13
+ package += Net::PTTH::CRLF
14
+
15
+ package += req.body if req.body
16
+ package
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ class Net::PTTH
2
+ OutgoingResponse = Struct.new(:status, :headers, :body) do
3
+ def to_s
4
+ packet = "HTTP/1.1 #{status} OK\n"
5
+ headers.each { |key, value| packet += "#{key}: #{value}\n" }
6
+ packet += "\r\n"
7
+ body.each { |chunk| packet += chunk }
8
+
9
+ packet
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,91 @@
1
+ require "http/parser"
2
+
3
+ class Net::PTTH
4
+ class Parser
5
+ attr_reader :headers, :chunk
6
+
7
+ # Public: Contructor
8
+ #
9
+ def initialize
10
+ @parser = Http::Parser.new(self)
11
+ reset
12
+ end
13
+
14
+ # Public: Adds data to be parsed.
15
+ #
16
+ # data: string to be parsed
17
+ #
18
+ def add(data)
19
+ @parser << data
20
+ end
21
+ alias_method :<<, :add
22
+
23
+ # Public: Check for an upgrade header in the parsed string
24
+ #
25
+ def upgrade?
26
+ @parser.upgrade?
27
+ end
28
+
29
+ # Public: Gets the http method parsed
30
+ #
31
+ def http_method
32
+ @parser.http_method
33
+ end
34
+
35
+ # Public: Gets the http version parsed
36
+ #
37
+ def http_version
38
+ @parser.http_version
39
+ end
40
+
41
+ # Public: Gets the status code parsed
42
+ #
43
+ def status_code
44
+ @parser.status_code
45
+ end
46
+
47
+ # Public: Gets the url parsed
48
+ #
49
+ def url
50
+ @parser.request_url
51
+ end
52
+
53
+ # Public: Internal flag access to know when parsing ended
54
+ #
55
+ def finished?; @finished; end
56
+
57
+ # Public: Resets the parser internal flags
58
+ #
59
+ def reset
60
+ @finished = false
61
+ @headers = nil
62
+ @chunk = ""
63
+ end
64
+
65
+ # Public: Access the body of the string being parsed
66
+ #
67
+ def body
68
+ @chunk
69
+ end
70
+
71
+ def body=(chunk)
72
+ @chunk << chunk
73
+ end
74
+
75
+ # Protected: Sets the headers when the parser completes.
76
+ #
77
+ def on_headers_complete(headers)
78
+ @headers = headers
79
+ end
80
+
81
+ def on_message_begin
82
+ reset
83
+ end
84
+
85
+ # Protected: Flags the parsing as ended
86
+ #
87
+ def on_message_complete
88
+ @finished = true
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,7 @@
1
+ class Net::PTTH
2
+ Response = Struct.new(:method, :status, :headers, :body) do
3
+ def [](key)
4
+ headers[key]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ require "celluloid/io"
2
+
3
+ class Net::PTTH
4
+ Socket = Struct.new(:host, :port) do
5
+ include Celluloid::IO
6
+
7
+ def read(bytes = 10240)
8
+ raw_socket.readpartial(bytes)
9
+ end
10
+
11
+ def write(data)
12
+ raw_socket.write(data)
13
+ end
14
+
15
+ def close
16
+ raw_socket.close
17
+ end
18
+
19
+ def open?
20
+ !raw_socket.closed?
21
+ end
22
+
23
+ def raw_socket
24
+ @_socket ||= TCPSocket.new(host, port)
25
+ end
26
+ end
27
+ end
data/lib/net/ptth.rb CHANGED
@@ -1,33 +1,35 @@
1
1
  require "uri"
2
2
  require "rack"
3
3
  require "net/http"
4
- require "http-parser"
5
- require "celluloid/io"
6
4
 
7
- class Net::PTTH
8
- class Request
9
- attr_accessor :path, :body, :headers
10
-
11
- def initialize(path = "", body = "", headers = {})
12
- @path, @body, @headers = path, body, headers
13
- end
14
- end
5
+ require "net/ptth/socket"
6
+ require "net/ptth/parser"
7
+ require "net/ptth/response"
8
+ require "net/ptth/incoming_request"
9
+ require "net/ptth/outgoing_response"
10
+ require "net/ptth/outgoing_request"
15
11
 
16
- include Celluloid::IO
12
+ # Public: PTTH constructor.
13
+ # Attempts to mimic HTTP when applicable
14
+ #
15
+ class Net::PTTH
16
+ include Celluloid
17
17
 
18
18
  attr_accessor :app
19
19
 
20
+ CRLF = "\r\n".freeze
21
+
20
22
  # Public: Constructor
21
23
  #
22
24
  # address: The address where the connection will be made
23
25
  # port: An optional port if the port is different from the one in the
24
26
  # address
25
27
  #
26
- def initialize(address, port = 80)
28
+ def initialize(address, options = {})
27
29
  info = URI.parse(address)
28
30
 
29
- @host, @port = info.host, info.port || port
30
- @parser = HTTP::Parser.new
31
+ @host, @port = info.host, info.port || options.fetch(:port, 80)
32
+ @keep_alive = options.fetch(:keep_alive, false)
31
33
  @debug_output = StringIO.new
32
34
  end
33
35
 
@@ -42,181 +44,118 @@ class Net::PTTH
42
44
  # Public: Closes de current connection
43
45
  #
44
46
  def close
45
- log "Closing connection"
46
- @socket.close if @socket
47
+ @keep_alive = false
48
+ socket.close if socket
49
+ end
50
+
51
+ # Public: Access to the PTTH::Socket
52
+ #
53
+ def socket
54
+ @_socket ||= Net::PTTH::Socket.new(@host, @port)
55
+ end
56
+
57
+ # Public: Access to the PTTH::Parser
58
+ #
59
+ def parser
60
+ @_parser ||= Net::PTTH::Parser.new
47
61
  end
48
62
 
49
63
  # Public: Generates a request based on a Net::HTTP object and yields a
50
64
  # response
51
65
  #
52
66
  # req: The request to be executed
53
- # &block: That will handle the response
54
67
  #
55
- def request(req, &block)
56
- @socket ||= TCPSocket.new(@host, @port)
57
-
58
- log "Connecting to #{@host}:#{@port}"
59
-
60
- packet = build(req)
68
+ def request(req)
69
+ outgoing = Net::PTTH::OutgoingRequest.new(req)
70
+ packet = outgoing.to_s + CRLF
61
71
 
62
- write(packet)
72
+ socket.write(packet)
63
73
 
64
- response = read
65
- @parser << response
74
+ parser.reset
75
+ response = ""
76
+ while !parser.finished?
77
+ buffer = socket.read
78
+ debug "= #{buffer}"
66
79
 
67
- if @parser.http_status == 101
68
- log "Switching protocols"
69
-
70
- while res = read
71
- request = Request.new
72
- build_request(request)
73
-
74
- @parser.on_message_complete do
75
- env = build_env(request)
76
- callbacks(env, &block)
77
- end
78
-
79
- @parser << res
80
- end
80
+ response << buffer
81
+ parser << buffer
81
82
  end
82
- rescue IOError => e
83
- rescue EOFError => e
84
- @socket.close
85
- end
86
83
 
87
- private
84
+ debug "* Initial Response: #{response}"
88
85
 
89
- def read(bytes = 1024)
90
- response = @socket.readpartial(bytes)
86
+ if parser.upgrade?
87
+ debug "* Upgrade response"
88
+ debug "* Reading socket"
89
+ parser.reset
91
90
 
92
- log "Reading response"
93
- log response, "-> "
91
+ while socket.open?
92
+ response = ""
93
+ parser.reset
94
94
 
95
- response
96
- end
95
+ buffer = socket.read
96
+ response << buffer
97
+ parser << buffer
97
98
 
98
- def write(string)
99
- log "Writting response"
100
- log string[0..200], "<- "
99
+ if parser.finished?
100
+ parser.body = buffer.split(CRLF*2).last
101
+ end
101
102
 
102
- bytes = @socket.write(string)
103
- log "#{bytes} bytes written"
104
- end
103
+ incoming = Net::PTTH::IncomingRequest.new(
104
+ parser.http_method,
105
+ parser.url,
106
+ parser.headers,
107
+ parser.body
108
+ )
105
109
 
106
- # Private: Executes the app and/or block callbacks
107
- #
108
- # env: The Rack compatible env
109
- # &block: From the request
110
- #
111
- def callbacks(env, &block)
112
- case
113
- when app
114
- response = build_response(*app.call(env))
115
- write response
116
- when block
117
- request = Rack::Request.new(env)
118
- block.call(request)
110
+ env = incoming.to_env
111
+ outgoing_response = Net::PTTH::OutgoingResponse.new(*app.call(env))
112
+ socket.write(outgoing_response.to_s)
113
+ end
119
114
  else
120
- close
121
- end
122
- end
123
-
124
- def build_response(status, headers, body)
125
- log "Building Response"
126
- response = "HTTP/1.1 #{status} OK\n"
127
- headers.each { |key, value| response += "#{key}: #{value}\n" }
128
- response += "\n\r"
129
- body.each { |chunk| response += chunk }
130
-
131
- response
132
- end
115
+ debug "* Simple request"
133
116
 
134
- # Private: Builds a Rack compatible env from a PTTH::Request
135
- #
136
- # request: A PTTH parsed request
137
- #
138
- def build_env(request)
139
- env = {
140
- "PATH_INFO" => request.path,
141
- "SCRIPT_NAME" => "",
142
- "rack.input" => request.body,
143
- "REQUEST_METHOD" => @parser.http_method,
144
- }
145
-
146
- env.tap do |h|
147
- h["CONTENT_LENGTH"] = request.body.length if !request.body.nil?
148
- end
149
-
150
- env.merge!(request.headers) if request.headers
151
- end
117
+ if parser.finished?
118
+ parser.body = buffer.split(CRLF*2).last
119
+ end
152
120
 
121
+ response = Net::PTTH::Response.new(
122
+ parser.http_method,
123
+ parser.status_code,
124
+ parser.headers,
125
+ parser.body
126
+ )
153
127
 
154
- # Private: Builds a PTTH::Request from the parsed input
155
- #
156
- # request: The object where the parsed content will be placed
157
- #
158
- def build_request(request)
159
- @parser.reset
160
- parse_headers(request.headers)
128
+ keep_connection_active if keep_alive?(response)
161
129
 
162
- @parser.on_url { |url| request.path = url }
163
- @parser.on_body do |response_body|
164
- request.body = StringIO.new(response_body)
130
+ response
165
131
  end
132
+
133
+ rescue IOError => e
134
+ rescue EOFError => e
135
+ close
166
136
  end
167
137
 
138
+ private
168
139
 
169
- # Private: Logs a debug message
170
- #
171
- # message: The string to be logged
172
- #
173
- def log(message, prepend = "* ")
174
- parts = (message || "").split("\n")
175
- @debug_output << parts.map { |line| prepend + line }.join("\n")
176
- @debug_output << "\n"
140
+ def keep_alive?(response)
141
+ response.status == 200 && @keep_alive
177
142
  end
178
143
 
179
- # Private: Parses the incoming request headers and adds the information to a
180
- # given object
181
- #
182
- # headers: The object in which the headers will be added
183
- #
184
- def parse_headers(headers)
185
- raw_headers = []
186
- add_header = proc { |header| raw_headers << header }
187
-
188
- @parser.on_header_field &add_header
189
- @parser.on_header_value &add_header
190
- @parser.on_headers_complete do
191
- raw_headers.each_slice(2) do |key, value|
192
- header_name = key.
193
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
194
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
195
- tr("-", "_").
196
- upcase
197
-
198
- headers["HTTP_" + header_name] = value
144
+ def keep_connection_active
145
+ after(1) do
146
+ while @keep_alive do
147
+ # All you need is love. Something to keep alive the connection of that
148
+ # given socket
149
+ socket.write("<3")
150
+ sleep 1
199
151
  end
200
152
  end
201
153
  end
202
154
 
203
- # Private: Builds a reversed request
155
+ # Private: outputs debug information
156
+ # string: The string to be logged
204
157
  #
205
- # req: The request to be build
206
- #
207
- def build(req)
208
- req["Upgrade"] = "PTTH/1.0"
209
- req["Connection"] ||= "Upgrade"
210
- req["Content-Length"] ||= req.body.length if !req.body.nil?
211
-
212
- package = "#{req.method} #{req.path} HTTP/1.1\n"
213
- req.each_header do |header, value|
214
- header_parts = header.split("-").map(&:capitalize)
215
- package += "#{header_parts.join("-")}: #{value}\n"
216
- end
217
-
218
- package += "\n\r#{req.body}" if req.body
219
- package += "\r\n\r\n"
220
- package
158
+ def debug(string)
159
+ @debug_output << string + "\n"
221
160
  end
222
161
  end
data/net-ptth.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "net-ptth"
3
- s.version = "0.0.5"
3
+ s.version = "0.0.6"
4
4
  s.summary = "Net::HTTP compatible reverse HTTP version"
5
5
  s.description = "PTTH Ruby client. Net::HTTP compatible... kind of"
6
6
  s.authors = ["elcuervo"]
@@ -10,9 +10,9 @@ Gem::Specification.new do |s|
10
10
  s.files = `git ls-files`.split("\n")
11
11
  s.test_files = `git ls-files test`.split("\n")
12
12
 
13
- s.add_dependency("rack", "~> 1.4.4")
14
- s.add_dependency("celluloid-io", "~> 0.12.1")
15
- s.add_dependency("http-parser-lite", "~> 0.6.0")
13
+ s.add_dependency("rack", "~> 1.5.2")
14
+ s.add_dependency("celluloid-io", "~> 0.15.0")
15
+ s.add_dependency("http_parser.rb", "~> 0.5.3")
16
16
 
17
17
  s.add_development_dependency("minitest", "~> 4.4.0")
18
18
  s.add_development_dependency("cuba", "~> 3.1.0")
data/test/ptth_test.rb CHANGED
@@ -4,27 +4,6 @@ require "sinatra/base"
4
4
  require "net/ptth"
5
5
  require "net/ptth/test"
6
6
 
7
- describe "PTTH connection" do
8
- before do
9
- @server = Net::PTTH::TestServer.new(port: 23045)
10
- Thread.new { @server.start }
11
- end
12
-
13
- after do
14
- @server.close
15
- end
16
-
17
- it "should stablish a reverse connection" do
18
- ptth = Net::PTTH.new("http://localhost:23045")
19
- request = Net::HTTP::Post.new("/reverse")
20
-
21
- ptth.request(request) do |incoming_request|
22
- assert_equal "reversed", incoming_request.body.read
23
- ptth.close
24
- end
25
- end
26
- end
27
-
28
7
  describe "Using a Rack compatible app to receive requests" do
29
8
  before do
30
9
  response = Net::HTTP::Get.new("/custom_app")
@@ -74,7 +53,8 @@ end
74
53
 
75
54
  describe "PTTH Test server" do
76
55
  before do
77
- response = Net::HTTP::Get.new("/other")
56
+ response = Net::HTTP::Post.new("/other")
57
+ response.body = "thing"
78
58
  @server = Net::PTTH::TestServer.new(port: 23045, response: response)
79
59
 
80
60
  Thread.new { @server.start }
@@ -88,9 +68,12 @@ describe "PTTH Test server" do
88
68
  ptth = Net::PTTH.new("http://localhost:23045")
89
69
  request = Net::HTTP::Post.new("/reverse")
90
70
 
91
- ptth.request(request) do |incoming_request|
92
- assert_equal "/other", incoming_request.path
93
- ptth.close
71
+ ptth.app = Cuba.define do
72
+ on "other" do
73
+ ptth.close
74
+ end
94
75
  end
76
+
77
+ ptth.request(request)
95
78
  end
96
79
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-ptth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-18 00:00:00.000000000 Z
12
+ date: 2013-10-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 1.4.4
21
+ version: 1.5.2
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 1.4.4
29
+ version: 1.5.2
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: celluloid-io
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: 0.12.1
37
+ version: 0.15.0
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,15 +42,15 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: 0.12.1
45
+ version: 0.15.0
46
46
  - !ruby/object:Gem::Dependency
47
- name: http-parser-lite
47
+ name: http_parser.rb
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
51
  - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: 0.6.0
53
+ version: 0.5.3
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
@@ -58,7 +58,7 @@ dependencies:
58
58
  requirements:
59
59
  - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: 0.6.0
61
+ version: 0.5.3
62
62
  - !ruby/object:Gem::Dependency
63
63
  name: minitest
64
64
  requirement: !ruby/object:Gem::Requirement
@@ -120,7 +120,14 @@ files:
120
120
  - LICENSE
121
121
  - README.md
122
122
  - Rakefile
123
+ - SPEC.md
123
124
  - lib/net/ptth.rb
125
+ - lib/net/ptth/incoming_request.rb
126
+ - lib/net/ptth/outgoing_request.rb
127
+ - lib/net/ptth/outgoing_response.rb
128
+ - lib/net/ptth/parser.rb
129
+ - lib/net/ptth/response.rb
130
+ - lib/net/ptth/socket.rb
124
131
  - lib/net/ptth/test.rb
125
132
  - net-ptth.gemspec
126
133
  - test/ptth_test.rb