i am trying to call the api through feign client and upload the file along with some string parameter through MultipartFile.
This is my client code:
package com.abc;
import feign.codec.Encoder;
#FeignClient(url = "https://xys.com", name = "uploadfile", configuration = UploadFileFeign.MultipartSupportConfig.class)
public interface UploadFileFeign {
#PostMapping(value = "leaveApplication", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ObjectRestResponse<?> handleFileUpload(#RequestParam(value = "request") String request,
#RequestPart(value = "file") MultipartFile srcFile);
class MultipartSupportConfig {
#Bean
public Encoder feignFormEncoder() {
return new FeignSpringFormEncoder();
}
#Bean
public feign.Logger.Level multipartLoggerLevel() {
return feign.Logger.Level.FULL;
}
}
}
Below is the API code which my client is calling.
#RequestMapping(value="/services/leaveApplication", method=Request.POST, produces = MediaType.MULTIPART_FORM_DATA_VALUE, headers="Accept=application/json")
public ResponseOutput leaveApplication(#RequestParam("request") String request, #RequestParam(value = "file", required=false) MultipartFile srcFile) throws Exception {
}
But i am getting error in response:
403 - Forbidden error.
You do not have permission to access the /services/leaveApplication
Other api's which do not involve file upload are working fine.
Typo here :
Request mapping URL is : /services/leaveApplication
But you are accessing : /service/leaveApplication
Change service to services
Related
I am working on a Spring Boot Project where I want to send JSON data and Multipart File (Image) in a single API call. For this, I referred- https://blogs.perficient.com/2020/07/27/requestbody-and-multipart-on-spring-boot/#:~:text=Usually%20we%20add%20%40RequestBody%20and,So%2C%20annotation%20should%20be%20altered.
My Controller is-
#PostMapping(value = "/create",consumes = {MediaType.APPLICATION_JSON_VALUE,MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<SuccessResponse<PostDto>> createPost(
#RequestPart("post") String post,
#RequestPart("image") MultipartFile file,
#RequestParam(name = "userid") Integer uid,
#RequestParam(name = "categoryid") Integer categoryId) {
log.info("Filename :" + file.getOriginalFilename());
log.info("Size:" + file.getSize());
log.info("Contenttype:" + file.getContentType());
//convert the post string to POJO
PostDto postDto=postService.getJson(post);
//Now create the post
PostDto newPost = this.postService.createPost(postDto, uid, categoryId, file);
SuccessResponse<PostDto> successResponse = new SuccessResponse<>(AppConstants.SUCCESS_CODE,
AppConstants.SUCCESS_MESSAGE, newPost);
return new ResponseEntity<>(successResponse, HttpStatus.OK);
}
I am using Postman for testing-
When I make the request, I get the following error (Note- I have set the error response in Spring Security as seen in the image.)
[nio-8085-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required part 'image' is not present.]
I tried another approach but it gives another error-
#PostMapping("/uploadimage/{postid}/{isUpdatingPost}")
public ResponseEntity<SuccessResponse<String>> uploadImage(#RequestParam(name="file") MultipartFile file, #PathVariable("postid") int postid, #PathVariable("isUpdatingPost")boolean isUpdatingPost){
String result=this.postService.uploadImage(file, postid, isUpdatingPost);
SuccessResponse<String> response=new SuccessResponse<>(AppConstants.SUCCESS_CODE,AppConstants.SUCCESS_MESSAGE,result);
return new ResponseEntity<>(response,HttpStatus.OK);
}
[Request processing failed: org.springframework.web.multipart.MultipartException: Current request is not a multipart request] with root cause
org.springframework.web.multipart.MultipartException: Current request is not a multipart request
I am not able to understand the problem in these approaches. I also set the content type to multipart/form-data in Postman and the consume parameter in #PostMapping, but still getting these errors.
Please help in finding the problem!
This worked for me.
For attaching single file with #RequestParam:
#PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<?> uploadImage(#RequestParam MultipartFile file) throws IOException {
// ...
}
For attaching file in DTO with #ModelAttribute:
public record FileDTO(
Integer id,
MultipartFile file) {
}
#PostMapping(value = "/2", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<?> uploadImage2(#ModelAttribute FileDTO dto) throws IOException {
// ...
}
BTW, you probably want use OpenAPI(Swagger UI) for manually testing your app, it's easier than using postman.
Reference article: https://www.baeldung.com/spring-file-upload
Please have a look at my codes below. The Java codes seemed to work just fine, but localhost:8080 gives me the error code 404 when I try to access it. I want to make localhost 8080 work. Please let me know if you need further information.
Application
#SpringBootApplication(exclude = { ErrorMvcAutoConfiguration.class })
// exclude part is to elimnate whitelabel error
#EnableScheduling
public class Covid19TrackerApplication {
public static void main(String[] args) {
SpringApplication.run(Covid19TrackerApplication.class, args);
}
}
Controller
#Controller
public class HomeController {
CovidDataService covidDataService;
#RequestMapping("/")
public #ResponseBody String home(Model model) {
model.addAttribute( "locationStats", covidDataService.getAllStats());
return "home";
}
}
Main Code
#Service
public class CovidDataService {
private static String Covid_Data_URL = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv";
private List<LocationStats> allStats = new ArrayList<>();
public List<LocationStats> getAllStats() {
return allStats;
}
#PostConstruct//?
#Scheduled(cron = "* * 1 * * *") //????
// * sec * min *hour and so on
public void fetchCovidData() throws IOException, InterruptedException {
List<LocationStats> newStats = new ArrayList<>(); // why we are adding this? To prevent user get an error while we are working on new data.
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(Covid_Data_URL))
.build(); // uri = uniform resource identifier
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
StringReader csvBodyReader = new StringReader(httpResponse.body()); //StringReader needs to be imported
Iterable<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().parse(csvBodyReader); // parse(in) had error, we needed a "reader" instance.
for (CSVRecord record : records) {
LocationStats locationStat = new LocationStats(); //create an instance
locationStat.setState(record.get("Province/State"));
locationStat.setCountry(record.get("Country/Region"));
locationStat.setLatestTotalCase(Integer.parseInt(record.get(record.size()-1)));
System.out.println(locationStat);
newStats.add(locationStat);
}
this.allStats = newStats;
}
}
The problem may come from this piece of code
#RequestMapping("/")
public #ResponseBody String home(Model model) {
model.addAttribute( "locationStats", covidDataService.getAllStats());
return "home";
}
it returns "home" which should be existing view, normally, the view will be a jsp file which is placed somewhere in WEB-INF, please see this tutorial: https://www.baeldung.com/spring-mvc-view-resolver-tutorial
In the case of wrong mapping, it may returns 404 error
when you run the server, you should be able to see which port it's taken in the console.
Also, is server.port=8080 in the src/main/resources/application.properties file?
In the controller, the RequestMapping annotation is missing the method type and header
#RequestMapping(
path="/",
method= RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE)
public String home(Model model) {
model.addAttribute( "locationStats", covidDataService.getAllStats());
return "home";
}
make sure to add consumes for POST or PUT methods
A bit unrelated to the question but the line in the controller is missing #Autowired annotation
CovidDataService covidDataService;
Preferrably, add the #Autowired in the constructor
#Autowired
public HomeController(CovidDataService covidDataService) {
this.covidDataService = covidDataService;
}
I am trying to get a JSON Object from an API while using an API key in the header.
This works perfectly when I test it in Postman, but when I try it in my Spring application.
I got an error:
There was an unexpected error (type=Not Found, status=404). No message available.
API-Key and the URL are changed out with dummy data
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
public #ResponseBody void testingAPI() throws ParseException {
final RestTemplate restTemplate = new RestTemplate();
final String response = restTemplate.getForObject("url", String.class);
System.out.println(response);
}
If your are testing your API in Postman and it works perfectly, and in your application it's not working, this means that your method mapping isn't correct or it's not correctly called.
But from the comments where you said that the same configuration works if you don't have an API key, this means that your header isn't correctly mapped, in this case I'd recommend using #RequestHeader annotation to handle your API key.
Your method mapping will be like this:
#RequestMapping(value = "/apitest", method = RequestMethod.GET)
public #ResponseBody void testingAPI(#RequestHeader("APIKey") String apiKey) throws ParseException {
final RestTemplate restTemplate = new RestTemplate();
final String response = restTemplate.getForObject("url", String.class);
System.out.println(response);
}
If you want to use 12345 as a default value for your API key param you can write:
#RequestMapping(value = "/apitest", method = RequestMethod.GET)
public #ResponseBody void testingAPI(#RequestHeader(name = "APIKey", defaultValue = "12345") String apiKey) throws ParseException {
You can check How to Read HTTP Headers in Spring REST Controllers tutorial for further reading about the #RequestHeader annotation.
A quick fix could be to change the void to a Class. like
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
#ResponseBody
public XXXResponse testingAPI() throws ParseException {
...
return new XXXRepsonse();
}
or:
#RequestMapping(value = "/apitest", method = RequestMethod.GET, headers ="APIKey=12345")
public void testingAPI() throws ParseException {
...
}
Where are you add header in your request? You controller should look like this:
#RestController
public class DemoController {
#GetMapping("/apitest" )
public void doRequest(#RequestHeader(name = "Ocp-Apim-Subscription-Key", defaultValue = "12345") String apiKey) {
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Ocp-Apim-Subscription-Key", apiKey);
ResponseEntity<String> responseEntity = restTemplate.exchange("https://api.kognif.ai/AIS/v1/aispositioncurrent?vesselimo=8505941&output=json",
HttpMethod.GET, new HttpEntity<String>(headers), String.class);
System.out.println(responseEntity.toString());
}
}
Postman request to your Spring app must be :
And of course, specify valid Ocp-Apim-Subscription-Key
I'm trying to attach a file to send to and endpoint as a MultipartFile but I'm getting this exception:
Expected no exception to be thrown, but got 'feign.codec.EncodeException'
//...
Caused by: feign.codec.EncodeException: Could not write request:
no suitable HttpMessageConverter found for request type [java.util.LinkedHashMap]
and content type [multipart/form-data]
My method is:
//...
final User user
//...
#Override
DocumentResponse attachDocument(File file, String userId, String documentId) {
String timestamp = String.valueOf(System.currentTimeMillis())
String url = "${myProperties.apiUrl}/documents/attach?ts=${timestamp}"
String digest = myJWT.sign(HttpMethod.POST, url)
MultipartFile multiFile = new MockMultipartFile("test.xml",
new FileInputStream(file))
DocumentResponse documentResponse = user.attachDocument(multiFile,
userId, documentId, timestamp, digest)
return documentResponse
}
My interface is:
#FeignClient(name = 'myUser', url = '${apiUrl}', configuration = myConfiguration)
interface User {
//...
#PostMapping(value = '/documents/attach', consumes = 'multipart/form-data')
DocumentResponse attachDocument(#PathVariable('file') MultipartFile multiFile,
#PathVariable('clientId') String userId,
#PathVariable('documentId') String documentId,
#RequestParam('ts') String timestamp,
#RequestParam('digest') String digest)
}
And my configuration file is:
#Slf4j
#Configuration
class myConfiguration {
#Bean
Retryer feignRetryer(#Value('${feign.client.config.myUser.period}') Long period,
#Value('${feign.client.config.myUser.maxInterval}') Long maxInterval,
#Value('${feign.client.config.myUser.maxAttempts}') Integer maxAttempts) {
return new Retryer.Default(period, maxInterval, maxAttempts)
}
#Bean
ErrorDecoder errorDecoder() {
return new ErrorDecoder() {
#Override
Exception decode(String methodKey, Response response) {
if (HttpStatus.OK.value() != response.status()) {
FeignException ex = FeignException.errorStatus(methodKey, response)
if (response.status() != HttpStatus.BAD_REQUEST.value()) {
return new RetryableException('getting conflict and retry', new Date(System.currentTimeMillis() + TimeUnit.SECONDS
.toMillis(1)))
}
return new MyDocumentException()
}
}
}
}
}
Also, I have tried to add this code to myConfiguration file:
#Bean
Encoder encoder() {
return new FormEncoder()
}
But I have another exception:
Cannot cast object 'feign.form.FormEncoder#5fa78e0a'
with class 'feign.form.FormEncoder' to class 'java.beans.Encoder'
I'm using Spring boot '2.0.2.RELEASE' with:
"io.github.openfeign.form:feign-form:3.4.1",
"io.github.openfeign.form:feign-form-spring:3.4.1",
I checked these posts:
How to send POST request by Spring cloud Feign
no suitable HttpMessageConverter found for response type
Could not write request: no suitable HttpMessageConverter found for request type and content type
Converting file to multipartfile
Any suggestion?
feign.codec.EncodeException raised when a problem occurs encoding a message.
I think the #PathVariable('file') MultipartFile multiFile, can be converted to a base64 sting and pass it to REST API or add an Encoder to MultipartFile
I am sending to backend a file, this is the upload code:
export class FileUploadComponent {
#Input() multiple: boolean = false;
#ViewChild('fileInput') inputEl: ElementRef;
constructor(private http: Http) {}
upload() {
let inputEl: HTMLInputElement = this.inputEl.nativeElement;
let fileCount: number = inputEl.files.length;
let formData = new FormData();
if (fileCount > 0) { // a file was selected
for (let i = 0; i < fileCount; i++) {
formData.append('file[]', inputEl.files.item(i));
}
this.http
.post('http://localhost:8080/upload', formData).toPromise().then(() => console.log('success')).catch(() => console.log('error'));
}
}
}
Now on the backendside I'd like to receive it via controller, but I don't know how to map the file property, following gives null's:
public #ResponseBody String handleFileUpload(#RequestBody MultipartFile file)
Your method signature is incorrect.
#RequestBody annotation parameters maps to the HTTP request body.
#RequestParam annotation parameters maps to the specific Servlet request parameters.
Use the following:
public #ResponseBody String handleFileUpload(#RequestParam MultipartFile file)
If you are sending multiple, then use array:
public #ResponseBody String handleFileUpload(#RequestParam MultipartFile[[] file)