net-ptth 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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