openc3 5.10.0 → 5.11.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of openc3 might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/Rakefile +3 -2
- data/data/config/target.yaml +9 -0
- data/ext/openc3/ext/packet/packet.c +3 -0
- data/ext/openc3/ext/reducer_microservice/extconf.rb +13 -0
- data/ext/openc3/ext/reducer_microservice/reducer_microservice.c +165 -0
- data/ext/openc3/ext/structure/structure.c +7 -9
- data/lib/openc3/accessors/accessor.rb +53 -3
- data/lib/openc3/accessors/binary_accessor.rb +16 -0
- data/lib/openc3/accessors/cbor_accessor.rb +3 -3
- data/lib/openc3/accessors/form_accessor.rb +78 -0
- data/lib/openc3/accessors/http_accessor.rb +145 -0
- data/lib/openc3/accessors/json_accessor.rb +19 -3
- data/lib/openc3/accessors/xml_accessor.rb +18 -1
- data/lib/openc3/accessors.rb +3 -1
- data/lib/openc3/config/config_parser.rb +7 -5
- data/lib/openc3/config/meta_config_parser.rb +1 -1
- data/lib/openc3/core_ext/string.rb +16 -1
- data/lib/openc3/interfaces/http_client_interface.rb +202 -0
- data/lib/openc3/interfaces/http_server_interface.rb +183 -0
- data/lib/openc3/interfaces/interface.rb +86 -16
- data/lib/openc3/interfaces/mqtt_interface.rb +6 -5
- data/lib/openc3/interfaces/protocols/burst_protocol.rb +11 -11
- data/lib/openc3/interfaces/protocols/cobs_protocol.rb +7 -7
- data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -7
- data/lib/openc3/interfaces/protocols/length_protocol.rb +6 -6
- data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +9 -5
- data/lib/openc3/interfaces/protocols/protocol.rb +8 -6
- data/lib/openc3/interfaces/protocols/slip_protocol.rb +8 -8
- data/lib/openc3/interfaces/protocols/template_protocol.rb +6 -7
- data/lib/openc3/interfaces/protocols/terminated_protocol.rb +4 -4
- data/lib/openc3/interfaces/simulated_target_interface.rb +2 -0
- data/lib/openc3/interfaces/stream_interface.rb +6 -4
- data/lib/openc3/interfaces/tcpip_server_interface.rb +2 -0
- data/lib/openc3/interfaces/udp_interface.rb +8 -5
- data/lib/openc3/interfaces.rb +2 -0
- data/lib/openc3/logs/buffered_packet_log_writer.rb +6 -7
- data/lib/openc3/logs/log_writer.rb +2 -10
- data/lib/openc3/logs/packet_log_constants.rb +13 -3
- data/lib/openc3/logs/packet_log_reader.rb +35 -98
- data/lib/openc3/logs/packet_log_writer.rb +24 -62
- data/lib/openc3/logs/text_log_writer.rb +32 -6
- data/lib/openc3/microservices/cleanup_microservice.rb +23 -16
- data/lib/openc3/microservices/decom_microservice.rb +8 -20
- data/lib/openc3/microservices/log_microservice.rb +3 -1
- data/lib/openc3/microservices/reaction_microservice.rb +22 -11
- data/lib/openc3/microservices/reducer_microservice.rb +174 -130
- data/lib/openc3/{models/notification_model.rb → microservices/scope_cleanup_microservice.rb} +20 -21
- data/lib/openc3/microservices/text_log_microservice.rb +2 -5
- data/lib/openc3/microservices/timeline_microservice.rb +0 -1
- data/lib/openc3/microservices/trigger_group_microservice.rb +6 -3
- data/lib/openc3/migrations/20230915000002_no_scope_log_messages.rb +44 -0
- data/lib/openc3/models/microservice_model.rb +1 -1
- data/lib/openc3/models/scope_model.rb +65 -34
- data/lib/openc3/models/target_model.rb +25 -5
- data/lib/openc3/models/tool_model.rb +14 -2
- data/lib/openc3/models/widget_model.rb +1 -1
- data/lib/openc3/packets/json_packet.rb +10 -2
- data/lib/openc3/packets/packet.rb +37 -22
- data/lib/openc3/packets/packet_config.rb +6 -2
- data/lib/openc3/packets/parsers/packet_item_parser.rb +11 -6
- data/lib/openc3/packets/structure.rb +19 -12
- data/lib/openc3/packets/structure_item.rb +0 -3
- data/lib/openc3/script/storage.rb +1 -1
- data/lib/openc3/script/web_socket_api.rb +17 -14
- data/lib/openc3/topics/command_decom_topic.rb +0 -4
- data/lib/openc3/topics/telemetry_decom_topic.rb +0 -4
- data/lib/openc3/topics/telemetry_reduced_topics.rb +1 -13
- data/lib/openc3/topics/telemetry_topic.rb +2 -1
- data/lib/openc3/topics/topic.rb +0 -8
- data/lib/openc3/utilities/aws_bucket.rb +40 -38
- data/lib/openc3/utilities/bucket_utilities.rb +2 -0
- data/lib/openc3/utilities/cli_generator.rb +1 -1
- data/lib/openc3/utilities/logger.rb +62 -47
- data/lib/openc3/utilities/metric.rb +19 -1
- data/lib/openc3/utilities/sleeper.rb +3 -1
- data/lib/openc3/utilities/throttle.rb +76 -0
- data/lib/openc3/version.rb +5 -5
- data/templates/tool_angular/package.json +20 -20
- data/templates/tool_angular/yarn.lock +1815 -1009
- data/templates/tool_react/package.json +24 -26
- data/templates/tool_react/yarn.lock +1269 -902
- data/templates/tool_svelte/.prettierrc.js +5 -0
- data/templates/tool_svelte/package.json +26 -26
- data/templates/tool_svelte/src/services/cable.js +1 -1
- data/templates/tool_svelte/src/services/openc3-api.js +173 -173
- data/templates/tool_svelte/yarn.lock +981 -735
- data/templates/tool_vue/package.json +13 -13
- data/templates/tool_vue/yarn.lock +363 -147
- data/templates/widget/package.json +14 -14
- data/templates/widget/yarn.lock +367 -99
- metadata +41 -4
- data/lib/openc3/topics/notifications_topic.rb +0 -31
@@ -13,7 +13,7 @@
|
|
13
13
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
#
|
16
|
-
# This file may also be used under the terms of a commercial license
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
17
|
# if purchased from OpenC3, Inc.
|
18
18
|
|
19
19
|
require 'json'
|
@@ -45,7 +45,7 @@ module OpenC3
|
|
45
45
|
buffer.replace(JSON.generate(decoded, :allow_nan => true))
|
46
46
|
end
|
47
47
|
|
48
|
-
return
|
48
|
+
return value
|
49
49
|
end
|
50
50
|
|
51
51
|
def self.read_items(items, buffer)
|
@@ -78,7 +78,7 @@ module OpenC3
|
|
78
78
|
buffer.replace(JSON.generate(decoded, :allow_nan => true))
|
79
79
|
end
|
80
80
|
|
81
|
-
return
|
81
|
+
return values
|
82
82
|
end
|
83
83
|
|
84
84
|
def self.write_item_internal(item, value, decoded)
|
@@ -130,5 +130,21 @@ module OpenC3
|
|
130
130
|
end
|
131
131
|
return decoded
|
132
132
|
end
|
133
|
+
|
134
|
+
def enforce_encoding
|
135
|
+
return nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def enforce_length
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
|
142
|
+
def enforce_short_buffer_allowed
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
|
146
|
+
def enforce_derived_write_conversion(item)
|
147
|
+
return true
|
148
|
+
end
|
133
149
|
end
|
134
150
|
end
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
#
|
16
|
-
# This file may also be used under the terms of a commercial license
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
17
|
# if purchased from OpenC3, Inc.
|
18
18
|
|
19
19
|
require 'nokogiri'
|
@@ -33,6 +33,7 @@ module OpenC3
|
|
33
33
|
node = doc.xpath(item.key).first
|
34
34
|
node.content = value.to_s
|
35
35
|
buffer.replace(doc_to_buffer(doc))
|
36
|
+
return value
|
36
37
|
end
|
37
38
|
|
38
39
|
def self.read_items(items, buffer)
|
@@ -56,6 +57,7 @@ module OpenC3
|
|
56
57
|
node.content = values[index].to_s
|
57
58
|
end
|
58
59
|
buffer.replace(doc_to_buffer(doc))
|
60
|
+
return values
|
59
61
|
end
|
60
62
|
|
61
63
|
def self.buffer_to_doc(buffer)
|
@@ -66,5 +68,20 @@ module OpenC3
|
|
66
68
|
doc.to_xml
|
67
69
|
end
|
68
70
|
|
71
|
+
def enforce_encoding
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def enforce_length
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
def enforce_short_buffer_allowed
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
def enforce_derived_write_conversion(item)
|
84
|
+
return true
|
85
|
+
end
|
69
86
|
end
|
70
87
|
end
|
data/lib/openc3/accessors.rb
CHANGED
@@ -13,14 +13,16 @@
|
|
13
13
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
#
|
16
|
-
# This file may also be used under the terms of a commercial license
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
17
|
# if purchased from OpenC3, Inc.
|
18
18
|
|
19
19
|
module OpenC3
|
20
20
|
autoload(:Accessor, 'openc3/accessors/accessor.rb')
|
21
21
|
autoload(:BinaryAccessor, 'openc3/accessors/binary_accessor.rb')
|
22
22
|
autoload(:CborAccessor, 'openc3/accessors/cbor_accessor.rb')
|
23
|
+
autoload(:FormAccessor, 'openc3/accessors/form_accessor.rb')
|
23
24
|
autoload(:HtmlAccessor, 'openc3/accessors/html_accessor.rb')
|
25
|
+
autoload(:HttpAccessor, 'openc3/accessors/http_accessor.rb')
|
24
26
|
autoload(:JsonAccessor, 'openc3/accessors/json_accessor.rb')
|
25
27
|
autoload(:XmlAccessor, 'openc3/accessors/xml_accessor.rb')
|
26
28
|
end
|
@@ -166,7 +166,7 @@ module OpenC3
|
|
166
166
|
options[:locals].each { |key, value| b.local_variable_set(key, value) }
|
167
167
|
end
|
168
168
|
|
169
|
-
return ERB.new(read_file(template_name), trim_mode: "-").result(b)
|
169
|
+
return ERB.new(read_file(template_name).comment_erb(), trim_mode: "-").result(b)
|
170
170
|
end
|
171
171
|
|
172
172
|
# Can be called during parsing to read a referenced file
|
@@ -178,7 +178,7 @@ module OpenC3
|
|
178
178
|
path = File.join(File.dirname(@filename), filename)
|
179
179
|
end
|
180
180
|
OpenC3.set_working_dir(File.dirname(path)) do
|
181
|
-
return File.read(path)
|
181
|
+
return File.read(path).force_encoding("UTF-8")
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
@@ -385,10 +385,10 @@ module OpenC3
|
|
385
385
|
output = nil
|
386
386
|
if run_erb
|
387
387
|
OpenC3.set_working_dir(File.dirname(filename)) do
|
388
|
-
output = ERB.new(File.read(filename), trim_mode: "-").result(binding.set_variables(variables))
|
388
|
+
output = ERB.new(File.read(filename).force_encoding("UTF-8").comment_erb(), trim_mode: "-").result(binding.set_variables(variables))
|
389
389
|
end
|
390
390
|
else
|
391
|
-
output = File.read(filename)
|
391
|
+
output = File.read(filename).force_encoding("UTF-8")
|
392
392
|
end
|
393
393
|
rescue => e
|
394
394
|
# The first line of the backtrace indicates the line where the ERB
|
@@ -453,8 +453,10 @@ module OpenC3
|
|
453
453
|
message += "\n#{File.basename(error.filename)}:#{error.line_number}: #{error.line}"
|
454
454
|
message += "\nError: #{error.message}"
|
455
455
|
message += "\nUsage: #{error.usage}" unless error.usage.empty?
|
456
|
+
message += "\nBacktrace:"
|
457
|
+
message += "\n#{error.backtrace.join("\n")}"
|
456
458
|
else
|
457
|
-
message += "\n#{error.
|
459
|
+
message += "\n#{error.formatted}"
|
458
460
|
end
|
459
461
|
message += "\n"
|
460
462
|
end
|
@@ -17,7 +17,7 @@
|
|
17
17
|
# All changes Copyright 2022, OpenC3, Inc.
|
18
18
|
# All Rights Reserved
|
19
19
|
#
|
20
|
-
# This file may also be used under the terms of a commercial license
|
20
|
+
# This file may also be used under the terms of a commercial license
|
21
21
|
# if purchased from OpenC3, Inc.
|
22
22
|
|
23
23
|
require 'erb'
|
@@ -389,4 +389,19 @@ class String
|
|
389
389
|
end
|
390
390
|
end
|
391
391
|
|
392
|
-
|
392
|
+
def comment_erb
|
393
|
+
output = self.lines.collect! do |line|
|
394
|
+
# If we have a commented out line that starts with #
|
395
|
+
# but not followed by % (allows for disabling ERB comments),
|
396
|
+
# which contains an ERB statement (<% ...)
|
397
|
+
# then comment out the ERB statement (<%# ...).
|
398
|
+
# We explicitly don't comment out trailing ERB statements
|
399
|
+
# as that is not typical and is difficult to regex
|
400
|
+
if line =~ /^\s*#[^%]*<%/
|
401
|
+
line.gsub!('<%', '<%#')
|
402
|
+
end
|
403
|
+
line
|
404
|
+
end
|
405
|
+
return output.join("")
|
406
|
+
end
|
407
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2023 OpenC3, Inc.
|
4
|
+
# All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This program is free software; you can modify and/or redistribute it
|
7
|
+
# under the terms of the GNU Affero General Public License
|
8
|
+
# as published by the Free Software Foundation; version 3 with
|
9
|
+
# attribution addendums as found in the LICENSE.txt
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Affero General Public License for more details.
|
15
|
+
#
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
|
+
# if purchased from OpenC3, Inc.
|
18
|
+
|
19
|
+
require 'openc3/interfaces/interface'
|
20
|
+
require 'openc3/config/config_parser'
|
21
|
+
require 'faraday'
|
22
|
+
require 'faraday/follow_redirects'
|
23
|
+
require 'openc3/accessors/http_accessor'
|
24
|
+
|
25
|
+
# TODO: Header Log Filtering? File Uploads? Authorization from secrets?
|
26
|
+
|
27
|
+
module OpenC3
|
28
|
+
class HttpClientInterface < Interface
|
29
|
+
# @param hostname [String] HTTP/HTTPS server to connect to
|
30
|
+
# @param port [Integer] HTTP/HTTPS port
|
31
|
+
# @param protocol [String] http or https
|
32
|
+
def initialize(hostname, port = 80, protocol = 'http', write_timeout = 5, read_timeout = nil, connect_timeout = 5, include_request_in_response = false)
|
33
|
+
super()
|
34
|
+
@hostname = hostname
|
35
|
+
@port = Integer(port)
|
36
|
+
@protocol = protocol
|
37
|
+
if (port == 80 and protocol == 'http') or (port == 443 and protocol == 'https')
|
38
|
+
@url = "#{protocol}://#{hostname}"
|
39
|
+
else
|
40
|
+
@url = "#{protocol}://#{hostname}:#{port}"
|
41
|
+
end
|
42
|
+
@write_timeout = ConfigParser.handle_nil(write_timeout)
|
43
|
+
@write_timeout = Float(@write_timeout) if @write_timeout
|
44
|
+
@read_timeout = ConfigParser.handle_nil(read_timeout)
|
45
|
+
@read_timeout = Float(@read_timeout) if @read_timeout
|
46
|
+
@connect_timeout = ConfigParser.handle_nil(connect_timeout)
|
47
|
+
@connect_timeout = Float(@connect_timeout) if @connect_timeout
|
48
|
+
@include_request_in_response = ConfigParser.handle_true_false(include_request_in_response)
|
49
|
+
|
50
|
+
@response_queue = Queue.new
|
51
|
+
end
|
52
|
+
|
53
|
+
# Connects the interface to its target(s)
|
54
|
+
def connect
|
55
|
+
# Per https://github.com/lostisland/faraday/blob/main/lib/faraday/options/env.rb
|
56
|
+
# :timeout - time limit for the entire request (Integer in seconds)
|
57
|
+
# :open_timeout - time limit for just the connection phase (e.g. handshake) (Integer in seconds)
|
58
|
+
# :read_timeout - time limit for the first response byte received from the server (Integer in seconds)
|
59
|
+
# :write_timeout - time limit for the client to send the request to the server (Integer in seconds)
|
60
|
+
request = {}
|
61
|
+
request['open_timeout'] = @connect_timeout if @connect_timeout
|
62
|
+
request['read_timeout'] = @read_timeout if @read_timeout
|
63
|
+
request['write_timeout'] = @write_timeout if @write_timeout
|
64
|
+
@http = Faraday.new(request: request) do |f|
|
65
|
+
f.response :follow_redirects # use Faraday::FollowRedirects::Middleware
|
66
|
+
f.adapter :net_http # adds the adapter to the connection, defaults to `Faraday.default_adapter`
|
67
|
+
end
|
68
|
+
super()
|
69
|
+
end
|
70
|
+
|
71
|
+
def connected?
|
72
|
+
if @http
|
73
|
+
return true
|
74
|
+
else
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Disconnects the interface from its target(s)
|
80
|
+
def disconnect
|
81
|
+
@http.close if @http
|
82
|
+
@http = nil
|
83
|
+
while @response_queue.length > 0
|
84
|
+
@response_queue.pop
|
85
|
+
end
|
86
|
+
super()
|
87
|
+
@response_queue.push(nil)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Reads from the socket if the read_port is defined
|
91
|
+
def read_interface
|
92
|
+
# Get the Faraday Response
|
93
|
+
data, extra = @response_queue.pop
|
94
|
+
return nil if data.nil?
|
95
|
+
|
96
|
+
read_interface_base(data, extra)
|
97
|
+
return data, extra
|
98
|
+
end
|
99
|
+
|
100
|
+
# Writes to the socket
|
101
|
+
# @param data [Hash] For the HTTP Interface, data is a hash with the needed request info
|
102
|
+
def write_interface(data, extra = nil)
|
103
|
+
extra ||= {}
|
104
|
+
queries = extra['HTTP_QUERIES']
|
105
|
+
queries ||= {}
|
106
|
+
headers = extra['HTTP_HEADERS']
|
107
|
+
headers ||= {}
|
108
|
+
uri = extra['HTTP_URI']
|
109
|
+
method = extra['HTTP_METHOD']
|
110
|
+
|
111
|
+
resp = nil
|
112
|
+
case method
|
113
|
+
when 'get'
|
114
|
+
resp = @http.get(uri, queries, headers)
|
115
|
+
when 'put'
|
116
|
+
resp = @http.put(uri) do |req|
|
117
|
+
req.params = queries
|
118
|
+
req.headers = headers
|
119
|
+
req.body = data
|
120
|
+
end
|
121
|
+
when 'delete'
|
122
|
+
resp = @http.delete(uri, queries, headers)
|
123
|
+
else # 'post'
|
124
|
+
resp = @http.post(uri) do |req|
|
125
|
+
req.params = queries
|
126
|
+
req.headers = headers
|
127
|
+
req.body = data
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Normalize Response into simple hash
|
132
|
+
response_data = nil
|
133
|
+
response_extra = {}
|
134
|
+
if resp
|
135
|
+
response_extra['HTTP_REQUEST'] = [data, extra]
|
136
|
+
if resp.headers and resp.headers.length > 0
|
137
|
+
response_extra['HTTP_HEADERS'] = resp.headers
|
138
|
+
end
|
139
|
+
response_extra['HTTP_STATUS'] = resp.status
|
140
|
+
response_data = resp.body
|
141
|
+
response_data ||= '' # Ensure an empty string if we got a response but no data
|
142
|
+
end
|
143
|
+
|
144
|
+
@response_queue.push([response_data, response_extra])
|
145
|
+
|
146
|
+
write_interface_base(data, extra)
|
147
|
+
return data, extra
|
148
|
+
end
|
149
|
+
|
150
|
+
# Called to convert the read data into a OpenC3 Packet object
|
151
|
+
#
|
152
|
+
# @param data [String] Raw packet data
|
153
|
+
# @return [Packet] OpenC3 Packet with buffer filled with data
|
154
|
+
def convert_data_to_packet(data, extra = nil)
|
155
|
+
packet = Packet.new(nil, nil, :BIG_ENDIAN, nil, data.to_s)
|
156
|
+
packet.accessor = HttpAccessor.new(packet)
|
157
|
+
if extra
|
158
|
+
# Identify the response
|
159
|
+
request_target_name = extra['HTTP_REQUEST_TARGET_NAME']
|
160
|
+
if request_target_name
|
161
|
+
request_target_name = request_target_name.to_s.upcase
|
162
|
+
response_packet_name = extra['HTTP_PACKET']
|
163
|
+
error_packet_name = extra['HTTP_ERROR_PACKET']
|
164
|
+
status = extra['HTTP_STATUS'].to_i
|
165
|
+
if status >= 300 and error_packet_name
|
166
|
+
# Handle error special case response packet
|
167
|
+
packet.target_name = request_target_name
|
168
|
+
packet.packet_name = error_packet_name.to_s.upcase
|
169
|
+
else
|
170
|
+
if response_packet_name
|
171
|
+
packet.target_name = request_target_name
|
172
|
+
packet.packet_name = response_packet_name.to_s.upcase
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if not @include_request_in_response
|
178
|
+
extra.delete("HTTP_REQUEST")
|
179
|
+
end
|
180
|
+
extra.delete("HTTP_REQUEST_TARGET_NAME")
|
181
|
+
extra.delete("HTTP_REQUEST_PACKET_NAME")
|
182
|
+
packet.extra = extra
|
183
|
+
end
|
184
|
+
|
185
|
+
return packet
|
186
|
+
end
|
187
|
+
|
188
|
+
# Called to convert a packet into the data to send
|
189
|
+
#
|
190
|
+
# @param packet [Packet] Packet to extract data from
|
191
|
+
# @return data
|
192
|
+
def convert_packet_to_data(packet)
|
193
|
+
extra = packet.extra
|
194
|
+
extra ||= {}
|
195
|
+
data = packet.buffer(true) # Copy buffer so logged command isn't modified
|
196
|
+
extra['HTTP_URI'] = URI("#{@url}#{packet.read('HTTP_PATH')}").to_s
|
197
|
+
extra['HTTP_REQUEST_TARGET_NAME'] = packet.target_name
|
198
|
+
extra['HTTP_REQUEST_PACKET_NAME'] = packet.packet_name
|
199
|
+
return data, extra
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
# Copyright 2023 OpenC3, Inc.
|
4
|
+
# All Rights Reserved.
|
5
|
+
#
|
6
|
+
# This program is free software; you can modify and/or redistribute it
|
7
|
+
# under the terms of the GNU Affero General Public License
|
8
|
+
# as published by the Free Software Foundation; version 3 with
|
9
|
+
# attribution addendums as found in the LICENSE.txt
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU Affero General Public License for more details.
|
15
|
+
#
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
|
+
# if purchased from OpenC3, Inc.
|
18
|
+
|
19
|
+
require 'openc3/interfaces/interface'
|
20
|
+
require 'openc3/config/config_parser'
|
21
|
+
require 'openc3/accessors/http_accessor'
|
22
|
+
require 'webrick'
|
23
|
+
|
24
|
+
module OpenC3
|
25
|
+
class HttpServerInterface < Interface
|
26
|
+
# @param port [Integer] HTTP port
|
27
|
+
def initialize(port = 80)
|
28
|
+
super()
|
29
|
+
@port = Integer(port)
|
30
|
+
@server = nil
|
31
|
+
@request_queue = Queue.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Connects the interface to its target(s)
|
35
|
+
def connect
|
36
|
+
@server = WEBrick::HTTPServer.new :Port => @port
|
37
|
+
@request_queue = Queue.new
|
38
|
+
|
39
|
+
# Create a response hook for every command packet
|
40
|
+
@target_names.each do |target_name|
|
41
|
+
System.commands.packets(target_name).each do |packet_name, packet|
|
42
|
+
packet.restore_defaults
|
43
|
+
path = nil
|
44
|
+
begin
|
45
|
+
path = packet.read('HTTP_PATH')
|
46
|
+
rescue => err
|
47
|
+
# No HTTP_PATH is an error
|
48
|
+
Logger.error("HttpServerInterface Packet #{target_name} #{packet_name} unable to read HTTP_PATH\n#{err.formatted}")
|
49
|
+
end
|
50
|
+
if path
|
51
|
+
@server.mount_proc path do |req, res|
|
52
|
+
# Build the Response
|
53
|
+
begin
|
54
|
+
status = packet.read('HTTP_STATUS')
|
55
|
+
if status
|
56
|
+
res.status = status
|
57
|
+
end
|
58
|
+
rescue
|
59
|
+
# No HTTP_STATUS - Leave at default
|
60
|
+
end
|
61
|
+
|
62
|
+
if packet.extra
|
63
|
+
headers = packet.extra['HTTP_HEADERS']
|
64
|
+
if headers
|
65
|
+
headers.each do |key, value|
|
66
|
+
res[key] = value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
res.body = packet.buffer
|
72
|
+
|
73
|
+
# Save the Request
|
74
|
+
packet_name = nil
|
75
|
+
begin
|
76
|
+
packet_name = packet.read('HTTP_PACKET')
|
77
|
+
rescue
|
78
|
+
# No packet name means dont save the request as telemetry
|
79
|
+
end
|
80
|
+
if packet_name
|
81
|
+
data = req.body.to_s.dup # Dup to remove frozen
|
82
|
+
extra = {}
|
83
|
+
extra['HTTP_REQUEST_TARGET_NAME'] = target_name
|
84
|
+
extra['HTTP_REQUEST_PACKET_NAME'] = packet_name
|
85
|
+
|
86
|
+
headers = req.header
|
87
|
+
if headers
|
88
|
+
extra['HTTP_HEADERS'] = {}
|
89
|
+
headers.each do |key, value|
|
90
|
+
extra['HTTP_HEADERS'][key.downcase] = value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
queries = req.query
|
95
|
+
if queries
|
96
|
+
extra['HTTP_QUERIES'] = {}
|
97
|
+
queries.each do |key, value|
|
98
|
+
extra['HTTP_QUERIES'][key] = value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@request_queue << [data, extra]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
super()
|
110
|
+
|
111
|
+
Thread.new do
|
112
|
+
# This blocks, but will be unblocked by server.shutdown called in disconnect()
|
113
|
+
@server.start
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def connected?
|
118
|
+
if @server
|
119
|
+
return true
|
120
|
+
else
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Disconnects the interface from its target(s)
|
126
|
+
def disconnect
|
127
|
+
@server.shutdown if @server
|
128
|
+
@server = nil
|
129
|
+
while @request_queue.length > 0
|
130
|
+
@request_queue.pop
|
131
|
+
end
|
132
|
+
super()
|
133
|
+
@request_queue.push(nil)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Reads from the socket if the read_port is defined
|
137
|
+
def read_interface
|
138
|
+
# Get the Faraday Response
|
139
|
+
data, extra = @request_queue.pop
|
140
|
+
return nil if data.nil?
|
141
|
+
|
142
|
+
read_interface_base(data, extra)
|
143
|
+
return data, extra
|
144
|
+
end
|
145
|
+
|
146
|
+
# Writes to the socket
|
147
|
+
# @param data [Hash] For the HTTP Interface, data is a hash with the needed request info
|
148
|
+
def write_interface(data, extra = nil)
|
149
|
+
raise "Commands cannot be sent to HttpServerInterface"
|
150
|
+
end
|
151
|
+
|
152
|
+
# Called to convert the read data into a OpenC3 Packet object
|
153
|
+
#
|
154
|
+
# @param data [String] Raw packet data
|
155
|
+
# @return [Packet] OpenC3 Packet with buffer filled with data
|
156
|
+
def convert_data_to_packet(data, extra = nil)
|
157
|
+
packet = Packet.new(nil, nil, :BIG_ENDIAN, nil, data.to_s)
|
158
|
+
packet.accessor = HttpAccessor.new(packet)
|
159
|
+
if extra
|
160
|
+
# Identify the response
|
161
|
+
request_target_name = extra['HTTP_REQUEST_TARGET_NAME']
|
162
|
+
request_packet_name = extra['HTTP_REQUEST_PACKET_NAME']
|
163
|
+
if request_target_name and request_packet_name
|
164
|
+
packet.target_name = request_target_name.to_s.upcase
|
165
|
+
packet.packet_name = request_packet_name.to_s.upcase
|
166
|
+
end
|
167
|
+
extra.delete("HTTP_REQUEST_TARGET_NAME")
|
168
|
+
extra.delete("HTTP_REQUEST_PACKET_NAME")
|
169
|
+
packet.extra = extra
|
170
|
+
end
|
171
|
+
|
172
|
+
return packet
|
173
|
+
end
|
174
|
+
|
175
|
+
# Called to convert a packet into the data to send
|
176
|
+
#
|
177
|
+
# @param packet [Packet] Packet to extract data from
|
178
|
+
# @return data
|
179
|
+
def convert_packet_to_data(packet)
|
180
|
+
raise "Commands cannot be sent to HttpServerInterface"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|