gRPC默认是使用自定义的二进制格式传输报文的,但gRPC不只是能通过二进制格式传输报文,它也可以使用json等其它格式传输报文。
gRPC使用json格式传输报文的例子:
https://github.com/grpc/grpc-java/tree/master/examples/src/main/java/io/grpc/examples/advanced
从这个例子中可以看到,通过扩展io.grpc.MethodDescriptor.Marshaller,实现了对象与json格式文本的互相转换。以下方法创建了一个对象与json格式文本可互相转换的io.grpc.MethodDescriptor.Marshaller实例:
public static <T extends Message> Marshaller<T> jsonMarshaller(
final T defaultInstance, final Parser parser, final Printer printer) {
final Charset charset = Charset.forName("UTF-8");
return new Marshaller<T>() {
@Override
public InputStream stream(T value) {
try {
return new ByteArrayInputStream(printer.print(value).getBytes(charset));
} catch (InvalidProtocolBufferException e) {
throw Status.INTERNAL
.withCause(e)
.withDescription("Unable to print json proto")
.asRuntimeException();
}
}
@SuppressWarnings("unchecked")
@Override
public T parse(InputStream stream) {
Builder builder = defaultInstance.newBuilderForType();
Reader reader = new InputStreamReader(stream, charset);
T proto;
try {
parser.merge(reader, builder);
proto = (T) builder.build();
reader.close();
} catch (InvalidProtocolBufferException e) {
throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
.withCause(e).asRuntimeException();
} catch (IOException e) {
// Same for now, might be unavailable
throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
.withCause(e).asRuntimeException();
}
return proto;
}
};
}
然后,使用这个Marshaller实例创建了MethodDescriptor<HelloRequest, HelloReply>对象:
static final MethodDescriptor<HelloRequest, HelloReply> METHOD_SAY_HELLO = GreeterGrpc.getSayHelloMethod()
.toBuilder(JsonMarshaller.jsonMarshaller(HelloRequest.getDefaultInstance()),
JsonMarshaller.jsonMarshaller(HelloReply.getDefaultInstance()))
.build();
在服务器端,GreeterImpl没有直接继承GreeterGrpc.GreeterImplBase,而是直接继承io.grpc.BindableService。因为GreeterGrpc.GreeterImplBase中的接口方法public io.grpc.ServerServiceDefinition bindService();已有默认实现(默认实现使用的是二进制报文),并且此实现方法被标记为final,因此通过继承GreeterGrpc.GreeterImplBase,并覆盖public io.grpc.ServerServiceDefinition bindService();已变得不可能。所以例子中服务器端代码直接继承io.grpc.BindableService接口,实现了其中的public io.grpc.ServerServiceDefinition bindService();方法,在此方法中注册了的MethodDescriptor<HelloRequest, HelloReply>,它包含有io.grpc.MethodDescriptor.Marshaller的json实现。
同时,在客户端,例子中没有直接使用GreeterGrpc.GreeterBlockingStub,而是直接实现了io.grpc.stub.AbstractStub,并在其中使用了上面的MethodDescriptor<HelloRequest, HelloReply>。
这个例子虽然实现了使用json格式作为报文格式,但需要对每个服务的每个RPC方法提供相对应的实现,如果项目中服务很多的话,每个都要在客户端代码和服务器端代码中实现一遍,这样一定非常麻烦,还容易出错。那有没有全局性的设置方法,只要在一个地方设置一次,所有的RPC调用都可以使用json格式传输报文呢?
本人在gRPC库中没有找到直接实现的方式(如果有谁找到这样的方法,请告之于我),但本人通过一些其它方式,实现了近似的功能。下面就是实现的代码:
首先,需要一个在json与object之间进行转换的Marshaller:
package com.example.tutorial.tool;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import com.google.common.io.CharStreams;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.Message.Builder;
import com.google.protobuf.util.JsonFormat;
import com.google.protobuf.util.JsonFormat.Parser;
import com.google.protobuf.util.JsonFormat.Printer;
import io.grpc.MethodDescriptor.Marshaller;
import io.grpc.Status;
/**
* 使用json格式传输报文,需要使用此报文轮换器
*
* @param <T>
*/
public final class JsonMarshaller<T extends Message> implements Marshaller<T> {
private final Parser parser = JsonFormat.parser();
private final Printer printer = JsonFormat.printer().omittingInsignificantWhitespace();
private final T defaultInstance;
public JsonMarshaller(T defaultInstance) {
this.defaultInstance = defaultInstance;
}
@Override
public InputStream stream(T value) {
try {
return new ByteArrayInputStream(printer.print(value).getBytes(StandardCharsets.UTF_8));
} catch (InvalidProtocolBufferException e) {
throw Status.INTERNAL.withCause(e).withDescription("Unable to create json").asRuntimeException();
}
}
@SuppressWarnings("unchecked")
@Override
public T parse(InputStream stream) {
T proto = null;
try {
Builder builder = defaultInstance.newBuilderForType();
String text = CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8));
parser.merge(text, builder);
proto = (T) builder.build();
} catch (Exception e) {
throw Status.INTERNAL.withDescription("Unable to parse json").withCause(e).asRuntimeException();
}
return proto;
}
}
然后,需要对所有gRPC服务注册上面的Marshaller:
package com.example.tutorial.tool;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.protobuf.Message;
import io.grpc.MethodDescriptor;
import io.grpc.MethodDescriptor.Marshaller;
import io.grpc.MethodDescriptor.PrototypeMarshaller;
public class GlobalMarshallerInitializer {
private static final Logger LOG = Logger.getLogger(GlobalMarshallerInitializer.class.getName());
// 注册定制的Marshaller
public static void initiateMarshaller(Class<?> serviceClass,
Class<? extends Marshaller<? extends Message>> marshallerClass) {
try {
Field serviceNameField = serviceClass.getField("SERVICE_NAME");
if (serviceNameField == null) {
return;
}
for (Method m : serviceClass.getDeclaredMethods()) {
if (Modifier.isStatic(m.getModifiers()) && m.getReturnType() == MethodDescriptor.class) {
// 为各RPC调用方法生成默认的MethodDescriptor,默认MethodDescriptor中的Marshaller使用的是二进制格式传输报文
MethodDescriptor<?, ?> md = (MethodDescriptor<?, ?>) m.invoke(null);
Field requestMarshallerField = md.getClass().getDeclaredField("requestMarshaller");
Field responseMarshallerField = md.getClass().getDeclaredField("responseMarshaller");
Object requestMessagePrototype = ((PrototypeMarshaller<?>) md.getRequestMarshaller())
.getMessagePrototype();
Object responseMessagePrototype = ((PrototypeMarshaller<?>) md.getResponseMarshaller())
.getMessagePrototype();
Marshaller<? extends Message> requestMarshallerInstance = marshallerClass
.getConstructor(Message.class).newInstance(requestMessagePrototype);
Marshaller<? extends Message> responseMarshallerInstance = marshallerClass
.getConstructor(Message.class).newInstance(responseMessagePrototype);
// 使用反射机制设置输入Marshaller
requestMarshallerField.setAccessible(true);
requestMarshallerField.set(md, requestMarshallerInstance);
requestMarshallerField.setAccessible(false);
// 使用反射机制设置输出Marshaller
responseMarshallerField.setAccessible(true);
responseMarshallerField.set(md, responseMarshallerInstance);
responseMarshallerField.setAccessible(false);
LOG.info("finish to initiate method: " + md.getFullMethodName());
}
}
} catch (Exception e) {
LOG.log(Level.SEVERE, "fail to initiate service " + serviceClass.getName(), e);
}
}
}
最后,只需要在服务端代码和客户端代码中,对需要使用json格式传输报文的服务,调用上述代码进行初始化:
GlobalMarshallerInitializer.initiateMarshaller(GreeterGrpc.class,
(Class<? extends Marshaller<? extends Message>>) JsonMarshaller.class);