plezi 0.7.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.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/CHANGELOG.md +450 -0
  4. data/Gemfile +4 -0
  5. data/KNOWN_ISSUES.md +13 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +341 -0
  8. data/Rakefile +2 -0
  9. data/TODO.md +19 -0
  10. data/bin/plezi +301 -0
  11. data/lib/plezi.rb +125 -0
  12. data/lib/plezi/base/cache.rb +77 -0
  13. data/lib/plezi/base/connections.rb +33 -0
  14. data/lib/plezi/base/dsl.rb +177 -0
  15. data/lib/plezi/base/engine.rb +85 -0
  16. data/lib/plezi/base/events.rb +84 -0
  17. data/lib/plezi/base/io_reactor.rb +41 -0
  18. data/lib/plezi/base/logging.rb +62 -0
  19. data/lib/plezi/base/rack_app.rb +89 -0
  20. data/lib/plezi/base/services.rb +57 -0
  21. data/lib/plezi/base/timers.rb +71 -0
  22. data/lib/plezi/handlers/controller_magic.rb +383 -0
  23. data/lib/plezi/handlers/http_echo.rb +27 -0
  24. data/lib/plezi/handlers/http_host.rb +215 -0
  25. data/lib/plezi/handlers/http_router.rb +69 -0
  26. data/lib/plezi/handlers/magic_helpers.rb +43 -0
  27. data/lib/plezi/handlers/route.rb +272 -0
  28. data/lib/plezi/handlers/stubs.rb +143 -0
  29. data/lib/plezi/server/README.md +33 -0
  30. data/lib/plezi/server/helpers/http.rb +169 -0
  31. data/lib/plezi/server/helpers/mime_types.rb +999 -0
  32. data/lib/plezi/server/protocols/http_protocol.rb +318 -0
  33. data/lib/plezi/server/protocols/http_request.rb +133 -0
  34. data/lib/plezi/server/protocols/http_response.rb +294 -0
  35. data/lib/plezi/server/protocols/websocket.rb +208 -0
  36. data/lib/plezi/server/protocols/ws_response.rb +92 -0
  37. data/lib/plezi/server/services/basic_service.rb +224 -0
  38. data/lib/plezi/server/services/no_service.rb +196 -0
  39. data/lib/plezi/server/services/ssl_service.rb +193 -0
  40. data/lib/plezi/version.rb +3 -0
  41. data/plezi.gemspec +26 -0
  42. data/resources/404.erb +68 -0
  43. data/resources/404.haml +64 -0
  44. data/resources/404.html +67 -0
  45. data/resources/404.slim +63 -0
  46. data/resources/500.erb +68 -0
  47. data/resources/500.haml +63 -0
  48. data/resources/500.html +67 -0
  49. data/resources/500.slim +63 -0
  50. data/resources/Gemfile +85 -0
  51. data/resources/anorexic_gray.png +0 -0
  52. data/resources/anorexic_websockets.html +47 -0
  53. data/resources/code.rb +8 -0
  54. data/resources/config.ru +39 -0
  55. data/resources/controller.rb +139 -0
  56. data/resources/db_ac_config.rb +58 -0
  57. data/resources/db_dm_config.rb +51 -0
  58. data/resources/db_sequel_config.rb +42 -0
  59. data/resources/en.yml +204 -0
  60. data/resources/environment.rb +41 -0
  61. data/resources/haml_config.rb +6 -0
  62. data/resources/i18n_config.rb +14 -0
  63. data/resources/rakefile.rb +22 -0
  64. data/resources/redis_config.rb +35 -0
  65. data/resources/routes.rb +26 -0
  66. data/resources/welcome_page.html +72 -0
  67. data/websocket chatroom.md +639 -0
  68. metadata +141 -0
@@ -0,0 +1,92 @@
1
+ module Plezi
2
+
3
+ # this class handles WebSocket response.
4
+ #
5
+ # the WSResponse supports only one method - the send method.
6
+ #
7
+ # use: `response << data` to send data. data should be a String object.
8
+ #
9
+ # the data wil be sent as text if the string is encoded as a UTF-8 string (default encoding).
10
+ # otherwise, the data will be sent as a binary stream.
11
+ #
12
+ # todo: extentions support, support frames longer then 125 bytes.
13
+ class WSResponse
14
+
15
+ #the service through which the response will be sent.
16
+ attr_reader :service
17
+ #the request.
18
+ attr_accessor :request
19
+
20
+ def initialize request
21
+ @request, @service = request,request.service
22
+ end
23
+
24
+ # pushes data to the body of the response. this is the preffered way to add data to the response.
25
+ def << str
26
+ service.send_nonblock self.class.frame_data(str.dup)
27
+ self
28
+ end
29
+
30
+ # sends the response object. headers will be frozen (they can only be sent at the head of the response).
31
+ #
32
+ # the response will remain open for more data to be sent through (using `response << data` and `response.send`).
33
+ def send str
34
+ service.send_nonblock self.class.frame_data(str.dup)
35
+ end
36
+
37
+ # makes sure any data held in the buffer is actually sent.
38
+ def flush
39
+ service.flush
40
+ end
41
+
42
+ # sends any pending data and closes the connection.
43
+ def close
44
+ service.send_nonblock self.class.frame_data('', 8)
45
+ service.disconnect
46
+ end
47
+
48
+ # Dangerzone! ()alters the string, use `send` instead: formats the data as one or more WebSocket frames.
49
+ def self.frame_data data, op_code = nil, fin = true
50
+ # set up variables
51
+ frame = ''.force_encoding('binary')
52
+ op_code ||= data.encoding.name == 'UTF-8' ? 1 : 2
53
+ data.force_encoding('binary')
54
+
55
+ # fragment big data chuncks into smaller frames
56
+ [frame << frame_data(data.slice!(0..1048576), op_code, false), op_code = 0] while data.length > 1048576
57
+
58
+ # apply extenetions to the frame
59
+ ext = 0
60
+ # ext |= call each service.protocol.extenetions with data #changes data and returns flags to be set
61
+ # service.protocol.extenetions.each { |ex| ext |= WSProtocol::SUPPORTED_EXTENTIONS[ex[0]][2].call data, ex[1..-1]}
62
+
63
+ # set
64
+ frame << ( (fin ? 0b10000000 : 0) | (op_code & 0b00001111) | ext).chr
65
+
66
+ if data.length < 125
67
+ frame << data.length.chr
68
+ elsif data.length.bit_length < 16
69
+ frame << 126.chr
70
+ frame << [data.length].pack('S>')
71
+ else
72
+ frame << 127.chr
73
+ frame << [data.length].pack('Q>')
74
+ end
75
+ frame << data
76
+ frame
77
+ end
78
+ end
79
+ end
80
+
81
+
82
+ ######
83
+ ## example requests
84
+
85
+ # GET / HTTP/1.1
86
+ # Host: localhost:2000
87
+ # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
88
+ # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
89
+ # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
90
+ # Accept-Language: en-us
91
+ # Accept-Encoding: gzip, deflate
92
+ # Connection: keep-alive
@@ -0,0 +1,224 @@
1
+ module Plezi
2
+
3
+ # this class is a basic TCP socket service.
4
+ #
5
+ # a protocol should be assigned, or the service will fall back to an echo service.
6
+ #
7
+ # a protocol should answer to: on_connect(service), on_message(service, data), on_disconnect(service) and on_exception(service, exception)
8
+ #
9
+ # the on_message method should return any data that wasn't used (to be sent again as part of the next `on_message` call, once more data is received).
10
+ #
11
+ # if the protocol is a class, these methods should be instance methods.
12
+ # a protocol class should support the initialize(service, parameters={}) method as well.
13
+ #
14
+ # to-do: fix logging
15
+ class BasicService
16
+
17
+ # create a listener (io) - will create a TCPServer socket
18
+ #
19
+ # listeners are 'server sockets' that answer to `accept` by creating a new connection socket (io).
20
+ def self.create_service port, parameters
21
+ TCPServer.new(port)
22
+ end
23
+
24
+ # instance methods
25
+
26
+ attr_reader :socket, :locker, :closed, :parameters, :out_que, :active_time
27
+ attr_accessor :protocol, :handler, :timeout
28
+
29
+ # creates a new connection wrapper object for the new socket that was recieved from the `accept_nonblock` method call.
30
+ def initialize socket, parameters = {}
31
+ @handler = parameters[:handler]
32
+ @socket = socket
33
+ # socket.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, "\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" #== [10 sec 0 usec].pack '1_2'
34
+ @out_que = []
35
+ @locker = Mutex.new
36
+ @parameters = parameters
37
+ @protocol = parameters[:protocol]
38
+ @protocol = protocol.new self, parameters if protocol.is_a?(Class)
39
+ @protocol.on_connect self if @protocol && @protocol.methods.include?(:on_connect)
40
+ touch
41
+ @timeout ||= 5
42
+ # Plezi.callback self, :on_message
43
+ end
44
+
45
+ # # sets a connection timeout
46
+ # def set_timeout timeout = 8
47
+ # @timeout = timeout
48
+ # end
49
+
50
+ # checks if a connection timed out
51
+ def timedout?
52
+ Time.now - @active_time > @timeout
53
+ end
54
+
55
+ # resets the timer for the connection timeout
56
+ def touch
57
+ @active_time = Time.now
58
+ end
59
+
60
+ # returns an IO-like object used for reading/writing (unlike the original IO object, this can be an SSL layer or any other wrapper object).
61
+ def io
62
+ touch
63
+ @socket
64
+ end
65
+
66
+ # sends data immidiately - forcing the data to be sent, flushing any pending messages in the que
67
+ def send data = nil
68
+ return if @out_que.empty? && data.nil?
69
+ locker.synchronize do
70
+ unless @out_que.empty?
71
+ @out_que.each { |d| _send d rescue disconnect }
72
+ @out_que.clear
73
+ end
74
+ (_send data rescue disconnect) if data
75
+ touch
76
+ end
77
+ end
78
+
79
+ # sends data immidiately, interrupting any pending que and ignoring thread safety.
80
+ def send_unsafe_interrupt data = nil
81
+ touch
82
+ _send data rescue disconnect
83
+ end
84
+
85
+ # sends data without waiting - data might be sent in a different order then intended.
86
+ def send_nonblock data
87
+ touch
88
+ locker.synchronize {@out_que << data}
89
+ Plezi.callback(self, :send)
90
+ end
91
+
92
+ # adds data to the out buffer - but doesn't send the data until a send event is called.
93
+ def << data
94
+ touch
95
+ locker.synchronize {@out_que << data}
96
+ end
97
+
98
+ # makes sure any data in the que is send and calls `flush` on the socket, to make sure the buffer is sent.
99
+ def flush
100
+ begin
101
+ send
102
+ socket.flush
103
+ rescue Exception => e
104
+
105
+ end
106
+ end
107
+
108
+ # event based interface for messages.
109
+
110
+ # notice: since it is all async evet base - multipart messages might be garbled up...?
111
+ # todo: protect from garbeling.
112
+ def on_message
113
+ # return false if locker.locked?
114
+ return false if locker.locked?
115
+ begin
116
+
117
+ locker.synchronize do
118
+ return disconnect if _disconnected?
119
+ protocol.on_message(self)
120
+ end
121
+
122
+ rescue Exception => e
123
+ return disconnect
124
+ end
125
+ end
126
+
127
+ # called once a socket is disconnected or needs to be disconnected.
128
+ def on_disconnect
129
+ Plezi.callback Plezi, :remove_connection, self
130
+ locker.synchronize do
131
+ @out_que.each { |d| _send d rescue true}
132
+ @out_que.clear
133
+ end
134
+ Plezi.callback protocol, :on_disconnect, self if protocol
135
+ close
136
+ end
137
+
138
+ # status markers
139
+
140
+ # closes the connection
141
+ def close
142
+ locker.synchronize do
143
+ _close rescue true
144
+ end
145
+ end
146
+ # returns true if the service is disconnected
147
+ def disconnected?
148
+ _disconnected?
149
+ end
150
+ # disconects the service.
151
+ def disconnect
152
+ Plezi.callback self, :on_disconnect
153
+ end
154
+ # returns true if the socket has content to be read.
155
+ def has_incoming_data?
156
+ (socket.stat.size > 0) rescue false
157
+ end
158
+
159
+
160
+ # identification markers
161
+
162
+ #returns the service type - set to normal
163
+ def service_type
164
+ 'normal'
165
+ end
166
+ #returns true if the service is encrypted using the OpenSSL library.
167
+ def ssl?
168
+ false
169
+ end
170
+
171
+ #################
172
+ # overide the followind methods for any child class.
173
+
174
+ # this is a public method and it should be used by child classes to implement each
175
+ # read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
176
+ def read size = 1048576
177
+ data = @socket.recv_nonblock( size )
178
+ touch unless data.nil? || data.empty?
179
+ return data
180
+ rescue Exception => e
181
+ return ''
182
+ end
183
+
184
+ protected
185
+
186
+ # this is a protected method, it should be used by child classes to implement each
187
+ # send action.
188
+ def _send data
189
+ # data.force_encoding "binary" rescue false
190
+ len = data.bytesize
191
+ act = @socket.send data, 0
192
+ while len > act
193
+ act += @socket.send data.byteslice(act..-1) , 0
194
+ touch
195
+ end
196
+ end
197
+ # this is a protected method, it should be used by child classes to implement each
198
+ # close action. doesn't accept any arguments.
199
+ def _close
200
+ socket.flush rescue true
201
+ socket.close
202
+ end
203
+ # this is a protected method, it should be used by child classes to tell if the socket
204
+ # was closed (returns true/false).
205
+ def _disconnected?
206
+ socket.closed? rescue true # || socket.stat.mode == 0140222 rescue true # if mode is read only, it's the same as closed.
207
+ end
208
+
209
+ end
210
+ end
211
+
212
+
213
+ ######
214
+ ## example requests
215
+
216
+ # GET /parsed_request HTTP/1.1
217
+ # Host: localhost:2000
218
+ # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
219
+ # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
220
+ # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
221
+ # Accept-Language: en-us
222
+ # Accept-Encoding: gzip, deflate
223
+ # Connection: keep-alive
224
+ # X-Forwarded-For: 127.0.0.3
@@ -0,0 +1,196 @@
1
+ module Plezi
2
+
3
+ # this class is a basic TCP socket service.
4
+ #
5
+ # a protocol should be assigned, or the service will fall back to an echo service.
6
+ #
7
+ # a protocol should answer to: on_connect(service), on_message(service, data), on_disconnect(service) and on_exception(service, exception)
8
+ #
9
+ # the on_message method should return any data that wasn't used (to be sent again as part of the next `on_message` call, once more data is received).
10
+ #
11
+ # if the protocol is a class, these methods should be instance methods.
12
+ # a protocol class should support the initialize(service, parameters={}) method as well.
13
+ #
14
+ # to-do: fix logging
15
+ class NoService
16
+
17
+ # create a listener (io) - will create a TCPServer socket
18
+ #
19
+ # listeners are 'server sockets' that answer to `accept` by creating a new connection socket (io).
20
+ def self.create_service port, parameters
21
+ self
22
+ end
23
+
24
+ def self.accept
25
+ false
26
+ end
27
+
28
+ # instance methods
29
+
30
+ attr_reader :socket, :locker, :closed, :parameters, :out_que, :active_time
31
+ attr_accessor :protocol, :handler, :timeout
32
+
33
+ # creates a new connection wrapper object for the new socket that was recieved from the `accept_nonblock` method call.
34
+ def initialize socket, parameters = {}
35
+ @handler = parameters[:handler]
36
+ @socket = nil
37
+ # socket.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, "\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" #== [10 sec 0 usec].pack '1_2'
38
+ @out_que = []
39
+ @locker = Mutex.new
40
+ @parameters = parameters
41
+ @protocol = parameters[:protocol]
42
+ @protocol = protocol.new self, parameters if protocol.is_a?(Class)
43
+ @protocol.on_connect self if @protocol && @protocol.methods.include?(:on_connect)
44
+ touch
45
+ @timeout ||= 5
46
+ # Plezi.callback self, :on_message
47
+ end
48
+
49
+ # # sets a connection timeout
50
+ # def set_timeout timeout = 8
51
+ # @timeout = timeout
52
+ # end
53
+
54
+ # checks if a connection timed out
55
+ def timedout?
56
+ Time.now - @active_time > @timeout
57
+ end
58
+
59
+ # resets the timer for the connection timeout
60
+ def touch
61
+ @active_time = Time.now
62
+ end
63
+
64
+ # returns an IO-like object used for reading/writing (unlike the original IO object, this can be an SSL layer or any other wrapper object).
65
+ def io
66
+ touch
67
+ @socket
68
+ end
69
+
70
+ # sends data immidiately - forcing the data to be sent, flushing any pending messages in the que
71
+ def send data = nil
72
+ touch
73
+ end
74
+
75
+ # sends data immidiately, interrupting any pending que and ignoring thread safety.
76
+ def send_unsafe_interrupt data = nil
77
+ touch
78
+ _send data rescue disconnect
79
+ end
80
+
81
+ # sends data without waiting - data might be sent in a different order then intended.
82
+ def send_nonblock data
83
+ touch
84
+ locker.synchronize {@out_que << data}
85
+ Plezi.callback(self, :send)
86
+ end
87
+
88
+ # adds data to the out buffer - but doesn't send the data until a send event is called.
89
+ def << data
90
+ touch
91
+ locker.synchronize {@out_que << data}
92
+ end
93
+
94
+ # makes sure any data in the que is send and calls `flush` on the socket, to make sure the buffer is sent.
95
+ def flush
96
+ end
97
+
98
+ # event based interface for messages.
99
+
100
+ # notice: since it is all async evet base - multipart messages might be garbled up...?
101
+ # todo: protect from garbeling.
102
+ def on_message
103
+ # return false if locker.locked?
104
+ return false if locker.locked?
105
+ return disconnect if (_disconnected? rescue true)
106
+ locker.synchronize do
107
+ begin
108
+ touch
109
+ if protocol
110
+ protocol.on_message self
111
+ end
112
+ rescue Exception => e
113
+ Plezi.error e
114
+ return disconnect
115
+ end
116
+ end
117
+ end
118
+
119
+ # called once a socket is disconnected or needs to be disconnected.
120
+ def on_disconnect
121
+ Plezi.callback Plezi, :remove_connection, self
122
+
123
+ close
124
+ end
125
+
126
+ # status markers
127
+
128
+ # closes the connection
129
+ def close
130
+ end
131
+ # returns true if the service is disconnected
132
+ def disconnected?
133
+ false
134
+ end
135
+ # disconects the service.
136
+ def disconnect
137
+ on_disconnect
138
+ end
139
+ # returns true if the socket has content to be read.
140
+ def has_incoming_data?
141
+ false
142
+ end
143
+
144
+
145
+ # identification markers
146
+
147
+ #returns the service type - set to normal
148
+ def service_type
149
+ 'no-service'
150
+ end
151
+ #returns true if the service is encrypted using the OpenSSL library.
152
+ def ssl?
153
+ false
154
+ end
155
+
156
+ #################
157
+ # overide the followind methods for any child class.
158
+
159
+ # this is a public method and it should be used by child classes to implement each
160
+ # read(_nonblock) action. accepts one argument ::size for an optional buffer size to be read.
161
+ def read size = 1048576
162
+ ''
163
+ end
164
+
165
+ protected
166
+
167
+ # this is a protected method, it should be used by child classes to implement each
168
+ # send action.
169
+ def _send data
170
+ ''
171
+ end
172
+ # this is a protected method, it should be used by child classes to implement each
173
+ # close action. doesn't accept any arguments.
174
+ def _close
175
+ end
176
+ # this is a protected method, it should be used by child classes to tell if the socket
177
+ # was closed (returns true/false).
178
+ def _disconnected?
179
+ false # if mode is read only, it's the same as closed.
180
+ end
181
+
182
+ end
183
+ end
184
+
185
+
186
+ ######
187
+ ## example requests
188
+
189
+ # GET / HTTP/1.1
190
+ # Host: localhost:2000
191
+ # Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
192
+ # Cookie: user_token=2INa32_vDgx8Aa1qe43oILELpSdIe9xwmT8GTWjkS-w
193
+ # User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/600.1.25 (KHTML, like Gecko) Version/8.0 Safari/600.1.25
194
+ # Accept-Language: en-us
195
+ # Accept-Encoding: gzip, deflate
196
+ # Connection: keep-alive