Feign httpclient file upload problem record

Problem statement

The original project http request is completed through feign + ribbon + urlconnection. Considering the network and cpu overhead caused by frequent connection release of urlconnection, http client is used as the connection pool. Upgraded, it is found that there are problems of calling downstream service code and tampering with the contents of files transferred.

Handle

Scrambling Problem

The original system feign-core, feign-httpclient uses 9.5.0, press GitHub issue See, this version does not use UTF-8 as encoding, resulting in Chinese scrambling. In view of the relatively old version of the project, this side only upgraded to 9.6.0 to solve the scrambling problem. Previously, it was also handled by feign interceptor, but this needs to determine the specific content-type for processing, to avoid trouble, open-feign emphasizes. Their code is utf-8. Some versions are not. They can be defined as bugs. Since they are bugs, they can be upgraded directly by upgrading.

The content of the file was tampered with

If you have a problem finding GitHub, or because of a version problem, A bug in the old version httpclient caused file upload content tampering, version 3.0.3 was repaired, and version 2.0.1 was used in earlier projects, a little bit old, referring to the following official recommendation requirements

Requirements

The feign-form extension depend on OpenFeign and its concrete versions:

  • all feign-form releases before 3.5.0 works with OpenFeign 9.* versions;
  • starting from feign-form's version 3.5.0, the module works with OpenFeign 10.1.0 versions and greater.

IMPORTANT: there is no backward compatibility and no any gurantee that the feign-form's versions after 3.5.0work with OpenFeign before 10.*. OpenFeign was refactored in 10th release, so the best approach - use the freshest OpenFeign and feign-form versions.

Notes:

  • spring-cloud-openfeign uses OpenFeign 9.* till v2.0.3.RELEASE and uses 10.* after. Anyway, the dependency already has suitable feign-form version, see dependency pom, so you don't need to specify it separately;
  • spring-cloud-starter-feign is a deprecated dependency and it always uses the OpenFeign's 9.* versions.

Using the following version of pom, the file upload content was tampered with under the interface httpclient

feign-version = 9.6.0
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-httpclient</artifactId>
			<version>${feign.version}</version>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-core</artifactId>
			<version>${feign.version}</version>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-hystrix</artifactId>
			<version>${feign.version}</version>
		</dependency>
		<dependency>
			<groupId>io.github.openfeign</groupId>
			<artifactId>feign-slf4j</artifactId>
			<version>${feign.version}</version>
		</dependency>

Upload of multi-attachment files

By default, SpringFormEncoder only handles single files, and a strong rollover is done.

@Override
public void encode (Object object, Type bodyType, RequestTemplate template) throws EncodeException {
  if (!bodyType.equals(MultipartFile.class)) {
    super.encode(object, bodyType, template);
    return;
  }

  val file = (MultipartFile) object;
  val data = singletonMap(file.getName(), object);
  super.encode(data, MAP_STRING_WILDCARD, template);
}


Our system file upload supports multiple attachments, which need to be rewritten

/**
 * @author yugj
 * @date 2019/8/28 4:27 p.m.
 */
public class FeignSpringFormEncoder extends FormEncoder {


    /**
     * Constructor with the default Feign's encoder as a delegate.
     */
    public FeignSpringFormEncoder() {
        this(new Default());
    }


    /**
     * Constructor with specified delegate encoder.
     *
     * @param delegate delegate encoder, if this encoder couldn't encode object.
     */
    public FeignSpringFormEncoder(Encoder delegate) {
        super(delegate);

        MultipartFormContentProcessor processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
        processor.addWriter(new SpringSingleMultipartFileWriter());
        processor.addWriter(new SpringManyMultipartFilesWriter());
    }


    @Override
    public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
        if (bodyType.equals(MultipartFile.class)) {
            MultipartFile file = (MultipartFile) object;
            Map data = Collections.singletonMap(file.getName(), object);
            super.encode(data, MAP_STRING_WILDCARD, template);
            return;
        } else if (bodyType.equals(MultipartFile[].class)) {
            MultipartFile[] file = (MultipartFile[]) object;
            if(file != null) {
                Map data = Collections.singletonMap(file.length == 0 ? "" : "file", object);
                super.encode(data, MAP_STRING_WILDCARD, template);
                return;
            }
        }
        super.encode(object, bodyType, template);
    }
}

Use this configuration for feignclient

@Service
@FeignClient(name = "xx-file-sv",
        fallbackFactory = FileGeneralHystrixFallbackAdapter.class,
        configuration = FeignSpringFormEncoder.class)
public interface IFileClient extends IFile {

}

Keywords: Programming github Spring network encoding

Added by watthehell on Wed, 28 Aug 2019 17:57:40 +0300