fix(kafka_consumer): rename `force_utf8` to `none`

We actually enforce the key/value to be a valid UTF-8 string when
using `emqx_json:encode`, if we do encode using that, which is
template-dependent.
This commit is contained in:
Thales Macedo Garitezi 2023-03-13 17:08:07 -03:00
parent 37a35b22df
commit fc5dfa108a
5 changed files with 134 additions and 19 deletions

View File

@ -633,10 +633,12 @@ emqx_ee_bridge_kafka {
desc {
en: "Defines how the key or value from the Kafka message is"
" dealt with before being forwarded via MQTT.\n"
"<code>force_utf8</code> Uses UTF-8 encoding directly from the original message.\n"
"<code>none</code> Uses the key or value from the Kafka message unchanged."
" Note: in this case, then the key or value must be a valid UTF-8 string.\n"
"<code>base64</code> Uses base-64 encoding on the received key or value."
zh: "定义了在通过MQTT转发之前如何处理Kafka消息的键或值。"
"<code>force_utf8</code> 直接使用原始信息的UTF-8编码。\n"
"<code>none</code> 使用Kafka消息中的键或值不改变。"
" 注意在这种情况下那么键或值必须是一个有效的UTF-8字符串。\n"
"<code>base64</code> 对收到的密钥或值使用base-64编码。"
}
label {

View File

@ -109,7 +109,7 @@ values(consumer) ->
offset_reset_policy => <<"reset_to_latest">>,
offset_commit_interval_seconds => 5
},
key_encoding_mode => <<"force_utf8">>,
key_encoding_mode => <<"none">>,
topic_mapping => [
#{
kafka_topic => <<"kafka-topic-1">>,
@ -124,7 +124,7 @@ values(consumer) ->
payload_template => <<"v = ${.value}">>
}
],
value_encoding_mode => <<"force_utf8">>
value_encoding_mode => <<"none">>
}.
%% -------------------------------------------------------------------------------------------------
@ -334,22 +334,16 @@ fields(consumer_opts) ->
#{
required => true,
desc => ?DESC(consumer_topic_mapping),
validator =>
fun
([]) ->
{error, "There must be at least one Kafka-MQTT topic mapping"};
([_ | _]) ->
ok
end
validator => fun consumer_topic_mapping_validator/1
}
)},
{key_encoding_mode,
mk(enum([force_utf8, base64]), #{
default => force_utf8, desc => ?DESC(consumer_encoding_mode)
mk(enum([none, base64]), #{
default => none, desc => ?DESC(consumer_encoding_mode)
})},
{value_encoding_mode,
mk(enum([force_utf8, base64]), #{
default => force_utf8, desc => ?DESC(consumer_encoding_mode)
mk(enum([none, base64]), #{
default => none, desc => ?DESC(consumer_encoding_mode)
})}
];
fields(consumer_topic_mapping) ->
@ -449,3 +443,16 @@ kafka_producer_converter(
kafka_producer_converter(Config, _HoconOpts) ->
%% new schema
Config.
consumer_topic_mapping_validator(_TopicMapping = []) ->
{error, "There must be at least one Kafka-MQTT topic mapping"};
consumer_topic_mapping_validator(TopicMapping = [_ | _]) ->
NumEntries = length(TopicMapping),
KafkaTopics = [KT || #{<<"kafka_topic">> := KT} <- TopicMapping],
DistinctKafkaTopics = length(lists:usort(KafkaTopics)),
case DistinctKafkaTopics =:= NumEntries of
true ->
ok;
false ->
{error, "Kafka topics must not be repeated in a bridge"}
end.

View File

@ -61,7 +61,7 @@
}.
-type offset_reset_policy() :: reset_to_latest | reset_to_earliest | reset_by_subscriber.
%% -type mqtt_payload() :: full_message | message_value.
-type encoding_mode() :: force_utf8 | base64.
-type encoding_mode() :: none | base64.
-type consumer_init_data() :: #{
hookpoint := binary(),
key_encoding_mode := encoding_mode(),
@ -490,7 +490,7 @@ render(FullMessage, PayloadTemplate) ->
},
emqx_plugin_libs_rule:proc_tmpl(PayloadTemplate, FullMessage, Opts).
encode(Value, force_utf8) ->
encode(Value, none) ->
Value;
encode(Value, base64) ->
base64:encode(Value).

View File

@ -576,8 +576,8 @@ kafka_config(TestCase, _KafkaType, Config) ->
" offset_reset_policy = reset_to_latest\n"
" }\n"
"~s"
" key_encoding_mode = force_utf8\n"
" value_encoding_mode = force_utf8\n"
" key_encoding_mode = none\n"
" value_encoding_mode = none\n"
" ssl {\n"
" enable = ~p\n"
" verify = verify_none\n"

View File

@ -76,6 +76,68 @@ kafka_producer_test() ->
ok.
kafka_consumer_test() ->
Conf1 = parse(kafka_consumer_hocon()),
?assertMatch(
#{
<<"bridges">> :=
#{
<<"kafka_consumer">> :=
#{
<<"my_consumer">> := _
}
}
},
check(Conf1)
),
%% Bad: can't repeat kafka topics.
BadConf1 = emqx_map_lib:deep_put(
[<<"bridges">>, <<"kafka_consumer">>, <<"my_consumer">>, <<"topic_mapping">>],
Conf1,
[
#{
<<"kafka_topic">> => <<"t1">>,
<<"mqtt_topic">> => <<"mqtt/t1">>,
<<"qos">> => 1,
<<"payload_template">> => <<"${.}">>
},
#{
<<"kafka_topic">> => <<"t1">>,
<<"mqtt_topic">> => <<"mqtt/t2">>,
<<"qos">> => 2,
<<"payload_template">> => <<"v = ${.value}">>
}
]
),
?assertThrow(
{_, [
#{
path := "bridges.kafka_consumer.my_consumer.topic_mapping",
reason := "Kafka topics must not be repeated in a bridge"
}
]},
check(BadConf1)
),
%% Bad: there must be at least 1 mapping.
BadConf2 = emqx_map_lib:deep_put(
[<<"bridges">>, <<"kafka_consumer">>, <<"my_consumer">>, <<"topic_mapping">>],
Conf1,
[]
),
?assertThrow(
{_, [
#{
path := "bridges.kafka_consumer.my_consumer.topic_mapping",
reason := "There must be at least one Kafka-MQTT topic mapping"
}
]},
check(BadConf2)
),
ok.
%%===========================================================================
%% Helper functions
%%===========================================================================
@ -179,3 +241,47 @@ kafka_producer_new_hocon() ->
" }\n"
"}\n"
"".
%% erlfmt-ignore
kafka_consumer_hocon() ->
"""
bridges.kafka_consumer.my_consumer {
enable = true
bootstrap_hosts = \"kafka-1.emqx.net:9292\"
connect_timeout = 5s
min_metadata_refresh_interval = 3s
metadata_request_timeout = 5s
authentication = {
mechanism = plain
username = emqxuser
password = password
}
kafka {
max_batch_bytes = 896KB
max_rejoin_attempts = 5
offset_commit_interval_seconds = 3
offset_reset_policy = reset_to_latest
}
topic_mapping = [
{
kafka_topic = \"kafka-topic-1\"
mqtt_topic = \"mqtt/topic/1\"
qos = 1
payload_template = \"${.}\"
},
{
kafka_topic = \"kafka-topic-2\"
mqtt_topic = \"mqtt/topic/2\"
qos = 2
payload_template = \"v = ${.value}\"
}
]
key_encoding_mode = none
value_encoding_mode = none
ssl {
enable = false
verify = verify_none
server_name_indication = \"auto\"
}
}
""".