gqtp 1.0.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.
@@ -0,0 +1,173 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "gqtp/error"
20
+ require "gqtp/header"
21
+
22
+ module GQTP
23
+ class ParseError < Error
24
+ end
25
+
26
+ class Parser
27
+ class << self
28
+ def parse(data, &block)
29
+ if block_given?
30
+ event_parse(data, &block)
31
+ else
32
+ stand_alone_parse(data)
33
+ end
34
+ end
35
+
36
+ private
37
+ def event_parse(data)
38
+ parser = new
39
+
40
+ parser.on_header do |header|
41
+ yield(:on_header, header)
42
+ end
43
+ parser.on_body do |chunk|
44
+ yield(:on_body, chunk)
45
+ end
46
+ parser.on_complete do
47
+ yield(:on_complete)
48
+ end
49
+
50
+ consume_data(parser, data)
51
+ end
52
+
53
+ def stand_alone_parse(data)
54
+ received_header = nil
55
+ body = "".force_encoding("ASCII-8BIT")
56
+ completed = false
57
+
58
+ parser = new
59
+ parser.on_header do |header|
60
+ received_header = header
61
+ end
62
+ parser.on_body do |chunk|
63
+ body << chunk
64
+ end
65
+ parser.on_complete do
66
+ completed = true
67
+ end
68
+
69
+ consume_data(parser, data)
70
+ raise ParseError, "not completed: <#{data.inspect}>" unless completed
71
+
72
+ [received_header, body]
73
+ end
74
+
75
+ def consume_data(parser, data)
76
+ if data.respond_to?(:each)
77
+ data.each do |chunk|
78
+ parser << chunk
79
+ end
80
+ else
81
+ parser << data
82
+ end
83
+ end
84
+ end
85
+
86
+ attr_reader :header
87
+ def initialize
88
+ reset
89
+ initialize_hooks
90
+ end
91
+
92
+ def <<(chunk)
93
+ if @header.nil?
94
+ parse_header(chunk)
95
+ else
96
+ parse_body(chunk)
97
+ end
98
+ self
99
+ end
100
+
101
+ # @overload on_header(header)
102
+ # @overload on_header {|header| }
103
+ def on_header(*arguments, &block)
104
+ if block_given?
105
+ @on_header_hook = block
106
+ else
107
+ @on_header_hook.call(*arguments) if @on_header_hook
108
+ end
109
+ end
110
+
111
+ # @overload on_body(chunk)
112
+ # @overload on_body {|chunk| }
113
+ def on_body(*arguments, &block)
114
+ if block_given?
115
+ @on_body_hook = block
116
+ else
117
+ @on_body_hook.call(*arguments) if @on_body_hook
118
+ end
119
+ end
120
+
121
+ # @overload on_complete
122
+ # @overload on_complete { }
123
+ def on_complete(&block)
124
+ if block_given?
125
+ @on_complete_hook = block
126
+ else
127
+ @on_complete_hook.call if @on_complete_hook
128
+ end
129
+ end
130
+
131
+ private
132
+ def reset
133
+ @buffer = "".force_encoding("ASCII-8BIT")
134
+ @header = nil
135
+ @body_size = 0
136
+ end
137
+
138
+ def initialize_hooks
139
+ @on_header_hook = nil
140
+ @on_body_hook = nil
141
+ @on_complete_hook = nil
142
+ end
143
+
144
+ def parse_header(chunk)
145
+ @buffer << chunk
146
+ return if @buffer.bytesize < Header.size
147
+ @header = Header.parse(@buffer)
148
+ on_header(@header)
149
+ buffer = @buffer
150
+ @buffer = nil
151
+ if buffer.bytesize > Header.size
152
+ parse_body(buffer[Header.size..-1])
153
+ end
154
+ end
155
+
156
+ def parse_body(chunk)
157
+ @body_size += chunk.bytesize
158
+ if @body_size < @header.size
159
+ on_body(chunk)
160
+ elsif @body_size == @header.size
161
+ on_body(chunk)
162
+ on_complete
163
+ reset
164
+ else
165
+ rest_body_size = @header.size - (@body_size - chunk.bytesize)
166
+ on_body(chunk[0, rest_body_size])
167
+ on_complete
168
+ reset
169
+ self << chunk[rest_body_size..-1]
170
+ end
171
+ end
172
+ end
173
+ end
data/lib/gqtp/proxy.rb ADDED
@@ -0,0 +1,74 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "gqtp/server"
20
+
21
+ module GQTP
22
+ class Proxy
23
+ attr_accessor :listen_address, :listen_port
24
+ attr_accessor :upstream_address, :upstream_port
25
+ def initialize(options={})
26
+ @options = options.dup
27
+ @listen_address = @options[:listen_address] || "0.0.0.0"
28
+ @listen_port = @options[:listen_port] || 10041
29
+ @upstream_address = @options[:upstream_address] || "127.0.0.1"
30
+ @upstream_port = @options[:upstream_port] || 10041
31
+ @connection = @options[:connection] || :thread
32
+ @server = Server.new(:address => @listen_address,
33
+ :port => @listen_port,
34
+ :connection => @connection)
35
+ end
36
+
37
+ def run
38
+ @server.on_connect do |client|
39
+ create_connection
40
+ end
41
+ @server.on_request do |request, client, connection|
42
+ connection.write(request.header.pack, request.body) do
43
+ read_header_request = connection.read(Header.size) do |header|
44
+ response_header = Header.parse(header)
45
+ read_body_request = connection.read(response_header.size) do |body|
46
+ client.write(header, body) do
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ @server.run
53
+ end
54
+
55
+ def shutdown
56
+ @server.shutdown
57
+ end
58
+
59
+ private
60
+ def create_connection
61
+ begin
62
+ require "gqtp/connection/#{@connection}"
63
+ rescue LoadError
64
+ raise "unknown connection: <#{@connection.inspect}>"
65
+ end
66
+
67
+ require "gqtp/connection/#{@connection}"
68
+ module_name = @connection.to_s.capitalize
69
+ connection_module = GQTP::Connection::const_get(module_name)
70
+ connection_module::Client.new(:address => @upstream_address,
71
+ :port => @upstream_port)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,99 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "gqtp/header"
20
+
21
+ module GQTP
22
+ class Server
23
+ attr_accessor :address, :port
24
+ def initialize(options={})
25
+ @options = options.dup
26
+ @options[:address] ||= "0.0.0.0"
27
+ @options[:port] ||= 10041
28
+ @on_request = nil
29
+ @on_connect = nil
30
+ end
31
+
32
+ def run
33
+ @connection = create_connection
34
+ @connection.run do |client|
35
+ process_request(client, on_connect(client))
36
+ end
37
+ end
38
+
39
+ def shutdown
40
+ @connection.shutdown
41
+ end
42
+
43
+ def on_connect(*arguments, &block)
44
+ if block_given?
45
+ @on_connect = block
46
+ else
47
+ client, = arguments
48
+ if @on_connect
49
+ @on_connect.call(client)
50
+ else
51
+ nil
52
+ end
53
+ end
54
+ end
55
+
56
+ def on_request(*arguments, &block)
57
+ if block_given?
58
+ @on_request = block
59
+ else
60
+ request, client, connect_info = arguments
61
+ @on_request.call(request, client, connect_info)
62
+ end
63
+ end
64
+
65
+ private
66
+ def create_connection
67
+ connection = @options[:connection] || :thread
68
+
69
+ begin
70
+ require "gqtp/connection/#{connection}"
71
+ rescue LoadError
72
+ raise "unknown connection: <#{connection.inspect}>"
73
+ end
74
+
75
+ module_name = connection.to_s.capitalize
76
+ connection_module = GQTP::Connection::const_get(module_name)
77
+ connection_module::Server.new(@options)
78
+ end
79
+
80
+ def process_request(client, connect_info)
81
+ read_header_request = client.read(Header.size) do |header|
82
+ request_header = Header.parse(header)
83
+ read_body_request = client.read(request_header.size) do |body|
84
+ request = Request.new(request_header, body)
85
+ on_request(request, client, connect_info)
86
+ process_request(client, connect_info)
87
+ end
88
+ end
89
+ end
90
+
91
+ class Request
92
+ attr_reader :header, :body
93
+ def initialize(header, body)
94
+ @header = header
95
+ @body = body
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,21 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ module GQTP
20
+ VERSION = "1.0.0"
21
+ end
data/lib/gqtp.rb ADDED
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "gqtp/version"
20
+ require "gqtp/client"
21
+ require "gqtp/server"
22
+ require "gqtp/proxy"
data/test/run-test.rb ADDED
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ $VERBOSE = true
20
+
21
+ base_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
22
+ lib_dir = File.join(base_dir, "lib")
23
+ test_dir = File.join(base_dir, "test")
24
+
25
+ $LOAD_PATH.unshift(lib_dir)
26
+
27
+ require "test-unit"
28
+ require "test/unit/notify"
29
+
30
+ Thread.abort_on_exception = true
31
+
32
+ exit Test::Unit::AutoRunner.run(true, test_dir)
@@ -0,0 +1,68 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "socket"
20
+
21
+ require "gqtp/client"
22
+
23
+ class ClientTest < Test::Unit::TestCase
24
+ def setup
25
+ @address = "127.0.0.1"
26
+ @server = TCPServer.new(@address, 0)
27
+ @port = @server.addr[1]
28
+
29
+ @request_body = nil
30
+ @response_body = nil
31
+ @thread = Thread.new do
32
+ client = @server.accept
33
+ @server.close
34
+
35
+ header = GQTP::Header.parse(client.read(GQTP::Header.size))
36
+ @request_body = client.read(header.size)
37
+
38
+ response_header = GQTP::Header.new
39
+ response_header.size = @response_body.bytesize
40
+ client.write(response_header.pack)
41
+ client.write(@response_body)
42
+ client.close
43
+ end
44
+ end
45
+
46
+ def teardown
47
+ @thread.kill
48
+ end
49
+
50
+ def test_sync
51
+ @response_body = "[false]"
52
+ client = GQTP::Client.new(:address => @address, :port => @port)
53
+ client.send("status")
54
+ header, body = client.read
55
+ assert_equal(["status", @response_body.bytesize, @response_body],
56
+ [@request_body, header.size, body])
57
+ end
58
+
59
+ def test_async
60
+ @response_body = "[false]"
61
+ client = GQTP::Client.new(:address => @address, :port => @port)
62
+ request = client.send("status") do |header, body|
63
+ assert_equal(["status", @response_body.bytesize, @response_body],
64
+ [@request_body, header.size, body])
65
+ end
66
+ request.wait
67
+ end
68
+ end
@@ -0,0 +1,49 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "gqtp/parser"
20
+
21
+ class HeaderTest < Test::Unit::TestCase
22
+ class EqualTest < self
23
+ def test_true
24
+ assert_equal(GQTP::Header.new,
25
+ GQTP::Header.new)
26
+ end
27
+
28
+ def test_false
29
+ header_size_0 = GQTP::Header.new
30
+ header_size_1 = GQTP::Header.new
31
+ header_size_1.size = 1
32
+ assert_not_equal(header_size_0, header_size_1)
33
+ end
34
+ end
35
+
36
+ class InitializeTest < self
37
+ def test_hash
38
+ header = GQTP::Header.new(:size => 29)
39
+ assert_equal(29, header.size)
40
+ end
41
+
42
+ def test_block
43
+ header = GQTP::Header.new do |h|
44
+ h.size = 29
45
+ end
46
+ assert_equal(29, header.size)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,131 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "gqtp/parser"
20
+
21
+ class ParserTest < Test::Unit::TestCase
22
+ def setup
23
+ @parser = GQTP::Parser.new
24
+ end
25
+
26
+ def test_on_header
27
+ received_header = nil
28
+ @parser.on_header do |header|
29
+ received_header = header
30
+ end
31
+
32
+ header = GQTP::Header.new
33
+ packed_header = header.pack
34
+ @parser << packed_header[0..-2]
35
+ assert_nil(received_header)
36
+ @parser << packed_header[-1..-1]
37
+ assert_equal(header, received_header)
38
+ end
39
+
40
+ def test_on_body
41
+ received_data = ""
42
+ @parser.on_body do |chunk|
43
+ received_data << chunk
44
+ end
45
+
46
+ data = "status"
47
+ header = GQTP::Header.new
48
+ header.size = data.bytesize
49
+
50
+ @parser << header.pack
51
+ assert_equal("", received_data)
52
+
53
+ @parser << data[0..-2]
54
+ assert_equal(data[0..-2], received_data)
55
+
56
+ @parser << data[-1..-1]
57
+ assert_equal(data, received_data)
58
+ end
59
+
60
+ def test_on_complete
61
+ completed = false
62
+ @parser.on_complete do
63
+ completed = true
64
+ end
65
+
66
+ data = "status"
67
+ header = GQTP::Header.new
68
+ header.size = data.bytesize
69
+
70
+ @parser << header.pack << data[0..-2]
71
+ assert_false(completed)
72
+
73
+ @parser << data[-1..-1]
74
+ assert_true(completed)
75
+ end
76
+
77
+ def test_parse_twice
78
+ body = "status"
79
+ header = GQTP::Header.new
80
+ header.size = body.bytesize
81
+
82
+ n_completed = 0
83
+ @parser.on_complete do
84
+ n_completed += 1
85
+ end
86
+ @parser << ((header.pack + body) * 2)
87
+ assert_equal(2, n_completed)
88
+ end
89
+
90
+ class ParseTest < self
91
+ def test_event
92
+ body = "status"
93
+ header = GQTP::Header.new
94
+ header.size = body.bytesize
95
+
96
+ events = []
97
+ GQTP::Parser.parse([header.pack, body]) do |event_type, *arguments|
98
+ events << [event_type, arguments]
99
+ end
100
+ assert_equal([
101
+ [:on_header, [header]],
102
+ [:on_body, [body]],
103
+ [:on_complete, []],
104
+ ],
105
+ events)
106
+ end
107
+
108
+ class StandAloneTest < self
109
+ def test_success
110
+ body = "status"
111
+ header = GQTP::Header.new
112
+ header.size = body.bytesize
113
+
114
+ parsed_header, parsed_body = GQTP::Parser.parse([header.pack, body])
115
+ assert_equal([ header, body],
116
+ [parsed_header, parsed_body])
117
+ end
118
+
119
+ def test_not_completed
120
+ header = GQTP::Header.new
121
+ not_completed_data = header.pack[0..1]
122
+
123
+ message = "not completed: <#{not_completed_data.inspect}>"
124
+ exception = GQTP::ParseError.new(message)
125
+ assert_raise(exception) do
126
+ GQTP::Parser.parse(not_completed_data)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end