banner
ZetoHkr

ZetoHkr

摸🐟从未停止,努力从未开始
github

Resolve the issue of Chinese name garbled characters when uploading files through Zuul Gateway in Spring Cloud.

Prerequisite Environment#

JDK: 8u423

Spring Cloud: Edgware.SR6 (this is quite an old version)

Gateway: Zuul (spring-cloud-starter-netflix-zuul - 1.4.7.RELEASE)

Cause#

Previously, while developing at the company, I encountered a strange issue where uploaded files with Chinese filenames (referring to the filename obtained through the MultipartFile's getOriginalFileName() method) would turn into "???" garbled text. Initially, I thought it was due to incorrect encoding in the Java environment and operating system. After some checks, I found that both were using UTF-8 encoding, which was quite odd.

Investigation#

Since the environment and system were fine, it must be a code issue. After further investigation, I found that there was no garbled text in the filenames within the monolithic project; it only occurred when passing through the Zuul gateway. I also tried searching online and found basically two solutions:

  1. Add configuration in the gateway application.yml
zuul:
    servlet-path: /
  1. Prefix the request with zuul
    For example, the actual API is /upload-file garbled
    Change it to /zuul/upload-file to resolve

However, the above solutions did not solve most cases. For instance, using method 1 resulted in no response for the POST request, and method 2 returned a 404. After some more investigation, we focused on the "FormHttpMessageConverter" class, which seemed suspicious. It is known that file uploads use Form Data, and according to relevant documentation, when I upload files through the gateway, this class is involved. Upon reading the source code of this class, I found a suspicious point:

The purpose of the FormHttpMessageConverter class is to read and write the "application/x-www-form-urlencoded" media type as MultiValueMap<String, String>, and to write "multipart/form-data" and "multipart/mixed" media types as MultiValueMap<String, Object> (but it cannot read them).
Original Java doc

Why is it using ASCII encoding here? Let's analyze briefly: ASCII has only about a hundred characters, so how could it convert Chinese characters? Based on this reasoning, we continued to analyze the source code. We know that the inner class "MultipartHttpOutputMessage" converts request headers, and we also know that MultipartFile places the filename (Original File Name) in the HTTP request headers (Reference link); this explains the situation. Now that we have a general idea of the cause, we can attempt to fix the issue.

Fix#

Here, we will make a simple and direct fix (referring to modifying the Spring Framework source code) (oops, actually I wanted to compile the Spring Framework source code as well).

It is also possible to upgrade dependencies (if you are unsure of the consequences of upgrading, it is not recommended to upgrade dependencies directly).

Fixing this issue is quite simple, just do this and that and finally do this, we only need to change this part of the class.

Spring Framework 4.3.x Source Code

Change the part in the red box to return name.getBytes("UTF-8");.

Tips: In fact, just looking at the current main branch code, you can see that it now defaults to UTF-8 encoding. (As shown in the picture)
Spring Framework Main Branch Source Code

Other Notes#

Compiling Spring Framework#

  • Use JDK 1.8
  • Be sure to read README.md first during compilation
  • Change the repository addresses in build.gradle to the following format (as some may not work or may be blocked), you need to change it in two places: one under buildscript and another under configure(allprojects)
repositories {
        // mavenCentral()
        // gradlePluginPortal()
        // jcenter()
        maven { url 'https://repo.spring.io/snapshot' }
        // maven { url "https://repo.spring.io/plugins-release" }
        maven { url "http://maven.aliyun.com/nexus/content/groups/public" }
        // Aliyun new repository
        maven { url "https://maven.aliyun.com/repository/central" }
        maven { url "https://maven.aliyun.com/repository/google" }
        maven { url "https://maven.aliyun.com/repository/gradle-plugin" }
        maven { url "https://maven.aliyun.com/repository/jcenter" }
        maven { url "https://maven.aliyun.com/repository/spring" }
        maven { url "https://maven.aliyun.com/repository/spring-plugin" }
        maven { url "https://maven.aliyun.com/repository/public" }
        maven { url "https://maven.aliyun.com/repository/releases" }
        maven { url "https://maven.aliyun.com/repository/snapshots" }
        maven { url "https://maven.aliyun.com/repository/grails-core" }
        maven { url "https://maven.aliyun.com/repository/mapr-public" }
        maven { url "http://repo.springsource.org/plugins-release" }
    }
  • The normal commands to execute are:
    ./gradlew cleanIdea :spring-oxm:compileTestJava
    ./gradlew install
    ./gradlew build -x test
    
  • If you want to upload to a private repository, you can write the following code in the gradle/publish-maven.gradle file in the project root directory (replace the Chinese text in the code with your required content):
    uploadArchives {
        onlyIf { project.file('build/libs').exists() }
        repositories {
            mavenDeployer {
                customizePom(pom, project)
                repository(url: "write your repository address here") {
                    authentication(userName: repository username, password: repository password)
                }
                snapshotRepository(url: "write your snapshot repository address here") {
                    authentication(userName: repository username, password: repository password)
                }
                pom.version = version
            }
        }
    }
    
  • Then execute ./gradlew uploadArchives to complete.
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.