plezi 0.7.0

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