|
|
@ -267,26 +267,220 @@ func (h *Handler) produceToSeaweedMQ(topic string, partition int32, recordSetDat |
|
|
return h.seaweedMQHandler.ProduceRecord(topic, partition, key, value) |
|
|
return h.seaweedMQHandler.ProduceRecord(topic, partition, key, value) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// extractFirstRecord extracts the first record from a Kafka record set (simplified)
|
|
|
|
|
|
// TODO: CRITICAL - This function returns placeholder data instead of parsing real records
|
|
|
|
|
|
// For real client compatibility, need to:
|
|
|
|
|
|
// - Parse record batch header properly
|
|
|
|
|
|
// - Extract actual key/value from first record in batch
|
|
|
|
|
|
// - Handle compressed record batches
|
|
|
|
|
|
// - Support all record formats (v0, v1, v2)
|
|
|
|
|
|
|
|
|
// extractFirstRecord extracts the first record from a Kafka record batch
|
|
|
func (h *Handler) extractFirstRecord(recordSetData []byte) ([]byte, []byte) { |
|
|
func (h *Handler) extractFirstRecord(recordSetData []byte) ([]byte, []byte) { |
|
|
// For Phase 2, create a simple placeholder record
|
|
|
|
|
|
// This represents what would be extracted from the actual Kafka record batch
|
|
|
|
|
|
|
|
|
fmt.Printf("DEBUG: extractFirstRecord - parsing %d bytes\n", len(recordSetData)) |
|
|
|
|
|
|
|
|
|
|
|
if len(recordSetData) < 61 { |
|
|
|
|
|
fmt.Printf("DEBUG: Record batch too short (%d bytes), returning placeholder\n", len(recordSetData)) |
|
|
|
|
|
// Fallback to placeholder
|
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
offset := 0 |
|
|
|
|
|
|
|
|
|
|
|
// Parse record batch header (Kafka v2 format)
|
|
|
|
|
|
// base_offset(8) + batch_length(4) + partition_leader_epoch(4) + magic(1) + crc(4) + attributes(2)
|
|
|
|
|
|
// + last_offset_delta(4) + first_timestamp(8) + max_timestamp(8) + producer_id(8) + producer_epoch(2)
|
|
|
|
|
|
// + base_sequence(4) + records_count(4) = 61 bytes header
|
|
|
|
|
|
|
|
|
|
|
|
offset += 8 // skip base_offset
|
|
|
|
|
|
batchLength := int32(binary.BigEndian.Uint32(recordSetData[offset:])) |
|
|
|
|
|
offset += 4 // batch_length
|
|
|
|
|
|
|
|
|
|
|
|
fmt.Printf("DEBUG: Record batch length: %d\n", batchLength) |
|
|
|
|
|
|
|
|
|
|
|
offset += 4 // skip partition_leader_epoch
|
|
|
|
|
|
magic := recordSetData[offset] |
|
|
|
|
|
offset += 1 // magic byte
|
|
|
|
|
|
|
|
|
|
|
|
fmt.Printf("DEBUG: Magic byte: %d\n", magic) |
|
|
|
|
|
|
|
|
|
|
|
if magic != 2 { |
|
|
|
|
|
fmt.Printf("DEBUG: Unsupported magic byte %d, returning placeholder\n", magic) |
|
|
|
|
|
// Fallback for older formats
|
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
offset += 4 // skip crc
|
|
|
|
|
|
offset += 2 // skip attributes
|
|
|
|
|
|
offset += 4 // skip last_offset_delta
|
|
|
|
|
|
offset += 8 // skip first_timestamp
|
|
|
|
|
|
offset += 8 // skip max_timestamp
|
|
|
|
|
|
offset += 8 // skip producer_id
|
|
|
|
|
|
offset += 2 // skip producer_epoch
|
|
|
|
|
|
offset += 4 // skip base_sequence
|
|
|
|
|
|
|
|
|
|
|
|
recordsCount := int32(binary.BigEndian.Uint32(recordSetData[offset:])) |
|
|
|
|
|
offset += 4 // records_count
|
|
|
|
|
|
|
|
|
|
|
|
fmt.Printf("DEBUG: Records count: %d\n", recordsCount) |
|
|
|
|
|
|
|
|
|
|
|
if recordsCount == 0 { |
|
|
|
|
|
fmt.Printf("DEBUG: No records in batch, returning placeholder\n") |
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Parse first record
|
|
|
|
|
|
if offset >= len(recordSetData) { |
|
|
|
|
|
fmt.Printf("DEBUG: Record data truncated at offset %d, returning placeholder\n", offset) |
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Read record length (varint)
|
|
|
|
|
|
recordLength, varintLen := decodeVarint(recordSetData[offset:]) |
|
|
|
|
|
if varintLen == 0 { |
|
|
|
|
|
fmt.Printf("DEBUG: Failed to decode record length varint, returning placeholder\n") |
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
offset += varintLen |
|
|
|
|
|
|
|
|
|
|
|
fmt.Printf("DEBUG: First record length: %d\n", recordLength) |
|
|
|
|
|
|
|
|
|
|
|
if offset+int(recordLength) > len(recordSetData) { |
|
|
|
|
|
fmt.Printf("DEBUG: Record data extends beyond batch, returning placeholder\n") |
|
|
key := []byte("kafka-key") |
|
|
key := []byte("kafka-key") |
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// In a real implementation, this would:
|
|
|
|
|
|
// 1. Parse the record batch header
|
|
|
|
|
|
// 2. Extract individual records with proper key/value/timestamp
|
|
|
|
|
|
// 3. Handle compression, transaction markers, etc.
|
|
|
|
|
|
|
|
|
recordData := recordSetData[offset : offset+int(recordLength)] |
|
|
|
|
|
recordOffset := 0 |
|
|
|
|
|
|
|
|
|
|
|
// Parse record: attributes(1) + timestamp_delta(varint) + offset_delta(varint) + key + value + headers
|
|
|
|
|
|
recordOffset += 1 // skip attributes
|
|
|
|
|
|
|
|
|
|
|
|
// Skip timestamp_delta (varint)
|
|
|
|
|
|
_, varintLen = decodeVarint(recordData[recordOffset:]) |
|
|
|
|
|
if varintLen == 0 { |
|
|
|
|
|
fmt.Printf("DEBUG: Failed to decode timestamp delta, returning placeholder\n") |
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
return key, []byte(value) |
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
recordOffset += varintLen |
|
|
|
|
|
|
|
|
|
|
|
// Skip offset_delta (varint)
|
|
|
|
|
|
_, varintLen = decodeVarint(recordData[recordOffset:]) |
|
|
|
|
|
if varintLen == 0 { |
|
|
|
|
|
fmt.Printf("DEBUG: Failed to decode offset delta, returning placeholder\n") |
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
recordOffset += varintLen |
|
|
|
|
|
|
|
|
|
|
|
// Read key length and key
|
|
|
|
|
|
keyLength, varintLen := decodeVarint(recordData[recordOffset:]) |
|
|
|
|
|
if varintLen == 0 { |
|
|
|
|
|
fmt.Printf("DEBUG: Failed to decode key length, returning placeholder\n") |
|
|
|
|
|
key := []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
recordOffset += varintLen |
|
|
|
|
|
|
|
|
|
|
|
var key []byte |
|
|
|
|
|
if keyLength == -1 { |
|
|
|
|
|
key = nil // null key
|
|
|
|
|
|
fmt.Printf("DEBUG: Record has null key\n") |
|
|
|
|
|
} else if keyLength == 0 { |
|
|
|
|
|
key = []byte{} // empty key
|
|
|
|
|
|
fmt.Printf("DEBUG: Record has empty key\n") |
|
|
|
|
|
} else { |
|
|
|
|
|
if recordOffset+int(keyLength) > len(recordData) { |
|
|
|
|
|
fmt.Printf("DEBUG: Key extends beyond record, returning placeholder\n") |
|
|
|
|
|
key = []byte("kafka-key") |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
key = recordData[recordOffset : recordOffset+int(keyLength)] |
|
|
|
|
|
recordOffset += int(keyLength) |
|
|
|
|
|
fmt.Printf("DEBUG: Extracted key: %s\n", string(key)) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Read value length and value
|
|
|
|
|
|
valueLength, varintLen := decodeVarint(recordData[recordOffset:]) |
|
|
|
|
|
if varintLen == 0 { |
|
|
|
|
|
fmt.Printf("DEBUG: Failed to decode value length, returning placeholder\n") |
|
|
|
|
|
if key == nil { |
|
|
|
|
|
key = []byte("kafka-key") |
|
|
|
|
|
} |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
recordOffset += varintLen |
|
|
|
|
|
|
|
|
|
|
|
var value []byte |
|
|
|
|
|
if valueLength == -1 { |
|
|
|
|
|
value = nil // null value
|
|
|
|
|
|
fmt.Printf("DEBUG: Record has null value\n") |
|
|
|
|
|
} else if valueLength == 0 { |
|
|
|
|
|
value = []byte{} // empty value
|
|
|
|
|
|
fmt.Printf("DEBUG: Record has empty value\n") |
|
|
|
|
|
} else { |
|
|
|
|
|
if recordOffset+int(valueLength) > len(recordData) { |
|
|
|
|
|
fmt.Printf("DEBUG: Value extends beyond record, returning placeholder\n") |
|
|
|
|
|
if key == nil { |
|
|
|
|
|
key = []byte("kafka-key") |
|
|
|
|
|
} |
|
|
|
|
|
value := fmt.Sprintf("kafka-message-data-%d", time.Now().UnixNano()) |
|
|
|
|
|
return key, []byte(value) |
|
|
|
|
|
} |
|
|
|
|
|
value = recordData[recordOffset : recordOffset+int(valueLength)] |
|
|
|
|
|
fmt.Printf("DEBUG: Extracted value: %s\n", string(value)) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if key == nil { |
|
|
|
|
|
key = []byte{} // convert null key to empty for consistency
|
|
|
|
|
|
} |
|
|
|
|
|
if value == nil { |
|
|
|
|
|
value = []byte{} // convert null value to empty for consistency
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fmt.Printf("DEBUG: Successfully extracted record - key: %s, value: %s\n", string(key), string(value)) |
|
|
|
|
|
return key, value |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// decodeVarint decodes a variable-length integer from bytes
|
|
|
|
|
|
// Returns the decoded value and the number of bytes consumed
|
|
|
|
|
|
func decodeVarint(data []byte) (int64, int) { |
|
|
|
|
|
if len(data) == 0 { |
|
|
|
|
|
return 0, 0 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var result int64 |
|
|
|
|
|
var shift uint |
|
|
|
|
|
var bytesRead int |
|
|
|
|
|
|
|
|
|
|
|
for i, b := range data { |
|
|
|
|
|
if i > 9 { // varints can be at most 10 bytes
|
|
|
|
|
|
return 0, 0 // invalid varint
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bytesRead++ |
|
|
|
|
|
result |= int64(b&0x7F) << shift |
|
|
|
|
|
|
|
|
|
|
|
if (b & 0x80) == 0 { |
|
|
|
|
|
// Most significant bit is 0, we're done
|
|
|
|
|
|
// Apply zigzag decoding for signed integers
|
|
|
|
|
|
return (result >> 1) ^ (-(result & 1)), bytesRead |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
shift += 7 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return 0, 0 // incomplete varint
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// handleProduceV2Plus handles Produce API v2-v7 (Kafka 0.11+)
|
|
|
// handleProduceV2Plus handles Produce API v2-v7 (Kafka 0.11+)
|
|
|
|