I am trying to upload a file in chunks in Java.
My chunk upload function:
#Async
private Future<String> sendChunk(byte[] chunk, int start, int end, int numberOfChunks, String name, String sessionId) throws IOException {
LinkedMultiValueMap<String, Object> requestParams = new LinkedMultiValueMap<>();
requestParams.add("data", new String(java.util.Base64.getEncoder().encode(chunk), "UTF-8"));
requestParams.add("start", start);
requestParams.add("end", end);
requestParams.add("numberOfChunks", numberOfChunks);
requestParams.add("fileName", name);
requestParams.add("session", sessionId);
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(requestParams);
UploadResponse uploadResponse = restTemplate.exchange(fileServiceUrl, HttpMethod.POST, requestEntity, new ParameterizedTypeReference<UploadResponse>(){}).getBody();
return new AsyncResult<>(uploadResponse.getSessionId());
}
This is how the File-Service-API looks like:
#RequestMapping(value = "/simpleUploader", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<UploadResponse> simpleUploader(
#RequestParam("data") String data,
#RequestParam("session") String sessionId,
#RequestParam("start") int start,
#RequestParam("end") int end,
#RequestParam("numberOfChunks") int numberOfChunks,
#RequestParam("fileName") String fileName) throws IOException {
....
}
Now, if I try to upload a Chunk the File-Service responds with a 400 - Bad Request.
Stack-Trace:
org.springframework.web.client.HttpClientErrorException: 400 null
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:526)
It will be rejected from Spring even before it gets to the API-Function.
I am already uploading the same way, but in Javascript. From Javascript everything is working as it should.
What am I missing?
The only problem I had with my code was, that "sessionId" will be null the first time I am calling the File-Service API.
When I set a value of a Map Entry to null, the whole Entry doesn't get passed. I didnt knew that.
My fix was to add a defaultValue to #RequestParam("session") String sessionId so that the method declaration looks like that:
#RequestMapping(value = "/simpleUploader", method = RequestMethod.POST, produces = "application/json")
public ResponseEntity<UploadResponse> simpleUploader(
...
#RequestParam(value = "session", defaultValue = "") String sessionId,
... ) throws IOException {
....
}
The #Async has two limitations.
it must be applied to public methods only.
self-invocation – calling the async method from within the same class – won’t work
http://www.baeldung.com/spring-async
Try changing private to public
#Async
public Future<String> sendChunk(byte[] chunk, int start, int end, int numberOfChunks, String name, String sessionId) throws IOException {
Related
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 have the following code:
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
public ResponseEntity<?> api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
String response="{SOME_JSON}";
URI callbackURL = new URI("http://otherAPIEnv/api2");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(callbackURL);
return new ResponseEntity<String>(response,httpHeaders, HttpStatus.OK);
}
I tried the above code, but when I hit the api1 through my curl I get the response on the same machine, but I want the response to be redirected to api2 at otherAPIEnv machine.
Could someone please suggest how to achieve this kind of request and response?
When you send a request to a URL it should respond to the same otherwise client will be in waiting for it until it times out.
So, the approach should be different in this scenario.
First, in your main rest API you have to send a response code to release the client.
Then, in the API method you have to call another method asynchronously which calls api2 and performs the desired operation.
Here is a simple example.
#Autowired
API2Caller api2Caller;
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
#ResponseStatus(HttpStatus.ACCEPTED)
public void api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
api2Caller.callApi2(requestBody);
}
and the APICaller should look like following
#Component
public class API2Caller {
#Async
public SomeResultPojo callApi2() {
// use RestTemplate to call the api2
return restTemplate.postForObject("http://otherAPIEnv/api2", request, SomeResultPojo.class);
}
}
But you can choose your most comfortable way to perform asynchronous operation.
Look like a job for redirect.
String redirectMe() {
return "redirect:http://otherAPIEnv/api2"
}
As for the curl. You have POST mapping of the method so be sure to try it with curl -X POST... or change it to GET.
This the more modular and more generic way to do such kind of things:
public #ResponseBody ClientResponse updateDocStatus(MyRequest myRequest) {
ClientResponse clientResponse = new ClientResponse(CTConstants.FAILURE);
try {
HttpHeaders headers = prepareHeaders();
ClientRequest request = prepareRequestData(myRequest);
logger.info("cpa request is " + new Gson().toJson(request));
HttpEntity<ClientRequest> entity = new HttpEntity<ClientRequest>(request, headers);
String uri = cpaBaseUrl + updateDocUrl ;
ClientResponse serviceResponse = Utilities.sendHTTPRequest(uri, entity);
clientResponse = serviceResponse;
if (serviceResponse != null) {
if (CTConstants.SUCCESS.equalsIgnoreCase(serviceResponse.getStatus())) {
clientResponse.setStatus(CTConstants.SUCCESS);
clientResponse.setMessage(" update success.");
}
}
} catch (Exception e) {
logger.error("exception occurred ", e);
clientResponse.setStatus(CTConstants.ERROR);
clientResponse.setMessage(e.getMessage());
}
return clientResponse;
}
public static ClientResponse sendHTTPRequest(String uri, HttpEntity<ClientRequest> entity) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory());
SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
rf.setReadTimeout(CTConstants.SERVICE_TIMEOUT);
rf.setConnectTimeout(CTConstants.SERVICE_TIMEOUT);
ParameterizedTypeReference<ClientResponse> ptr = new ParameterizedTypeReference<ClientResponse>() {
};
ResponseEntity<ClientResponse> postForObject = restTemplate.exchange(uri, HttpMethod.POST, entity, ptr);
return postForObject.getBody();
}
You need to use redirect and modify the return type of your method
public String api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException {
return "redirect:http://otherAPIEnv/api2";
}
I am building a Spring rest service for uploading a file. There is a form that consists of various field and one field for uploading a file. On submitting that form, I am sending a multipart form request i.e. Content-Type as multipart/form-data.
So I tried with below
#RequestMapping(value = "/companies", method = RequestMethod.POST)
public void createCompany(#RequestBody CompanyDTO companyDTO, #RequestParam(value = "image", required = false) MultipartFile image){
.................
But, the above didn't work. So for time being,i sent JSON data as String and forming Company Object from that String in rest service like
#RequestMapping(value = "/companies", method = RequestMethod.POST)
public void createCompany(#RequestParam("companyJson") String companyJson, #RequestParam(value = "image",required = false) MultipartFile image) throws JsonParseException, JsonMappingException, IOException{
CompanyDTO companyDTO = new ObjectMapper().readValue(companyJson, CompanyDTO.class);
.............................
Can't I send JSON data with #RequestBody without passing JSON as String?
Appending the values to the URL what u have been doing now using #RequestParam.
#RequestParam annotation will not work for complex JSON Objects , it is specifi for Integer or String .
If it is a Http POST method , use of #RequestBody will make the Spring to map the incoming request to the POJO what u have created (condition: if the POJO maps the incoming JSON)
create FormData() and append your json and file
if (form.validate()) {
var file = $scope.file;
var fd = new FormData();
fd.append('jsondata', $scope.jsonData);
fd.append('file', file);
MyService.submitFormWithFile('doc/store.html', fd, '', (response){
console.log(response)
});
}
//Service called in above
MyService.submitFormWithFile = function(url, data, config, callback) {
$http({
method : 'POST',
url : url,
headers : {
'Content-Type' : undefined
},
data : data,
transformRequest : function(data, headersGetterFunction) {
return data;
}
}).success(function(response, status, header, config) {
if (status === 200) {
callback(response);
} else {
console.log("error")
}
}).error(function(response, status, header, config) {
console.log(response);
});
};
// in your java part using ObjectMapper
//it is like string
fd.append('jsondata', JSON.stringify($scope.jsonData));
#Autowired
private ObjectMapper mapper;
#RequestMapping(value = "/companies", method = RequestMethod.POST)
public void createCompany(#RequestParam String jsondata,
#RequestParam(required = true) MultipartFile file){
CompanyDto companyDto=mapper.readValue(jsondata, CompanyDTO.class);
......
}
Use below code snippet:
#RequestMapping(value= "/path", method=RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseObject methodName(MyData input, #RequestParam(required=false) MultipartFile file) {
// To Do
}
my service code :
#RequestMapping(value = "/projects/{projectId}/resources/web/{path}", method = RequestMethod.GET)
#ResponseBody
public void getWebFileContent(#PathVariable("projectId") String projectId,#PathVariable("path") String path, HttpServletRequest httpServletRequest) throws Exception {
}
And my request will be
/projects/pro1/resources/web/src/main/webapp
/projects/pro1/resources/web/src/main/test/com/pro1...
and is it possible to get "src/main/webapp/../....." into "path" variable
Spring provide three patterns in the url handler mappings
? - zero or one charecter
*- one charecter
** - one or more charecters
And below approach resolved my issue
#RequestMapping(value = "/projects/{projectId}/resources/web/**", method = RequestMethod.GET)
#ResponseBody
public void getWebFileContent(#PathVariable("projectId") String projectIdHttpServletRequest httpServletRequest) throws Exception {
String path = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
// will get path = /projects/pro1/resources/web/src/main/webapp
String bestMatchPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
// will get bestMatchPattern = /projects/pro1/resources/web/**
AntPathMatcher apm = new AntPathMatcher();
String exactPath = apm.extractPathWithinPattern(bestMatchPattern, path);
// will get exactPath = src/main/webapp
.....
}
Any other approaches are appreciated....
I've a Spring (3.0) Controller with a method which has HttpServletRequest as one of the parameters, since it's handling (multiple) file uploads.
#RequestMapping(value = "/classified/{idClassified}/dealer/{idPerson}/upload",
method = RequestMethod.POST)
#ResponseBody
public final String uploadClassifiedPicture(
#PathVariable int idClassified,
#PathVariable int idPerson,
#RequestParam String token,
HttpServletRequest request);
How to Unit Test it? I know I can create a MockHttpServletRequest, but I don't know how to pass one or more files to it.
MockHttpServletRequest request = new MockHttpServletRequest("POST",
"/classified/38001/dealer/54/upload?token=dfak241adf");
I recommend to change the method signature a bit, to make the uploaded file a normal parameter (of type MultipartFile (not CommonsMultipartFile)):
#RequestMapping(value = "/classified/{idClassified}/dealer/{idPerson}/upload",
method = RequestMethod.POST)
#ResponseBody
public final String uploadClassifiedPicture(
#PathVariable int idClassified,
#PathVariable int idPerson,
#RequestParam String token,
#RequestParam MultipartFile content);
Then you can use a MockMultipartFile in your test:
final String fileName = "test.txt";
final byte[] content = "Hallo Word".getBytes();
MockMultipartFile mockMultipartFile =
new MockMultipartFile("content", fileName, "text/plain", content);
uploadClassifiedPicture(1, 1, "token", mockMultipartFile);
If you do not want to change the method signature, then you can use MockMultipartHttpServletRequest instead.
It has a method addFile(MultipartFile file). And of course the required parameter can be a MockMultipartFile.
You can also use the MockMvc object as well as MockMvcRequestBuilders to send a test file upload request to your controller:
#Test
public void testSendNotEmptyFile() throws Exception {
mvc.perform(MockMvcRequestBuilders.fileUpload("Your controller URL")
.file("file", "Test Content".getBytes())
.contentType(MediaType.MULTIPART_FORM_DATA)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}