Protocol Buffers:如何在 Java 中解析 .proto 文件
Protocol Buffers: How to parse a .proto file in Java
我正在尝试动态解析 Java 中给定的 .proto 文件以解码 Protobuf 编码的二进制文件。
我有如下解析方式,其中"proto"字符串包含.proto文件的内容:
public static Descriptors.FileDescriptor parseProto (String proto) throws InvalidProtocolBufferException, Descriptors.DescriptorValidationException {
DescriptorProtos.FileDescriptorProto descriptorProto = DescriptorProtos.FileDescriptorProto.parseFrom(proto.getBytes());
return Descriptors.FileDescriptor.buildFrom(descriptorProto, null);
}
但是,在执行之前的方法时会抛出异常并显示消息 "Protocol message tag had invalid wire type."。我使用 Google 中的示例 .proto 文件,所以我猜它是有效的:https://github.com/google/protobuf/blob/master/examples/addressbook.proto
这是堆栈跟踪:
15:43:24.707 [pool-1-thread-1] ERROR com.github.whiver.nifi.processor.ProtobufDecoderProcessor - ProtobufDecoderProcessor[id=42c8ab94-2d8a-491b-bd99-b4451d127ae0] Protocol message tag had invalid wire type.
com.google.protobuf.InvalidProtocolBufferException$InvalidWireTypeException: Protocol message tag had invalid wire type.
at com.google.protobuf.InvalidProtocolBufferException.invalidWireType(InvalidProtocolBufferException.java:115)
at com.google.protobuf.UnknownFieldSet$Builder.mergeFieldFrom(UnknownFieldSet.java:551)
at com.google.protobuf.GeneratedMessageV3.parseUnknownField(GeneratedMessageV3.java:293)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.<init>(DescriptorProtos.java:88)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.<init>(DescriptorProtos.java:53)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.parsePartialFrom(DescriptorProtos.java:773)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.parsePartialFrom(DescriptorProtos.java:768)
at com.google.protobuf.AbstractParser.parsePartialFrom(AbstractParser.java:163)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:197)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:209)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:214)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:49)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.parseFrom(DescriptorProtos.java:260)
at com.github.whiver.nifi.parser.SchemaParser.parseProto(SchemaParser.java:9)
at com.github.whiver.nifi.processor.ProtobufDecoderProcessor.lambda$onTrigger[=12=](ProtobufDecoderProcessor.java:103)
at org.apache.nifi.util.MockProcessSession.write(MockProcessSession.java:895)
at org.apache.nifi.util.MockProcessSession.write(MockProcessSession.java:62)
at com.github.whiver.nifi.processor.ProtobufDecoderProcessor.onTrigger(ProtobufDecoderProcessor.java:100)
at org.apache.nifi.processor.AbstractProcessor.onTrigger(AbstractProcessor.java:27)
at org.apache.nifi.util.StandardProcessorTestRunner$RunProcessor.call(StandardProcessorTestRunner.java:251)
at org.apache.nifi.util.StandardProcessorTestRunner$RunProcessor.call(StandardProcessorTestRunner.java:245)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access1(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
有什么想法吗?
谢谢!
不要使用 java String
来保存 protobuf 负载。问题是 String
在幕后进行翻译,并对字符集做出假设。
Protobuf 适用于字节数组,数组中的确切表示必须保持不变。往返 String
无效。
您似乎在尝试使用 FileDescriptorSet.parseFrom
填充 FileDescriptorSet
。这仅在您提供的字节 是 二进制 protobuf 内容时才有效 - 也就是说:compiled 模式。您可以通过使用带有 --descriptor_set_out
选项的 protoc
命令行工具来获得 compiled 模式。您现在实际传递的是构成文本模式的文本字节,不是 parseFrom
所期望的。
如果没有编译模式,您将需要一个运行时 .proto 解析器。 Java 我不知道其中一个; protobuf-net 包括一个 (protobuf-net.Reflection),但那是 C#/.NET。如果没有可用的运行时 .proto 解析器,您需要 shell-执行 protoc
。
根据其他答案,这里是我正在开发的库中的一段有效的 Kotlin 代码。
https://github.com/asarkar/okgrpc
private fun lookupProtos(
protoPaths: List<String>,
protoFile: String,
tempDir: Path,
resolved: MutableSet<String>
): List<DescriptorProtos.FileDescriptorProto> {
val schema = generateSchema(protoPaths, protoFile, tempDir)
return schema.fileList
.filter { resolved.add(it.name) }
.flatMap { fd ->
fd.dependencyList
.filterNot(resolved::contains)
.flatMap { lookupProtos(protoPaths, it, tempDir, resolved) } + fd
}
}
private fun generateSchema(
protoPaths: List<String>,
protoFile: String,
tempDir: Path
): DescriptorProtos.FileDescriptorSet {
val outFile = Files.createTempFile(tempDir, null, null)
val stderr = ByteArrayOutputStream()
val exitCode = Protoc.runProtoc(
(protoPaths.map { "--proto_path=$it" } + listOf("--descriptor_set_out=$outFile", protoFile)).toTypedArray(),
DevNull,
stderr
)
if (exitCode != 0) {
throw IllegalStateException("Failed to generate schema for: $protoFile")
}
return Files.newInputStream(outFile).use { DescriptorProtos.FileDescriptorSet.parseFrom(it) }
}
想法是使用os72/protoc-jar写出编译后的schema/file描述符。然后使用 FileDescriptorSet.parseFrom
读取该文件,并递归其依赖项。
我正在尝试动态解析 Java 中给定的 .proto 文件以解码 Protobuf 编码的二进制文件。
我有如下解析方式,其中"proto"字符串包含.proto文件的内容:
public static Descriptors.FileDescriptor parseProto (String proto) throws InvalidProtocolBufferException, Descriptors.DescriptorValidationException {
DescriptorProtos.FileDescriptorProto descriptorProto = DescriptorProtos.FileDescriptorProto.parseFrom(proto.getBytes());
return Descriptors.FileDescriptor.buildFrom(descriptorProto, null);
}
但是,在执行之前的方法时会抛出异常并显示消息 "Protocol message tag had invalid wire type."。我使用 Google 中的示例 .proto 文件,所以我猜它是有效的:https://github.com/google/protobuf/blob/master/examples/addressbook.proto
这是堆栈跟踪:
15:43:24.707 [pool-1-thread-1] ERROR com.github.whiver.nifi.processor.ProtobufDecoderProcessor - ProtobufDecoderProcessor[id=42c8ab94-2d8a-491b-bd99-b4451d127ae0] Protocol message tag had invalid wire type.
com.google.protobuf.InvalidProtocolBufferException$InvalidWireTypeException: Protocol message tag had invalid wire type.
at com.google.protobuf.InvalidProtocolBufferException.invalidWireType(InvalidProtocolBufferException.java:115)
at com.google.protobuf.UnknownFieldSet$Builder.mergeFieldFrom(UnknownFieldSet.java:551)
at com.google.protobuf.GeneratedMessageV3.parseUnknownField(GeneratedMessageV3.java:293)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.<init>(DescriptorProtos.java:88)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.<init>(DescriptorProtos.java:53)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.parsePartialFrom(DescriptorProtos.java:773)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.parsePartialFrom(DescriptorProtos.java:768)
at com.google.protobuf.AbstractParser.parsePartialFrom(AbstractParser.java:163)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:197)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:209)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:214)
at com.google.protobuf.AbstractParser.parseFrom(AbstractParser.java:49)
at com.google.protobuf.DescriptorProtos$FileDescriptorSet.parseFrom(DescriptorProtos.java:260)
at com.github.whiver.nifi.parser.SchemaParser.parseProto(SchemaParser.java:9)
at com.github.whiver.nifi.processor.ProtobufDecoderProcessor.lambda$onTrigger[=12=](ProtobufDecoderProcessor.java:103)
at org.apache.nifi.util.MockProcessSession.write(MockProcessSession.java:895)
at org.apache.nifi.util.MockProcessSession.write(MockProcessSession.java:62)
at com.github.whiver.nifi.processor.ProtobufDecoderProcessor.onTrigger(ProtobufDecoderProcessor.java:100)
at org.apache.nifi.processor.AbstractProcessor.onTrigger(AbstractProcessor.java:27)
at org.apache.nifi.util.StandardProcessorTestRunner$RunProcessor.call(StandardProcessorTestRunner.java:251)
at org.apache.nifi.util.StandardProcessorTestRunner$RunProcessor.call(StandardProcessorTestRunner.java:245)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access1(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
有什么想法吗? 谢谢!
不要使用 java String
来保存 protobuf 负载。问题是 String
在幕后进行翻译,并对字符集做出假设。
Protobuf 适用于字节数组,数组中的确切表示必须保持不变。往返 String
无效。
您似乎在尝试使用 FileDescriptorSet.parseFrom
填充 FileDescriptorSet
。这仅在您提供的字节 是 二进制 protobuf 内容时才有效 - 也就是说:compiled 模式。您可以通过使用带有 --descriptor_set_out
选项的 protoc
命令行工具来获得 compiled 模式。您现在实际传递的是构成文本模式的文本字节,不是 parseFrom
所期望的。
如果没有编译模式,您将需要一个运行时 .proto 解析器。 Java 我不知道其中一个; protobuf-net 包括一个 (protobuf-net.Reflection),但那是 C#/.NET。如果没有可用的运行时 .proto 解析器,您需要 shell-执行 protoc
。
根据其他答案,这里是我正在开发的库中的一段有效的 Kotlin 代码。 https://github.com/asarkar/okgrpc
private fun lookupProtos(
protoPaths: List<String>,
protoFile: String,
tempDir: Path,
resolved: MutableSet<String>
): List<DescriptorProtos.FileDescriptorProto> {
val schema = generateSchema(protoPaths, protoFile, tempDir)
return schema.fileList
.filter { resolved.add(it.name) }
.flatMap { fd ->
fd.dependencyList
.filterNot(resolved::contains)
.flatMap { lookupProtos(protoPaths, it, tempDir, resolved) } + fd
}
}
private fun generateSchema(
protoPaths: List<String>,
protoFile: String,
tempDir: Path
): DescriptorProtos.FileDescriptorSet {
val outFile = Files.createTempFile(tempDir, null, null)
val stderr = ByteArrayOutputStream()
val exitCode = Protoc.runProtoc(
(protoPaths.map { "--proto_path=$it" } + listOf("--descriptor_set_out=$outFile", protoFile)).toTypedArray(),
DevNull,
stderr
)
if (exitCode != 0) {
throw IllegalStateException("Failed to generate schema for: $protoFile")
}
return Files.newInputStream(outFile).use { DescriptorProtos.FileDescriptorSet.parseFrom(it) }
}
想法是使用os72/protoc-jar写出编译后的schema/file描述符。然后使用 FileDescriptorSet.parseFrom
读取该文件,并递归其依赖项。