rupnp 0.2.2 → 0.3.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.
- data/lib/rupnp.rb +1 -2
- data/lib/rupnp/control_point.rb +1 -7
- data/lib/rupnp/cp/event_server.rb +89 -29
- data/lib/rupnp/cp/remote_service.rb +328 -257
- data/lib/rupnp/event.rb +4 -0
- data/spec/cp/event_server_spec.rb +140 -0
- data/spec/cp/remote_service_spec.rb +15 -3
- data/spec/spec_helper.rb +40 -0
- metadata +3 -3
- data/lib/rupnp/cp/event_subscriber.rb +0 -47
data/lib/rupnp.rb
CHANGED
@@ -6,7 +6,7 @@ require 'eventmachine-le'
|
|
6
6
|
module RUPNP
|
7
7
|
|
8
8
|
# RUPNP version
|
9
|
-
VERSION = '0.
|
9
|
+
VERSION = '0.3.0'
|
10
10
|
|
11
11
|
@logdev = STDERR
|
12
12
|
@log_level = :info
|
@@ -57,7 +57,6 @@ require_relative 'rupnp/cp/base'
|
|
57
57
|
require_relative 'rupnp/cp/remote_service'
|
58
58
|
require_relative 'rupnp/cp/remote_device'
|
59
59
|
require_relative 'rupnp/cp/event_server'
|
60
|
-
require_relative 'rupnp/cp/event_subscriber'
|
61
60
|
require_relative 'rupnp/ssdp'
|
62
61
|
|
63
62
|
require_relative 'rupnp/device'
|
data/lib/rupnp/control_point.rb
CHANGED
@@ -24,10 +24,6 @@ module RUPNP
|
|
24
24
|
# Get event listening port
|
25
25
|
# @return [Integer]
|
26
26
|
attr_reader :event_port
|
27
|
-
# Return channel to add event URL (URL to listen for a specific
|
28
|
-
# event)
|
29
|
-
# @return [EM::Channel]
|
30
|
-
attr_reader :add_event_url
|
31
27
|
# Return remote devices controlled by this control point
|
32
28
|
# @return [Array<CP::RemoteDevice>]
|
33
29
|
attr_reader :devices
|
@@ -48,7 +44,6 @@ module RUPNP
|
|
48
44
|
@devices = []
|
49
45
|
@new_device_channel = EM::Channel.new
|
50
46
|
@bye_device_channel = EM::Channel.new
|
51
|
-
@add_event_url = EM::Channel.new
|
52
47
|
end
|
53
48
|
|
54
49
|
# Start control point.
|
@@ -79,8 +74,7 @@ module RUPNP
|
|
79
74
|
# @return [void]
|
80
75
|
def start_event_server(port=EVENT_SUB_DEFAULT_PORT)
|
81
76
|
@event_port ||= port
|
82
|
-
@event_server ||= EM.start_server('0.0.0.0', port, CP::EventServer
|
83
|
-
@add_event_url)
|
77
|
+
@event_server ||= EM.start_server('0.0.0.0', port, CP::EventServer)
|
84
78
|
end
|
85
79
|
|
86
80
|
# Stop event server
|
@@ -7,44 +7,104 @@ module RUPNP
|
|
7
7
|
class CP::EventServer < EM::HttpServer::Server
|
8
8
|
include LogMixin
|
9
9
|
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
# @private
|
11
|
+
@@add_event = EM::Channel.new
|
12
|
+
# @private
|
13
|
+
@@events = []
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
@@add_event.subscribe do |url|
|
16
|
+
@@events << url
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
class << self
|
20
|
+
# Channel to add url for listening to
|
21
|
+
# @param [Event] event
|
22
|
+
# @return [void]
|
23
|
+
def add_event(event)
|
24
|
+
@@add_event << event
|
25
|
+
end
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
# Remove an event
|
28
|
+
# @param [Event] event
|
29
|
+
# @return [void]
|
30
|
+
def remove_event(event)
|
31
|
+
@@events.delete_if { |e| e == event }
|
25
32
|
end
|
26
33
|
end
|
27
34
|
|
35
|
+
|
36
|
+
# Channel to return received updated variables from events
|
37
|
+
# @return [EM::Channel]
|
38
|
+
attr_reader :events
|
39
|
+
|
40
|
+
|
28
41
|
# Process a HTTP request received from a service/device
|
29
42
|
def process_http_request
|
30
|
-
log :debug,
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
log :debug, "EventServer: receive request: #@http"
|
44
|
+
|
45
|
+
response = EM::DelegatedHttpResponse.new(self)
|
46
|
+
|
47
|
+
if @http_request_method != 'NOTIFY'
|
48
|
+
log :info, "EventServer: method #@http_request_method not allowed"
|
49
|
+
response.status = 405
|
50
|
+
response.headers['Allow'] = 'NOTIFY'
|
51
|
+
response.send_response
|
52
|
+
return
|
53
|
+
end
|
54
|
+
|
55
|
+
event = @@events.find { |e| e.callback_url == @http_request_uri }
|
56
|
+
|
57
|
+
if event.nil?
|
58
|
+
log :info, "EventServer: Requested URI #@http_request_uri unknown"
|
59
|
+
response.status = 404
|
60
|
+
response.send_response
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
if !event.is_a? EM::Channel
|
65
|
+
log :error, "EventServer: internal error!"
|
66
|
+
response.status = 500
|
67
|
+
response.send_response
|
68
|
+
return
|
69
|
+
end
|
70
|
+
|
71
|
+
if !@http.has_key?(:nt) or !@http.has_key?(:nts)
|
72
|
+
log :warn, 'EventServer: malformed NOTIFY event message'
|
73
|
+
log :debug, "#@http_headers\n#@http_content"
|
74
|
+
response.status = 400
|
75
|
+
response.send_response
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
if @http[:nt] != 'upnp:event' or @http[:nts] != 'upnp:propchange' or
|
80
|
+
!@http.has_key?(:sid) or @http[:sid] == ''
|
81
|
+
log :warn, 'EventServer: precondition failed'
|
82
|
+
log :debug, "#@http_headers\n#@http_content"
|
83
|
+
response.status = 412
|
84
|
+
response.send_response
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
if event.sid != @http[:sid]
|
89
|
+
log :warn, 'EventServer: precondition failed (unknown SID)'
|
90
|
+
log :debug, "#@http_headers\n#@http_content"
|
91
|
+
response.status = 412
|
92
|
+
response.send_response
|
93
|
+
return
|
47
94
|
end
|
95
|
+
|
96
|
+
seq = @http[:seq].nil? ? 0 : @http[:seq].to_i
|
97
|
+
notification = {
|
98
|
+
:seq => seq,
|
99
|
+
:content => Nori.new.parse(@http_content)
|
100
|
+
}
|
101
|
+
|
102
|
+
log :debug, "send notification #{notification.inspect}\n" +
|
103
|
+
" to #{event}"
|
104
|
+
event << notification
|
105
|
+
|
106
|
+
response_status = 200
|
107
|
+
response.send_response
|
48
108
|
end
|
49
109
|
|
50
110
|
end
|
@@ -3,304 +3,375 @@ require 'savon'
|
|
3
3
|
require_relative 'base'
|
4
4
|
|
5
5
|
module RUPNP
|
6
|
+
module CP
|
7
|
+
|
8
|
+
# Service class for device's services.
|
9
|
+
#
|
10
|
+
# ==Actions
|
11
|
+
# This class defines ruby methods from actions defined in
|
12
|
+
# service description, as provided by the device.
|
13
|
+
#
|
14
|
+
# By example, from this description:
|
15
|
+
# <action>
|
16
|
+
# <name>actionName</name>
|
17
|
+
# <argumentList>
|
18
|
+
# <argument>
|
19
|
+
# <name>argumentNameIn</name>
|
20
|
+
# <direction>in</direction>
|
21
|
+
# <relatedStateVariable>stateVariableName</relatedStateVariable>
|
22
|
+
# </argument>
|
23
|
+
# <argument>
|
24
|
+
# <name>argumentNameOut</name>
|
25
|
+
# <direction>out</direction>
|
26
|
+
# <relatedStateVariable>stateVariableName</relatedStateVariable>
|
27
|
+
# </argument>
|
28
|
+
# </action>
|
29
|
+
# a +#action_name+ method is created. This method requires a hash with
|
30
|
+
# an element named +argument_name_in+.
|
31
|
+
# If no <i>in</i> argument is required, an empty hash (<code>{}</code>)
|
32
|
+
# must be passed to the method. Hash keys may not be symbols.
|
33
|
+
#
|
34
|
+
# A Hash is returned, with a key for each <i>out</i> argument.
|
35
|
+
#
|
36
|
+
# ==Evented variables
|
37
|
+
# Some variables in state (see {#state_table}, +:@send_events variable
|
38
|
+
# attribute) are evented. Events to update these variables are received
|
39
|
+
# only after subscription. To subscribe, use {#subscribe_to_event}.
|
40
|
+
#
|
41
|
+
# After subscribing to events, state variables are automagically updated
|
42
|
+
# on events. Their value may be accessed through {#variables}.
|
43
|
+
#
|
44
|
+
# A block may be passed to {#subscribe_to_event} to do a user action
|
45
|
+
# on receiving events.
|
46
|
+
#
|
47
|
+
# service.subscribe_to_event do |msg|
|
48
|
+
# puts "receive #{msg}"
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# @author Sylvain Daubert
|
52
|
+
class RemoteService < Base
|
53
|
+
|
54
|
+
# @private
|
55
|
+
@@event_sub_count = 0
|
56
|
+
|
57
|
+
# Get event subscription count for all services
|
58
|
+
# (unique ID for subscription)
|
59
|
+
# @return [Integer]
|
60
|
+
def self.event_sub_count
|
61
|
+
@@event_sub_count += 1
|
62
|
+
end
|
6
63
|
|
7
|
-
# Service class for device's services.
|
8
|
-
#
|
9
|
-
# ==Actions
|
10
|
-
# This class defines ruby methods from actions defined in
|
11
|
-
# service description, as provided by the device.
|
12
|
-
#
|
13
|
-
# By example, from this description:
|
14
|
-
# <action>
|
15
|
-
# <name>actionName</name>
|
16
|
-
# <argumentList>
|
17
|
-
# <argument>
|
18
|
-
# <name>argumentNameIn</name>
|
19
|
-
# <direction>in</direction>
|
20
|
-
# <relatedStateVariable>stateVariableName</relatedStateVariable>
|
21
|
-
# </argument>
|
22
|
-
# <argument>
|
23
|
-
# <name>argumentNameOut</name>
|
24
|
-
# <direction>out</direction>
|
25
|
-
# <relatedStateVariable>stateVariableName</relatedStateVariable>
|
26
|
-
# </argument>
|
27
|
-
# </action>
|
28
|
-
# a +#action_name+ method is created. This method requires a hash with
|
29
|
-
# an element named +argument_name_in+.
|
30
|
-
# If no <i>in</i> argument is required, an empty hash (<code>{}</code>)
|
31
|
-
# must be passed to the method. Hash keys may not be symbols.
|
32
|
-
#
|
33
|
-
# A Hash is returned, with a key for each <i>out</i> argument.
|
34
|
-
#
|
35
|
-
# @author Sylvain Daubert
|
36
|
-
class CP::RemoteService < CP::Base
|
37
|
-
|
38
|
-
# @private
|
39
|
-
@@event_sub_count = 0
|
40
|
-
|
41
|
-
# Get event subscription count for all services
|
42
|
-
# (unique ID for subscription)
|
43
|
-
# @return [Integer]
|
44
|
-
def self.event_sub_count
|
45
|
-
@@event_sub_count += 1
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
# @private SOAP integer types
|
50
|
-
INTEGER_TYPES = %w(ui1 ui2 ui4 i1 i2 i4 int).freeze
|
51
|
-
# @private SOAP float types
|
52
|
-
FLOAT_TYPES = %w(r4 r8 number float).freeze
|
53
|
-
# @private SOAP string types
|
54
|
-
STRING_TYPES = %w(char string uuid).freeze
|
55
|
-
# @private SOAP true values
|
56
|
-
TRUE_TYPES = %w(1 true yes).freeze
|
57
|
-
# @private SOAP false values
|
58
|
-
FALSE_TYPES = %w(0 false no).freeze
|
59
|
-
|
60
|
-
# Get device to which this service belongs to
|
61
|
-
# @return [Device]
|
62
|
-
attr_reader :device
|
63
|
-
|
64
|
-
# Get service type
|
65
|
-
# @return [String]
|
66
|
-
attr_reader :type
|
67
|
-
# Get service id
|
68
|
-
# @return [String]
|
69
|
-
attr_reader :id
|
70
|
-
# URL for service description
|
71
|
-
# @return [String]
|
72
|
-
attr_reader :scpd_url
|
73
|
-
# URL for control
|
74
|
-
# @return [String]
|
75
|
-
attr_reader :control_url
|
76
|
-
# URL for eventing
|
77
|
-
# @return [String]
|
78
|
-
attr_reader :event_sub_url
|
79
|
-
|
80
|
-
# XML namespace for device description
|
81
|
-
# @return [String]
|
82
|
-
attr_reader :xmlns
|
83
|
-
# Define architecture on which the service is implemented
|
84
|
-
# @return [String]
|
85
|
-
attr_reader :spec_version
|
86
|
-
# Available actions on this service
|
87
|
-
# @return [Array<Hash>]
|
88
|
-
attr_reader :actions
|
89
|
-
# State table for the service
|
90
|
-
# @return [Array<Hash>]
|
91
|
-
attr_reader :state_table
|
92
|
-
|
93
|
-
# @param [Device] device
|
94
|
-
# @param [String] url_base
|
95
|
-
# @param [Hash] service
|
96
|
-
def initialize(device, url_base, service)
|
97
|
-
super()
|
98
|
-
@device = device
|
99
|
-
@description = service
|
100
|
-
|
101
|
-
@type = service[:service_type].to_s
|
102
|
-
@id = service[:service_id].to_s
|
103
|
-
@scpd_url = build_url(url_base, service[:scpdurl].to_s)
|
104
|
-
@control_url = build_url(url_base, service[:control_url].to_s)
|
105
|
-
@event_sub_url = build_url(url_base, service[:event_sub_url].to_s)
|
106
|
-
@actions = []
|
107
|
-
|
108
|
-
initialize_savon
|
109
|
-
end
|
110
64
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
65
|
+
# @private SOAP integer types
|
66
|
+
INTEGER_TYPES = %w(ui1 ui2 ui4 i1 i2 i4 int).freeze
|
67
|
+
# @private SOAP float types
|
68
|
+
FLOAT_TYPES = %w(r4 r8 number float).freeze
|
69
|
+
# @private SOAP string types
|
70
|
+
STRING_TYPES = %w(char string uuid).freeze
|
71
|
+
# @private SOAP true values
|
72
|
+
TRUE_TYPES = %w(1 true yes).freeze
|
73
|
+
# @private SOAP false values
|
74
|
+
FALSE_TYPES = %w(0 false no).freeze
|
75
|
+
|
76
|
+
# Get device to which this service belongs to
|
77
|
+
# @return [Device]
|
78
|
+
attr_reader :device
|
79
|
+
|
80
|
+
# Get service type
|
81
|
+
# @return [String]
|
82
|
+
attr_reader :type
|
83
|
+
# Get service id
|
84
|
+
# @return [String]
|
85
|
+
attr_reader :id
|
86
|
+
# URL for service description
|
87
|
+
# @return [String]
|
88
|
+
attr_reader :scpd_url
|
89
|
+
# URL for control
|
90
|
+
# @return [String]
|
91
|
+
attr_reader :control_url
|
92
|
+
# URL for eventing
|
93
|
+
# @return [String]
|
94
|
+
attr_reader :event_sub_url
|
95
|
+
|
96
|
+
# XML namespace for device description
|
97
|
+
# @return [String]
|
98
|
+
attr_reader :xmlns
|
99
|
+
# Define architecture on which the service is implemented
|
100
|
+
# @return [String]
|
101
|
+
attr_reader :spec_version
|
102
|
+
# Available actions on this service
|
103
|
+
# @return [Array<Hash>]
|
104
|
+
attr_reader :actions
|
105
|
+
# State table for the service
|
106
|
+
# @return [Array<Hash>]
|
107
|
+
attr_reader :state_table
|
108
|
+
# Variables from state table
|
109
|
+
# @return [Hash]
|
110
|
+
attr_reader :variables
|
111
|
+
|
112
|
+
# @param [Device] device
|
113
|
+
# @param [String] url_base
|
114
|
+
# @param [Hash] service
|
115
|
+
def initialize(device, url_base, service)
|
116
|
+
super()
|
117
|
+
@device = device
|
118
|
+
@description = service
|
119
|
+
|
120
|
+
@type = service[:service_type].to_s
|
121
|
+
@id = service[:service_id].to_s
|
122
|
+
@scpd_url = build_url(url_base, service[:scpdurl].to_s)
|
123
|
+
@control_url = build_url(url_base, service[:control_url].to_s)
|
124
|
+
@event_sub_url = build_url(url_base, service[:event_sub_url].to_s)
|
125
|
+
@actions = []
|
126
|
+
@variables = {}
|
127
|
+
@update_variables = EM::Channel.new
|
128
|
+
|
129
|
+
@update_variables.subscribe do |var, value|
|
130
|
+
@variables[var] = value
|
131
|
+
log :debug, "varibale #{var} set to #{value}"
|
132
|
+
end
|
115
133
|
|
116
|
-
|
117
|
-
fail "cannot get SCPD from #@scpd_url"
|
118
|
-
next
|
134
|
+
initialize_savon
|
119
135
|
end
|
120
136
|
|
121
|
-
|
122
|
-
|
123
|
-
|
137
|
+
# Get service from its description
|
138
|
+
# @return [void]
|
139
|
+
def fetch
|
140
|
+
scpd_getter = EM::DefaultDeferrable.new
|
141
|
+
|
142
|
+
scpd_getter.errback do
|
143
|
+
fail "cannot get SCPD from #@scpd_url"
|
124
144
|
next
|
125
145
|
end
|
126
146
|
|
127
|
-
|
128
|
-
|
147
|
+
scpd_getter.callback do |scpd|
|
148
|
+
if bad_description?(scpd)
|
149
|
+
fail 'not a UPNP 1.0/1.1 SCPD'
|
150
|
+
next
|
151
|
+
end
|
129
152
|
|
130
|
-
|
131
|
-
|
153
|
+
extract_service_state_table scpd
|
154
|
+
extract_actions scpd
|
132
155
|
|
133
|
-
|
134
|
-
|
156
|
+
succeed self
|
157
|
+
end
|
135
158
|
|
136
|
-
|
137
|
-
# @param [Hash] options
|
138
|
-
# @option options [Integer] timeout
|
139
|
-
# @yieldparam [Event] event event received
|
140
|
-
# @yieldparam [Object] msg message received
|
141
|
-
# @return [Integer] subscribe id. May be used to unsubscribe on event
|
142
|
-
def subscribe_to_event(options={}, &blk)
|
143
|
-
cp = device.control_point
|
144
|
-
|
145
|
-
cp.start_event_server
|
146
|
-
|
147
|
-
port = cp.event_port
|
148
|
-
num = self.class.event_sub_count
|
149
|
-
@callback_url = "http://#{HOST_IP}:#{port}/event#{num}}"
|
150
|
-
|
151
|
-
uri = URI(@event_sub_url)
|
152
|
-
options[:timeout] ||= EVENT_SUB_DEFAULT_TIMEOUT
|
153
|
-
|
154
|
-
log :info, "send SUBSCRIBE request to #{uri}"
|
155
|
-
con = EM::HttpRequest.new(@event_sub_url)
|
156
|
-
http = con.setup_request(:subscribe, :head => {
|
157
|
-
'HOST' => "#{uri.host}:#{uri.port}",
|
158
|
-
'USER-AGENT' => RUPNP::USER_AGENT,
|
159
|
-
'CALLBACK' => @callback_url,
|
160
|
-
'NT' => 'upnp:event',
|
161
|
-
'TIMEOUT' => "Second-#{options[:timeout]}"})
|
162
|
-
|
163
|
-
http.errback do |client|
|
164
|
-
log :warn, "Cannot subscribe to event: #{client.error}"
|
159
|
+
get_description @scpd_url, scpd_getter
|
165
160
|
end
|
166
161
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
162
|
+
# Subscribe to event
|
163
|
+
# @param [Hash] options
|
164
|
+
# @option options [Integer] timeout
|
165
|
+
# @yieldparam [Event] event event received
|
166
|
+
# @yieldparam [Object] msg message received
|
167
|
+
# @return [Integer] subscribe id. May be used to unsubscribe on event
|
168
|
+
# @since 0.3.0
|
169
|
+
def subscribe_to_event(options={}, &blk)
|
170
|
+
cp = device.control_point
|
171
|
+
|
172
|
+
cp.start_event_server
|
173
|
+
|
174
|
+
port = cp.event_port
|
175
|
+
num = self.class.event_sub_count
|
176
|
+
@callback_url = "http://#{HOST_IP}:#{port}/events/#{num}"
|
177
|
+
|
178
|
+
uri = URI(@event_sub_url)
|
179
|
+
options[:timeout] ||= EVENT_SUB_DEFAULT_TIMEOUT
|
180
|
+
|
181
|
+
log :info, "send SUBSCRIBE request to #{uri}"
|
182
|
+
con = EM::HttpRequest.new(@event_sub_url)
|
183
|
+
http = con.setup_request(:subscribe, :head => {
|
184
|
+
'HOST' => "#{uri.host}:#{uri.port}",
|
185
|
+
'USER-AGENT' => RUPNP::USER_AGENT,
|
186
|
+
'CALLBACK' => @callback_url,
|
187
|
+
'NT' => 'upnp:event',
|
188
|
+
'TIMEOUT' => "Second-#{options[:timeout]}"})
|
189
|
+
|
190
|
+
http.errback do |client|
|
191
|
+
log :warn, "Cannot subscribe to event: #{client.error}"
|
192
|
+
end
|
193
|
+
|
194
|
+
http.callback do
|
195
|
+
log :debug, 'Close connection to subscribe event URL'
|
196
|
+
con.close
|
197
|
+
if http.response_header.status != 200
|
198
|
+
log :warn, "Cannot subscribe to event #@event_sub_url:" +
|
199
|
+
" #{http.response_header.http_reason}"
|
200
|
+
else
|
201
|
+
timeout = http.response_header['TIMEOUT'].match(/(\d+)/)[1] || 1800
|
202
|
+
event = Event.new(@event_sub_url, URI(@callback_url).path,
|
203
|
+
http.response_header['SID'], timeout.to_i)
|
204
|
+
EventServer.add_event event
|
205
|
+
log :info, 'event subscribtion registered'
|
206
|
+
log :debug, "event: #{event.inspect}"
|
207
|
+
|
208
|
+
event.subscribe do |msg|
|
209
|
+
log :debug, "event #{event} received"
|
210
|
+
if msg.is_a? Hash and msg[:content].is_a? Hash
|
211
|
+
msg[:content].each do |k, v|
|
212
|
+
if @variables.has_key? k
|
213
|
+
log :info, "update evented variable #{k}"
|
214
|
+
type = get_data_type_from_table(k)
|
215
|
+
@update_variables << [k, get_value_from_type(type, v)]
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
log :debug, "call user block"
|
220
|
+
blk.call(msg) if blk
|
221
|
+
end
|
222
|
+
end
|
179
223
|
end
|
180
224
|
end
|
181
|
-
end
|
182
225
|
|
183
226
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
227
|
+
private
|
228
|
+
|
229
|
+
def bad_description?(scpd)
|
230
|
+
if scpd[:scpd]
|
231
|
+
bd = false
|
232
|
+
@xmlns = scpd[:scpd][:@xmlns]
|
233
|
+
bd |= @xmlns != "urn:schemas-upnp-org:service-1-0"
|
234
|
+
bd |= scpd[:scpd][:spec_version][:major].to_i != 1
|
235
|
+
@spec_version = scpd[:scpd][:spec_version][:major] + '.'
|
236
|
+
@spec_version += scpd[:scpd][:spec_version][:minor]
|
237
|
+
bd |= !scpd[:scpd][:service_state_table]
|
238
|
+
bd | scpd[:scpd][:service_state_table].empty?
|
239
|
+
else
|
240
|
+
true
|
241
|
+
end
|
198
242
|
end
|
199
|
-
end
|
200
243
|
|
201
|
-
|
202
|
-
|
203
|
-
|
244
|
+
def extract_service_state_table(scpd)
|
245
|
+
if scpd[:scpd][:service_state_table][:state_variable]
|
246
|
+
@state_table = scpd[:scpd][:service_state_table][:state_variable]
|
247
|
+
|
248
|
+
if @state_table.is_a? Hash
|
249
|
+
@state_table = [@state_table]
|
250
|
+
end
|
204
251
|
|
205
|
-
|
206
|
-
|
252
|
+
@state_table.each do |var|
|
253
|
+
name = var[:name]
|
254
|
+
if INTEGER_TYPES.include? var[:data_type]
|
255
|
+
value = var[:default_value] ||
|
256
|
+
var[:allowed_value_range][:minimum] || 0
|
257
|
+
elsif FLOAT_TYPES.include? var[:data_type]
|
258
|
+
value = var[:default_value] ||
|
259
|
+
var[:allowed_value_range][:minimum] || 0
|
260
|
+
elsif STRING_TYPES.include? var[:data_type]
|
261
|
+
value = var[:default_value] ||
|
262
|
+
var[:allowed_value_list][:allowed_value].first || ''
|
263
|
+
else
|
264
|
+
value = nil
|
265
|
+
end
|
266
|
+
|
267
|
+
value = get_value_from_type(var[:data_type], value)
|
268
|
+
@update_variables << [name, value]
|
269
|
+
end
|
207
270
|
end
|
208
271
|
end
|
209
|
-
end
|
210
272
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
273
|
+
def extract_actions(scpd)
|
274
|
+
if scpd[:scpd][:action_list] and scpd[:scpd][:action_list][:action]
|
275
|
+
log :info, "extract actions for service #@type"
|
276
|
+
@actions = scpd[:scpd][:action_list][:action]
|
277
|
+
@actions = [@actions] unless @actions.is_a? Array
|
278
|
+
@actions.each do |action|
|
279
|
+
action[:arguments] = action[:argument_list][:argument]
|
280
|
+
action.delete :argument_list
|
281
|
+
define_method_from_action action
|
282
|
+
end
|
220
283
|
end
|
221
284
|
end
|
222
|
-
end
|
223
285
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
286
|
+
def define_method_from_action(action)
|
287
|
+
action[:name] = action[:name].to_s
|
288
|
+
action_name = action[:name]
|
289
|
+
name = snake_case(action_name).to_sym
|
228
290
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
291
|
+
define_singleton_method(name) do |params|
|
292
|
+
if params
|
293
|
+
unless params.is_a? Hash
|
294
|
+
raise ArgumentError, 'only hash arguments are accepted'
|
295
|
+
end
|
233
296
|
end
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
locals.message params
|
239
|
-
end
|
240
|
-
|
241
|
-
if action[:arguments].is_a? Hash
|
242
|
-
log :debug, 'only one argument in argument list'
|
243
|
-
if action[:arguments][:direction] == 'out'
|
244
|
-
process_soap_response name, response, action[:arguments]
|
297
|
+
response = @soap.call(action_name) do |locals|
|
298
|
+
locals.attributes 'xmlns:u' => @type
|
299
|
+
locals.soap_action "#{type}##{action_name}"
|
300
|
+
locals.message params
|
245
301
|
end
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
302
|
+
|
303
|
+
if action[:arguments].is_a? Hash
|
304
|
+
log :debug, 'only one argument in argument list'
|
305
|
+
if action[:arguments][:direction] == 'out'
|
306
|
+
process_soap_response name, response, action[:arguments]
|
307
|
+
end
|
308
|
+
else
|
309
|
+
log :debug, 'true argument list'
|
310
|
+
hsh = {}
|
311
|
+
outer = action[:arguments].select { |arg| arg[:direction] == 'out' }
|
312
|
+
outer.each do |arg|
|
313
|
+
hsh.merge! process_soap_response(name, response, arg)
|
314
|
+
end
|
315
|
+
hsh
|
252
316
|
end
|
253
|
-
hsh
|
254
317
|
end
|
255
318
|
end
|
256
|
-
end
|
257
319
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
320
|
+
def process_soap_response(action, resp, out_arg)
|
321
|
+
if resp.success? and resp.to_xml.empty?
|
322
|
+
log :debug, 'Successful SOAP request but empty response'
|
323
|
+
return {}
|
324
|
+
end
|
325
|
+
|
326
|
+
state_var = @state_table.find do |h|
|
327
|
+
h[:name] == out_arg[:related_state_variable]
|
328
|
+
end
|
329
|
+
|
330
|
+
action_response = "#{action}_response".to_sym
|
331
|
+
out_arg_name = snake_case(out_arg[:name]).to_sym
|
332
|
+
value = resp.hash[:envelope][:body][action_response][out_arg_name]
|
333
|
+
|
334
|
+
{ out_arg_name => get_value_from_type(state_var[:data_type], value) }
|
262
335
|
end
|
263
336
|
|
264
|
-
|
265
|
-
|
337
|
+
def get_value_from_type(type, value)
|
338
|
+
transform_method = if INTEGER_TYPES.include? type
|
339
|
+
:to_i
|
340
|
+
elsif FLOAT_TYPES.include? type
|
341
|
+
:to_f
|
342
|
+
elsif STRING_TYPES.include? type
|
343
|
+
:to_s
|
344
|
+
end
|
345
|
+
if transform_method
|
346
|
+
value.send(transform_method)
|
347
|
+
elsif TRUE_TYPES.include? type
|
348
|
+
true
|
349
|
+
elsif FALSE_TYPES.include? type
|
350
|
+
false
|
351
|
+
else
|
352
|
+
log :warn, "SOAP response has an unknown type: #{type}"
|
353
|
+
nil
|
354
|
+
end
|
266
355
|
end
|
267
356
|
|
268
|
-
|
269
|
-
|
270
|
-
value = resp.hash[:envelope][:body][action_response][out_arg_name]
|
271
|
-
|
272
|
-
transform_method = if INTEGER_TYPES.include? state_var[:data_type]
|
273
|
-
:to_i
|
274
|
-
elsif FLOAT_TYPES.include? state_var[:data_type]
|
275
|
-
:to_f
|
276
|
-
elsif STRING_TYPES.include? state_var[:data_type]
|
277
|
-
:to_s
|
278
|
-
end
|
279
|
-
if transform_method
|
280
|
-
{ out_arg_name => value.send(transform_method) }
|
281
|
-
elsif TRUE_TYPES.include? state_var[:data_type]
|
282
|
-
{ out_arg_name => true }
|
283
|
-
elsif FALSE_TYPES.include? state_var[:data_type]
|
284
|
-
{ out_arg_name => false }
|
285
|
-
else
|
286
|
-
log :warn, "SOAP response has an unknown type: #{state_var[:data_type]}"
|
287
|
-
{}
|
357
|
+
def get_data_type_from_table(var_name)
|
358
|
+
@state_table.find { |v| v[:name] == var_name }[:data_type]
|
288
359
|
end
|
289
|
-
end
|
290
360
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
361
|
+
def initialize_savon
|
362
|
+
@soap = Savon.client do |globals|
|
363
|
+
globals.log_level :error
|
364
|
+
globals.endpoint @control_url
|
365
|
+
globals.namespace @type
|
366
|
+
globals.convert_request_keys_to :camel_case
|
367
|
+
globals.log true
|
368
|
+
globals.headers :HOST => "#{HOST_IP}"
|
369
|
+
globals.env_namespace 's'
|
370
|
+
globals.namespace_identifier 'u'
|
371
|
+
end
|
301
372
|
end
|
373
|
+
|
302
374
|
end
|
303
375
|
|
304
376
|
end
|
305
|
-
|
306
377
|
end
|
data/lib/rupnp/event.rb
CHANGED
@@ -8,6 +8,7 @@ module RUPNP
|
|
8
8
|
# Get service ID
|
9
9
|
# @return [Integer]
|
10
10
|
attr_reader :sid
|
11
|
+
attr_reader :callback_url
|
11
12
|
|
12
13
|
# @param [String] event_suburl Event subscription URL
|
13
14
|
# @param [String] callback_url Callback URL to receive events
|
@@ -16,17 +17,20 @@ module RUPNP
|
|
16
17
|
def initialize(event_suburl, callback_url, sid, timeout)
|
17
18
|
super()
|
18
19
|
@event_suburl = event_suburl
|
20
|
+
@callback_url = callback_url
|
19
21
|
@sid, @timeout = sid, timeout
|
20
22
|
|
21
23
|
@timeout_timer = EM.add_timer(@timeout) { self << :timeout }
|
22
24
|
end
|
23
25
|
|
24
26
|
# Renew subscription to event
|
27
|
+
# @todo
|
25
28
|
def renew_subscription
|
26
29
|
raise NotImplementedError
|
27
30
|
end
|
28
31
|
|
29
32
|
# Cancel subscription to event
|
33
|
+
# @todo
|
30
34
|
def cancel_subscription
|
31
35
|
raise NotImplementedError
|
32
36
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
def start_server
|
4
|
+
EM.start_server('127.0.0.1', RUPNP::EVENT_SUB_DEFAULT_PORT,
|
5
|
+
RUPNP::CP::EventServer)
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
module RUPNP
|
10
|
+
module CP
|
11
|
+
|
12
|
+
describe EventServer do
|
13
|
+
include EM::SpecHelper
|
14
|
+
|
15
|
+
let(:timeout) { 2 }
|
16
|
+
let(:sid) { "uuid:#{UUID.generate}" }
|
17
|
+
let(:event_uri) { '/event/1' }
|
18
|
+
let(:event) { Event.new('', event_uri, sid, timeout) }
|
19
|
+
let(:port) { RUPNP::EVENT_SUB_DEFAULT_PORT }
|
20
|
+
let(:req) {EM::HttpRequest.new("http://127.0.0.1:#{port}#{event_uri}")}
|
21
|
+
|
22
|
+
it 'should return 404 error on bad HTTP method URI' do
|
23
|
+
em do
|
24
|
+
start_server
|
25
|
+
|
26
|
+
req = EM::HttpRequest.new("http://127.0.0.1:#{port}/unknown")
|
27
|
+
http = send_notify_request(req)
|
28
|
+
http.errback { fail 'must not fail!' }
|
29
|
+
http.callback do
|
30
|
+
expect(http.response_header.status).to eq(404)
|
31
|
+
done
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should return 405 error on bad HTTP method' do
|
37
|
+
em do
|
38
|
+
start_server
|
39
|
+
|
40
|
+
http = req.get
|
41
|
+
http.callback do
|
42
|
+
expect(http.response_header.status).to eq(405)
|
43
|
+
done
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should return 400 error on malformed request' do
|
49
|
+
em do
|
50
|
+
req2 = req.dup
|
51
|
+
EventServer.add_event event
|
52
|
+
start_server
|
53
|
+
|
54
|
+
http = send_notify_request(req, :delete => 'NT')
|
55
|
+
http.errback { fail 'must not fail!' }
|
56
|
+
http.callback do
|
57
|
+
expect(http.response_header.status).to eq(400)
|
58
|
+
http2 = send_notify_request(req2, :delete => 'NTS')
|
59
|
+
http2.callback do
|
60
|
+
expect(http2.response_header.status).to eq(400)
|
61
|
+
done
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
EventServer.remove_event event
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should return 412 error on bad request' do
|
69
|
+
em do
|
70
|
+
EventServer.add_event event
|
71
|
+
start_server
|
72
|
+
http = send_notify_request(req, 'SID' => "uuid:#{UUID.generate}")
|
73
|
+
http.errback { fail 'must not fail!' }
|
74
|
+
http.callback do
|
75
|
+
expect(http.response_header.status).to eq(412)
|
76
|
+
done
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
em do
|
81
|
+
start_server
|
82
|
+
http = send_notify_request(req, 'NT' => "upnp:other")
|
83
|
+
http.errback { fail 'must not fail!' }
|
84
|
+
http.callback do
|
85
|
+
expect(http.response_header.status).to eq(412)
|
86
|
+
done
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
em do
|
92
|
+
start_server
|
93
|
+
http = send_notify_request(req, 'NTS' => "upnp:other")
|
94
|
+
http.errback { fail 'must not fail!' }
|
95
|
+
http.callback do
|
96
|
+
expect(http.response_header.status).to eq(412)
|
97
|
+
done
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
em do
|
102
|
+
start_server
|
103
|
+
http = send_notify_request(req, :delete => 'SID')
|
104
|
+
http.errback { fail 'must not fail!' }
|
105
|
+
http.callback do
|
106
|
+
expect(http.response_header.status).to eq(412)
|
107
|
+
done
|
108
|
+
end
|
109
|
+
end
|
110
|
+
EventServer.remove_event event
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should receive a NOTIFY request' do
|
114
|
+
em do
|
115
|
+
EventServer.add_event event
|
116
|
+
start_server
|
117
|
+
http = send_notify_request(req, 'SID' => sid)
|
118
|
+
http.errback { fail 'must not fail!' }
|
119
|
+
http_ok = event_ok = false
|
120
|
+
http.callback do
|
121
|
+
expect(http.response_header.status).to eq(200)
|
122
|
+
http_ok = true
|
123
|
+
done if event_ok
|
124
|
+
end
|
125
|
+
event.subscribe do |h|
|
126
|
+
expect(h[:seq]).to be_a(Integer)
|
127
|
+
expect(h[:content]).to be_a(Hash)
|
128
|
+
event_ok = true
|
129
|
+
done if http_ok
|
130
|
+
end
|
131
|
+
end
|
132
|
+
EventServer.remove_event event
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should return updated variables through 'events' channel"
|
136
|
+
it 'should serve multiple URLs'
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -37,12 +37,19 @@ module RUPNP
|
|
37
37
|
em do
|
38
38
|
rs.errback { fail 'RemoteService#fetch should work' }
|
39
39
|
rs.callback do
|
40
|
-
expect(rs.state_table).to have(
|
40
|
+
expect(rs.state_table).to have(5).items
|
41
41
|
expect(rs.state_table[0][:name]).to eq('X_variableName1')
|
42
42
|
expect(rs.state_table[1][:data_type]).to eq('ui4')
|
43
43
|
expect(rs.state_table[2][:default_value]).to eq('2')
|
44
44
|
expect(rs.state_table[3][:allowed_value_range][:maximum]).
|
45
45
|
to eq('255')
|
46
|
+
expect(rs.state_table[4][:data_type]).to eq('string')
|
47
|
+
expect(rs.state_table[4][:default_value]).to eq('none')
|
48
|
+
|
49
|
+
expect(rs.variables['X_variableName1']).to be_a(Fixnum)
|
50
|
+
4.times do |i|
|
51
|
+
expect(rs.variables["X_variableName#{i+1}"]).to eq(i)
|
52
|
+
end
|
46
53
|
done
|
47
54
|
end
|
48
55
|
rs.fetch
|
@@ -147,14 +154,19 @@ module RUPNP
|
|
147
154
|
em do
|
148
155
|
rs.errback { fail 'RemoteService#fetch should work' }
|
149
156
|
rs.callback do
|
157
|
+
expect(rs.variables['X_variableName1']).to eq(0)
|
150
158
|
rs.subscribe_to_event do |msg|
|
159
|
+
expect(rs.variables['X_variableName1']).to eq(12)
|
151
160
|
done
|
152
161
|
end
|
153
162
|
end
|
154
163
|
rs.fetch
|
155
164
|
|
156
|
-
|
157
|
-
|
165
|
+
EM.add_timer(1) do
|
166
|
+
event = class EventServer; @@events.last; end
|
167
|
+
url = "http://127.0.0.1:8080#{event.callback_url}"
|
168
|
+
conn = EM::HttpRequest.new(url)
|
169
|
+
send_notify_request(conn, 'SID' => event.sid)
|
158
170
|
end
|
159
171
|
end
|
160
172
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -10,6 +10,7 @@ require 'webmock/rspec'
|
|
10
10
|
|
11
11
|
RUPNP.log_level = :failure
|
12
12
|
|
13
|
+
WebMock.disable_net_connect!(allow_localhost: true)
|
13
14
|
|
14
15
|
class FakeMulticast < RUPNP::SSDP::MulticastConnection
|
15
16
|
attr_reader :handshake_response, :packets
|
@@ -151,6 +152,17 @@ EOAL
|
|
151
152
|
</stateVariable>
|
152
153
|
EOSV
|
153
154
|
end
|
155
|
+
scpd << <<EOSV2
|
156
|
+
<stateVariable sendEvents="#{opt[:send_event] ? 'yes' : 'no'}">
|
157
|
+
<name>X_variableStr</name>
|
158
|
+
<dataType>string</dataType>
|
159
|
+
<defaultValue>none</defaultValue>
|
160
|
+
<allowedValueList>
|
161
|
+
<allowedValue>none</allowedValue>
|
162
|
+
<allowedValue>another value</allowedValue>
|
163
|
+
</allowedValueList>
|
164
|
+
</stateVariable>
|
165
|
+
EOSV2
|
154
166
|
scpd << " </serviceStateTable>\n</scpd>\n"
|
155
167
|
end
|
156
168
|
|
@@ -175,6 +187,34 @@ EOD
|
|
175
187
|
end
|
176
188
|
|
177
189
|
|
190
|
+
def event_body
|
191
|
+
<<EOD
|
192
|
+
<?xml version="1.0"?>
|
193
|
+
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
|
194
|
+
<e:property>
|
195
|
+
<X_variableName1>12</variableName>
|
196
|
+
</e:property>
|
197
|
+
</e:propertyset>
|
198
|
+
EOD
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
def send_notify_request(req, options={})
|
203
|
+
delete = options.delete(:delete)
|
204
|
+
headers = {
|
205
|
+
'HOST' => "127.0.0.1:1234",
|
206
|
+
'USER-AGENT' => RUPNP::USER_AGENT,
|
207
|
+
'CONTENT-TYPE' => 'text/xml; charset="utf-8"',
|
208
|
+
'NT' => 'upnp:event',
|
209
|
+
'NTS' => 'upnp:propchange',
|
210
|
+
'SID' => "uuid:#{UUID.generate}",
|
211
|
+
'SEQ' => 0 }.merge(options)
|
212
|
+
headers.delete delete if delete
|
213
|
+
|
214
|
+
req.setup_request(:notify, :head => headers, :body => event_body)
|
215
|
+
end
|
216
|
+
|
217
|
+
|
178
218
|
NOTIFY_REGEX = {
|
179
219
|
:common => [
|
180
220
|
/^NOTIFY \* HTTP\/1.1\r\n/,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rupnp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-02-
|
12
|
+
date: 2014-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: uuid
|
@@ -187,6 +187,7 @@ files:
|
|
187
187
|
- spec/ssdp/searcher_spec.rb
|
188
188
|
- spec/cp/remote_service_spec.rb
|
189
189
|
- spec/cp/remote_device_spec.rb
|
190
|
+
- spec/cp/event_server_spec.rb
|
190
191
|
- spec/control_point_spec.rb
|
191
192
|
- spec/spec_helper.rb
|
192
193
|
- lib/rupnp.rb
|
@@ -203,7 +204,6 @@ files:
|
|
203
204
|
- lib/rupnp/cp/base.rb
|
204
205
|
- lib/rupnp/cp/event_server.rb
|
205
206
|
- lib/rupnp/cp/remote_device.rb
|
206
|
-
- lib/rupnp/cp/event_subscriber.rb
|
207
207
|
- lib/rupnp/discover.rb
|
208
208
|
- lib/rupnp/ssdp.rb
|
209
209
|
- lib/rupnp/log_mixin.rb
|
@@ -1,47 +0,0 @@
|
|
1
|
-
module RUPNP
|
2
|
-
|
3
|
-
# Event subscriber to an event's service
|
4
|
-
# @author Sylvain Daubert
|
5
|
-
class CP::EventSubscriber < EM::Connection
|
6
|
-
include LogMixin
|
7
|
-
|
8
|
-
# Response from device
|
9
|
-
# @return [EM::Channel]
|
10
|
-
attr_reader :response
|
11
|
-
|
12
|
-
|
13
|
-
# @param [String] msg message to send for subscribing
|
14
|
-
def initialize(msg)
|
15
|
-
@msg = msg
|
16
|
-
@response = EM::Channel.new
|
17
|
-
end
|
18
|
-
|
19
|
-
# @return [void]
|
20
|
-
def post_init
|
21
|
-
log :debug, "send event subscribe request:\n#@msg"
|
22
|
-
send_data @msg
|
23
|
-
end
|
24
|
-
|
25
|
-
# Receive response from device and send it through {#response}
|
26
|
-
# @param [String] data
|
27
|
-
# @return [void]
|
28
|
-
def receive_data(data)
|
29
|
-
log :debug, "receive data from subscribe event action:\n#{data}"
|
30
|
-
resp = {}
|
31
|
-
io = StringIO.new(data)
|
32
|
-
|
33
|
-
status = io.readline
|
34
|
-
|
35
|
-
if status =~ /HTTP\/1\.1 (\d+) (.+)/
|
36
|
-
resp[:status] = $2
|
37
|
-
resp[:status_code] = $1
|
38
|
-
|
39
|
-
resp.merge!(get_http_headers(io))
|
40
|
-
|
41
|
-
@response << resp
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|