I'm trying to post Atom xml and file with multipart/related request using RestTemplate.
The question is - is it possible to change headers of parts for example Content-Type presented after boundary in atom part or add Content-ID in file part or how to properly create post request in this case.
My request should look like this:
POST /app/psw HTTP/1.1
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: localhost
Accept: */*
Authorization: Basic YWdzOmFnczEyMw==
Content-Type: multipart/related;boundary===9B752C681081408==;type=application/atom+xml
Content-Length: 7019
Expect: 100-continue
--==9B752C681081408==
Content-Type: application/atom+xml
<?xml version="1.0" encoding="utf-8"?>
<atom:entry ...>
...
</atom:entry>
--==9B752C681081408==
Content-Type: video/mp2t
Content-ID: <prod#example.com>
123f3242e34...binary data...12313ed
--==9B752C681081408==--
I must use the RestTemplate or Spring WebClient.
For now it looks like presented below, but part with atom has Content-Type: application/xml instead of application/atom+xml
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().stream()
.filter(FormHttpMessageConverter.class::isInstance)
.map(FormHttpMessageConverter.class::cast)
.findFirst()
.ifPresent(formHttpMessageConverter -> {
List<MediaType> supportedMediaTypes = new ArrayList<>(formHttpMessageConverter.getSupportedMediaTypes());
supportedMediaTypes.add(new MediaType("multipart","related"));
formHttpMessageConverter.setSupportedMediaTypes(supportedMediaTypes);
});
ResponseEntity<String> response;
LinkedMultiValueMap<String,Object> map = new LinkedMultiValueMap<>();
map.add("atom",e); //e is xml object created with javax.xml.bind package
map.add("file",new FileSystemResource(file));
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type","multipart/related;type=\"application/atom+xml\"");
HttpEntity<LinkedMultiValueMap<String,Object>> request = new HttpEntity<>(map,headers);
response = restTemplate.postForEntity(url,request,String.class);
Thank you in advance
Ok, I found solution which works for me. I will try to explain step by step how i did it.
Prepare your RestTemplate
private RestTemplate prepareRestTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
RestTemplate template = new RestTemplate(requestFactory);
template.getMessageConverters().stream()
.filter(FormHttpMessageConverter.class::isInstance)
.map(FormHttpMessageConverter.class::cast)
.findFirst()
.ifPresent(formHttpMessageConverter -> {
List<MediaType> supportedMediaTypes = new ArrayList<>(formHttpMessageConverter.getSupportedMediaTypes());
supportedMediaTypes.add(new MediaType("multipart", "related"));
formHttpMessageConverter.setSupportedMediaTypes(supportedMediaTypes);
});
return template;
}
Create Headers
private HttpHeaders createHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "multipart/related;type=\"application/atom+xml\"");
headers.setBasicAuth(properties.getProperty("service.login"), properties.getProperty("service.password"));
return headers;
}
Create atom xml part.
private HttpEntity<String> createAtomPart(String xml) {
MultiValueMap<String, String> atomMap = new LinkedMultiValueMap<>();
atomMap.add(HttpHeaders.CONTENT_TYPE, "application/atom+xml");
return new HttpEntity<>(xml, atomMap);
}
Create file part
private HttpEntity<InputStreamResource> createFilePart(InputStream file, String contentId, String contentType) {
MultiValueMap<String, String> fileMap = new LinkedMultiValueMap<>();
fileMap.add(HttpHeaders.CONTENT_TYPE, contentType);
fileMap.add("Content-ID", contentId);
return new HttpEntity<>(new InputStreamResource(file), fileMap);
}
Prepare your request
private HttpEntity<MultiValueMap<String, Object>> prepareRequest(InputStream file, String xml, String contentId, String contentType) {
MultiValueMap<String, Object> bodyMap = new LinkedMultiValueMap<>();
bodyMap.add("atom", createAtomPart(xml));
bodyMap.add("file", createFilePart(file, contentId, contentType));
return new HttpEntity<>(bodyMap, createHeaders());
}
Post it
public ResponseEntity<String> sendPostRequest(InputStream file, String xml, String contentId, String contentType) throws ClientException {
HttpEntity<MultiValueMap<String, Object>> request = prepareRequest(file, xml, contentId, contentType);
ResponseEntity<String> response;
try {
response = restTemplate.postForEntity(uri, request, String.class);
} catch (HttpServerErrorException e) {
log.info("Error occurred on server side, reason:", e);
return new ResponseEntity<>(e.getResponseBodyAsString(), e.getStatusCode());
} catch (HttpClientErrorException e) {
throw new ClientException(e.getStatusCode(), e.getResponseBodyAsString(), e);
}
return response;
}
Related
I'm creating my first REST API using JAVA Spring and when I'm making a rest call to an external API, I get
401 Unauthorized: [no body]
I think my problem is here:
requestParams.add("Grant_type", "client_credentials");
I saw some questions related to this but none well able to solve my problem.
Spring REST template - 401 Unauthorized error
Spring Boot Callable - 401 Unauthorized: [no body]
JAVA code:
public String getAuth(String client_id, String app_secret) {
String auth = client_id + ":" + app_secret;
return Base64.getEncoder().encodeToString(auth.getBytes());
}
#GetMapping(value = "/token")
public Object generateAccessToken() {
String auth = this.getAuth(
"CLIENT_ID",
"APP_SECRET"
);
RestTemplate restTemplate = new RestTemplate();
String base = "https://external-api.com";
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Basic " + auth);
MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
requestParams.add("Grant_type", "client_credentials");
ResponseEntity<Object> response = restTemplate.postForEntity(
base + "/v1/oauth2/token",
requestParams,
Object.class,
headers
);
return response.getBody();
}
Here's the solution to my own question.
This is what I had to change;
MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
requestBody.add("grant_type", "client_credentials");
HttpEntity<?> request = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(
base +"/v1/oauth2/token",
request,
String.class
);
Here's the final solution:
public String generateAccessToken() {
String base = "example-api.com";
String auth = this.getAuth(
"client id",
"app_id"
);
// create an instance of RestTemplate
RestTemplate restTemplate = new RestTemplate();
// create headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.set("Authorization", "Basic " + auth);
MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
requestBody.add("grant_type", "client_credentials");
HttpEntity<?> request = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(
base +"/v1/oauth2/token",
request,
String.class
);
// check response
if (response.getStatusCode() == HttpStatus.OK) {
System.out.println("Request Successful");
System.out.println(response.getBody());
} else {
System.out.println("Request Failed");
System.out.println(response.getStatusCode());
}
JSONObject object = new JSONObject(response.getBody());
return object.getString("access_token");
}
I want to upload the file with Json request in rest template along with other properties. But I couldn't able to do this.
#Bean
public RestTemplate getRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
#Autowired
private RestTemplate restTemplate;
#Scheduled(fixedDelay = 1000)
public void _do() throws Exception {
HashMap<String, String> documentProperties = new HashMap<>();
documentProperties.put("number", "123");
MultipartFile file = Somefile;
UploadDocumentRequest uploadDocumentRequest = new UploadDocumentRequest();
uploadDocumentRequest.setDocumentClass("DocClass");
uploadDocumentRequest.setDocumentProperties(documentProperties);
uploadDocumentRequest.setFile(file); ----???
ResponseEntity<String> value = restTemplate.postForEntity("URL", uploadDocumentRequest, String.class);
}
You have to create HttpEntity with header and body.
Set the content-type header value to MediaType.MULTIPART_FORM_DATA.
Build the request body as an instance of LinkedMultiValueMap class.
Construct an HttpEntity instance that wraps the header and the body object and post it using a RestTemplate.
A sample code is shown as follows:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", getFileToBeUploaded());
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.postForEntity(serviveUrl, requestEntity, String.class);
Title might look common but none of them fit in my issue.
I have a rest service which accept normal parameters and file in form of multipart.
i want to use resttemplate to send data and file to above rest service.
till the time i was sending normal string data there was no issue. once i add code of sending bytes then i start getting 400 Bad request error.
if i comment code to send ByteArrayResource then it start working for normal parameters.
below is sample code
Rest service controller
#RestController
#RequestMapping(value="/ticket")
public class UserTicketController {
#RequestMapping(value="/createTicket.do",method={RequestMethod.POST},
consumes = {MediaType.MULTIPART_FORM_DATA_VALUE},headers={"content-type="+MediaType.MULTIPART_FORM_DATA_VALUE})
public void createTicket(#ModelAttribute ServiceDeskRequest serviceDeskRequest, HttpServletRequest request,HttpServletResponse response) throws Exception{
}
}
Servicedeskrequest model attribute is
public class ServiceDeskRequest implements Serializable{
private String jsonData;
private MultipartFile attachment;
}
application-context.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
Client Side code
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, Object> requestParamerterMap = new LinkedMultiValueMap<String, Object>();
requestParamerterMap.add("jsonData", jsonData);
MultipartFile attachment = userRequest.getAttachment();
if(attachment!=null && attachment.getOriginalFilename()!=null) {
ByteArrayResource byteArrayResource = new ByteArrayResource(attachment.getBytes(), attachment.getOriginalFilename());
requestParamerterMap.add("attachment", byteArrayResource);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(requestParamerterMap, headers);
String response = restTemplate.postForObject(targetUrl, requestEntity, String.class);
I figured it out. There are two piece in this puzzle. No change in service code.
Providing right converter to resttemplate. In list of default converts spring doesn't add FormHttpMessageConverter.
FormHttpMessageConverter converter = new FormHttpMessageConverter();
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(converter);
overriding bytearrayresource class. plz note you need to override getFilename method so that document name can be received at service side.
public class MultipartByteArrayResource extends ByteArrayResource{
private String fileName;
public MultipartByteArrayResource(byte[] byteArray) {
super(byteArray);
}
public String getFilename() {
return fileName;
}
public void setFilename(String fileName) {
this.fileName= fileName;
}
}
After above changes client code will be
FormHttpMessageConverter converter = new FormHttpMessageConverter();
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(converter);
MultiValueMap<String, Object> requestParamerterMap = new LinkedMultiValueMap<String, Object>();
requestParamerterMap.add("jsonData", jsonData);
MultipartFile attachment = userRequest.getAttachment();
if(attachment!=null && attachment.getOriginalFilename()!=null) {
//ByteArrayResource byteArrayResource = new ByteArrayResource(attachment.getBytes(), attachment.getOriginalFilename());
MultipartByteArrayResource resource = new MultipartByteArrayResource(attachment.getBytes());
//pass file name sepratly
resource.setFilename(attachment.getOriginalFilename());
requestParamerterMap.add("attachment", resource);
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<MultiValueMap<String, Object>>(requestParamerterMap, headers);
String response = restTemplate.postForObject(targetUrls.get("sdCreateTicketsUrl"), requestEntity, String.class);
First, value="/createTicket.do" is way off the REST convention. Same goes for /ticket.
Creation of a ticket should be done by POST to URL: .../tickets/
I need to send post request with custom parameter("data" containing path) and set content type as text/plain. I looked through a ton of similar question but none of the solutions posted helped.
The method should list files from this directory.
my code is
public List<FileWrapper> getFileList() {
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("data", "/public/");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(
map, headers);
String url = "http://192.168.1.51:8080/pi/FilesServlet";
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
String response = restTemplate
.postForObject(url, request, String.class);
List<FileWrapper> list = new ArrayList<>();
for (String part : response.split("\\|")) {
System.out.println("part " + part);
list.add(new FileWrapper(part));
}
return list;
}
Here's working code equivalent written in javascript:
function getFileList(direction){
$("div.file-list").html("<center><progress></progress></center>");
$.ajax({
url: "http://192.168.1.51:8080/pi/FilesServlet",
type: "POST",
data: direction ,
contentType: "text/plain"
})
The parameter is not added as the request returns empty string meaning the path is not valid. The expected response is file_name*file_size|file_name*file_size ...
Thanks in advance.
From the discussion in the comments, it's quite clear that your request object isn't correct. If you are passing a plain string containing folder name, then you don't need a MultiValueMap. Just try sending a string,
String data = "/public/"
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<String> request = new HttpEntity<String>(
data, headers);
String url = "http://192.168.1.51:8080/pi/FilesServlet";
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
String response = restTemplate
.postForObject(url, request, String.class);
When I run this code, I get the response in httpResponse. It's reporting the correct number of bytes in the header. But the body is empty. when I call .getBody() it's null.
Header:
<200 OK,{Date=[Thu, 29 Nov 2012 16:26:06 GMT], Server=[Apache], Vary=[Accept-Encoding], Content-Length=[5072], Keep-Alive=[timeout=10, max=100], Connection=[Keep-Alive], Content-Type=[text/html]}>
What am I doing wrong?
String url = new String("http://www.myurl.com/scripts/json/v1/slipmanager.php");
MultiValueMap<String, Object> formData = new LinkedMultiValueMap<String, Object>();
formData.add("username", userName);
formData.add("password", md5(userPassword));
formData.add("method", "getslips");
RestTemplate template = new RestTemplate(true);
template.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(new MediaType("multipart", "form-data"));
template.getMessageConverters().add(new StringHttpMessageConverter());
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(formData, requestHeaders);
ResponseEntity<?> httpResponse = null;
try
{
httpResponse = template.exchange(url, HttpMethod.POST, request, null);
String tmp = (String) httpResponse.getBody();
//THIS IS WHERE THE BODY IS NULL
}
catch (Exception e)
{
Log.e("POST", e.getMessage(), e);
}
You asked for a response type "null" so Spring thinks you want to discard the body. If you wanted it to be a String, String.class should work. You don't even need the explicit StringHttpMessageConverter as far as I know.