I want to dialogue with this API service programmatically using Jersey (https://eclipse-ee4j.github.io/jersey/)
here's the Rest Controller implementation in Spring:
#PostMapping(
value = "/api/my-endpoint",
consumes = MediaType.MULTIPART_MIXED_VALUE)
public void enrichInvoice(#RequestPart("metadata") Map<String, Object> request,
#RequestPart("human") MultipartFile humanFile) {
log.info(String.format("received request:%n%s", request));
}
my client implementation would be like this
...
final Client client = ClientBuilder.newClient(new ClientConfig()
.register(MultiPartFeature.class)
.register(JacksonFeature.class)
);
final FileDataBodyPart filePart = new FileDataBodyPart("human",myFile()));
final BodyPart metadata = new BodyPart().entity(voBuilder.generateMetadata());
final MultiPart multiPartEntity = new MultiPart();
multiPartEntity.bodyPart(metadata, MediaType.APPLICATION_JSON_TYPE);
multiPartEntity.bodyPart(filePart);
final WebTarget target = client
.target("http://localhost:8080/api/my-endpoint");
final Entity<MultiPart> entity = Entity
.entity(multiPartEntity, multiPartEntity.getMediaType());
log.info(entity.toString());
final Response response = target
.request()
.post(entity);
log.info(String.format("%s", response.readEntity(String.class)));
response.close();
...
But i keep getting this error:
Resolved [org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'metadata' is not present]
which is because the metadata part has to be named "metadata". and I cannot find a way to name it using BodyPart. I also tried using FormDataBodyPart to build the metadata
FormDataBodyPart metadataBodyPart = new FormDataBodyPart("metadata", metadata,
MediaType.APPLICATION_JSON_TYPE);
but with the same result.
Can you help me figure out what am I missing in the bodyPart definition?
Thanks
EDIT: here's the http request sent from my client implementation
Content-Type: multipart/mixed;boundary=Boundary_1_1972899462_1597045386454
User-Agent: Jersey/2.29 (HttpUrlConnection 11.0.8)
MIME-Version: 1.0
Host: localhost:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 765
--Boundary_1_1972899462_1597045386454
Content-Type: application/json
{"value":"key"}
--Boundary_1_1972899462_1597045386454
Content-Type: application/octet-stream
Content-Disposition: form-data; filename="file.zip"; modification-date="Wed, 05 Aug 2020 16:52:52 GMT"; size=0; name="human"
--Boundary_1_1972899462_1597045386454--
]
The solution, even if not optimal, was to treat the metadata as a text file
final Path tempFile = Files.createTempFile("prefix", "suffix");
File fileMetadata = Files.write(tempFile.toAbsolutePath(), JsonUtils.toString(metadata).getBytes());
final FileDataBodyPart metadataBodyPart = new FileDataBodyPart(
"metadata",
fileMetadata,
MediaType.APPLICATION_JSON_TYPE);
final FileDataBodyPart human = new FileDataBodyPart("human", new File(humanReadableFile.getFileKey()));
try (final MultiPart multiPartEntity = new MultiPart()) {
multiPartEntity.bodyPart(metadataBodyPart);
multiPartEntity.bodyPart(human);
final Response response = client
.target("http://localhost:8080/api/my-endpoint")
.request()
.post(Entity.entity(multiPartEntity, multiPartEntity.getMediaType()));
log.debug(String.valueOf(response.getStatus()));
log.debug(response.readEntity(String.class));
}
In this way the request body has to parts named "metadata" and "human" as requested by the controller implementation and still maintain the multipart/mixed content-type.
Related
Trying to make a post call on one of our servers, but getting 400 BAD_REQUEST all the time
static void postUserToken()
{
final String url = "SERVER ADDRESS";
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
MultiValueMap<String, String> requestBody= new LinkedMultiValueMap<>();
requestBody.add("userName", "TESTUSER");
requestBody.add("password", "TESTPASSWORD");
requestBody.add("auth", "secEnterprise");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestBody, headers);
ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class );
System.out.println(response);
}
get request to the same address works, post request via Postman works fine with the same body and headers
what am I missing ?
EDIT
calls from postman
POST /api/call/ HTTP/1.1
Host: SEREVERADDRESS:6405
Content-Type: application/json
Accept: application/json
User-Agent: PostmanRuntime/7.15.0
Cache-Control: no-cache
Postman-Token: token1,token2
Host: SEREVERADDRESS:6405
accept-encoding: gzip, deflate
content-length: 92
Connection: keep-alive
cache-control: no-cache
{
"password": "PASSWORD",
"auth": "secEnterprise",
"userName": "USER"
}
in response I get an object like this {"token":"longtoken"}
You are using a MultiValueMap however the json you send from postman looks like a simple Map.
This will produce {"key1":["val1"]} instead of {"key1":"val1"}
The problem might be in the
headers.setContentType(MediaType.APPLICATION_JSON);
Try using headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); or convert your data to a proper JSON.
More on this: https://www.baeldung.com/rest-template (4.4. Submit Form Data)
As far as I understand the problem and since I do not know your rest call details, I provide below the approach you can try.
Remove the following line.
requestBody.add("auth", "secEnterprise");
Add the line
headers.setHeader("auth", "secEnterprise");
If you are using other version of Apache Http Client, you can use the following code snippet.
HttpPost httpPost = new HttpPost(url);
httpPost.addHeader("header-name" , "header-value");
I have to use Spring's RestTemplate to call an external API that takes a POST request with Content-Type: multipart/form-data. The input data are only key-values, no attachments but the server enforce me the use multipart/form-data.
Following is the raw request that works fine.
POST http://the-api:8080 HTTP/1.1
Content-Type: multipart/form-data; boundary=--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL
Accept: */*
Host: the-api:8080
accept-encoding: gzip, deflate
content-length: 680
Connection: keep-alive
--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL
Content-Disposition: form-data; name="param1"
value1
--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL
Content-Disposition: form-data; name="param2"
value2
--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL--
Following is the raw request that I extracted and rearranged from the log of the RestTemplate, it did not work because the server mistook the header for the value.
POST http://the-api:8080 HTTP/1.1
Content-Type: multipart/form-data; boundary=--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL
Accept: */*
Host: the-api:8080
accept-encoding: gzip, deflate
content-length: 680
Connection: keep-alive
--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL
Content-Disposition: form-data; name="param1"
Content-Type: text/plain;charset=UTF-8
Content-Length: 29
value1
--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL
Content-Disposition: form-data; name="param2"
Content-Type: text/plain;charset=UTF-8
Content-Length: 14
value2
--Eh0oKOHPOSEIJTzFevDxHhPNKhQl7AP6kQL--
Following is the code
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("param1", "value1);
params.add("param2", "value2);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
URI uri = UriComponentsBuilder.fromHttpUrl("http://the-api:8080")
.build().encode(Charset.forName("UTF-8")).toUri();
return restTemplate.postForObject(uri, request, KKPMailResponse.class);
Question
How to prevent Spring's RestTemplate from automatically add the header Content-Type: text/plain;charset=UTF-8 and Content-Length: xx for each parameters
I didn't find a way to prevent Spring from generating the entries, but you can use an interceptor to remove them before sending the request. For that you have to manipulate the request body in the interceptor as follows:
public class MultiPartFormDataCleaningInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
final MediaType contentType = request.getHeaders().getContentType();
if (contentType != null
&& MediaType.MULTIPART_FORM_DATA.getType().equals(contentType.getType())
&& MediaType.MULTIPART_FORM_DATA.getSubtype().equals(contentType.getSubtype())) {
return execution.execute(request, stripContentTypeAndLength(body));
}
return execution.execute(request, body);
}
private byte[] stripContentTypeAndLength(byte[] body) {
final String bodyStr = new String(body);
final StringBuilder builder = new StringBuilder();
try (final Scanner scanner = new Scanner(bodyStr)) {
while (scanner.hasNextLine()) {
final String line = scanner.nextLine();
if (!line.startsWith("Content-Type:")
&& !line.startsWith("Content-Length:")) {
builder.append(line).append("\r\n");
}
}
}
final String newBodyStr = builder.toString();
return newBodyStr.getBytes();
}
}
If think you can use ClientHttpRequestInterceptor to remove headers:
public class SomeHttpRequestInterceptor implements ClientHttpRequestInterceptor
{
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException
{
HttpHeaders headers = request.getHeaders();
headers.remove("your header 1);
headers.remove("your header 2);
return execution.execute(request, body);
}
}
And set it in RestTemplate in this way:
RestTemplate restTemplate = new RestTemplate();
List<ClientHttpRequestInterceptor> interceptors = Arrays.asList(new CustomHttpRequestInterceptor())
restTemplate.setInterceptors(interceptors);
I'm using RESTeasy 3.x to submit a REST request with an attachment to a third party service. They've asked that the request have the following headers set:
Accept: application/json
Content-Disposition: attachment; filename="attamentFilename"
Content-Type: application/octet-stream
My first attempt was simply to add the headers as I've seen done with Authorization header like this:
protected Response getResponse(final String url, final String filename, final InputStream attachment) {
ResteasyWebTarget target = resteasyClient.target(url);
MultipartFormDataOutput output = new MultipartFormDataOutput();
output.addFormData("attachment", attachment, MediaType.APPLICATION_OCTET_STREAM_TYPE);
Response response = target.request()
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_TYPE)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=\"" + filename + "\"")
.post(Entity.entity(output, MediaType.MULTIPART_FORM_DATA));
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
}
return response;
}
But, when I debugged from the server side the headers weren't being set as expected. I also tried a ClientRequestFilter, but again the headers weren't quite what I needed.
I've managed to get something close to what I need by using addFormData like this:
protected Response getResponse(final String url, final String filename, final InputStream attachment) {
ResteasyWebTarget target = resteasyClient.target(url);
MultipartFormDataOutput output = new MultipartFormDataOutput();
output.addFormData("attachment", attachment, MediaType.APPLICATION_OCTET_STREAM_TYPE, filename);
Response response = target.request()
.post(Entity.entity(output, MediaType.MULTIPART_FORM_DATA));
if (response.getStatus() != 200) {
throw new RuntimeException("Failed : HTTP error code : " + response.getStatus());
}
return response;
}
This produces two of the three headers I need:
Content-Disposition: form-data; name="attachment"; filename="attamentFilename"
Content-Type: application/octet-stream
I'm assuming MultipartFormDataOutput is discarding the headers I try to set and building its own, and that I can't use the .header method for all types of requests. Does this sound right?
I'm developping a web application capable of upload video on Dailymotion all was find during 1 years and i have seen an error recently occuring when i upload a video on Dailymotion.
{
"error": "missing content"
"seal": "540f4ad5a0f9c6a7e85a46be98361581"
}
I use java and the lib "org.apache.http" for doing my call on dailymotion.
my code look like this :
Path temp = Files.createTempFile(multipartFile.getName(), "." + suffix);
multipartFile.transferTo(temp.toFile());
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.addPart(multipartFile.getOriginalFilename(), new FileBody(temp.toFile(),
ContentType.APPLICATION_OCTET_STREAM, multipartFile.getOriginalFilename()));
httpPost.setEntity(builder.build());
try (CloseableHttpClient httpclient = HttpClients.createDefault();
CloseableHttpResponse response = httpclient.execute(httpPost);) {
HttpEntity entity = response.getEntity();
ObjectMapper mapper = new ObjectMapper();
DailymotionUploadVideoResponse dmUploadResponse = mapper.readValue(entity.getContent(),
DailymotionUploadVideoResponse.class);
// Delete temp file after upload
Files.deleteIfExists(temp);
if (dmUploadResponse.getError() != null) {
throw new DailymotionJsonException(dmUploadResponse.getError().getMessage());
}
EntityUtils.consume(entity);
response.close();
POST on url retrieve by dailymotion :
http://upload-12.dc3.dailymotion.com/upload?uuid=035e365c5b2355616e381f43c1b2b391&seal=edad1d3ad9e348c65e975582571e5815
Header of the POST request :
Content-Disposition:
form-data;
name="2015-07-16-192550-1.webm";
filename="2015-07-16-192550-1.webm",
Content-Type: application/octet-stream,
Content-Transfer-Encoding: binary
I don't understand why i'm doing wrong.
I test via curl and i have the same error.
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryImx1443wQZZBF0Fb
Content-Length: 1398401
Source message
POST /upload?uuid=035e365c5b2355616e381f43c1b2b391&seal=edad1d3ad9e348c65e975582571e5815 HTTP/1.1
HOST: upload-12.dc3.dailymotion.com
content-type: multipart/form-data; boundary=----WebKitFormBoundaryImx1443wQZZBF0Fb
content-length: 1398401
------WebKitFormBoundaryImx1443wQZZBF0Fb
Content-Disposition: form-data; name="2015-07-16-192550-1.webm"; filename="2015-07-16-192550-1.webm"
Content-Type: video/webm
API dailymotion have changed, change the code
builder.addPart(multipartFile.getOriginalFilename(), new FileBody(temp.toFile(),
ContentType.APPLICATION_OCTET_STREAM, multipartFile.getOriginalFilename()));
by :
builder.addPart("file", new FileBody(temp.toFile(),
ContentType.APPLICATION_OCTET_STREAM, multipartFile.getOriginalFilename()));
I´m making a Web Service, using Java and Glassfish as server.
I´m also using Apache Server for Processing HTTP requests, i.e, when I make a request, I´m able to get the standard informations, like:
HTTP/1.1 200 OK[\r][\n]
Server: GlassFish Server Open Source Edition 4.1 [\r][\n]"
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 4.1 Java/Oracle Corporation/1.8)[\r][\n]
Set-Cookie: JSESSIONID=efc5aa919b55321d3aeaf2c9b3b6; Path=/context; HttpOnly[\r][\n]
Content-Type: text/xml; charset=utf-8[\r][\n]
Date: Thu, 07 May 2015 15:26:40 GMT[\r][\n]
Transfer-Encoding: chunked[\r][\n]
WWW-Authenticate: Basic realm="file"[\r][\n]
Content-Language: [\r][\n]
Content-Type: text/html[\r][\n]
Content-Length: 1090[\r][\n]
SOAPAction: ""[\r][\n]
Host: localhost:8080[\r][\n]
Connection: Keep-Alive[\r][\n]
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)[\r][\n]
One Example of a Web Operation that I developed is:
#WebMethod(operationName = "someoperation")
#Produces(MediaType.APPLICATION_XML)
public void makeHappen(#WebParam(name = "req") Object obj,
#WebParam(name = "resp", mode = WebParam.Mode.OUT) Holder<String> response) {
To List of information that I get, I want to add own specifications, like:
--> OperationName: someOperation
HTTP/1.1 200 OK[\r][\n]
Server: GlassFish Server Open Source Edition 4.1 [\r][\n]"
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 4.1 Java/Oracle Corporation/1.8)[\r][\n]
Set-Cookie: JSESSIONID=efc5aa919b55321d3aeaf2c9b3b6; Path=/context; HttpOnly[\r][\n]
Content-Type: text/xml; charset=utf-8[\r][\n]
Date: Thu, 07 May 2015 15:26:40 GMT[\r][\n]
Transfer-Encoding: chunked[\r][\n]
WWW-Authenticate: Basic realm="file"[\r][\n]
Content-Language: [\r][\n]
Content-Type: text/html[\r][\n]
Content-Length: 1090[\r][\n]
SOAPAction: ""[\r][\n]
Host: localhost:8080[\r][\n]
Connection: Keep-Alive[\r][\n]
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)[\r][\n]
For WebSphere Application Server:
Refer to the documentation and examples in Sending transport headers with JAX-WS
Here is a short programming example that illustrates how request
transport headers are sent by a JAX-WS Web services client
application:
public class MyApplicationClass {
// Inject an instance of the service's port-type.
#WebServiceRef(EchoService.class)
private EchoPortType port;
// This method will invoke the web service operation and send transport headers on the request.
public void invokeService() {
// Set up the Map that will contain the request headers.
Map<String, Object> requestHeaders = new HashMap<String, Object>();
requestHeaders.put(“MyHeader1”, “This is a string value”);
requestHeaders.put(“MyHeader2”, new Integer(33));
requestHeaders.put(“MyHeader3”, new Boolean(true));
// Set the Map as a property on the RequestContext.
BindingProvider bp = (BindingProvider) port;
bp.getRequestContext().put(com.ibm.websphere.webservices.Constants.REQUEST_TRANSPORT_PROPERTIES, requestHeaders);
// Invoke the web services operation.
String result = port.echoString(“Hello, world!”);
}
}
Here is a short programming example that illustrates how response
transport headers are sent by a JAX-WS Web services endpoint
implementation class:
#WebService
public class EchoServiceImpl implements EchoServicePortType {
// Inject an instance of WebServiceContext so we can retrieve
// the MessageContext for each invocation of this endpoint.
#Resource
WebServiceContext ctxt;
/**
* Default constructor.
*/
public EchoServiceImpl() {
....
}
public String echoString(String input) {
String result = “Echo result: “ + input;
// Retrieve the MessageContext from the injected WebServiceContext.
MessageContext mc = ctxt.getMessageContext();
// Send some headers back in the response message.
Map<String, Object> responseHeaders = new HashMap<String, Object>();
responseHeaders.put("MyHeader1", "This is a string response value");
responseHeaders.put("MyHeader2", new Integer(33));
responseHeaders.put("MyHeader3”, new Boolean(false));
// Set the response header Map on the MessageContext.
mc.put(com.ibm.websphere.webservices.Constants.RESPONSE_TRANSPORT_PROPERTIES, responseHeaders);
return result;
}
}
For GlassFish Application Server:
You can get the javax.xml.ws.WebServiceContext and from it javax.xml.ws.handler.MessageContext. Then add to the MessageContext your headers, something like this:
...
Map<String, List<String>> headers = new HashMap<String, List<String>>();
headers.put("OperationName", someOperation);
messageContext.put(MessageContext.HTTP_REQUEST_HEADERS, headers)
...
Also you can try to append the HTTP header to the request by using this approach:
...
Dispatch<SOAPMessage> dispatch =
service.createDispatch(portName, SOAPMessage.class, Service.Mode.MESSAGE);
Map<String, List<String>> headers =
new HashMap<String, List<String>>();
headers.put("OperationName", someOperation);
dispatch.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS,
headers);
...
See Also:
How to modify request headers in a J2EE web application
How to add HTTP header to SOAP Webservice Glassfish