英文:
Feign multipart with Json request part
问题
以下是翻译好的部分:
我在一个服务中有一个带有方法的 Feign 客户端:
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
MyDto uploadDocument(@RequestPart("file") MultipartFile file,
@RequestPart("myDto") String myDto);
我在另一个服务中有一个控制器:
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<MyDto> uploadDocument(@RequestParam("file") MultipartFile file,
@RequestPart("myDto") MyDto myDto) {
// ... 这里是一些代码
}
我面临的问题是,Feign 使用 Content-type: text/plain 发送 myDto,导致出现 HttpMediaTypeNotSupportedException 错误。
是否可能将 @RequestPart("myDto") String myDto
以 Content-type: application/json 的形式发送?
期望的原始请求:
----------------------------boundary
Content-Disposition: form-data; name="file"; filename="fileName"
<file>
----------------------------boundary
Content-Disposition: form-data; name="myDto";
**Content-Type: application/json**
{"myDto": ""}
当前的原始请求:
----------------------------boundary
Content-Disposition: form-data; name="file"; filename="fileName"
<file>
----------------------------boundary
Content-Disposition: form-data; name="myDto";
**Content-Type: text/plain**
{"myDto": ""}
英文:
I have Feign client in one service with a method
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
MyDto uploadDocument(@RequestPart("file") MultipartFile file,
@RequestPart("myDto") String myDto);
I have a controller in another service
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<MyDto> uploadDocument(@RequestParam("file") MultipartFile file,
@RequestPart("myDto") MyDto myDto) {
.... some code here
}
The issue I faced is that Feign sends myDto with Content-type : text/plain and I have HttpMediaTypeNotSupportedException
Is it possible to send @RequestPart("myDto") String myDto
with Content-type : application/json ?
expected Raw request:
----------------------------boundary
Content-Disposition: form-data; name="file"; filename="fileName"
<file>
----------------------------boundary
Content-Disposition: form-data; name="myDto"
**Content-Type: application/json**
{"myDto": ""}
Current raw request:
----------------------------boundary
Content-Disposition: form-data; name="file"; filename="fileName"
<file>
----------------------------boundary
Content-Disposition: form-data; name="myDto"
**Content-Type: text/plain**
{"myDto": ""}
答案1
得分: 10
成功解决方法是通过替换feign-form的PojoWriter。默认情况下,它会将对象的每个字段序列化为单独的部分。
@Bean
public Encoder feignEncoder() {
return new MyFormEncoder(objectMapper, new SpringEncoder(messageConverters));
}
public class MyFormEncoder extends SpringFormEncoder {
/**
* 使用指定的委托编码器构造函数。
*
* @param delegate 委托编码器,在无法对对象进行编码时使用。
*/
public MyFormEncoder(ObjectMapper objectMapper, Encoder delegate) {
super(delegate);
val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
processor.addFirstWriter(new MyPojoWriter(objectMapper));
}
}
@FieldDefaults(level = PRIVATE, makeFinal = true)
public class MyPojoWriter extends AbstractWriter {
private ObjectMapper objectMapper;
public MyPojoWriter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean isApplicable(Object object) {
return isUserPojo(object);
}
@Override
protected void write(Output output, String key, Object value) throws EncodeException {
var data = "";
try {
data = objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
}
val string = new StringBuilder()
.append("Content-Disposition: form-data; name=\"").append(key).append('"').append(CRLF)
.append("Content-Type: application/json; charset=").append(output.getCharset().name()).append(CRLF)
.append(CRLF)
.append(data)
.toString();
output.write(string);
}
private boolean isUserPojo(@NonNull Object object) {
val type = object.getClass();
val typePackage = type.getPackage();
return typePackage != null && typePackage.getName().startsWith("com.my-package.");
}
}
英文:
Managed to solve this by replacing the feign-form PojoWriter. By default it's serializing each field of an object as a separate part.
@Bean
public Encoder feignEncoder () {
return new MyFormEncoder(objectMapper, new SpringEncoder(messageConverters));
}
public class MyFormEncoder extends SpringFormEncoder {
/**
* Constructor with specified delegate encoder.
*
* @param delegate delegate encoder, if this encoder couldn't encode object.
*/
public MyFormEncoder(ObjectMapper objectMapper, Encoder delegate) {
super(delegate);
val processor = (MultipartFormContentProcessor) getContentProcessor(MULTIPART);
processor.addFirstWriter(new MyPojoWriter(objectMapper));
}
}
@FieldDefaults(level = PRIVATE, makeFinal = true)
public class MyPojoWriter extends AbstractWriter {
private ObjectMapper objectMapper;
public MyPojoWriter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public boolean isApplicable(Object object) {
return isUserPojo(object);
}
@Override
protected void write(Output output, String key, Object value) throws EncodeException {
var data = "";
try {
data = objectMapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
}
val string = new StringBuilder()
.append("Content-Disposition: form-data; name=\"").append(key).append('"').append(CRLF)
.append("Content-Type: application/json; charset=").append(output.getCharset().name()).append(CRLF)
.append(CRLF)
.append(data)
.toString();
output.write(string);
}
private boolean isUserPojo(@NonNull Object object) {
val type = object.getClass();
val typePackage = type.getPackage();
return typePackage != null && typePackage.getName().startsWith("com.my-package.");
}
}
答案2
得分: 5
Update to 2021.
//spring-cloud-openfeign-core
import org.springframework.cloud.openfeign.support.JsonFormWriter;
@Import(JsonFormWriter.class)
public class MyConfig {
@Bean
Encoder feignEncoder(JsonFormWriter jsonFormWriter) {
return new SpringFormEncoder() {{
var processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
processor.addFirstWriter(jsonFormWriter);
processor.addFirstWriter(new SpringSingleMultipartFileWriter());
processor.addFirstWriter(new SpringManyMultipartFilesWriter());
}};
英文:
Update to 2021.
//spring-cloud-openfeign-core
import org.springframework.cloud.openfeign.support.JsonFormWriter;
@Import(JsonFormWriter.class)
public class MyConfig {
@Bean
Encoder feignEncoder(JsonFormWriter jsonFormWriter) {
return new SpringFormEncoder() {{
var processor = (MultipartFormContentProcessor) getContentProcessor(ContentType.MULTIPART);
processor.addFirstWriter(jsonFormWriter);
processor.addFirstWriter(new SpringSingleMultipartFileWriter());
processor.addFirstWriter(new SpringManyMultipartFilesWriter());
}};
答案3
得分: 3
你需要在你的Feign客户端配置中定义名为JsonFormWriter
的Bean。
以下是客户端的示例代码:
@FeignClient(
name = "my-client",
configuration = MyClientConfiguration.class
)
public interface MyClient {
@PostMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
void uploadFile(@RequestPart("request") MyFileUploadRequest request,
@RequestPart("file") MultipartFile file);
}
public class MyClientConfiguration {
@Bean
JsonFormWriter jsonFormWriter() {
return new JsonFormWriter();
}
}
以及控制器的示例代码:
@RestController
public class FileUploadApi {
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void uploadFile(
@RequestPart("request") MyFileUploadRequest request,
@RequestPart("file") MultipartFile file) {
}
此功能是在此 Pull Request 的范围内添加的:https://github.com/spring-cloud/spring-cloud-openfeign/pull/314
英文:
You need to define bean JsonFormWriter
in your feign client's configuration.
Here is an example of the client:
@FeignClient(
name = "my-client",
configuration = MyClientConfiguration.class
)
public interface MyClient {
@PostMapping(value = "/file/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
void uploadFile(@RequestPart("request") MyFileUploadRequest request,
@RequestPart("file") MultipartFile file);
}
public class MyClientConfiguration {
@Bean
JsonFormWriter jsonFormWriter() {
return new JsonFormWriter();
}
}
And an example of the controller:
@RestController
public class FileUploadApi {
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void uploadFile(
@RequestPart("request") MyFileUploadRequest request,
@RequestPart("file") MultipartFile file) {
}
This feature was added in scope of this PR: https://github.com/spring-cloud/spring-cloud-openfeign/pull/314
答案4
得分: 0
使用 @PathVariable 注解,并且使用已注册的 SpringFormEncoder,你需要将 "myDto" 转换为 MultipartFile。
客户端:
@PostMapping(value = "/files/upload", consumes = MULTIPART_FORM_DATA_VALUE)
MyDto uploadDocument(@PathVariable("file") MultipartFile file, @PathVariable("myDto") MultipartFile myDto)
编码器:
@RequiredArgsConstructor
public class FeignClientConfiguration {
private final ObjectFactory<HttpMessageConverters> messageConverters;
// 为了支持多文件上传
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
从 DTO 创建 MultipartFile:
public MultipartFile createMultipartFile(@NotNull MyDto myDto) throws JsonProcessingException {
return new org.springframework.mock.web.MockMultipartFile(
"fileName",
"originalFileName",
MediaType.APPLICATION_JSON.toString(),
objectMapper.writeValueAsBytes(myDto));
}
关于为什么使用 @PathVariable 的这种解决方案可以工作,可以在这里找到说明:https://github.com/spring-cloud/spring-cloud-netflix/issues/867
英文:
Using the @PathVariable annotation and with a registered SpringFormEncoder you need to convert the "myDto" into a MultipartFile.
The client:
@PostMapping(value = "/files/upload", consumes = MULTIPART_FORM_DATA_VALUE)
MyDto uploadDocument(@PathVariable("file") MultipartFile file, @PathVariable("myDto") MultipartFile myDto)
The encoder:
@RequiredArgsConstructor
public class FeignClientConfiguration {
private final ObjectFactory<HttpMessageConverters> messageConverters;
//To support multipart file upload
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
Creating MultipartFile from the DTO:
public MultipartFile createMultipartFile(@NotNull MyDto myDto) throws JsonProcessingException {
return new org.springframework.mock.web.MockMultipartFile(
"fileName",
"originalFileName",
MediaType.APPLICATION_JSON.toString(),
objectMapper.writeValueAsBytes(myDto));
}
Why this solution with @PathVariable works is described here https://github.com/spring-cloud/spring-cloud-netflix/issues/867
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论