riddl 1.0.17 → 1.0.18

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ef59133f554419dd4322999ae748a7b8621f1dcac6274ce22c0a344e355c099
4
- data.tar.gz: 6f6b9003686cca007198a22a4407eced0d2a41925c28a8ec67b4dd4337bc4827
3
+ metadata.gz: 6ca83f5fe99631dcde4aaa4ad94204809098fd7b23c500c6dc5b46cd065c7759
4
+ data.tar.gz: 78f8b8be0e7063354fbf1242dad5daf1f0302bd39cce9953fec99c2ed7c5a8f8
5
5
  SHA512:
6
- metadata.gz: d25f40fcba3d5b54091956009bcd03786840bec3475930f6d3582a744f628f3d7f61c3c5a25bcdd0656b2498b7229ae81f48f2736e804b4af2fe26e47a042283
7
- data.tar.gz: 9d5e7cb3600e60bf40b44dee8ba48c19f54639be94427e3073c84d90f904c8683827aa12d2ef3e372e32bfe6c12846df4780645a6388b6c94e2a817f9e0adcbf
6
+ metadata.gz: 6563f7de6527989c4cd6f7a6dbb225ebd9da1ed9c593dcf355df00c90fbb5d80060972248eaebebdfe692ffd95dfbf5885ad70629ed1717bd7a1a95745090eab
7
+ data.tar.gz: a21e42d70f98fedd372a5911e007d23e2426a4a6d4406b2a8ac3ff26bdb565d2ec8e82558a7770286fb8b8fa7c8997d3b79981d9cb91662343345196e02afb56
@@ -36,6 +36,7 @@ end
36
36
 
37
37
  Riddl::Server.new(File.dirname(__FILE__) + '/description.xml', :port => 9001, :bind => '::') do
38
38
  accessible_description true
39
+ p @riddl_logger
39
40
 
40
41
  on resource do
41
42
  run Riddl::Utils::ERBServe, "static/info.txt" if get
@@ -4,26 +4,29 @@ require File.expand_path(File.dirname(__FILE__) + '/../../lib/ruby/riddl/server'
4
4
 
5
5
  class Bar < Riddl::Implementation
6
6
  def response
7
- $connections.each do |c|
8
- c.send('some data')
7
+ $connections_mutex.synchronize do
8
+ $connections.each do |c|
9
+ c.send('some data')
10
+ end
9
11
  end
10
12
  Riddl::Parameter::Complex.new("return","text/plain","hello world")
11
13
  end
12
14
  end
13
15
 
14
16
  $connections = []
17
+ $connections_mutex = Mutex.new
15
18
 
16
19
  class Echo < Riddl::SSEImplementation
17
20
  def onopen
18
21
  # if true (or any object for that matter) is returned, then 200 is sent
19
22
  # if false (or nil) is returned 404, the sse is denied
20
23
  puts "Connection established"
21
- $connections << self
24
+ $connections_mutex.synchronize { $connections << self }
22
25
  end
23
26
 
24
27
  def onclose
25
28
  puts "Connection closed"
26
- $connections.delete(self)
29
+ $connections_mutex.synchronize { $connections.delete(self) }
27
30
  end
28
31
  end
29
32
 
@@ -32,8 +35,10 @@ Riddl::Server.new(File.dirname(__FILE__) + '/description.xml', :port => 9292) do
32
35
 
33
36
  parallel do
34
37
  loop do
35
- $connections.each do |c|
36
- c.send('heartbeat')
38
+ $connections_mutex.synchronize do
39
+ $connections.each do |c|
40
+ c.send('heartbeat')
41
+ end
37
42
  end
38
43
  sleep 15
39
44
  end
@@ -14,21 +14,23 @@
14
14
  <data type="anyURI"/>
15
15
  </attribute>
16
16
  <ref name="riddl-description-other-atts"/>
17
- <zeroOrMore>
18
- <choice>
19
- <element name="message">
20
- <ref name="riddl-description-other-atts"/>
21
- <ref name="riddl-message"/>
22
- </element>
23
- <element name="transformation">
24
- <ref name="riddl-description-other-atts"/>
25
- <ref name="riddl-transformation"/>
26
- </element>
27
- </choice>
28
- </zeroOrMore>
29
- <element name="resource">
30
- <ref name="riddl-resource-dynamic"/>
31
- </element>
17
+ <interleave>
18
+ <zeroOrMore>
19
+ <choice>
20
+ <element name="message">
21
+ <ref name="riddl-description-other-atts"/>
22
+ <ref name="riddl-message"/>
23
+ </element>
24
+ <element name="transformation">
25
+ <ref name="riddl-description-other-atts"/>
26
+ <ref name="riddl-transformation"/>
27
+ </element>
28
+ </choice>
29
+ </zeroOrMore>
30
+ <element name="resource">
31
+ <ref name="riddl-resource-dynamic"/>
32
+ </element>
33
+ </interleave>
32
34
  </define>
33
35
 
34
36
  <define name="riddl-message">
@@ -199,7 +201,6 @@
199
201
  <data type="anyURI"/>
200
202
  </attribute>
201
203
  </optional>
202
- <ref name="riddl-description-others"/>
203
204
  </define>
204
205
 
205
206
  <define name="riddl-resource-recursive-choice">
@@ -222,21 +223,28 @@
222
223
  <data type="boolean"/>
223
224
  </attribute>
224
225
  </optional>
225
- <ref name="riddl-resource-sub"/>
226
+ </define>
227
+
228
+ <define name="riddl-resource-body">
229
+ <interleave>
230
+ <ref name="riddl-description-others"/>
231
+ <ref name="riddl-resource-content"/>
232
+ <ref name="riddl-resource-sub"/>
233
+ </interleave>
226
234
  </define>
227
235
 
228
236
  <define name="riddl-resource-dynamic">
229
237
  <ref name="riddl-resource-attributes"/>
230
- <ref name="riddl-resource-content"/>
231
238
  <ref name="riddl-resource-recursive-choice"/>
239
+ <ref name="riddl-resource-body"/>
232
240
  </define>
233
241
  <define name="riddl-resource-named">
234
242
  <ref name="riddl-resource-attributes"/>
235
243
  <attribute name="relative">
236
244
  <ref name="riddl-datatype-relpath"/>
237
245
  </attribute>
238
- <ref name="riddl-resource-content"/>
239
246
  <ref name="riddl-resource-recursive-choice"/>
247
+ <ref name="riddl-resource-body"/>
240
248
  </define>
241
249
 
242
250
  <define name="riddl-resource-sub">
@@ -1,72 +1,168 @@
1
1
  module Riddl
2
2
  module Protocols
3
- class SSE
3
+ module SSE
4
4
  class Error < RuntimeError; end
5
5
 
6
6
  class ParserData
7
7
  attr_accessor :headers, :request_path, :query_string, :http_method, :body, :request_url
8
8
  end
9
9
 
10
- class DeferrableBody
11
- include EventMachine::Deferrable
10
+ class Base #{{{
11
+ def initialize(app, env)
12
+ @app = app
13
+ @env = env
14
+ @closed = true
15
+ end
12
16
 
13
- def call(body)
14
- body.each do |chunk|
15
- @body_callback.call(chunk)
16
- end
17
- end
17
+ def send_with_id(id, data)
18
+ return if closed?
19
+ push("#{id}: #{data}" + EOL + EOL)
20
+ end
18
21
 
19
- def each(&blk)
20
- @body_callback = blk
21
- end
22
- end
22
+ def close
23
+ return if closed?
24
+ @closed = true
25
+ do_close
26
+ end
23
27
 
24
- def send_with_id(id, data)
25
- EM.next_tick do
26
- @body.call [ "#{id}: #{data}" + EOL + EOL ]
28
+ def closed?
29
+ @closed
27
30
  end
28
- end
29
31
 
30
- def close
31
- EM.next_tick do
32
- @body.succeed
32
+ def trigger_on_open; @closed = false; res = @app.onopen; res ? true : false; end
33
+ def trigger_on_close; @closed = true; @app.onclose; end
34
+
35
+ def response_headers(cross_site_xhr)
36
+ headers = {
37
+ 'Content-Type' => 'text/event-stream',
38
+ 'Cache-Control' => 'no-cache',
39
+ 'X-Accel-Buffering' => 'no'
40
+ }
41
+ if @env['HTTP_ORIGIN'] && cross_site_xhr
42
+ headers['Access-Control-Allow-Origin'] = '*'
43
+ headers['Access-Control-Max-Age'] = '0'
44
+ end
45
+ headers
33
46
  end
34
- end
47
+ protected :response_headers
48
+ end #}}}
35
49
 
36
- def trigger_on_open(); @closed = false; res = @app.onopen; res ? true : false; end
37
- def trigger_on_close; @closed = true; @app.onclose; end
50
+ class Thin < Base #{{{
51
+ class DeferrableBody #{{{
52
+ include EventMachine::Deferrable
38
53
 
39
- def initialize(app, env)
40
- @app = app
41
- @env = env
42
- @env['async.close'].callback { trigger_on_close }
43
- @body = DeferrableBody.new
44
- @closed = true
45
- end
54
+ def call(body)
55
+ body.each { |chunk| @body_callback.call(chunk) }
56
+ end
57
+
58
+ def each(&blk)
59
+ @body_callback = blk
60
+ end
61
+ end #}}}
62
+
63
+ def initialize(app, env)
64
+ super
65
+ @env['async.close'].callback { trigger_on_close }
66
+ @body = DeferrableBody.new
67
+ end
68
+
69
+ def dispatch(data, cross_site_xhr)
70
+ headers = response_headers(cross_site_xhr)
71
+ EventMachine::next_tick {
72
+ if trigger_on_open
73
+ @env['async.callback'].call [200, headers, @body]
74
+ else
75
+ @body.fail
76
+ @env['async.callback'].call [404, headers, {}]
77
+ end
78
+ }
79
+ nil
80
+ end
81
+
82
+ def push(str)
83
+ EM.next_tick { @body.call [str] }
84
+ end
85
+ protected :push
86
+
87
+ def do_close
88
+ EM.next_tick { @body.succeed }
89
+ end
90
+ protected :do_close
91
+ end #}}}
46
92
 
93
+ class Generic < Base # other servers like PUMA
94
+ class QueueBody #{{{
95
+ def initialize
96
+ @queue = Queue.new
97
+ @on_close = nil
98
+ @closed_fired = false
99
+ end
100
+
101
+ def on_close(&blk)
102
+ @on_close = blk
103
+ end
104
+
105
+ def call(chunk)
106
+ Array(chunk).each { |c| @queue << chunk_encode(c) }
107
+ end
47
108
 
48
- def dispatch(data,cross_site_xhr)
49
- headers = {
50
- 'Content-Type' => 'text/event-stream',
51
- 'Cache-Control' => 'no-cache',
52
- 'X-Accel-Buffering' => 'no'
53
- }
54
- if @env['HTTP_ORIGIN'] && cross_site_xhr
55
- headers['Access-Control-Allow-Origin'] = '*'
56
- headers['Access-Control-Max-Age'] = '0'
109
+ def each
110
+ loop do
111
+ chunk = @queue.pop
112
+ break if chunk.nil?
113
+ begin
114
+ yield chunk
115
+ rescue IOError, Errno::EPIPE, Errno::ECONNRESET
116
+ break
117
+ end
118
+ end
119
+ ensure
120
+ fire_on_close
121
+ end
122
+
123
+ def close
124
+ @queue << "0#{EOL}#{EOL}"
125
+ @queue << nil
126
+ end
127
+
128
+ def chunk_encode(c)
129
+ c.bytesize.to_s(16) + EOL + c + EOL
130
+ end
131
+ private :chunk_encode
132
+
133
+ def fire_on_close
134
+ return if @closed_fired
135
+ @closed_fired = true
136
+ @on_close.call if @on_close
137
+ end
138
+ private :fire_on_close
139
+ end #}}}
140
+
141
+ def initialize(app, env)
142
+ super
143
+ @body = QueueBody.new
144
+ @body.on_close { trigger_on_close }
57
145
  end
58
- EventMachine::next_tick {
146
+
147
+ def dispatch(data, cross_site_xhr)
148
+ headers = response_headers(cross_site_xhr).merge('Transfer-Encoding' => 'chunked')
59
149
  if trigger_on_open
60
- @env['async.callback'].call [200, headers, @body]
150
+ [200, headers, @body]
61
151
  else
62
- @body.fail
63
- @env['async.callback'].call [404, headers, {}]
152
+ @body.close
153
+ [404, headers, []]
64
154
  end
65
- }
66
- end
155
+ end
156
+
157
+ def push(str)
158
+ @body.call [str]
159
+ end
160
+ protected :push
67
161
 
68
- def closed?
69
- @closed
162
+ def do_close
163
+ @body.close
164
+ end
165
+ protected :do_close
70
166
  end
71
167
  end
72
168
  end
@@ -1,109 +1,44 @@
1
- gem 'em-websocket', '>= 0.4.0'
2
- require 'em-websocket'
3
- require 'thin'
4
-
5
- module Thin #{{{
6
- class Connection
7
- attr_accessor :websocket
8
- def websocket?
9
- !self.websocket.nil?
10
- end
11
-
12
- def pre_process_with_websocket
13
- @request.env['thin.connection'] = self
14
- pre_process_without_websocket
15
- end
16
- alias :pre_process_without_websocket :pre_process
17
- alias :pre_process :pre_process_with_websocket
18
-
19
- def receive_data_with_websocket(data)
20
- if self.websocket?
21
- self.websocket.receive_data(data)
22
- else
23
- receive_data_without_websocket(data)
24
- end
25
- end
26
- alias :receive_data_without_websocket :receive_data
27
- alias :receive_data :receive_data_with_websocket
28
-
29
- def unbind_with_websocket
30
- if self.websocket?
31
- self.websocket.unbind
32
- else
33
- unbind_without_websocket
34
- end
35
- end
36
- alias :unbind_without_websocket :unbind
37
- alias :unbind :unbind_with_websocket
38
- end
39
- end #}}}
40
-
41
- module EventMachine
42
- module WebSocket
43
- class Handshake
44
- def receive_data(data)
45
- data.request_url = Riddl::Protocols::Utils::escape(data.request_url)
46
- @parser = data
47
- @headers = data.headers
48
- process(@headers, data.body)
49
- end
50
- end
51
- end
52
- end
1
+ require 'faye/websocket'
2
+ Faye::WebSocket.load_adapter('thin')
53
3
 
54
4
  module Riddl
55
5
  module Protocols
56
- class WebSocket < ::EventMachine::WebSocket::Connection
6
+ class WebSocket
57
7
  class Error < RuntimeError; end
58
8
 
59
- class ParserData
60
- attr_accessor :headers, :request_path, :query_string, :http_method, :body, :request_url
61
- def match(what)
62
- @body =~ what
63
- end
64
- def upgrade?
65
- true
66
- end
67
- end
9
+ def initialize(app, env)
10
+ @app = app
11
+ @closed = true
12
+ @socket = Faye::WebSocket.new(env)
68
13
 
69
- def self.new(*args)
70
- instance = allocate
71
- instance.__send__(:initialize, *args)
72
- instance
14
+ @socket.on(:open) { trigger_on_open }
15
+ @socket.on(:message) { |event| trigger_on_message(event.data) }
16
+ @socket.on(:close) { trigger_on_close }
17
+ @socket.on(:error) { |event| trigger_on_error(event.message) }
73
18
  end
74
19
 
75
- def send_data(data)
76
- EM.next_tick do
77
- @socket.send_data(data) unless closed?
78
- end
20
+ def dispatch
21
+ @socket.rack_response
79
22
  end
80
23
 
81
- def close_connection(*args)
82
- EM.next_tick do
83
- unless closed?
84
- @socket.close_connection(*args)
85
- trigger_on_close
86
- end
87
- end
24
+ def send(data)
25
+ EM.next_tick { @socket.send(data) unless closed? }
88
26
  end
89
27
 
90
- def trigger_on_message(msg); @app.onmessage(msg); end
91
- def trigger_on_open(handshake); @closed = false; @app.onopen; end
92
- def trigger_on_close; @closed = true; @app.onclose; end
93
- def trigger_on_error(error); @closed = true; @app.onerror(error); true; end
94
-
95
- def initialize(app, socket)
96
- @app = app
97
- @socket = socket
98
- @ssl = socket.backend.respond_to?(:ssl?) && socket.backend.ssl?
99
- @closed = true
100
- socket.websocket = self
101
- socket.comm_inactivity_timeout = 0
28
+ def close_connection
29
+ EM.next_tick { @socket.close unless closed? }
102
30
  end
103
31
 
104
32
  def closed?
105
33
  @closed
106
34
  end
35
+
36
+ private
37
+
38
+ def trigger_on_open; @closed = false; @app.onopen; end
39
+ def trigger_on_message(m); @app.onmessage(m); end
40
+ def trigger_on_close; @closed = true; @app.onclose; end
41
+ def trigger_on_error(msg); @app.onerror(msg); end
107
42
  end
108
43
  end
109
44
  end
@@ -53,6 +53,7 @@ module Riddl
53
53
  :secure => false,
54
54
  :verbose => false,
55
55
  :http_only => false,
56
+ :server => 'thin',
56
57
  :runtime_opts => [
57
58
  ["--port [PORT]", "-p [PORT]", "Specify http port.", ->(p){
58
59
  @riddl_opts[:port] = p.to_i
@@ -102,12 +103,16 @@ module Riddl
102
103
  app.use Rack::CommonLogger, @riddl_logger
103
104
  end
104
105
 
106
+ if @riddl_opts[:secure] && @riddl_opts[:server].to_s != 'thin'
107
+ raise NotImplementedError, "secure (TLS) mode is not yet supported with the '#{@riddl_opts[:server]}' backend, only 'thin'"
108
+ end
109
+
105
110
  server = Rack::Server.new(
106
111
  :app => app,
107
112
  :Host => @riddl_opts[:bind],
108
113
  :Port => @riddl_opts[:port],
109
114
  :environment => @riddl_opts[:verbose] ? 'deployment' : 'none',
110
- :server => 'thin',
115
+ :server => @riddl_opts[:server],
111
116
  :signals => false
112
117
  )
113
118
  @riddl_opts[:startup].call if @riddl_opts[:startup]
@@ -115,46 +120,68 @@ module Riddl
115
120
  @riddl_opts[:custom_protocol] = @riddl_opts[:custom_protocol].new(@riddl_opts)
116
121
  puts @riddl_opts[:custom_protocol].support if @riddl_opts[:custom_protocol].support
117
122
  end
118
- begin
119
- EM.run do
120
- if @riddl_opts[:secure]
121
- server.start do |srv|
122
- srv.ssl = true
123
- srv.ssl_options = @riddl_opts[:secure_options]
123
+
124
+ if @riddl_opts[:server].to_s == 'thin'
125
+ begin
126
+ EM.run do
127
+ if @riddl_opts[:secure]
128
+ server.start do |srv|
129
+ srv.ssl = true
130
+ srv.ssl_options = @riddl_opts[:secure_options]
131
+ end
132
+ else
133
+ server.start
124
134
  end
125
- else
126
- server.start
127
- end
128
135
 
129
- if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
130
- @riddl_opts[:custom_protocol].start
131
- end
136
+ if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
137
+ @riddl_opts[:custom_protocol].start
138
+ end
132
139
 
133
- [:INT, :TERM].each do |signal|
134
- Signal.trap(signal) do
135
- if @riddl_opts[:cleanup]
136
- @riddl_opts[:cleanup].call
140
+ [:INT, :TERM].each do |signal|
141
+ Signal.trap(signal) do
142
+ if @riddl_opts[:cleanup]
143
+ @riddl_opts[:cleanup].call
144
+ end
145
+ EM.stop
137
146
  end
138
- EM.stop
139
147
  end
140
- end
141
- [:HUP].each do |signal|
142
- Signal.trap(signal) do
143
- EM.stop
148
+ [:HUP].each do |signal|
149
+ Signal.trap(signal) do
150
+ EM.stop
151
+ end
144
152
  end
145
- end
146
153
 
147
- if @riddl_opts[:parallel]
148
- EM.defer do
149
- @riddl_opts[:parallel].call
154
+ if @riddl_opts[:parallel]
155
+ EM.defer do
156
+ @riddl_opts[:parallel].call
157
+ end
150
158
  end
151
159
  end
160
+ rescue => e
161
+ if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
162
+ @riddl_opts[:custom_protocol].error_handling(e)
163
+ end
164
+ puts "Server (#{@riddl_opts[:cmdl_info]}) stopped due to connection error (PID:#{Process.pid})"
152
165
  end
153
- rescue => e
166
+ else # other servers like PUMA
167
+ Faye::WebSocket.ensure_reactor_running
168
+
154
169
  if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
155
- @riddl_opts[:custom_protocol].error_handling(e)
170
+ @riddl_opts[:custom_protocol].start
171
+ end
172
+
173
+ Thread.new { @riddl_opts[:parallel].call } if @riddl_opts[:parallel]
174
+
175
+ begin
176
+ server.start do |launcher|
177
+ launcher.events.after_stopped { @riddl_opts[:cleanup].call if @riddl_opts[:cleanup] }
178
+ end
179
+ rescue => e
180
+ if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
181
+ @riddl_opts[:custom_protocol].error_handling(e)
182
+ end
183
+ puts "Server (#{@riddl_opts[:cmdl_info]}) stopped due to connection error (PID:#{Process.pid})"
156
184
  end
157
- puts "Server (#{@riddl_opts[:cmdl_info]}) stopped due to connection error (PID:#{Process.pid})"
158
185
  end
159
186
  end #}}}
160
187
 
@@ -237,6 +264,7 @@ module Riddl
237
264
  @riddl_log = @riddl_logger || @riddl_env['rack.errors']
238
265
  @riddl_res = Rack::Response.new
239
266
  @riddl_status = 404
267
+ @riddl_async_response = nil
240
268
 
241
269
  @riddl_pinfo = Riddl::Protocols::Utils::unescape(@riddl_env["PATH_INFO"].gsub(/\/+/,'/'))
242
270
  @riddl_matching_path = @riddl_paths.find{ |e| @riddl_pinfo.match(e[1]).to_s.length == @riddl_pinfo.length }
@@ -294,7 +322,7 @@ module Riddl
294
322
  end
295
323
  end
296
324
  end
297
- throw :async
325
+ throw :async if @riddl_opts[:server].to_s == 'thin'
298
326
  elsif @riddl_info[:env]['HTTP_ACCEPT'] == 'text/event-stream'
299
327
  @riddl_info[:m] = @riddl_method = 'sse'
300
328
  @riddl_message = @riddl.io_messages(@riddl_matching_path[0],'sse',@riddl_parameters,@riddl_headers)
@@ -313,7 +341,7 @@ module Riddl
313
341
  end
314
342
  end
315
343
  end
316
- throw :async
344
+ throw :async if @riddl_opts[:server].to_s == 'thin'
317
345
  else
318
346
  __call
319
347
  end
@@ -321,6 +349,7 @@ module Riddl
321
349
  @riddl_log.write "404: this resource for sure does not exist.\n"
322
350
  @riddl_status = 404 # client requests wrong path
323
351
  end
352
+ return @riddl_async_response if @riddl_async_response
324
353
  if @riddl_exe
325
354
  parts = Protocols::HTTP::Generator.new(@riddl_exe.response,@riddl_res).generate
326
355
  @riddl_exe.headers.each do |n,h|
@@ -382,22 +411,14 @@ module Riddl
382
411
  @riddl_headers.map { |key, value| [key.downcase.gsub('_','-'), value] }
383
412
  ]
384
413
  w = what.new(@riddl_info.merge!(:a => args, :match => matching_path))
385
- w.io = Riddl::Protocols::SSE.new(w, @riddl_env)
386
- w.io.dispatch(data, @riddl_cross_site_xhr)
414
+ sse = @riddl_opts[:server].to_s == 'thin' ? Riddl::Protocols::SSE::Thin : Riddl::Protocols::SSE::Generic
415
+ w.io = sse.new(w, @riddl_env)
416
+ @riddl_async_response = w.io.dispatch(data, @riddl_cross_site_xhr)
387
417
  end
388
418
  if what.class == Class && what.superclass == Riddl::WebSocketImplementation
389
- data = Riddl::Protocols::WebSocket::ParserData.new
390
- data.request_path = @riddl_pinfo
391
- data.request_url = @riddl_pinfo + '?' + @riddl_query_string
392
- data.query_string = @riddl_query_string
393
- data.http_method = @riddl_env['REQUEST_METHOD']
394
- data.body = @riddl_env['rack.input'].read
395
- data.headers = Hash[
396
- @riddl_headers.map { |key, value| [key.downcase.gsub('_','-'), value] }
397
- ]
398
- w = what.new(@riddl_info.merge!(:a => args, :version => @riddl_env['HTTP_SEC_WEBSOCKET_VERSION'], :match => matching_path))
399
- w.io = Riddl::Protocols::WebSocket.new(w, @riddl_env['thin.connection'])
400
- w.io.dispatch(data)
419
+ w = what.new(@riddl_info.merge!(:a => args, :match => matching_path))
420
+ w.io = Riddl::Protocols::WebSocket.new(w, @riddl_env)
421
+ @riddl_async_response = w.io.dispatch
401
422
  end
402
423
  if what.class == Class && what.superclass == Riddl::Implementation
403
424
  w = what.new(@riddl_info.merge!(:a => args, :match => matching_path))
@@ -90,7 +90,8 @@ module Riddl
90
90
  xml = Typhoeus.get(name, ssl_verifypeer: false).response_body
91
91
  end
92
92
  @doc = XML::Smart.string(xml)
93
- rescue
93
+ rescue => e
94
+ raise SpecificationError, "#{name.inspect} is not well-formed XML: #{e.message}" if e.is_a?(Nokogiri::XML::SyntaxError) && !xml.to_s.strip.empty?
94
95
  begin
95
96
  @doc = XML::Smart.string(name)
96
97
  rescue
@@ -99,11 +100,12 @@ module Riddl
99
100
  end
100
101
  end
101
102
 
102
- @doc.register_namespace 'x', XINCLUDE
103
- @doc.find('//x:include/@href').each do |i|
104
- if i.value =~ /^http:\/\/(www\.)?riddl\.org(\/ns\/common-patterns\/.*)/
105
- t = File.expand_path(File.dirname(__FILE__)) + $2
106
- i.value = t if File.exist?(t)
103
+ if @doc.register_namespace 'x', XINCLUDE
104
+ @doc.find('//x:include/@href').each do |i|
105
+ if i.value =~ /^http:\/\/(www\.)?riddl\.org(\/ns\/common-patterns\/.*)/
106
+ t = File.expand_path(File.dirname(__FILE__)) + $2
107
+ i.value = t if File.exist?(t)
108
+ end
107
109
  end
108
110
  end
109
111
  fpath.nil? ? @doc.xinclude! : @doc.xinclude!(fpath)
@@ -119,13 +121,19 @@ module Riddl
119
121
  rrds = XML::Smart::open_unprotected(RIDDL_DESCRIPTION_RESOURCE_SHOW)
120
122
  rds.register_namespace('des','http://riddl.org/ns/description/1.0')
121
123
  rrds.register_namespace('des','http://riddl.org/ns/description/1.0')
122
- @doc.root.prepend rds.find('/des:description/des:message')
123
- @doc.root.prepend rrds.find('/des:description/des:message')
124
+ prepend = lambda do |node,what,allowed|
125
+ first = node.find(allowed.map{ |e| "des:#{e}" }.join('|')).first
126
+ first ? first.add_before(what) : node.add(what)
127
+ end
128
+ des_allowed = %w{message transformation resource}
129
+ res_allowed = %w{post get put patch delete request websocket sse resource}
130
+ prepend.call @doc.root, rds.find('/des:description/des:message'), des_allowed
131
+ prepend.call @doc.root, rrds.find('/des:description/des:message'), des_allowed
124
132
  @doc.find("/des:description/des:resource").each do |r|
125
- r.prepend rds.find('/des:description/des:resource/*')
133
+ prepend.call r, rds.find('/des:description/des:resource/*'), res_allowed
126
134
  end
127
135
  @doc.find("/des:description//des:resource").each do |r|
128
- r.prepend rrds.find('/des:description/des:resource/*')
136
+ prepend.call r, rrds.find('/des:description/des:resource/*'), res_allowed
129
137
  end
130
138
  end
131
139
  if @is_declaration && get_description
@@ -247,13 +255,13 @@ module Riddl
247
255
  def validate!
248
256
  #{{{
249
257
  if @is_description
250
- xval = @doc.validate_against(XML::Smart.open_unprotected(DESCRIPTION_FILE))
258
+ xval = @doc.validate_against(XML::Smart.open_unprotected(DESCRIPTION_FILE)) { |err| puts err }
251
259
  xchk = ResourceChecker.new(@doc).check
252
260
  puts xchk.join("\n") unless xchk.empty?
253
261
  return xval && xchk.empty?
254
262
  end
255
263
  if @is_declaration
256
- xval = @doc.validate_against(XML::Smart.open_unprotected(DECLARATION_FILE))
264
+ xval = @doc.validate_against(XML::Smart.open_unprotected(DECLARATION_FILE)) { |err| puts err }
257
265
  xchk = LayerChecker.new(@doc).check
258
266
  puts xchk.join("\n") unless xchk.empty?
259
267
  return xval && xchk.empty?
@@ -14,21 +14,23 @@
14
14
  <data type="anyURI"/>
15
15
  </attribute>
16
16
  <ref name="riddl-description-other-atts"/>
17
- <zeroOrMore>
18
- <choice>
19
- <element name="message">
20
- <ref name="riddl-description-other-atts"/>
21
- <ref name="riddl-message"/>
22
- </element>
23
- <element name="transformation">
24
- <ref name="riddl-description-other-atts"/>
25
- <ref name="riddl-transformation"/>
26
- </element>
27
- </choice>
28
- </zeroOrMore>
29
- <element name="resource">
30
- <ref name="riddl-resource-dynamic"/>
31
- </element>
17
+ <interleave>
18
+ <zeroOrMore>
19
+ <choice>
20
+ <element name="message">
21
+ <ref name="riddl-description-other-atts"/>
22
+ <ref name="riddl-message"/>
23
+ </element>
24
+ <element name="transformation">
25
+ <ref name="riddl-description-other-atts"/>
26
+ <ref name="riddl-transformation"/>
27
+ </element>
28
+ </choice>
29
+ </zeroOrMore>
30
+ <element name="resource">
31
+ <ref name="riddl-resource-dynamic"/>
32
+ </element>
33
+ </interleave>
32
34
  </define>
33
35
 
34
36
  <define name="riddl-message">
@@ -199,7 +201,6 @@
199
201
  <data type="anyURI"/>
200
202
  </attribute>
201
203
  </optional>
202
- <ref name="riddl-description-others"/>
203
204
  </define>
204
205
 
205
206
  <define name="riddl-resource-recursive-choice">
@@ -222,21 +223,28 @@
222
223
  <data type="boolean"/>
223
224
  </attribute>
224
225
  </optional>
225
- <ref name="riddl-resource-sub"/>
226
+ </define>
227
+
228
+ <define name="riddl-resource-body">
229
+ <interleave>
230
+ <ref name="riddl-description-others"/>
231
+ <ref name="riddl-resource-content"/>
232
+ <ref name="riddl-resource-sub"/>
233
+ </interleave>
226
234
  </define>
227
235
 
228
236
  <define name="riddl-resource-dynamic">
229
237
  <ref name="riddl-resource-attributes"/>
230
- <ref name="riddl-resource-content"/>
231
238
  <ref name="riddl-resource-recursive-choice"/>
239
+ <ref name="riddl-resource-body"/>
232
240
  </define>
233
241
  <define name="riddl-resource-named">
234
242
  <ref name="riddl-resource-attributes"/>
235
243
  <attribute name="relative">
236
244
  <ref name="riddl-datatype-relpath"/>
237
245
  </attribute>
238
- <ref name="riddl-resource-content"/>
239
246
  <ref name="riddl-resource-recursive-choice"/>
247
+ <ref name="riddl-resource-body"/>
240
248
  </define>
241
249
 
242
250
  <define name="riddl-resource-sub">
data/riddl.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "riddl"
3
- s.version = "1.0.17"
3
+ s.version = "1.0.18"
4
4
  s.platform = Gem::Platform::RUBY
5
5
  s.license = "LGPL-3.0-or-later"
6
6
  s.summary = "Restful Interface Description and Declaration Language: tools and client/server libs"
@@ -31,8 +31,8 @@ Gem::Specification.new do |s|
31
31
  s.add_development_dependency 'rdf-smart', '>=0.0.160', '~>0'
32
32
  s.add_runtime_dependency 'rack', '~>2.2'
33
33
  s.add_runtime_dependency 'thin', '~>1.6'
34
+ s.add_runtime_dependency 'puma', '~>8.0'
34
35
  s.add_runtime_dependency 'eventmachine', '>= 1.0.0', '~>1.0'
35
- s.add_runtime_dependency 'em-websocket', '>= 0.5.2', '~>0'
36
36
  s.add_runtime_dependency 'faye-websocket', '>= 0.1', '~>0'
37
37
  s.add_runtime_dependency 'mime-types', '>= 3.0', '~>3'
38
38
  s.add_runtime_dependency 'minitest', '>= 5.0.0', '~>5'
data/test/tc_sse.rb ADDED
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/smartrunner.rb')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/ruby/riddl/client')
3
+ require 'net/http'
4
+ require 'timeout'
5
+
6
+ module SSEScenario
7
+ def test_sse
8
+ info = self.class::SERVER[0]
9
+ ep = Riddl::Client.interface(info.url, info.schema)
10
+
11
+ test = ep.resource('/')
12
+ status, res = test.get
13
+ assert status == 200
14
+ assert res.length == 1
15
+ assert res[0].mimetype == 'text/plain'
16
+ assert res[0].value.read == 'hello world'
17
+
18
+ uri = URI(info.url)
19
+ events = Queue.new
20
+ opened = Queue.new
21
+
22
+ reader = Thread.new do
23
+ begin
24
+ Net::HTTP.start(uri.host, info.port) do |http|
25
+ req = Net::HTTP::Get.new('/', { 'Accept' => 'text/event-stream' })
26
+ http.request(req) do |response|
27
+ opened << response.code
28
+ buffer = +''
29
+ response.read_body do |chunk|
30
+ buffer << chunk
31
+ while (idx = buffer.index("\r\n\r\n"))
32
+ events << buffer.slice!(0..idx + 3)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ rescue IOError, Errno::ECONNRESET
38
+ # expected once the reader thread is killed below
39
+ end
40
+ end
41
+
42
+ assert Timeout.timeout(5) { opened.pop } == '200'
43
+
44
+ # a plain GET on the same server broadcasts to every open SSE connection
45
+ # (examples/sse/server.rb's Bar#response) -- deterministic, no need to
46
+ # wait on the 15s heartbeat from the `parallel` block.
47
+ status, _res = test.get
48
+ assert status == 200
49
+
50
+ event = Timeout.timeout(5) { events.pop }
51
+ assert event == "data: some data\r\n\r\n"
52
+
53
+ reader.kill
54
+ reader.join
55
+ end
56
+ end
57
+
58
+ class TestSSEThin < Minitest::Test
59
+ include ServerCase
60
+ include SSEScenario
61
+
62
+ SERVER = [
63
+ TestServerInfo.new(
64
+ File.expand_path(File.dirname(__FILE__) + '/../examples/sse/server.rb') + ' -o server=thin',
65
+ File.expand_path(File.dirname(__FILE__) + '/../examples/sse/description.xml')
66
+ )
67
+ ]
68
+ NORUN = false
69
+ end
70
+
71
+ class TestSSEPuma < Minitest::Test
72
+ include ServerCase
73
+ include SSEScenario
74
+
75
+ SERVER = [
76
+ TestServerInfo.new(
77
+ File.expand_path(File.dirname(__FILE__) + '/../examples/sse/server.rb') + ' -o server=puma',
78
+ File.expand_path(File.dirname(__FILE__) + '/../examples/sse/description.xml')
79
+ )
80
+ ]
81
+ NORUN = false
82
+ end
data/test/tc_websocket.rb CHANGED
@@ -1,21 +1,11 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/smartrunner.rb')
2
2
  require File.expand_path(File.dirname(__FILE__) + '/../lib/ruby/riddl/client')
3
3
  require 'xml/smart'
4
- require 'pp'
5
-
6
- class TestWebsocket < Minitest::Test
7
- include ServerCase
8
-
9
- SERVER = [
10
- TestServerInfo.new(
11
- File.expand_path(File.dirname(__FILE__) + '/../examples/websocket/server.rb'),
12
- File.expand_path(File.dirname(__FILE__) + '/../examples/websocket/description.xml')
13
- )
14
- ]
15
- NORUN = false
16
4
 
5
+ module WebSocketScenario
17
6
  def test_websocket
18
- ep = Riddl::Client.interface(SERVER[0].url,SERVER[0].schema)
7
+ info = self.class::SERVER[0]
8
+ ep = Riddl::Client.interface(info.url, info.schema)
19
9
 
20
10
  test = ep.resource('/')
21
11
  status, res = test.get
@@ -61,3 +51,29 @@ class TestWebsocket < Minitest::Test
61
51
 
62
52
  end
63
53
  end
54
+
55
+ class TestWebsocketThin < Minitest::Test
56
+ include ServerCase
57
+ include WebSocketScenario
58
+
59
+ SERVER = [
60
+ TestServerInfo.new(
61
+ File.expand_path(File.dirname(__FILE__) + '/../examples/websocket/server.rb') + ' -o server=thin',
62
+ File.expand_path(File.dirname(__FILE__) + '/../examples/websocket/description.xml')
63
+ )
64
+ ]
65
+ NORUN = false
66
+ end
67
+
68
+ class TestWebsocketPuma < Minitest::Test
69
+ include ServerCase
70
+ include WebSocketScenario
71
+
72
+ SERVER = [
73
+ TestServerInfo.new(
74
+ File.expand_path(File.dirname(__FILE__) + '/../examples/websocket/server.rb') + ' -o server=puma',
75
+ File.expand_path(File.dirname(__FILE__) + '/../examples/websocket/description.xml')
76
+ )
77
+ ]
78
+ NORUN = false
79
+ end
data/tools/riddlcheck CHANGED
@@ -21,7 +21,12 @@ if ARGV.length == 0 || !File.exist?(ARGV[0])
21
21
  end
22
22
  fname = ARGV[0]
23
23
 
24
- riddl = Riddl::Wrapper::new(fname)
24
+ riddl = begin
25
+ Riddl::Wrapper::new(fname)
26
+ rescue Riddl::SpecificationError => e
27
+ puts e.message
28
+ exit 1
29
+ end
25
30
 
26
31
  if riddl.description?
27
32
  puts 'RIDDL description found.'
@@ -29,8 +34,9 @@ elsif riddl.declaration?
29
34
  puts 'RIDDL declaration found.'
30
35
  else
31
36
  puts 'Neither a RIDDL description, nor a RIDDL declaration.'
32
- exit
37
+ exit 1
33
38
  end
34
39
 
35
40
  messages = riddl.validate!
36
41
  puts messages ? "RIDDL looks valid." : "RIDDL not valid."
42
+ exit 1 unless messages
data/tools/riddlprocess CHANGED
@@ -27,7 +27,12 @@ if ARGV.length == 0 || !File.exist?(ARGV[0]) || dowhat.nil?
27
27
  end
28
28
  fname = ARGV[0]
29
29
 
30
- riddl = Riddl::Wrapper.new(fname)
30
+ riddl = begin
31
+ Riddl::Wrapper.new(fname)
32
+ rescue Riddl::SpecificationError => e
33
+ puts e.message
34
+ exit 1
35
+ end
31
36
 
32
37
  unless riddl.declaration?
33
38
  puts 'Not a RIDDL declaration.'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: riddl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.17
4
+ version: 1.0.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Juergen 'eTM' Mangler
@@ -115,45 +115,39 @@ dependencies:
115
115
  - !ruby/object:Gem::Version
116
116
  version: '1.6'
117
117
  - !ruby/object:Gem::Dependency
118
- name: eventmachine
118
+ name: puma
119
119
  requirement: !ruby/object:Gem::Requirement
120
120
  requirements:
121
- - - ">="
122
- - !ruby/object:Gem::Version
123
- version: 1.0.0
124
121
  - - "~>"
125
122
  - !ruby/object:Gem::Version
126
- version: '1.0'
123
+ version: '8.0'
127
124
  type: :runtime
128
125
  prerelease: false
129
126
  version_requirements: !ruby/object:Gem::Requirement
130
127
  requirements:
131
- - - ">="
132
- - !ruby/object:Gem::Version
133
- version: 1.0.0
134
128
  - - "~>"
135
129
  - !ruby/object:Gem::Version
136
- version: '1.0'
130
+ version: '8.0'
137
131
  - !ruby/object:Gem::Dependency
138
- name: em-websocket
132
+ name: eventmachine
139
133
  requirement: !ruby/object:Gem::Requirement
140
134
  requirements:
141
135
  - - ">="
142
136
  - !ruby/object:Gem::Version
143
- version: 0.5.2
137
+ version: 1.0.0
144
138
  - - "~>"
145
139
  - !ruby/object:Gem::Version
146
- version: '0'
140
+ version: '1.0'
147
141
  type: :runtime
148
142
  prerelease: false
149
143
  version_requirements: !ruby/object:Gem::Requirement
150
144
  requirements:
151
145
  - - ">="
152
146
  - !ruby/object:Gem::Version
153
- version: 0.5.2
147
+ version: 1.0.0
154
148
  - - "~>"
155
149
  - !ruby/object:Gem::Version
156
- version: '0'
150
+ version: '1.0'
157
151
  - !ruby/object:Gem::Dependency
158
152
  name: faye-websocket
159
153
  requirement: !ruby/object:Gem::Requirement
@@ -325,7 +319,6 @@ files:
325
319
  - examples/declaration-test/declaration2.xml
326
320
  - examples/declaration-test/goal/goal.txt
327
321
  - examples/declaration-test/goal/goal.xml
328
- - examples/declaration-test/main.log
329
322
  - examples/declaration-test/main.rb
330
323
  - examples/declaration-test/main.xml
331
324
  - examples/declaration-test/over.xml
@@ -531,6 +524,7 @@ files:
531
524
  - test/tc_library.rb
532
525
  - test/tc_producer.rb
533
526
  - test/tc_properties.rb
527
+ - test/tc_sse.rb
534
528
  - test/tc_websocket.rb
535
529
  - tools/flash-policy-server.rb
536
530
  - tools/riddlcheck
@@ -539,7 +533,7 @@ homepage: http://github.com/etm/riddl/
539
533
  licenses:
540
534
  - LGPL-3.0-or-later
541
535
  metadata:
542
- documentation_uri: https://gemdocs.org/gems/riddl/1.0.17/
536
+ documentation_uri: https://gemdocs.org/gems/riddl/1.0.18/
543
537
  rdoc_options: []
544
538
  require_paths:
545
539
  - lib/ruby
@@ -567,4 +561,5 @@ test_files:
567
561
  - test/tc_library.rb
568
562
  - test/tc_producer.rb
569
563
  - test/tc_properties.rb
564
+ - test/tc_sse.rb
570
565
  - test/tc_websocket.rb
@@ -1,57 +0,0 @@
1
- # Logfile created on 2013-05-21 22:59:18 +0200 by logger.rb/31641
2
- Main: 127.0.0.1 - - [21/May/2013 23:04:19] "GET / HTTP/1.1" 200 - 0.0376
3
- Main: 127.0.0.1 - - [21/May/2013 23:04:19] "GET /favicon.ico HTTP/1.1" 404 - 0.0001
4
- Main: 127.0.0.1 - - [21/May/2013 23:04:19] "GET /favicon.ico HTTP/1.1" 404 - 0.0001
5
- Main: 127.0.0.1 - - [21/May/2013 23:04:45] "GET /?a=1 HTTP/1.1" 200 - 0.0321
6
- Main: 127.0.0.1 - - [21/May/2013 23:04:49] "GET /?b=1 HTTP/1.1" 200 - 0.0056
7
- Main: 127.0.0.1 - - [21/May/2013 23:04:52] "GET /?b=2 HTTP/1.1" 200 - 0.0055
8
- Main: 127.0.0.1 - - [21/May/2013 23:04:57] "GET /?c=2 HTTP/1.1" 200 - 0.0056
9
- Main: 127.0.0.1 - - [21/May/2013 23:05:00] "GET /?d=2 HTTP/1.1" 200 - 0.0053
10
- Main: 127.0.0.1 - - [21/May/2013 23:05:08] "GET /?e=2 HTTP/1.1" 200 - 0.0052
11
- Main: 127.0.0.1 - - [21/May/2013 23:07:26] "GET /?a=2 HTTP/1.1" 200 - 0.0510
12
- Main: 127.0.0.1 - - [21/May/2013 23:08:22] "GET /?a=2 HTTP/1.1" 200 - 0.0334
13
- Main: 127.0.0.1 - - [21/May/2013 23:08:50] "GET /?a=2 HTTP/1.1" 200 - 0.0322
14
- Main: 127.0.0.1 - - [21/May/2013 23:09:14] "GET /?a=2 HTTP/1.1" 200 - 0.0324
15
- Main: 127.0.0.1 - - [21/May/2013 23:09:49] "GET /?a=2 HTTP/1.1" 200 - 0.0321
16
- Main: 127.0.0.1 - - [21/May/2013 23:48:23] "GET /?a=2 HTTP/1.1" 200 - 0.0485
17
- Main: 127.0.0.1 - - [21/May/2013 23:51:30] "GET /?a=2 HTTP/1.1" 200 - 0.0326
18
- Main: 127.0.0.1 - - [21/May/2013 23:52:03] "GET /?a=2 HTTP/1.1" 200 - 0.0311
19
- Main: 127.0.0.1 - - [21/May/2013 23:53:37] "GET /?a=2 HTTP/1.1" 200 - 0.0337
20
- Main: 127.0.0.1 - - [21/May/2013 23:55:32] "GET /?a=2 HTTP/1.1" 200 - 0.0316
21
- 127.0.0.1 - - [21/May/2013 23:55:32] "GET /?a=2 HTTP/1.1" 200 1 0.0321
22
- Main: 127.0.0.1 - - [21/May/2013 23:55:49] "GET /?a=2 HTTP/1.1" 200 - 0.0324
23
- Main: 127.0.0.1 - - [21/May/2013 23:59:45] "GET /?a=2 HTTP/1.1" 200 - 0.0114
24
- Main: 127.0.0.1 - - [22/May/2013 00:00:23] "GET /?a=2 HTTP/1.1" 200 - 0.0114
25
- Main: 127.0.0.1 - - [22/May/2013 00:00:31] "GET /?a=2 HTTP/1.1" 200 - 0.0074
26
- Main: 127.0.0.1 - - [22/May/2013 00:01:13] "GET /?a=2 HTTP/1.1" 200 - 0.0316
27
- Main: 127.0.0.1 - - [22/May/2013 00:02:24] "GET /?a=2 HTTP/1.1" 200 - 0.0114
28
- Main: 127.0.0.1 - - [22/May/2013 00:02:59] "GET /?a=2 HTTP/1.1" 200 - 0.0156
29
- Main: 127.0.0.1 - - [22/May/2013 00:14:10] "GET /?a=2 HTTP/1.1" 200 - 0.0112
30
- Main: 127.0.0.1 - - [22/May/2013 00:14:11] "GET /?a=2 HTTP/1.1" 200 - 0.0062
31
- Main: 127.0.0.1 - - [22/May/2013 00:18:11] "GET /?a=2 HTTP/1.1" 200 - 0.0115
32
- Main: 127.0.0.1 - - [22/May/2013 00:18:14] "GET /?a=2 HTTP/1.1" 200 - 0.0054
33
- Main: 127.0.0.1 - - [22/May/2013 00:19:07] "GET /?a=2 HTTP/1.1" 200 - 0.0368
34
- Main: 127.0.0.1 - - [22/May/2013 00:19:31] "GET /?a=2 HTTP/1.1" 200 - 0.0321
35
- Main: 127.0.0.1 - - [22/May/2013 00:19:34] "GET /?a=2 HTTP/1.1" 200 - 0.0104
36
- Main: 127.0.0.1 - - [22/May/2013 00:20:01] "GET /?a=2 HTTP/1.1" 200 - 0.0323
37
- Main: 127.0.0.1 - - [22/May/2013 00:20:01] "GET /?a=2 HTTP/1.1" 200 - 0.0094
38
- Main: 127.0.0.1 - - [22/May/2013 00:20:01] "GET /?a=2 HTTP/1.1" 200 - 0.0051
39
- Main: 127.0.0.1 - - [22/May/2013 00:21:14] "GET /?a=2 HTTP/1.1" 200 - 0.0485
40
- Main: 127.0.0.1 - - [22/May/2013 00:21:15] "GET /?a=2 HTTP/1.1" 200 - 0.0064
41
- Main: 127.0.0.1 - - [22/May/2013 00:21:15] "GET /?a=2 HTTP/1.1" 200 - 0.0094
42
- Main: 127.0.0.1 - - [22/May/2013 00:22:12] "GET /?a=2 HTTP/1.1" 200 - 0.0327
43
- Main: 127.0.0.1 - - [22/May/2013 00:22:36] "GET /?a=2 HTTP/1.1" 200 - 0.0316
44
- Main: 127.0.0.1 - - [22/May/2013 00:22:53] "GET /?a=2 HTTP/1.1" 200 - 0.0480
45
- 127.0.0.1 - - [22/May/2013 00:22:53] "GET /?a=2 HTTP/1.1" 200 1 0.0488
46
- Main::: 127.0.0.1 - - [22/May/2013 00:26:52] "GET /?a=2 HTTP/1.1" 200 1 0.0332
47
- Main::: 127.0.0.1 - - [22/May/2013 00:27:16] "GET / HTTP/1.1" 200 - 0.0326
48
- Main::: 127.0.0.1 - - [22/May/2013 00:27:21] "GET / HTTP/1.1" 200 - 0.0054
49
- Main::: 127.0.0.1 - - [22/May/2013 00:28:05] "GET /?a=1&b=2 HTTP/1.1" 200 7 0.0396
50
- Main::: 127.0.0.1 - - [22/May/2013 00:28:35] "GET /?a=1&b=2 HTTP/1.1" 501 - 0.0334
51
- Main::: 501: the get parameters are not matching anything in the description.Main::: 127.0.0.1 - - [22/May/2013 00:31:19] "GET /?a=1&b=2 HTTP/1.1" 501 - 0.0347
52
- Main::: 501: the get parameters are not matching anything in the description.
53
- Main::: 127.0.0.1 - - [22/May/2013 00:32:10] "GET /?a=1&b=2 HTTP/1.1" 501 - 0.0321
54
- Main::: 501: the get parameters are not matching anything in the description.
55
- Main::: 127.0.0.1 - - [22/May/2013 00:37:50] "GET /?a=1&b=2 HTTP/1.1" 501 - 0.0328
56
- Main: 127.0.0.1 - - [22/May/2013 00:38:45] "GET /?a=1&b=2 HTTP/1.1" 200 7 0.0331
57
- Main: 127.0.0.1 - - [22/May/2013 00:38:49] "GET /?a=1 HTTP/1.1" 200 1 0.0067