Feign: Protobuf encoder/decoder support?

Created on 16 Aug 2016  路  6Comments  路  Source: OpenFeign/feign

Just wondering if there is (can't find at the moment), or if someone is planning on, implementing protobuf support for decoding/encoding? I write the etcd-rest library which is currently using jclouds (api only) in the backend for communicating with etcd's v2 api. Their v3 api uses protobuf to and from the server and I wanted to take a stab at doing things feign rather jclouds as it appears this library is potentially more flexible and lighter weight.

Most helpful comment

if a protobuf decoder is still needed i've developed a simple decoder not based on spring

new Decoder() {

    Map<Type, Message> protoMessagesCache = new HashMap<>();

    @Override
    public Object decode(Response response, Type type) throws IOException {

      if(response.body() == null) return null;

      Message protoMessage = protoMessagesCache.computeIfAbsent(type, t -> {
        try {
        Class<?> typeClass = Class.forName(type.getTypeName());
        if (Message.class.isAssignableFrom(typeClass)) {
          Method builderMethod = typeClass.getMethod("newBuilder");
          return ((Message.Builder) builderMethod.invoke(typeClass)).getDefaultInstanceForType();
        }
        } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
          | NoSuchMethodException | SecurityException e) {
        throw new RuntimeException(e);
        }
        throw new NoClassDefFoundError();
      });

      return protoMessage.toBuilder().mergeFrom(response.body().asInputStream()).build();

    }
}

the protoMessagesCache is used only to cache the result of reflection calls used to build a new protobuf message and increase performace (java.lang.reflect methods are very slow)

this class was developed using protobuf-java v 3.1.0

hope it helps someone

All 6 comments

there's no plan, but if you end up creating a codec, we could open a repo
in OpenFeign to host it.

This is very simple. Create a FiegnConfiguration and override the encoder and decoder

 @Configuration
 public class MyFeignConfiguration {
     //Autowire the message converters.
      @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    //add the protobuf http message converter
    @Bean
    ProtobufHttpMessageConverter protobufHttpMessageConverter() {
        return new ProtobufHttpMessageConverter();
    }

    //override the encoder
    @Bean
    public Encoder springEncoder(){
        return new SpringEncoder(this.messageConverters);
    }

    //override the encoder
    @Bean
    public Decoder springDecoder(){
        return new ResponseEntityDecoder(new SpringDecoder(this.messageConverters));
    }
}

This works perfectly and is able to do the encoding/decoding. Make sure the APIResource endpoints has the consumes="application/x-protobuf" and produces="application/x-protobuf".

@FeignClient(name = "my-service", configuration=MyFeignConfiguration.class)
public interface PaymentsApiResource {

    @RequestMapping(value="/v1/api/tryitout", method = RequestMethod.GET, consumes="application/x-protobuf", produces="application/x-protobuf")
    public ResponseEntity<MyResponse> tryitout();

}

@ashokayengar this requires adding spring deps to the path right?

I think something like square wire or protobuf itself is more appropriate for this org. spring stuff usually is best in the spring repo (spring-cloud-netflix), since there are spring experts there to help maintain it.

if a protobuf decoder is still needed i've developed a simple decoder not based on spring

new Decoder() {

    Map<Type, Message> protoMessagesCache = new HashMap<>();

    @Override
    public Object decode(Response response, Type type) throws IOException {

      if(response.body() == null) return null;

      Message protoMessage = protoMessagesCache.computeIfAbsent(type, t -> {
        try {
        Class<?> typeClass = Class.forName(type.getTypeName());
        if (Message.class.isAssignableFrom(typeClass)) {
          Method builderMethod = typeClass.getMethod("newBuilder");
          return ((Message.Builder) builderMethod.invoke(typeClass)).getDefaultInstanceForType();
        }
        } catch (ClassNotFoundException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
          | NoSuchMethodException | SecurityException e) {
        throw new RuntimeException(e);
        }
        throw new NoClassDefFoundError();
      });

      return protoMessage.toBuilder().mergeFrom(response.body().asInputStream()).build();

    }
}

the protoMessagesCache is used only to cache the result of reflection calls used to build a new protobuf message and increase performace (java.lang.reflect methods are very slow)

this class was developed using protobuf-java v 3.1.0

hope it helps someone

Very cool solution. I'll keep this in mind should I swing back around to things.

Was this page helpful?
0 / 5 - 0 ratings