kafka-python 3.0.0__py3-none-any.whl
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.
- kafka/__init__.py +34 -0
- kafka/__main__.py +5 -0
- kafka/admin/__init__.py +29 -0
- kafka/admin/__main__.py +5 -0
- kafka/admin/_acls.py +355 -0
- kafka/admin/_cluster.py +359 -0
- kafka/admin/_configs.py +479 -0
- kafka/admin/_groups.py +754 -0
- kafka/admin/_partitions.py +595 -0
- kafka/admin/_topics.py +281 -0
- kafka/admin/_transactions.py +450 -0
- kafka/admin/_users.py +194 -0
- kafka/admin/client.py +373 -0
- kafka/benchmarks/__init__.py +0 -0
- kafka/benchmarks/consumer_performance.py +138 -0
- kafka/benchmarks/load_example.py +109 -0
- kafka/benchmarks/producer_encode_path.py +201 -0
- kafka/benchmarks/producer_performance.py +161 -0
- kafka/benchmarks/profile_protocol.py +138 -0
- kafka/benchmarks/protocol_old_vs_new.py +447 -0
- kafka/benchmarks/record_batch_compose.py +77 -0
- kafka/benchmarks/record_batch_read.py +82 -0
- kafka/benchmarks/varint_speed.py +426 -0
- kafka/cli/__init__.py +36 -0
- kafka/cli/admin/__init__.py +117 -0
- kafka/cli/admin/acls/__init__.py +9 -0
- kafka/cli/admin/acls/common.py +76 -0
- kafka/cli/admin/acls/create.py +19 -0
- kafka/cli/admin/acls/delete.py +23 -0
- kafka/cli/admin/acls/describe.py +16 -0
- kafka/cli/admin/cluster/__init__.py +14 -0
- kafka/cli/admin/cluster/describe.py +11 -0
- kafka/cli/admin/cluster/describe_quorum.py +11 -0
- kafka/cli/admin/cluster/features.py +52 -0
- kafka/cli/admin/cluster/log_dirs.py +43 -0
- kafka/cli/admin/cluster/versions.py +33 -0
- kafka/cli/admin/configs/__init__.py +10 -0
- kafka/cli/admin/configs/alter.py +43 -0
- kafka/cli/admin/configs/common.py +17 -0
- kafka/cli/admin/configs/describe.py +30 -0
- kafka/cli/admin/configs/list.py +16 -0
- kafka/cli/admin/configs/reset.py +20 -0
- kafka/cli/admin/groups/__init__.py +16 -0
- kafka/cli/admin/groups/alter_offsets.py +30 -0
- kafka/cli/admin/groups/delete.py +11 -0
- kafka/cli/admin/groups/delete_offsets.py +29 -0
- kafka/cli/admin/groups/describe.py +11 -0
- kafka/cli/admin/groups/list.py +28 -0
- kafka/cli/admin/groups/list_offsets.py +29 -0
- kafka/cli/admin/groups/remove_members.py +40 -0
- kafka/cli/admin/groups/reset_offsets.py +139 -0
- kafka/cli/admin/partitions/__init__.py +21 -0
- kafka/cli/admin/partitions/alter_reassignments.py +37 -0
- kafka/cli/admin/partitions/create.py +27 -0
- kafka/cli/admin/partitions/delete_records.py +31 -0
- kafka/cli/admin/partitions/describe.py +36 -0
- kafka/cli/admin/partitions/elect_leaders.py +53 -0
- kafka/cli/admin/partitions/list_offsets.py +88 -0
- kafka/cli/admin/partitions/list_reassignments.py +35 -0
- kafka/cli/admin/topics/__init__.py +10 -0
- kafka/cli/admin/topics/create.py +13 -0
- kafka/cli/admin/topics/delete.py +19 -0
- kafka/cli/admin/topics/describe.py +18 -0
- kafka/cli/admin/topics/list.py +11 -0
- kafka/cli/admin/transactions/__init__.py +17 -0
- kafka/cli/admin/transactions/abort.py +38 -0
- kafka/cli/admin/transactions/describe.py +24 -0
- kafka/cli/admin/transactions/describe_producers.py +29 -0
- kafka/cli/admin/transactions/find_hanging.py +26 -0
- kafka/cli/admin/transactions/list.py +37 -0
- kafka/cli/admin/users/__init__.py +8 -0
- kafka/cli/admin/users/alter_user_scram_credentials.py +34 -0
- kafka/cli/admin/users/describe_user_scram_credentials.py +15 -0
- kafka/cli/common.py +95 -0
- kafka/cli/consumer/__init__.py +63 -0
- kafka/cli/producer/__init__.py +57 -0
- kafka/cluster.py +824 -0
- kafka/codec.py +325 -0
- kafka/consumer/__init__.py +5 -0
- kafka/consumer/__main__.py +5 -0
- kafka/consumer/fetcher.py +2012 -0
- kafka/consumer/group.py +1347 -0
- kafka/consumer/subscription_state.py +897 -0
- kafka/coordinator/__init__.py +0 -0
- kafka/coordinator/assignors/__init__.py +0 -0
- kafka/coordinator/assignors/abstract.py +90 -0
- kafka/coordinator/assignors/cooperative_sticky.py +167 -0
- kafka/coordinator/assignors/range.py +81 -0
- kafka/coordinator/assignors/roundrobin.py +101 -0
- kafka/coordinator/assignors/sticky/StickyAssignorUserData.json +37 -0
- kafka/coordinator/assignors/sticky/__init__.py +0 -0
- kafka/coordinator/assignors/sticky/partition_movements.py +149 -0
- kafka/coordinator/assignors/sticky/sorted_set.py +63 -0
- kafka/coordinator/assignors/sticky/sticky_assignor.py +665 -0
- kafka/coordinator/assignors/sticky/user_data.py +8 -0
- kafka/coordinator/base.py +1215 -0
- kafka/coordinator/consumer.py +1224 -0
- kafka/coordinator/heartbeat.py +82 -0
- kafka/coordinator/subscription.py +34 -0
- kafka/errors.py +1004 -0
- kafka/future.py +166 -0
- kafka/metrics/__init__.py +13 -0
- kafka/metrics/compound_stat.py +33 -0
- kafka/metrics/dict_reporter.py +81 -0
- kafka/metrics/kafka_metric.py +36 -0
- kafka/metrics/measurable.py +27 -0
- kafka/metrics/measurable_stat.py +13 -0
- kafka/metrics/metric_config.py +33 -0
- kafka/metrics/metric_name.py +105 -0
- kafka/metrics/metrics.py +261 -0
- kafka/metrics/metrics_reporter.py +53 -0
- kafka/metrics/quota.py +41 -0
- kafka/metrics/stat.py +19 -0
- kafka/metrics/stats/__init__.py +15 -0
- kafka/metrics/stats/avg.py +24 -0
- kafka/metrics/stats/count.py +17 -0
- kafka/metrics/stats/histogram.py +99 -0
- kafka/metrics/stats/max_stat.py +17 -0
- kafka/metrics/stats/min_stat.py +19 -0
- kafka/metrics/stats/percentile.py +14 -0
- kafka/metrics/stats/percentiles.py +75 -0
- kafka/metrics/stats/rate.py +118 -0
- kafka/metrics/stats/sampled_stat.py +99 -0
- kafka/metrics/stats/sensor.py +136 -0
- kafka/metrics/stats/total.py +15 -0
- kafka/net/__init__.py +19 -0
- kafka/net/compat.py +165 -0
- kafka/net/connection.py +593 -0
- kafka/net/http_connect.py +144 -0
- kafka/net/inet.py +122 -0
- kafka/net/manager.py +451 -0
- kafka/net/metrics.py +149 -0
- kafka/net/sasl/__init__.py +32 -0
- kafka/net/sasl/abc.py +28 -0
- kafka/net/sasl/gssapi.py +95 -0
- kafka/net/sasl/msk.py +245 -0
- kafka/net/sasl/oauth.py +98 -0
- kafka/net/sasl/plain.py +42 -0
- kafka/net/sasl/scram.py +135 -0
- kafka/net/sasl/sspi.py +111 -0
- kafka/net/selector.py +644 -0
- kafka/net/socks5.py +262 -0
- kafka/net/transport.py +415 -0
- kafka/net/wakeup_notifier.py +72 -0
- kafka/partitioner/__init__.py +8 -0
- kafka/partitioner/abc.py +8 -0
- kafka/partitioner/default.py +89 -0
- kafka/partitioner/sticky.py +109 -0
- kafka/producer/__init__.py +5 -0
- kafka/producer/__main__.py +5 -0
- kafka/producer/future.py +101 -0
- kafka/producer/kafka.py +1123 -0
- kafka/producer/producer_batch.py +192 -0
- kafka/producer/record_accumulator.py +647 -0
- kafka/producer/sender.py +884 -0
- kafka/producer/transaction_manager.py +1326 -0
- kafka/protocol/__init__.py +0 -0
- kafka/protocol/admin/__init__.py +29 -0
- kafka/protocol/admin/acl.py +83 -0
- kafka/protocol/admin/acl.pyi +375 -0
- kafka/protocol/admin/client_quotas.py +14 -0
- kafka/protocol/admin/client_quotas.pyi +265 -0
- kafka/protocol/admin/cluster.py +31 -0
- kafka/protocol/admin/cluster.pyi +620 -0
- kafka/protocol/admin/configs.py +22 -0
- kafka/protocol/admin/configs.pyi +437 -0
- kafka/protocol/admin/groups.py +24 -0
- kafka/protocol/admin/groups.pyi +261 -0
- kafka/protocol/admin/topics.py +53 -0
- kafka/protocol/admin/topics.pyi +982 -0
- kafka/protocol/admin/transactions.py +18 -0
- kafka/protocol/admin/transactions.pyi +311 -0
- kafka/protocol/admin/users.py +14 -0
- kafka/protocol/admin/users.pyi +223 -0
- kafka/protocol/api_data.py +125 -0
- kafka/protocol/api_header.py +55 -0
- kafka/protocol/api_key.py +97 -0
- kafka/protocol/api_message.py +277 -0
- kafka/protocol/broker_version_data.py +246 -0
- kafka/protocol/consumer/__init__.py +13 -0
- kafka/protocol/consumer/fetch.py +16 -0
- kafka/protocol/consumer/fetch.pyi +298 -0
- kafka/protocol/consumer/group.py +38 -0
- kafka/protocol/consumer/group.pyi +824 -0
- kafka/protocol/consumer/metadata.py +30 -0
- kafka/protocol/consumer/metadata.pyi +89 -0
- kafka/protocol/consumer/offsets.py +75 -0
- kafka/protocol/consumer/offsets.pyi +288 -0
- kafka/protocol/data_container.py +166 -0
- kafka/protocol/frame.py +30 -0
- kafka/protocol/generate_stubs.py +468 -0
- kafka/protocol/metadata/__init__.py +10 -0
- kafka/protocol/metadata/api_versions.py +41 -0
- kafka/protocol/metadata/api_versions.pyi +128 -0
- kafka/protocol/metadata/find_coordinator.py +19 -0
- kafka/protocol/metadata/find_coordinator.pyi +105 -0
- kafka/protocol/metadata/metadata.py +34 -0
- kafka/protocol/metadata/metadata.pyi +160 -0
- kafka/protocol/old/__init__.py +0 -0
- kafka/protocol/old/abstract.py +17 -0
- kafka/protocol/old/add_offsets_to_txn.py +54 -0
- kafka/protocol/old/add_partitions_to_txn.py +71 -0
- kafka/protocol/old/admin.py +1086 -0
- kafka/protocol/old/api.py +205 -0
- kafka/protocol/old/api_versions.py +133 -0
- kafka/protocol/old/commit.py +355 -0
- kafka/protocol/old/consumer_protocol.py +36 -0
- kafka/protocol/old/end_txn.py +53 -0
- kafka/protocol/old/fetch.py +408 -0
- kafka/protocol/old/find_coordinator.py +72 -0
- kafka/protocol/old/group.py +451 -0
- kafka/protocol/old/init_producer_id.py +42 -0
- kafka/protocol/old/list_offsets.py +186 -0
- kafka/protocol/old/metadata.py +290 -0
- kafka/protocol/old/offset_for_leader_epoch.py +133 -0
- kafka/protocol/old/produce.py +247 -0
- kafka/protocol/old/sasl_authenticate.py +38 -0
- kafka/protocol/old/sasl_handshake.py +39 -0
- kafka/protocol/old/struct.py +87 -0
- kafka/protocol/old/txn_offset_commit.py +73 -0
- kafka/protocol/old/types.py +440 -0
- kafka/protocol/parser.py +191 -0
- kafka/protocol/producer/__init__.py +7 -0
- kafka/protocol/producer/produce.py +17 -0
- kafka/protocol/producer/produce.pyi +197 -0
- kafka/protocol/producer/transaction.py +30 -0
- kafka/protocol/producer/transaction.pyi +663 -0
- kafka/protocol/sasl.py +52 -0
- kafka/protocol/sasl.pyi +126 -0
- kafka/protocol/schemas/__init__.py +7 -0
- kafka/protocol/schemas/fields/__init__.py +7 -0
- kafka/protocol/schemas/fields/array.py +127 -0
- kafka/protocol/schemas/fields/base.py +156 -0
- kafka/protocol/schemas/fields/codecs/__init__.py +12 -0
- kafka/protocol/schemas/fields/codecs/encode_buffer.py +82 -0
- kafka/protocol/schemas/fields/codecs/tagged_fields.py +109 -0
- kafka/protocol/schemas/fields/codecs/types.py +505 -0
- kafka/protocol/schemas/fields/codegen.py +40 -0
- kafka/protocol/schemas/fields/simple.py +127 -0
- kafka/protocol/schemas/fields/struct.py +357 -0
- kafka/protocol/schemas/fields/struct_array.py +142 -0
- kafka/protocol/schemas/load_json.py +42 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnRequest.json +40 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnResponse.json +35 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnRequest.json +65 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnResponse.json +60 -0
- kafka/protocol/schemas/resources/AlterClientQuotasRequest.json +47 -0
- kafka/protocol/schemas/resources/AlterClientQuotasResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterConfigsRequest.json +43 -0
- kafka/protocol/schemas/resources/AlterConfigsResponse.json +39 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsRequest.json +42 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsResponse.json +47 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsRequest.json +41 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsRequest.json +45 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsResponse.json +35 -0
- kafka/protocol/schemas/resources/ApiVersionsRequest.json +34 -0
- kafka/protocol/schemas/resources/ApiVersionsResponse.json +79 -0
- kafka/protocol/schemas/resources/ConsumerProtocolAssignment.json +42 -0
- kafka/protocol/schemas/resources/ConsumerProtocolSubscription.json +49 -0
- kafka/protocol/schemas/resources/CreateAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/CreateAclsResponse.json +37 -0
- kafka/protocol/schemas/resources/CreatePartitionsRequest.json +47 -0
- kafka/protocol/schemas/resources/CreatePartitionsResponse.json +41 -0
- kafka/protocol/schemas/resources/CreateTopicsRequest.json +65 -0
- kafka/protocol/schemas/resources/CreateTopicsResponse.json +72 -0
- kafka/protocol/schemas/resources/DeleteAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/DeleteAclsResponse.json +59 -0
- kafka/protocol/schemas/resources/DeleteGroupsRequest.json +30 -0
- kafka/protocol/schemas/resources/DeleteGroupsResponse.json +36 -0
- kafka/protocol/schemas/resources/DeleteRecordsRequest.json +42 -0
- kafka/protocol/schemas/resources/DeleteRecordsResponse.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsRequest.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeAclsRequest.json +43 -0
- kafka/protocol/schemas/resources/DescribeAclsResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasRequest.json +37 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasResponse.json +47 -0
- kafka/protocol/schemas/resources/DescribeClusterRequest.json +35 -0
- kafka/protocol/schemas/resources/DescribeClusterResponse.json +56 -0
- kafka/protocol/schemas/resources/DescribeConfigsRequest.json +42 -0
- kafka/protocol/schemas/resources/DescribeConfigsResponse.json +69 -0
- kafka/protocol/schemas/resources/DescribeGroupsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeGroupsResponse.json +74 -0
- kafka/protocol/schemas/resources/DescribeLogDirsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeLogDirsResponse.json +65 -0
- kafka/protocol/schemas/resources/DescribeProducersRequest.json +32 -0
- kafka/protocol/schemas/resources/DescribeProducersResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeQuorumRequest.json +39 -0
- kafka/protocol/schemas/resources/DescribeQuorumResponse.json +82 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsRequest.json +40 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsResponse.json +66 -0
- kafka/protocol/schemas/resources/DescribeTransactionsRequest.json +27 -0
- kafka/protocol/schemas/resources/DescribeTransactionsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsRequest.json +30 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsResponse.json +45 -0
- kafka/protocol/schemas/resources/ElectLeadersRequest.json +41 -0
- kafka/protocol/schemas/resources/ElectLeadersResponse.json +45 -0
- kafka/protocol/schemas/resources/EndTxnRequest.json +43 -0
- kafka/protocol/schemas/resources/EndTxnResponse.json +41 -0
- kafka/protocol/schemas/resources/FetchRequest.json +125 -0
- kafka/protocol/schemas/resources/FetchResponse.json +124 -0
- kafka/protocol/schemas/resources/FindCoordinatorRequest.json +43 -0
- kafka/protocol/schemas/resources/FindCoordinatorResponse.json +58 -0
- kafka/protocol/schemas/resources/HeartbeatRequest.json +39 -0
- kafka/protocol/schemas/resources/HeartbeatResponse.json +35 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsRequest.json +44 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsResponse.json +38 -0
- kafka/protocol/schemas/resources/InitProducerIdRequest.json +50 -0
- kafka/protocol/schemas/resources/InitProducerIdResponse.json +47 -0
- kafka/protocol/schemas/resources/JoinGroupRequest.json +63 -0
- kafka/protocol/schemas/resources/JoinGroupResponse.json +69 -0
- kafka/protocol/schemas/resources/LeaveGroupRequest.json +47 -0
- kafka/protocol/schemas/resources/LeaveGroupResponse.json +47 -0
- kafka/protocol/schemas/resources/ListConfigResourcesRequest.json +31 -0
- kafka/protocol/schemas/resources/ListConfigResourcesResponse.json +37 -0
- kafka/protocol/schemas/resources/ListGroupsRequest.json +36 -0
- kafka/protocol/schemas/resources/ListGroupsResponse.json +49 -0
- kafka/protocol/schemas/resources/ListOffsetsRequest.json +72 -0
- kafka/protocol/schemas/resources/ListOffsetsResponse.json +71 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsRequest.json +34 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsResponse.json +46 -0
- kafka/protocol/schemas/resources/ListTransactionsRequest.json +40 -0
- kafka/protocol/schemas/resources/ListTransactionsResponse.json +42 -0
- kafka/protocol/schemas/resources/MetadataRequest.json +56 -0
- kafka/protocol/schemas/resources/MetadataResponse.json +101 -0
- kafka/protocol/schemas/resources/OffsetCommitRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetCommitResponse.json +71 -0
- kafka/protocol/schemas/resources/OffsetDeleteRequest.json +39 -0
- kafka/protocol/schemas/resources/OffsetDeleteResponse.json +42 -0
- kafka/protocol/schemas/resources/OffsetFetchRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetFetchResponse.json +107 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochRequest.json +52 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochResponse.json +51 -0
- kafka/protocol/schemas/resources/ProduceRequest.json +73 -0
- kafka/protocol/schemas/resources/ProduceResponse.json +96 -0
- kafka/protocol/schemas/resources/RequestHeader.json +44 -0
- kafka/protocol/schemas/resources/ResponseHeader.json +26 -0
- kafka/protocol/schemas/resources/SaslAuthenticateRequest.json +29 -0
- kafka/protocol/schemas/resources/SaslAuthenticateResponse.json +34 -0
- kafka/protocol/schemas/resources/SaslHandshakeRequest.json +31 -0
- kafka/protocol/schemas/resources/SaslHandshakeResponse.json +32 -0
- kafka/protocol/schemas/resources/SyncGroupRequest.json +56 -0
- kafka/protocol/schemas/resources/SyncGroupResponse.json +46 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitRequest.json +68 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitResponse.json +47 -0
- kafka/protocol/schemas/resources/UpdateFeaturesRequest.json +43 -0
- kafka/protocol/schemas/resources/UpdateFeaturesResponse.json +39 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersRequest.json +49 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersResponse.json +45 -0
- kafka/protocol/schemas/resources/__init__.py +0 -0
- kafka/record/__init__.py +3 -0
- kafka/record/_crc32c.py +161 -0
- kafka/record/abc.py +144 -0
- kafka/record/default_records.py +782 -0
- kafka/record/legacy_records.py +587 -0
- kafka/record/memory_records.py +255 -0
- kafka/record/util.py +135 -0
- kafka/serializer/__init__.py +4 -0
- kafka/serializer/abstract.py +20 -0
- kafka/serializer/default.py +16 -0
- kafka/serializer/json.py +17 -0
- kafka/serializer/wrapper.py +21 -0
- kafka/structs.py +69 -0
- kafka/util.py +159 -0
- kafka/vendor/__init__.py +0 -0
- kafka/version.py +1 -0
- kafka_python-3.0.0.dist-info/METADATA +319 -0
- kafka_python-3.0.0.dist-info/RECORD +373 -0
- kafka_python-3.0.0.dist-info/WHEEL +5 -0
- kafka_python-3.0.0.dist-info/entry_points.txt +2 -0
- kafka_python-3.0.0.dist-info/licenses/LICENSE +202 -0
- kafka_python-3.0.0.dist-info/top_level.txt +1 -0
kafka/net/socks5.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import errno
|
|
2
|
+
import logging
|
|
3
|
+
import random
|
|
4
|
+
import socket
|
|
5
|
+
import struct
|
|
6
|
+
from urllib.parse import urlparse
|
|
7
|
+
|
|
8
|
+
from kafka.errors import KafkaConnectionError
|
|
9
|
+
from kafka.net.inet import KafkaNetSocket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProxyConnectionStates:
|
|
16
|
+
DISCONNECTED = '<disconnected>'
|
|
17
|
+
CONNECTING = '<connecting>'
|
|
18
|
+
NEGOTIATE_PROPOSE = '<negotiate_propose>'
|
|
19
|
+
NEGOTIATING = '<negotiating>'
|
|
20
|
+
AUTHENTICATING = '<authenticating>'
|
|
21
|
+
REQUEST_SUBMIT = '<request_submit>'
|
|
22
|
+
REQUESTING = '<requesting>'
|
|
23
|
+
READ_ADDRESS = '<read_address>'
|
|
24
|
+
COMPLETE = '<complete>'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Socks5Proxy(KafkaNetSocket):
|
|
28
|
+
"""Socks5 proxy
|
|
29
|
+
|
|
30
|
+
Manages connection through socks5 proxy with support for username/password
|
|
31
|
+
authentication.
|
|
32
|
+
"""
|
|
33
|
+
# socks5h for remote dns
|
|
34
|
+
SCHEMES = ('socks5', 'socks5h')
|
|
35
|
+
|
|
36
|
+
def __init__(self, proxy_url):
|
|
37
|
+
self._buffer_in = b''
|
|
38
|
+
self._buffer_out = b''
|
|
39
|
+
self._proxy_url = urlparse(proxy_url)
|
|
40
|
+
if self._proxy_url.scheme not in self.SCHEMES:
|
|
41
|
+
raise ValueError('Unsupported proxy scheme: %s' % (self._proxy_url.scheme,))
|
|
42
|
+
self._sock = None
|
|
43
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
44
|
+
self._target_afi = socket.AF_UNSPEC
|
|
45
|
+
self._proxy_addr = self._get_proxy_addr()
|
|
46
|
+
|
|
47
|
+
def _get_proxy_addr(self):
|
|
48
|
+
proxy_addrs = self.dns_lookup(self._proxy_url.hostname, self._proxy_url.port, proxy=True)
|
|
49
|
+
if not proxy_addrs:
|
|
50
|
+
raise KafkaConnectionError('Unable to resolve proxy_url via dns')
|
|
51
|
+
return random.choice(proxy_addrs)
|
|
52
|
+
|
|
53
|
+
def _use_remote_lookup(self):
|
|
54
|
+
return self._proxy_url.scheme == 'socks5h'
|
|
55
|
+
|
|
56
|
+
def dns_lookup(self, host, port, proxy=False):
|
|
57
|
+
if proxy:
|
|
58
|
+
return super().dns_lookup(host, port, raise_error=True)
|
|
59
|
+
elif self._use_remote_lookup():
|
|
60
|
+
return [(socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, '', (host, port))]
|
|
61
|
+
else:
|
|
62
|
+
return super().dns_lookup(host, port)
|
|
63
|
+
|
|
64
|
+
def socket(self, family=socket.AF_UNSPEC, sock_type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP):
|
|
65
|
+
"""Open and record a socket.
|
|
66
|
+
|
|
67
|
+
Returns the actual underlying socket
|
|
68
|
+
object to ensure e.g. selects and ssl wrapping works as expected.
|
|
69
|
+
"""
|
|
70
|
+
self._target_afi = family # Store the address family of the target
|
|
71
|
+
proxy_family, _, _, _, _ = self._proxy_addr
|
|
72
|
+
self._sock = socket.socket(proxy_family, sock_type, proto)
|
|
73
|
+
return self._sock
|
|
74
|
+
|
|
75
|
+
def _flush_buf(self):
|
|
76
|
+
"""Send out all data that is stored in the outgoing buffer.
|
|
77
|
+
|
|
78
|
+
It is expected that the caller handles error handling, including non-blocking
|
|
79
|
+
as well as connection failure exceptions.
|
|
80
|
+
"""
|
|
81
|
+
while self._buffer_out:
|
|
82
|
+
sent_bytes = self._sock.send(self._buffer_out)
|
|
83
|
+
self._buffer_out = self._buffer_out[sent_bytes:]
|
|
84
|
+
|
|
85
|
+
def _peek_buf(self, datalen):
|
|
86
|
+
"""Ensure local inbound buffer has enough data, and return that data without
|
|
87
|
+
consuming the local buffer
|
|
88
|
+
|
|
89
|
+
It's expected that the caller handles e.g. blocking exceptions"""
|
|
90
|
+
while True:
|
|
91
|
+
bytes_remaining = datalen - len(self._buffer_in)
|
|
92
|
+
if bytes_remaining <= 0:
|
|
93
|
+
break
|
|
94
|
+
data = self._sock.recv(bytes_remaining)
|
|
95
|
+
if not data:
|
|
96
|
+
break
|
|
97
|
+
self._buffer_in = self._buffer_in + data
|
|
98
|
+
|
|
99
|
+
return self._buffer_in[:datalen]
|
|
100
|
+
|
|
101
|
+
def _read_buf(self, datalen):
|
|
102
|
+
"""Read and consume bytes from socket connection
|
|
103
|
+
|
|
104
|
+
It's expected that the caller handles e.g. blocking exceptions"""
|
|
105
|
+
buf = self._peek_buf(datalen)
|
|
106
|
+
if buf:
|
|
107
|
+
self._buffer_in = self._buffer_in[len(buf):]
|
|
108
|
+
return buf
|
|
109
|
+
|
|
110
|
+
def connect_ex(self, sock, addr):
|
|
111
|
+
"""Runs a state machine through connection to authentication to
|
|
112
|
+
proxy connection request.
|
|
113
|
+
|
|
114
|
+
The somewhat strange setup is to facilitate non-intrusive use from
|
|
115
|
+
BrokerConnection state machine.
|
|
116
|
+
|
|
117
|
+
This function is called with a socket in non-blocking mode. Both
|
|
118
|
+
send and receive calls can return in EWOULDBLOCK/EAGAIN which we
|
|
119
|
+
specifically avoid handling here. These are handled in main
|
|
120
|
+
BrokerConnection connection loop, which then would retry calls
|
|
121
|
+
to this function."""
|
|
122
|
+
assert sock is self._sock
|
|
123
|
+
if self._state == ProxyConnectionStates.DISCONNECTED:
|
|
124
|
+
self._state = ProxyConnectionStates.CONNECTING
|
|
125
|
+
|
|
126
|
+
if self._state == ProxyConnectionStates.CONNECTING:
|
|
127
|
+
_, _, _, _, sockaddr = self._proxy_addr
|
|
128
|
+
ret = self._sock.connect_ex(sockaddr)
|
|
129
|
+
if not ret or ret == errno.EISCONN:
|
|
130
|
+
self._state = ProxyConnectionStates.NEGOTIATE_PROPOSE
|
|
131
|
+
else:
|
|
132
|
+
return ret
|
|
133
|
+
|
|
134
|
+
if self._state == ProxyConnectionStates.NEGOTIATE_PROPOSE:
|
|
135
|
+
if self._proxy_url.username and self._proxy_url.password:
|
|
136
|
+
# Propose username/password
|
|
137
|
+
self._buffer_out = b"\x05\x01\x02"
|
|
138
|
+
else:
|
|
139
|
+
# Propose no auth
|
|
140
|
+
self._buffer_out = b"\x05\x01\x00"
|
|
141
|
+
self._state = ProxyConnectionStates.NEGOTIATING
|
|
142
|
+
|
|
143
|
+
if self._state == ProxyConnectionStates.NEGOTIATING:
|
|
144
|
+
self._flush_buf()
|
|
145
|
+
buf = self._read_buf(2)
|
|
146
|
+
if buf[0:1] != b"\x05":
|
|
147
|
+
log.error("Unrecognized SOCKS version")
|
|
148
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
149
|
+
self._sock.close()
|
|
150
|
+
return errno.ECONNREFUSED
|
|
151
|
+
|
|
152
|
+
if buf[1:2] == b"\x00":
|
|
153
|
+
# No authentication required
|
|
154
|
+
self._state = ProxyConnectionStates.REQUEST_SUBMIT
|
|
155
|
+
elif buf[1:2] == b"\x02":
|
|
156
|
+
# Username/password authentication selected
|
|
157
|
+
userlen = len(self._proxy_url.username)
|
|
158
|
+
passlen = len(self._proxy_url.password)
|
|
159
|
+
self._buffer_out = struct.pack(
|
|
160
|
+
"!bb{}sb{}s".format(userlen, passlen),
|
|
161
|
+
1, # version
|
|
162
|
+
userlen,
|
|
163
|
+
self._proxy_url.username.encode(),
|
|
164
|
+
passlen,
|
|
165
|
+
self._proxy_url.password.encode(),
|
|
166
|
+
)
|
|
167
|
+
self._state = ProxyConnectionStates.AUTHENTICATING
|
|
168
|
+
else:
|
|
169
|
+
log.error("Unrecognized SOCKS authentication method")
|
|
170
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
171
|
+
self._sock.close()
|
|
172
|
+
return errno.ECONNREFUSED
|
|
173
|
+
|
|
174
|
+
if self._state == ProxyConnectionStates.AUTHENTICATING:
|
|
175
|
+
self._flush_buf()
|
|
176
|
+
buf = self._read_buf(2)
|
|
177
|
+
if buf == b"\x01\x00":
|
|
178
|
+
# Authentication succesful
|
|
179
|
+
self._state = ProxyConnectionStates.REQUEST_SUBMIT
|
|
180
|
+
else:
|
|
181
|
+
log.error("Socks5 proxy authentication failure")
|
|
182
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
183
|
+
self._sock.close()
|
|
184
|
+
return errno.ECONNREFUSED
|
|
185
|
+
|
|
186
|
+
if self._state == ProxyConnectionStates.REQUEST_SUBMIT:
|
|
187
|
+
if self._use_remote_lookup():
|
|
188
|
+
addr_type = 3
|
|
189
|
+
addr_len = len(addr[0])
|
|
190
|
+
elif self._target_afi == socket.AF_INET:
|
|
191
|
+
addr_type = 1
|
|
192
|
+
addr_len = 4
|
|
193
|
+
elif self._target_afi == socket.AF_INET6:
|
|
194
|
+
addr_type = 4
|
|
195
|
+
addr_len = 16
|
|
196
|
+
else:
|
|
197
|
+
log.error("Unknown address family, %r", self._target_afi)
|
|
198
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
199
|
+
self._sock.close()
|
|
200
|
+
return errno.ECONNREFUSED
|
|
201
|
+
|
|
202
|
+
self._buffer_out = struct.pack(
|
|
203
|
+
"!bbbb",
|
|
204
|
+
5, # version
|
|
205
|
+
1, # command: connect
|
|
206
|
+
0, # reserved
|
|
207
|
+
addr_type, # 1 for ipv4, 4 for ipv6 address, 3 for domain name
|
|
208
|
+
)
|
|
209
|
+
# Addr format depends on type
|
|
210
|
+
if addr_type == 3:
|
|
211
|
+
# len + domain name (no null terminator)
|
|
212
|
+
self._buffer_out += struct.pack(
|
|
213
|
+
"!b{}s".format(addr_len),
|
|
214
|
+
addr_len,
|
|
215
|
+
addr[0].encode('ascii'),
|
|
216
|
+
)
|
|
217
|
+
else:
|
|
218
|
+
# either 4 (type 1) or 16 (type 4) bytes of actual address
|
|
219
|
+
self._buffer_out += struct.pack(
|
|
220
|
+
"!{}s".format(addr_len),
|
|
221
|
+
socket.inet_pton(self._target_afi, addr[0]),
|
|
222
|
+
)
|
|
223
|
+
self._buffer_out += struct.pack("!H", addr[1]) # port
|
|
224
|
+
|
|
225
|
+
self._state = ProxyConnectionStates.REQUESTING
|
|
226
|
+
|
|
227
|
+
if self._state == ProxyConnectionStates.REQUESTING:
|
|
228
|
+
self._flush_buf()
|
|
229
|
+
buf = self._read_buf(2)
|
|
230
|
+
if buf[0:2] == b"\x05\x00":
|
|
231
|
+
self._state = ProxyConnectionStates.READ_ADDRESS
|
|
232
|
+
else:
|
|
233
|
+
log.error("Proxy request failed: %r", buf[1:2])
|
|
234
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
235
|
+
self._sock.close()
|
|
236
|
+
return errno.ECONNREFUSED
|
|
237
|
+
|
|
238
|
+
if self._state == ProxyConnectionStates.READ_ADDRESS:
|
|
239
|
+
# we don't really care about the remote endpoint address, but need to clear the stream
|
|
240
|
+
buf = self._peek_buf(2)
|
|
241
|
+
if buf[0:2] == b"\x00\x01":
|
|
242
|
+
_ = self._read_buf(2 + 4 + 2) # ipv4 address + port
|
|
243
|
+
elif buf[0:2] == b"\x00\x05":
|
|
244
|
+
_ = self._read_buf(2 + 16 + 2) # ipv6 address + port
|
|
245
|
+
else:
|
|
246
|
+
log.error("Unrecognized remote address type %r", buf[1:2])
|
|
247
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
248
|
+
self._sock.close()
|
|
249
|
+
return errno.ECONNREFUSED
|
|
250
|
+
self._state = ProxyConnectionStates.COMPLETE
|
|
251
|
+
|
|
252
|
+
if self._state == ProxyConnectionStates.COMPLETE:
|
|
253
|
+
return 0
|
|
254
|
+
|
|
255
|
+
# not reached;
|
|
256
|
+
# Send and recv will raise socket error on EWOULDBLOCK/EAGAIN that is assumed to be handled by
|
|
257
|
+
# the caller. The caller re-enters this state machine from retry logic with timer or via select & family
|
|
258
|
+
log.error("Internal error, state %r not handled correctly", self._state)
|
|
259
|
+
self._state = ProxyConnectionStates.DISCONNECTED
|
|
260
|
+
if self._sock:
|
|
261
|
+
self._sock.close()
|
|
262
|
+
return errno.ECONNREFUSED
|
kafka/net/transport.py
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
import logging
|
|
3
|
+
import selectors
|
|
4
|
+
import socket
|
|
5
|
+
import ssl
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
import kafka.errors as Errors
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KafkaTCPTransport:
|
|
15
|
+
def __init__(self, net, sock, host=None):
|
|
16
|
+
self._net = net
|
|
17
|
+
self._sock = sock
|
|
18
|
+
self.host = host
|
|
19
|
+
self._closed = False
|
|
20
|
+
self._write_buffer = deque()
|
|
21
|
+
self._writing = False
|
|
22
|
+
self._protocol = None
|
|
23
|
+
self._read = False
|
|
24
|
+
self._write = True
|
|
25
|
+
self.last_write = time.monotonic()
|
|
26
|
+
self.last_read = time.monotonic()
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def last_activity(self):
|
|
30
|
+
return max(self.last_write, self.last_read)
|
|
31
|
+
|
|
32
|
+
# AsyncIO
|
|
33
|
+
def is_closing(self):
|
|
34
|
+
"""Return True if the transport is closing or closed."""
|
|
35
|
+
return self._closed
|
|
36
|
+
|
|
37
|
+
def close(self):
|
|
38
|
+
"""Close the transport.
|
|
39
|
+
|
|
40
|
+
Buffered data will be flushed asynchronously. No more data
|
|
41
|
+
will be received. After all buffered data is flushed, the
|
|
42
|
+
protocol's connection_lost() method will (eventually) be
|
|
43
|
+
called with None as its argument.
|
|
44
|
+
"""
|
|
45
|
+
if not self._closed:
|
|
46
|
+
log.info('%s: Closing transport', self)
|
|
47
|
+
self._closed = True
|
|
48
|
+
self._read = False
|
|
49
|
+
if not self._write_buffer:
|
|
50
|
+
self._close()
|
|
51
|
+
|
|
52
|
+
def set_protocol(self, protocol):
|
|
53
|
+
"""Set a new protocol."""
|
|
54
|
+
self._protocol = protocol
|
|
55
|
+
log.debug('%s: Set protocol %s', self, protocol)
|
|
56
|
+
|
|
57
|
+
def get_protocol(self):
|
|
58
|
+
"""Return the current protocol."""
|
|
59
|
+
return self._protocol
|
|
60
|
+
|
|
61
|
+
"""Interface for read-only transports."""
|
|
62
|
+
|
|
63
|
+
def is_reading(self):
|
|
64
|
+
"""Return True if the transport is receiving."""
|
|
65
|
+
return self._read
|
|
66
|
+
|
|
67
|
+
def pause_reading(self):
|
|
68
|
+
"""Pause the receiving end.
|
|
69
|
+
|
|
70
|
+
No data will be passed to the protocol's data_received()
|
|
71
|
+
method until resume_reading() is called.
|
|
72
|
+
"""
|
|
73
|
+
self._read = False
|
|
74
|
+
log.debug('%s: Paused reading', self)
|
|
75
|
+
|
|
76
|
+
def resume_reading(self):
|
|
77
|
+
"""Resume the receiving end.
|
|
78
|
+
|
|
79
|
+
Data received will once again be passed to the protocol's
|
|
80
|
+
data_received() method.
|
|
81
|
+
"""
|
|
82
|
+
if not self._read:
|
|
83
|
+
self._net.call_soon(self._read_from_sock)
|
|
84
|
+
self._read = True
|
|
85
|
+
log.debug('%s: Resumed reading', self)
|
|
86
|
+
|
|
87
|
+
async def _read_from_sock(self):
|
|
88
|
+
while self._read and not self._closed:
|
|
89
|
+
await self._net.wait_read(self._sock)
|
|
90
|
+
recvd_data, err = self._sock_recv()
|
|
91
|
+
if err:
|
|
92
|
+
return self.abort(error=err)
|
|
93
|
+
log.debug('%s: received %d bytes', self, len(recvd_data))
|
|
94
|
+
self.last_read = time.monotonic()
|
|
95
|
+
if self._protocol and self._protocol._sensors:
|
|
96
|
+
self._protocol._sensors.bytes_received.record(len(recvd_data))
|
|
97
|
+
try:
|
|
98
|
+
self._protocol.data_received(recvd_data)
|
|
99
|
+
except Errors.KafkaProtocolError as e:
|
|
100
|
+
return self.abort(error=e)
|
|
101
|
+
|
|
102
|
+
def _sock_recv(self):
|
|
103
|
+
recvd = []
|
|
104
|
+
err = None
|
|
105
|
+
while True:
|
|
106
|
+
try:
|
|
107
|
+
data = self._sock.recv(4096)
|
|
108
|
+
# We expect socket.recv to raise an exception if there are no
|
|
109
|
+
# bytes available to read from the socket in non-blocking mode.
|
|
110
|
+
# but if the socket is disconnected, we will get empty data
|
|
111
|
+
# without an exception raised
|
|
112
|
+
if not data:
|
|
113
|
+
log.error('%s: socket disconnected', self)
|
|
114
|
+
err = Errors.KafkaConnectionError('socket disconnected')
|
|
115
|
+
break
|
|
116
|
+
else:
|
|
117
|
+
recvd.append(data)
|
|
118
|
+
|
|
119
|
+
except (BlockingIOError, InterruptedError):
|
|
120
|
+
break
|
|
121
|
+
except BaseException as e:
|
|
122
|
+
log.exception('%s: Error receiving network data'
|
|
123
|
+
' closing socket', self)
|
|
124
|
+
err = Errors.KafkaConnectionError(e)
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
recvd_data = b''.join(recvd)
|
|
128
|
+
return recvd_data, err
|
|
129
|
+
|
|
130
|
+
"""Interface for write-only transports."""
|
|
131
|
+
|
|
132
|
+
def set_write_buffer_limits(self, high=None, low=None):
|
|
133
|
+
"""Set the high- and low-water limits for write flow control.
|
|
134
|
+
|
|
135
|
+
These two values control when to call the protocol's
|
|
136
|
+
pause_writing() and resume_writing() methods. If specified,
|
|
137
|
+
the low-water limit must be less than or equal to the
|
|
138
|
+
high-water limit. Neither value can be negative.
|
|
139
|
+
|
|
140
|
+
The defaults are implementation-specific. If only the
|
|
141
|
+
high-water limit is given, the low-water limit defaults to an
|
|
142
|
+
implementation-specific value less than or equal to the
|
|
143
|
+
high-water limit. Setting high to zero forces low to zero as
|
|
144
|
+
well, and causes pause_writing() to be called whenever the
|
|
145
|
+
buffer becomes non-empty. Setting low to zero causes
|
|
146
|
+
resume_writing() to be called only once the buffer is empty.
|
|
147
|
+
Use of zero for either limit is generally sub-optimal as it
|
|
148
|
+
reduces opportunities for doing I/O and computation
|
|
149
|
+
concurrently.
|
|
150
|
+
"""
|
|
151
|
+
raise NotImplementedError
|
|
152
|
+
|
|
153
|
+
def get_write_buffer_size(self):
|
|
154
|
+
"""Return the current size of the write buffer."""
|
|
155
|
+
raise NotImplementedError
|
|
156
|
+
|
|
157
|
+
def get_write_buffer_limits(self):
|
|
158
|
+
"""Get the high and low watermarks for write flow control.
|
|
159
|
+
Return a tuple (low, high) where low and high are
|
|
160
|
+
positive number of bytes."""
|
|
161
|
+
raise NotImplementedError
|
|
162
|
+
|
|
163
|
+
def write(self, data):
|
|
164
|
+
"""Write some data bytes to the transport.
|
|
165
|
+
|
|
166
|
+
This does not block; it buffers the data and arranges for it
|
|
167
|
+
to be sent out asynchronously.
|
|
168
|
+
"""
|
|
169
|
+
if not self._write or self._closed:
|
|
170
|
+
raise RuntimeError('Transport closed for writes')
|
|
171
|
+
if not data:
|
|
172
|
+
raise ValueError('Cant write empty data')
|
|
173
|
+
self._write_buffer.append(data)
|
|
174
|
+
if not self._writing:
|
|
175
|
+
self._writing = True
|
|
176
|
+
self._net.call_soon(self._write_to_sock)
|
|
177
|
+
|
|
178
|
+
def writelines(self, list_of_data):
|
|
179
|
+
"""Write a list (or any iterable) of data bytes to the transport."""
|
|
180
|
+
if not self._write or self._closed:
|
|
181
|
+
raise RuntimeError('Transport closed for writes')
|
|
182
|
+
self._write_buffer.extend(list_of_data)
|
|
183
|
+
if not self._writing:
|
|
184
|
+
self._writing = True
|
|
185
|
+
self._net.call_soon(self._write_to_sock)
|
|
186
|
+
|
|
187
|
+
async def _write_to_sock(self):
|
|
188
|
+
try:
|
|
189
|
+
while self._write and not self._closed and self._write_buffer:
|
|
190
|
+
await self._net.wait_write(self._sock)
|
|
191
|
+
total_bytes, err = self._sock_send()
|
|
192
|
+
if err:
|
|
193
|
+
return self.abort(error=err)
|
|
194
|
+
log.debug('%s: sent %d bytes', self, total_bytes)
|
|
195
|
+
self.last_write = time.monotonic()
|
|
196
|
+
if self._protocol and self._protocol._sensors:
|
|
197
|
+
self._protocol._sensors.bytes_sent.record(total_bytes)
|
|
198
|
+
finally:
|
|
199
|
+
self._writing = False
|
|
200
|
+
if not self._write:
|
|
201
|
+
self._sock.shutdown(socket.SHUT_WR)
|
|
202
|
+
|
|
203
|
+
def _sock_send(self):
|
|
204
|
+
total_bytes = 0
|
|
205
|
+
while self._write_buffer:
|
|
206
|
+
next_chunk = self._write_buffer.popleft()
|
|
207
|
+
# Wrap in memoryview so partial-send slicing is O(1) instead of
|
|
208
|
+
# copying the unsent tail on every BlockingIOError / short write.
|
|
209
|
+
if not isinstance(next_chunk, memoryview):
|
|
210
|
+
next_chunk = memoryview(next_chunk)
|
|
211
|
+
while next_chunk:
|
|
212
|
+
try:
|
|
213
|
+
sent_bytes = self._sock.send(next_chunk)
|
|
214
|
+
total_bytes += sent_bytes
|
|
215
|
+
next_chunk = next_chunk[sent_bytes:]
|
|
216
|
+
except (BlockingIOError, InterruptedError):
|
|
217
|
+
self._write_buffer.appendleft(next_chunk)
|
|
218
|
+
return total_bytes, None
|
|
219
|
+
except BaseException as e:
|
|
220
|
+
log.exception("%s: Error sending request data: %s", self, e)
|
|
221
|
+
return total_bytes, Errors.KafkaConnectionError(e)
|
|
222
|
+
return total_bytes, None
|
|
223
|
+
|
|
224
|
+
def write_eof(self):
|
|
225
|
+
"""Close the write end after flushing buffered data.
|
|
226
|
+
|
|
227
|
+
(This is like typing ^D into a UNIX program reading from stdin.)
|
|
228
|
+
|
|
229
|
+
Data may still be received.
|
|
230
|
+
"""
|
|
231
|
+
log.debug('%s: write_eof', self)
|
|
232
|
+
self._write = False
|
|
233
|
+
if not self._write_buffer:
|
|
234
|
+
self._sock.shutdown(socket.SHUT_WR)
|
|
235
|
+
|
|
236
|
+
def can_write_eof(self):
|
|
237
|
+
"""Return True if this transport supports write_eof(), False if not."""
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
def abort(self, error=None):
|
|
241
|
+
"""Close the transport immediately.
|
|
242
|
+
|
|
243
|
+
Buffered data will be lost. No more data will be received.
|
|
244
|
+
The protocol's connection_lost() method will (eventually) be
|
|
245
|
+
called with None as its argument.
|
|
246
|
+
"""
|
|
247
|
+
if not self._closed:
|
|
248
|
+
log.error('%s: Abort (%s)', self, error)
|
|
249
|
+
self._closed = True
|
|
250
|
+
self._write_buffer.clear()
|
|
251
|
+
self._read = self._write = False
|
|
252
|
+
self._close(error)
|
|
253
|
+
|
|
254
|
+
def _close(self, error=None):
|
|
255
|
+
# idempotent; no lock
|
|
256
|
+
sock = self._sock
|
|
257
|
+
self._sock = None
|
|
258
|
+
if sock is not None:
|
|
259
|
+
try:
|
|
260
|
+
self._net.unregister_event(sock, selectors.EVENT_READ | selectors.EVENT_WRITE)
|
|
261
|
+
except (KeyError, ValueError):
|
|
262
|
+
pass
|
|
263
|
+
sock.close()
|
|
264
|
+
proto = self._protocol
|
|
265
|
+
self._protocol = None
|
|
266
|
+
if proto is not None:
|
|
267
|
+
proto.connection_lost(error)
|
|
268
|
+
|
|
269
|
+
# Twisted
|
|
270
|
+
def abortConnection(self):
|
|
271
|
+
"""Close the connection abruptly."""
|
|
272
|
+
return self.abort()
|
|
273
|
+
|
|
274
|
+
def getHost(self):
|
|
275
|
+
"""Similar to getPeer, but returns an address describing this side of the connection.
|
|
276
|
+
|
|
277
|
+
Returns IPv4Address or IPv6Address.
|
|
278
|
+
"""
|
|
279
|
+
return self._sock.getsockname()
|
|
280
|
+
|
|
281
|
+
def getPeer(self):
|
|
282
|
+
"""Get the remote address of this connection.
|
|
283
|
+
|
|
284
|
+
Treat this method with caution. It is the unfortunate result of the CGI and Jabber standards,
|
|
285
|
+
but should not be considered reliable for the usual host of reasons;
|
|
286
|
+
port forwarding, proxying, firewalls, IP masquerading, etc.
|
|
287
|
+
|
|
288
|
+
Returns IPv4Address or IPv6Address.
|
|
289
|
+
"""
|
|
290
|
+
return self._sock.getpeername()
|
|
291
|
+
|
|
292
|
+
def getTcpKeepAlive(self):
|
|
293
|
+
"""Return if SO_KEEPALIVE is enabled."""
|
|
294
|
+
return self._sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE)
|
|
295
|
+
|
|
296
|
+
def getTcpNoDelay(self):
|
|
297
|
+
"""Return if TCP_NODELAY is enabled."""
|
|
298
|
+
return self._sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
|
|
299
|
+
|
|
300
|
+
def loseWriteConnection(self):
|
|
301
|
+
"""Half-close the write side of a TCP connection."""
|
|
302
|
+
return self.write_eof()
|
|
303
|
+
|
|
304
|
+
def setTcpKeepAlive(self, enabled):
|
|
305
|
+
"""Enable/disable SO_KEEPALIVE."""
|
|
306
|
+
return self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, enabled)
|
|
307
|
+
|
|
308
|
+
def setTcpNoDelay(self, enabled):
|
|
309
|
+
"""Enable/disable TCP_NODELAY."""
|
|
310
|
+
return self._sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, enabled)
|
|
311
|
+
|
|
312
|
+
def loseConnection(self):
|
|
313
|
+
"""Close my connection, after writing all pending data.
|
|
314
|
+
|
|
315
|
+
Note that if there is a registered producer on a transport it will not be closed until the producer has been unregistered.
|
|
316
|
+
"""
|
|
317
|
+
return self.close()
|
|
318
|
+
|
|
319
|
+
#def write(self, data):
|
|
320
|
+
# """Write some data to the physical connection, in sequence, in a non-blocking fashion.
|
|
321
|
+
#
|
|
322
|
+
# If possible, make sure that it is all written. No data will ever be lost,
|
|
323
|
+
# although (obviously) the connection may be closed before it all gets through.
|
|
324
|
+
# """
|
|
325
|
+
# pass
|
|
326
|
+
|
|
327
|
+
def writeSequence(self, data):
|
|
328
|
+
"""Write an iterable of byte strings to the physical connection.
|
|
329
|
+
|
|
330
|
+
If possible, make sure that all of the data is written to the socket at once,
|
|
331
|
+
without first copying it all into a single byte string.
|
|
332
|
+
"""
|
|
333
|
+
return self.writelines(data)
|
|
334
|
+
|
|
335
|
+
async def handshake(self):
|
|
336
|
+
pass
|
|
337
|
+
|
|
338
|
+
def host_port(self):
|
|
339
|
+
if self._sock is None:
|
|
340
|
+
return 'none'
|
|
341
|
+
try:
|
|
342
|
+
host, port = self._sock.getpeername()[0:2]
|
|
343
|
+
except (OSError, ValueError):
|
|
344
|
+
return 'none'
|
|
345
|
+
try:
|
|
346
|
+
local_port = self._sock.getsockname()[1]
|
|
347
|
+
except (OSError, ValueError):
|
|
348
|
+
return f'{host}:{port}'
|
|
349
|
+
return f'{host}:{port}<-{local_port}'
|
|
350
|
+
|
|
351
|
+
def __str__(self):
|
|
352
|
+
state = ' (closed)' if self._closed else ''
|
|
353
|
+
return f"<{self.__class__.__name__} [{self.host_port()}]{state}>"
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class KafkaSSLTransport(KafkaTCPTransport):
|
|
357
|
+
def __init__(self, net, sock, ssl_context, host=None, ssl_check_hostname=False):
|
|
358
|
+
self._ssl_context = ssl_context
|
|
359
|
+
server_hostname = host if ssl_check_hostname else None
|
|
360
|
+
sock = ssl_context.wrap_socket(
|
|
361
|
+
sock, server_hostname=server_hostname, do_handshake_on_connect=False)
|
|
362
|
+
super().__init__(net, sock, host=host)
|
|
363
|
+
|
|
364
|
+
async def handshake(self):
|
|
365
|
+
while True:
|
|
366
|
+
try:
|
|
367
|
+
self._sock.do_handshake()
|
|
368
|
+
return
|
|
369
|
+
except ssl.SSLWantReadError:
|
|
370
|
+
await self._net.wait_read(self._sock)
|
|
371
|
+
except ssl.SSLWantWriteError:
|
|
372
|
+
await self._net.wait_write(self._sock)
|
|
373
|
+
|
|
374
|
+
def _sock_recv(self):
|
|
375
|
+
recvd = []
|
|
376
|
+
err = None
|
|
377
|
+
while True:
|
|
378
|
+
try:
|
|
379
|
+
data = self._sock.recv(4096)
|
|
380
|
+
if not data:
|
|
381
|
+
log.error('%s: socket disconnected', self)
|
|
382
|
+
err = Errors.KafkaConnectionError('socket disconnected')
|
|
383
|
+
break
|
|
384
|
+
else:
|
|
385
|
+
recvd.append(data)
|
|
386
|
+
except (BlockingIOError, InterruptedError,
|
|
387
|
+
ssl.SSLWantReadError, ssl.SSLWantWriteError):
|
|
388
|
+
break
|
|
389
|
+
except BaseException as e:
|
|
390
|
+
log.exception('%s: Error receiving network data'
|
|
391
|
+
' closing socket', self)
|
|
392
|
+
err = Errors.KafkaConnectionError(e)
|
|
393
|
+
break
|
|
394
|
+
recvd_data = b''.join(recvd)
|
|
395
|
+
return recvd_data, err
|
|
396
|
+
|
|
397
|
+
def _sock_send(self):
|
|
398
|
+
total_bytes = 0
|
|
399
|
+
err = None
|
|
400
|
+
while self._write_buffer:
|
|
401
|
+
next_chunk = self._write_buffer.popleft()
|
|
402
|
+
while next_chunk:
|
|
403
|
+
try:
|
|
404
|
+
sent_bytes = self._sock.send(next_chunk)
|
|
405
|
+
total_bytes += sent_bytes
|
|
406
|
+
next_chunk = next_chunk[sent_bytes:]
|
|
407
|
+
except (BlockingIOError, InterruptedError,
|
|
408
|
+
ssl.SSLWantReadError, ssl.SSLWantWriteError):
|
|
409
|
+
self._write_buffer.appendleft(next_chunk)
|
|
410
|
+
return total_bytes, err
|
|
411
|
+
except BaseException as e:
|
|
412
|
+
log.exception("%s: Error sending request data: %s", self, e)
|
|
413
|
+
err = Errors.KafkaConnectionError(e)
|
|
414
|
+
return total_bytes, err
|
|
415
|
+
return total_bytes, err
|