I'm trying to accomplish a multipart file upload using feign, but I can't seem to find a good example of it anywhere. I essentially want the HTTP request to turn out similar to this:
...
Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="name"
Larry
--AaB03x
Content-Disposition: form-data; name="file"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x--
Or even...
------fGsKo01aQ1qXn2C
Content-Disposition: form-data; name="file"; filename="file.doc"
Content-Type: application/octet-stream
... binary data ...
------fGsKo01aQ1qXn2C--
Do I need to manually build the request body, including generating the multipart boundaries? That seems a bit excessive considering everything else this client can do.
No, you don't. You just need to define a kind of proxy interface method, specify the content-type as: multipart/form-data and other info such as parameters required by the remote API. Here is an example:
public interface FileUploadResource {
#RequestLine("POST /upload")
#Headers("Content-Type: multipart/form-data")
Response uploadFile(#Param("name") String name, #Param("file") File file);
}
The completed example can be found here: File Uploading with Open Feign
For spring boot 2 and spring-cloud-starter-openfeign use this code:
#PostMapping(value="/upload", consumes = "multipart/form-data" )
QtiPackageBasicInfo upload(#RequestPart("package") MultipartFile package);
You need to change #RequestParam to #RequestPart in the feign client call to make it work, and also add consumes to the #PostMapping.
MBozic solution not full, you will also need to enable an Encoder for this:
public class FeignConfig {
#Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
#Bean
public Encoder feignFormEncoder () {
return new SpringFormEncoder(new SpringEncoder(messageConverters));
}
}
#FeignClient(name = "file", url = "http://localhost:8080", configuration = FeignConfig.class)
public interface UploadClient {
#PostMapping(value = "/upload-file", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
String fileUpload(#RequestPart(value = "file") MultipartFile file);
}
If you are already using Spring Web, you can try my implementation of a Feign Encoder that is able to create Multipart requests. It can send a single file, an array of files alongwith one or more additional JSON payloads.
Here is my test project. If you don't use Spring, you can refactor the code by changing the encodeRequest method in FeignSpringFormEncoder.
Let me add Answer for latest OpenFeign :
Add dependency for Feign-Form:
io.github.openfeign.form
feign-form
3.8.0
Add FormEncoder to your Feign.Builder like so:
SomeApi github = Feign.builder()
.encoder(new FormEncoder())
.target(SomeApi.class, "http://api.some.org");
API endpoint
#RequestLine("POST /send_photo")
#Headers("Content-Type: multipart/form-data")
void sendPhoto (#Param("is_public") Boolean isPublic, #Param("photo") FormData photo);
Refer : https://github.com/OpenFeign/feign-form
Call from one service to another service for file transfer/upload/send using feign client interface:
#FeignClient(name = "service-name", url = "${service.url}", configuration = FeignTokenForwarderConfiguration.class)
public interface UploadFeignClient {
#PostMapping(value = "upload", headers = "Content-Type= multipart/form-data", consumes = "multipart/form-data")
public void upload(#RequestPart MultipartFile file) throws IOException;
}
**Actual API:**
#RestController
#RequestMapping("upload")
public class UploadController {
#PostMapping(value = "/upload", consumes = { "multipart/form-data" })
public void upload(#RequestParam MultipartFile file) throws IOException {
//implementation
}
}
Related
I have a DTO that contains other DTOs and a list of multipart files. I am trying to process that DTO but I can't seem to be able to read the requst.
class TeacherDTO {
private SpecializationDto specializationDto;
private List<MultipartFile> files;
}
#PostMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Object> saveNewTeacher(#ModelAttribute #Valid TeacherDTO teacherDto){
//process request
}
When creating an example request from Swagger UI, I get the following exception:
type 'java.lang.String' to required type 'SpecializationDto' for property 'specializationDto': no matching editors or conversion strategy found
If I put #RequestBody instead of #ModelAttribute then I get
Content type 'multipart/form-data;boundary=----WebKitFormBoundaryVEgYwEbpl1bAOjAs;charset=UTF-8' not supported]
Swagger dependencies:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-data-rest</artifactId>
<version>1.5.2</version>
</dependency>
OpenAPI3.0 config:
#Configuration
public class OpenApi30Config {
private final String moduleName;
private final String apiVersion;
public OpenApi30Config(
#Value("${spring.application.name}") String moduleName,
#Value("${api.version}") String apiVersion) {
this.moduleName = moduleName;
this.apiVersion = apiVersion;
}
#Bean
public OpenAPI customOpenAPI() {
final var securitySchemeName = "bearerAuth";
final var apiTitle = String.format("%s API", StringUtils.capitalize(moduleName));
return new OpenAPI()
.addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
.components(
new Components()
.addSecuritySchemes(securitySchemeName,
new SecurityScheme()
.name(securitySchemeName)
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
)
)
.info(new Info().title(apiTitle).version(apiVersion));
}
}
This seems to be an issue with how the springdoc-openapi-ui builds the form-data request. I was able to reproduce this and noticed that it sends a multipart-request like (intercepted through browser's dev-tools):
-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="specializationDto"\r\n\r\n{\r\n "something": "someValue"\r\n}
-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream
<content>
-----------------------------207598777410513073071314493349
Content-Disposition: form-data; name="files"; filename="somefile.txt"
Content-Type: application/octet-stream
<content>
With that payload Spring is not able to deserialize the specializationDto, resulting in the "no matching editors or conversion strategy found" exception that you've observed. However, if you send the request through postman or curl with (note the dot-notation for the specializationDto object)
curl --location --request POST 'http://localhost:8080/upload' \
--form 'files=#"/path/to/somefile"' \
--form 'files=#"/path/to/somefile"' \
--form 'specializationDto.something="someValue"'
then Spring is able to parse it correctly. Here's my rest-mapping that will log the following as expected:
#RequestMapping(value = "/upload", method = RequestMethod.POST,
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public void upload(#ModelAttribute TeacherDto requestDto) {
System.out.println(requestDto);
}
// logs:
TeacherDto(specializationDto=SpecializationDto(something=someValue), files=[org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile#78186ea6, org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile#461c9cbc])
I suggest you open a bug on their github page.
EDIT:
After OP opened a github ticket, here's part of the author's feedback:
[...] With spring, you can use #RequestPart spring annotation to describe
the different parts, with the related encoding media type. Note that
there is a limitation with the current swagger-ui implementation as
the encoding attribute is not respected on the request.[...]
They also provided a possible workaround, which looks like this:
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> saveNewTeacher( #RequestPart(value = "specializationDto") #Parameter(schema =#Schema(type = "string", format = "binary")) final SpecializationDto specializationDto,
#RequestPart(value = "files") final List<MultipartFile> files){
return null;
}
Image
I want to write a client code to consume an API. The API is expecting a text file. When I select the binary file option in the postman tool and select any text file from my local it worked. how to implement this in spring ?. I have tried MULTIPART_FORM_DATA but no luck.
If You mean file
#RestController
public class FileContentController {
#RequestMapping(value="/up", method = RequestMethod.POST)
public ResponseEntity<?> upload(#RequestParam("file") MultipartFile file)
throws IOException {
String contentType=file.getContentType());
InputStream i=file.getInputStream();
return new ResponseEntity<>(HttpStatus.OK);
}
return null;
}
also spring boot has multi part confs, you should enable it and set size and tempdir
,In Earlier version spring boot need to add:
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=${java.io.tmpdir}
However in your client code you should not set content-type application/json in your header post request
simple fetch should be such
const input = document.getElementById('uploadInput');
const data = new FormData();
data.append('file', input.files[0]);
var resp = await fetch('upload/', {
method: 'POST',
body: data
});
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
if (resp.ok) {
await this.images();
}
This is related to an existing spring boot question raised by me(Request Body is not properly encoded and hidden when using spring form encoder in Feign Client).
According to this question, we can add either content type in headers or add during request mapping itself as consumes.
So what I did was added content type in headers in the client configuration class
public class EmailClientConfiguration {
#Bean
public RequestInterceptor requestInterceptor(Account<Account> account) {
return template -> {
template.header("Content-Type", "application/x-www-form-urlencoded");
};
}
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
#Bean
public Decoder feignDecoder() {
return new JacksonDecoder();
}
#Bean
public Encoder feignFormEncoder () {
return new SpringFormEncoder(new JacksonEncoder());
}
}
and I see in the headers the content type is correctly set as application/x-www-form-urlencoded when the request is sent. But the request body is still sent in json format and also not hidden.
Request Body:
Map<String, String> requestBody = new HashMap<>();
requestBody.put("username", "xyz");
requestBody.put("email", "xyz#gmail.com");
requestBody.put("key", "xxx");
Request Body received in server end:
{"{\n \"key\" : \"xxx\",\n \"email\" : \"xyz#gmail.com\",\n \"username\" : \"xyz\"\n}"
When I add consumes in my request mapping as application/x-www-form-urlencoded
#FeignClient(name = "email", url = "localhost:3000",
configuration = EmailClientConfiguration.class)
public interface EmailClient {
#PostMapping(value = "/email/send", consumes = "application/x-www-form-urlencoded")
ResponseDto sendEmail(#RequestBody Map<String, String> requestBody);
}
it works fine(request body is hidden in server end and also properly encoded). And when I removed the header in the configuration class and adding only consumes works fine without no issues but the vice versa has this problem.
I searched in internet for this and couldn't find any answer.
Feign encodes the request body and parameters before passing the request to any RequestInterceptor (and rightly so). If you do not declare consumes = "application/x-www-form-urlencoded", SprinFormEncoder doesn't know that you're trying to send form data, so it delegates serialization to the inner JacksonEncoder which only does JSON (see for yourself by printing template.body() before setting the header).
Handling such a well-supported header in the interceptor doesn't seem like a good idea, when you already have consumes. If you insist on doing so, you have to provide your own encoder which doesn't rely on the header value and always outputs form-urlencoded data.
I am making a spring boot REST application. I am trying to make a multipart form upload controller which will handle a form data and a file upload together. This is my controller code at the moment :
#RequestMapping(value = "", method = RequestMethod.POST, headers="Content-Type=multipart/form-data")
#PreAuthorize("hasRole('ROLE_MODERATOR')")
#ResponseStatus(HttpStatus.CREATED)
public void createNewObjectWithImage(
/*#RequestParam(value="file", required=true) MultipartFile file,
#RequestParam(value="param_name_1", required=true) final String param_name_1,
#RequestParam(value="param_name_2", required=true) final String param_name_2,
#RequestParam(value="param_name_3", required=true) final String param_name_3,
#RequestParam(value="param_name_4", required=true) final String param_name_4,
#RequestParam(value="param_name_5", required=true) final String param_name_5*/
#ModelAttribute ModelDTO model,
BindingResult result) throws MyRestPreconditionsException {
//ModelDTO model = new ModelDTO(param_name_1, param_name_2, param_name_3, param_name_4, param_name_5);
modelValidator.validate(model, result);
if(result.hasErrors()){
MyRestPreconditionsException ex = new MyRestPreconditionsException(
"Model creation error",
"Some of the elements in the request are missing or invalid");
ex.getErrors().addAll(
result.getFieldErrors().stream().map(f -> f.getField()+" - "+f.getDefaultMessage()).collect(Collectors.toList()));
throw ex;
}
// at the moment, model has a MultipartFile property
//model.setImage(file);
modelServiceImpl.addNew(model);
}
I have tried both with the #ModelAttribute annotation and sending request parameters, but both of these methods have failed.
This is the request i am sending :
---------------------------acebdf13572468
Content-Disposition: form-data; name="file"; filename="mint.jpg"
Content-Type: image/jpeg
<#INCLUDE *C:\Users\Lazaruss\Desktop\mint.jpg*#>
---------------------------acebdf13572468
Content-Disposition: form-data; name=”param_name_1”
string_value_1
---------------------------acebdf13572468
Content-Disposition: form-data; name=”param_name_2”
string_value_2
---------------------------acebdf13572468
Content-Disposition: form-data; name=”param_name_3”
string_value_3
---------------------------acebdf13572468
Content-Disposition: form-data; name=”param_name_4”
string_value_4
---------------------------acebdf13572468
Content-Disposition: form-data; name=”param_name_5”
string_value_5
---------------------------acebdf13572468--
My application is stateless, and uses spring security with authorities.
In my security package, i have included the AbstractSecurityWebApplicationInitializer class
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
#Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
I also use a StandardServletMultipartResolver in my #Configuration class
And in my WebInitializer, i add this code :
MultipartConfigElement multipartConfigElement = new MultipartConfigElement("/tmp",
3 * 1024 * 1024, 6 * 1024 * 1024, 1 * 512 * 1024);
apiSR.setMultipartConfig(multipartConfigElement);
When i try to use the controller with the commented code (#RequestParams annotations), i get a 404 not found error.
And when i try to use the controller with the #ModuleAttribute annotation, the model object is empty.
I had a similar problem. When you want to send Object + Multipart. You have to (or at least I don't know other solution) make your controller like that:
public void createNewObjectWithImage(#RequestParam("model") String model, #RequestParam(value = "file", required = false) MultipartFile file)
And then: Convert String to your Object using:
ObjectMapper mapper = new ObjectMapper();
ModelDTO modelDTO = mapper.readValue(model, ModelDTO.class);
And in Postman you can send it like that:
can receive objects and files
#PostMapping(value = "/v1/catalog/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE )
public void createNewObjectWithImage(
#RequestPart ModelTO modelTO,
#RequestPart MultipartFile image)
ModelTO
public class ModelTO {
private String name;
public ModelTO() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and curl example:
curl -X POST "https://your-url.com/v1/catalog/create" -H "accept: application/json;charset=UTF-8" -H "Content-Type: multipart/form-data" -F "image=#/pathtoimage/powerRager.jpg;type=image/jpeg" -F "modelTO={\"name\":\"White\"};type=application/json;charset=utf-8"
Postman and other software not support send application/json type for form-data params.
I have a service where I want to be able to optionally upload a file (including a file will run a separate function) with a POST request.
A simplified version of what my ReqestMapping looks like is this:
#ApiOperation(value = "Data", nickname = "Create a new data object")
#RequestMapping(value = "/add/{user_id}", produces = "application/json", method = RequestMethod.POST)
public ResponseEntity<Data> addData(#RequestParam("note") String body,
#RequestParam("location") String location,
#RequestParam(value = "file", required = false) List<MultipartFile> file,
#PathVariable String user_id){
if (file != null) {
doSomething(file);
}
doRegularStuff(body, location, user_id);
return new ResponseEntity(HttpStatus.OK);
}
As can be seen, I have the required = false option for my List of multipart files. However, when I attempt to curl the endpoint without any files and while stating that my content type is Content-Type: application/json, I get the error that my request isn't a multipart request.
Fine. So I change to Content-Type: multipart/form-data and without any files, I get the request was rejected because no multipart boundary was found (obviously, since I don't have a file).
This leads me to wonder how I can have a optional multipart parameter in my Spring endpoints? I would like to avoid having to add additional parameters to my request, such as "File Attached: True/False" as that can become cumbersome and unnecessary when the server can just check for existence.
Thanks!
There is no problem in your code, but the problem in client request, because Content-Type should be like below if you want to upload image,
multipart/form-data; boundary="123123"
try to remove the Content-Type header and test, i will put one example for server code and client request
Server code:
#RequestMapping(method = RequestMethod.POST, value = "/users/profile")
public ResponseEntity<?> handleFileUpload(#RequestParam("name") String name,
#RequestParam(name="file", required=false) MultipartFile file) {
log.info(" name : {}", name);
if(file!=null)
{
log.info("image : {}", file.getOriginalFilename());
log.info("image content type : {}", file.getContentType());
}
return new ResponseEntity<String>("Uploaded",HttpStatus.OK);
}
Client Request using Postman
with image
without image
Curl example:
without image, with Content-Type
curl -X POST -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "name=test" "http://localhost:8080/api/users/profile"
without image, without Content-Type
curl -X POST -F "name=test" "http://localhost:8080/api/users/profile"