I am trying to upload a file to my server using an endpoint exposed through spring. However when I try to test the api through postman, I get Current request is not a multipart request error. I went through this question MultipartException: Current request is not a multipart request but still couldn't fix this. Please Help. Thanks in advance.
Here is my controller:
#RestController
#RequestMapping
public class UploadController {
#Autowired
StorageService storageService;
List<String> files = new ArrayList<String>();
#PostMapping("/post")
public ResponseEntity<String> handleFileUpload(#RequestParam("file") MultipartFile file) {
String message = "";
try {
storageService.store(file);
files.add(file.getOriginalFilename());
message = "You successfully uploaded " + file.getOriginalFilename() + "!";
return ResponseEntity.status(HttpStatus.OK).body(message);
} catch (Exception e) {
message = "FAIL to upload " + file.getOriginalFilename() + "!";
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(message);
}
}
#GetMapping("/getallfiles")
public ResponseEntity<List<String>> getListFiles(Model model) {
List<String> fileNames = files
.stream().map(fileName -> MvcUriComponentsBuilder
.fromMethodName(UploadController.class, "getFile", fileName).build().toString())
.collect(Collectors.toList());
return ResponseEntity.ok().body(fileNames);
}
#GetMapping("/files/{filename:.+}")
#ResponseBody
public ResponseEntity<Resource> getFile(#PathVariable String filename) {
Resource file = storageService.loadFile(filename);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getFilename() + "\"")
.body(file);
}
}
My Service:
#Service
public class StorageService {
Logger log = LoggerFactory.getLogger(this.getClass().getName());
private final Path rootLocation = Paths.get("upload-dir");
public void store(MultipartFile file) {
try {
Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename()));
} catch (Exception e) {
throw new RuntimeException("FAIL!");
}
}
public Resource loadFile(String filename) {
try {
Path file = rootLocation.resolve(filename);
Resource resource = new UrlResource(file.toUri());
if (resource.exists() || resource.isReadable()) {
return resource;
} else {
throw new RuntimeException("FAIL!");
}
} catch (MalformedURLException e) {
throw new RuntimeException("FAIL!");
}
}
public void deleteAll() {
FileSystemUtils.deleteRecursively(rootLocation.toFile());
}
public void init() {
try {
Files.createDirectory(rootLocation);
} catch (IOException e) {
throw new RuntimeException("Could not initialize storage!");
}
}
}
As you can see below I am sending file as form Data and no headers are being set
see below in the image, and add key value as file
Your Controller expects a request parameter "file":
#RequestParam("file") MultipartFile file
You have to set the key "file" in postman, where the value is your file (last screenshot).
Try adding in your request header Content-Type: multipart/form-data (as far as I see in postman it is missing)
Related
I mean, this is what i have in my code:
#GetMapping("/get/player/{csvName}")
public void loadPlayers(#PathVariable String csvName) {
/*Irrelevant code here*/
}
This works just because the csv file is in the root of my project.
Is there any way to set the relative path of the csv file on the url?
////////////////////////////////////////////////////EDIT///////////////////////////////////////////////////////
Here is the code of the class:
#RestController
#RequestMapping("/csv")
public class CsvController {
private static final Logger log = LoggerFactory.getLogger(FutbolApplication.class);
#Autowired
private PlayerRepository playerRepository;
#Autowired
private TeamRepository teamRepository;
#Autowired
private MembershipRepository memberRepository;
#GetMapping("/get/player/{csvName}")
public void loadPlayers(#PathVariable String csvName) {
CSVReader reader;
try {
reader = new CSVReaderBuilder(new FileReader(csvName))
.withSkipLines(1).build();
String[] values;
int i;
int count=0;
while ((values = reader.readNext()) != null) {
count++;
i=0;
try {
Player player = new Player(values[i++],values[i++],values[i++],Date.valueOf(values[i++]));
System.out.println(player.getName() + "//" + player.getSurname() + "//" + player.getPosition()
+ "//" + player.getBirthDate());
playerRepository.save(player);
}catch (Exception e) {
log.error("ERROR INTENTANDO ASIGNAR LOS DATOS AL JUGADOR "+(count));
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CsvValidationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
What I can to, is to insert the path of the csv instead of just the name.
At the moment my project's structure is:
>project
>src
>main
>test
>.Settings
>mycsvfile.csv
that's why i can just type "mycsvfile.csv" in the url and it works
But this is what i'd like to get:
>project
>src
>main
>test
>.Settings
>csvs
>mycsvfile.csv
And get it to work by typing "/csvs/mycsvfile.csv"
Because now i just can type "https:localhost:8080/csv/get/player/mycsvfile.csv"
Is it possible?
Use #RequestParam instead of #PathVariable.
If I understood correctly, you want to send the path of the file, you want to load your Player from, via the request.
Sending a file path in the URI won't work from the get go as it will change the path of the request and it will lead to a 404 NOT FOUND.
Using #RequestParam is a different story, you can add full file path there.
#GetMapping("/get/player")
public void loadPlayers(#RequestParam String csvName) {
/*Rest of your code here*/
}
This way your request would look like this:
https://localhost:8080/csv/get/player?csvName=csvs/mycsvfile.csv
If you really want to use #PathVariable to send the path of your file, then you will have to change your endpoint to work with wildcard and extract the file path from the request URI like explained by N. Chicoine here.
And if you need to use this in multiple places you can even get a more elegant solution by implementing an annotation that makes use of the HandlerMethodArgumentResolver interface:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface FilePath {
class Resolver implements HandlerMethodArgumentResolver {
private final PathMatcher pathMatcher;
public Resolver() {
this.pathMatcher = new AntPathMatcher();
}
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
Annotation annotation = methodParameter.getParameterAnnotation(FilePath.class);
return annotation != null;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modeContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
if (servletRequest == null) {
return null;
}
String patternAttribute = (String) servletRequest.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
String mappingAttribute = (String) servletRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
return this.pathMatcher.extractPathWithinPattern(patternAttribute, mappingAttribute);
}
}
}
Then you will have to register the annotation in application configuration:
#Configuration
public class Config implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new FilePath.Resolver());
}
}
And finaly you can use it like this:
#GetMapping("/get/player/**")
public void loadPlayers(#FilePath String csvName) {
/*Rest of your code here*/
}
Enabling you to execute the request like:
https://localhost:8080/csv/get/player/csvs/mycsvfile.csv
Hope this will help you.
#Service
public class RequestSender {
private static final Logger logger = LoggerFactory.getLogger(RequestSender.class);
#Autowired
private RestTemplate restTemplate;
public MbsFtResponseData sendJsonDataToMBS(final MBSTransactionData transactionData) {
String mbsUrl = MBSConfigConstants.mbsUrl;
try {
logger.info("Sending request method Is Initiated");
HttpEntity<MBSTransactionData> httpEntity = new HttpEntity<>(transactionData);
ResponseEntity<MbsFtResponseData> response = restTemplate.exchange(mbsUrl, HttpMethod.POST, httpEntity,
MbsFtResponseData.class);
if (response != null) {
HttpStatus status = response.getStatusCode();
if (status.is2xxSuccessful()) {
logger.info("Response getting back is succeded with the status code {}", status.value());
return response.getBody();
} else {
logger.error("ERROR Response getting back is with the status code {}", status.value());
throw new BBPSMBSException("Error is while connecting to mBS server", status.value());
}
} else {
logger.error("Null value::::::::::::response is null");
}
} catch (Exception e) {
e.printStackTrace();
logger.error("ERROR :::{}:: is occered ", e.getCause());
}
return new MbsFtResponseData("Server Not responding or Busy", 500, "FAILED");
}
}
java.lang.NullPointerException at com.npst.bbps.middleware.mbs.RequestSender.sendJsonDataToMBS(RequestSender.java:26) at com.npst.bbps.middleware.mbs.MbsftServiceImpl.mbsFundTransfer(MbsftServiceImpl.java:27) at com.npst.bbps.middleware.controller.ThirdPartyIntegrationCtrl.initiateRefund(ThirdPartyIntegrationCtrl.java:64) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
RestTemplate bean is not found in the BeanFactory, because you wouldn't configured.
You have to define a bean like below in the configuration file.
#Configuration
public class Config {
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
I have a unit test, where i am attempting to check the response of a async request being made, after converting the method to return a StreamingResponseBody using Spring 4.3.
The test method is below :
final MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
MvcResult mvcResult1 = mockMvc.perform(
get("/reports/generic/100?FIELD1=&FIELD3=").headers(STANDARD_HEADERS.get()))
.andExpect(status().isOk())
.andExpect(request().asyncStarted())
.andReturn();
mvcResult1.getAsyncResult();
mockMvc.perform(asyncDispatch(mvcResult1))
.andExpect(status().isOk())
.andExpect(content().contentType("text/csv"))
.andExpect(content().string("Test Data" + System.lineSeparator() + "FIELD1=" + System.lineSeparator() + "FIELD3=" + System.lineSeparator()))
The method it is calling looks like :
public StreamingResponseBody streamReport(#PathVariable("type") #NotNull String type, #PathVariable("id") #NotNull Long id, ReportConfiguration config, HttpServletResponse response) throws Exception {
ReportServiceHandler handler = reportHandlerFactory.getHandler(type);
final String reportFilename = handler.getReportFileName(id, reportConfiguration);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + reportFilename);
response.setContentType("text/csv");
return new StreamingResponseBody() {
#Override
public void writeTo(OutputStream outputStream) throws IOException {
try {
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + reportFilename);
response.setContentType("text/csv");
ServletOutputStream out = (ServletOutputStream) outputStream;
handler.generateReport(out, id, reportConfiguration);
out.flush();
} catch ( Exception e ) {
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline");
response.setContentType("");
throw new IOException(e);
}
}
};
}
Debugging shows that the original request has the response from the async within it, but the async response object (within mvcResult1) is not being copied over during asyncDispatch so both the contextType and content string is null.
Is there a test configuration being missed here that handles async mvcResult so that the content can be asserted ?
I have been quite successful with this. Here is what I do differently:
return new StreamingResponseBody() {
#Override
public void writeTo(OutputStream outputStream) throws IOException {
try {
... same
out.flush();
} catch ( Exception e ) {
... your error handling
} finally {
// Try to close the stream
try {
if (out != null) {
out.close();
}
} catch (Throwable t) {
// not much we can do
}
}
}
};
Now, my tests look as such when made parallel to yours. When reading your code, I'm not sure why you call perform() twice. To me it's necessary only once.
//Same
final MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(wac)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
// Same
MvcResult mvcResult1 = mockMvc.perform(
get(....))
.andExpect(status().isOk())
.andExpect(request().asyncStarted())
.andReturn();
mvcResult1.getAsyncResult();
// From this point on, I have complete response,
// if it's not bugging you to place the entire file in memory
String thisIsYourFileContent = mvcResult1.getResponse().getContentAsString();
// Go on asserting the content of the file...
In the hope this helps someone.
I have created simple rest api to serve files from hdfs (Files are large and I don't want to copy them locally).
I would like to log information that file download completed successfully i.e. whole stream was read, but I do not know how. I can only log information that file download was started.
I will appreciate any help.
#Autowired
private FileDownloadService fds;
#RequestMapping(value = GET_FILE_PATH, method = RequestMethod.GET)
#Produces(MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity getFileStream(#RequestParam("name") String name) {
LOG.info("Processing for filename: " + name);
try {
Path p = fds.getFilePath(name);
org.apache.hadoop.fs.FSDataInputStream is = fds.getFileStream(p);
return ResponseEntity.ok().header("Content-Disposition", "attachment; filename=\"" + p.getName() + "\"'").contentLength(fds.getFileLength(p))
.contentType(MediaType.APPLICATION_OCTET_STREAM).body(new InputStreamResource(is));
} catch (Exception e) {
LOG.error(e.getLocalizedMessage(), e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Internal Server Error");
} finally {
LOG.info("File: " + name + " download started");
}
}
You can try to create a wrapper over InputStream and trigger some flag on stream closing (close()).
For instance you can take ProxyInputStream as a basis:
ProxyInputStreamis = new ProxyInputStream(fds.getFileStream(p)) {
#Override
public void close() throws IOException {
super.close();
// some trigger
}
};
I am working on the GET request and I am trying to make it generic. I do not understand what is the mistake I have done here. Can any one help me out in tracing this out. The data from excel wil be generic in the GET request. The code can be found below.
Working on: Java, RestAssured, TestNG, Maven
This is my Properties file:
RestAssured.baseURI = http://website.com:8080/DBName;
This is my Readproperties file:
public class DemoTestcase {
String url;
#BeforeSuite
public void SetBrowser() {
System.out.println("Before Suite....");
}
#BeforeMethod
public void load_property_file() throws IOException {
Properties prop = new Properties();
InputStream input = getClass().getClassLoader()
.getResourceAsStream("Configuration/config.properties");
prop.load(input);
System.out.println();
url = prop.getProperty("RestAssured.baseURI");
System.out.println(url);
}
My Code for RestAssured API: Here I am Trying to make it generic by using Data Provider and reading it from excel.
#Test(dataProviderClass = <packageName.className>.class, dataProvider = "urlParameters")
public void tc_008_using_BDD_keywords(final String readExcelData) {
try {
ValidatableResponse response = given()
.when().get(url + "/{readExcelData}").then()
.contentType(ContentType.JSON);
System.out.println(((ResponseBodyData) response).asString());
} catch (Exception e) {
System.out.println("e.printStackTrace()");
}
}
This is my DataProvider. I have written the logic for reading from the excel file and returning as Object [][] to #Test.
#DataProvider(name = "urlParameters")
public static Object[][] createUrlParameters() throws IOException, ParseException {
<//Logic for reading Excel sheet containing 3 row and 1 column>
If I'm correct, you're using the variable wrong:
public void tc_008_using_BDD_keywords(final String readExcelData) {
try {
ValidatableResponse response = given()
.when().get(url + "/{readExcelData}").then()
.contentType(ContentType.JSON);
System.out.println(((ResponseBodyData) response).asString());
} catch (Exception e) {
System.out.println("e.printStackTrace()");
}
}
should be:
public void tc_008_using_BDD_keywords(final String readExcelData) {
try {
ValidatableResponse response = given()
.when().get(url + "/" + readExcelData).then()
.contentType(ContentType.JSON);
System.out.println(((ResponseBodyData) response).asString());
} catch (Exception e) {
System.out.println("e.printStackTrace()");
}}
.get(url + "/{readExcelData}") is reading url/{readExcelData} in Java. You're not using the actual variable.