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?
Related
Here is my code :
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
File file = new File("/home/xxxxxxxxx/Téléchargements/XxxXx");
File[] files = file.listFiles();
for (File f : files) {
HttpRequest request = HttpRequest.newBuilder().uri(URI.create("https://exemple.demo.monapi.com/api/v5/submissions"))
.POST(BodyPublishers.ofFile(FileSystems.getDefault().getPath(f.getAbsolutePath())))
.header("Authorization", "Token c0198efc82c36812a6a32af0579a8332aa37f7a7")
.build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());
System.out.println(response);
}
}
The proble is that i get a 415 response because, by default content type is application/pdf and the supported content types for encoding the bodies of POST requests for my api are:
multipart/form-data (usually used when uploading files)
application/x-www-form-urlencode (for all remaining cases)
So how do i change the content type ?
You can add multiple headers by adding multiple .header("key", "value").
Content type header is added along with Authorization header in below code
HttpRequest request = HttpRequest.newBuilder().uri(URI.create("https://exemple.demo.monapi.com/api/v5/submissions"))
.POST(BodyPublishers.ofFile(FileSystems.getDefault().getPath(f.getAbsolutePath())))
.header("Authorization", "Token c0198efc82c36812a6a32af0579a8332aa37f7a7")
.header("Content-Type", "multipart/form-data")
.build();
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.
I'm making a request to a website. However, I keep getting a returned JSON of {"error":"invalid_client"}. Additionally, when I navigate to the URL I'm making the request to through a web browser it shows HTTP ERROR 405.
From what I read on those errors that might mean that my request isn't structured correctly.
According to the API's documentation, this is an example of the request type I'm trying to do:
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
RequestBody body = RequestBody.create(mediaType, "client_secret={your_client_secret}&client_id={your_client_id}&code={your_authorization_code}&grant_type=authorization_code&redirect_uri={your_redirect_uri}");
Request request = new Request.Builder()
.url("https://api.website.com/v2/oauth2/token")
.post(body)
.addHeader("content-type", "application/x-www-form-urlencoded")
.addHeader("cache-control", "no-cache")
.build();
Response response = client.newCall(request).execute();
From what I can tell mine should be doing the same thing, just a little differently.
Here is a Pastebin of my doInBackground method (I'm using AsynchTask). Here is the more applicable part:
OkHttpClient client = new OkHttpClient();
// A section here gets strings from a JSON file storing values such as client_id
RequestBody bodyBuilder = new FormBody.Builder()
.add("client_secret", CLIENT_SECRET)
.add("client_id", CLIENT_ID)
.add("code", AUTHORIZATION_CODE)
.add("grant_type", GRANT_TYPE)
.add("redirect_uri", REDIRECT_URI)
.build();
System.out.println("Built body: " + bodyBuilder.toString());
String mediaTypeString = "application/x-www-form-urlencoded";
MediaType mediaType = MediaType.parse(mediaTypeString);
RequestBody body = RequestBody.create(mediaType, requestbodyToString(bodyBuilder)); // See Edit 1
Request request = new Request.Builder()
.url(TARGET_URL)
.post(body)
.addHeader("content-type", mediaTypeString)
.addHeader("cache-control", "no-cache")
.build();
try {
System.out.println("Starting request.");
Response response = client.newCall(request).execute();
String targetUrl = request.url().toString() + bodyToString(request);
System.out.println("request: " + targetUrl);
String responseBodyString = response.body().string();
System.out.println("response: " + responseBodyString);
return responseBodyString;
} catch (IOException ex) {
System.out.println(ex);
}
Like I said, I keep getting a returned JSON of {"error":"invalid_client"}, and when I navigate to the URL I'm making the request to through a web browser it shows HTTP ERROR 405.
I'd love to provide as much additional information as you need. Thanks!
Edit 1: The second parameter of this used to be "bodyBuilder.toString()", but I changed it because I realized it wasn't actually sending the body. The result is still the same - {"error":"invalid_client"}. The method now used comes from here.
I figured out what it was - I hadn't actually been writing the authentication_code to the file, only adding it to another JSONObject. Oops. :)
I am trying to emulate this request using RestTemplate in Spring Boot
curl -X POST
'https://my.craftar.net/api/v0/image/?api_key=123456789abcdefghijk123456789abcdefghijk'
-F "item=/api/v0/item/4fe672886ec142f6ab6d72d54acf046f/"
-F "file=#back_cover.png"
Here's my code:
MultiValueMap<String, Object> params= new LinkedMultiValueMap<>();
params.add("item", "/api/v0/item/4fe672886ec142f6ab6d72d54acf046f/");
final String filename=file.getOriginalFilename();
Resource contentsAsResource = new ByteArrayResource(file.getBytes()){
#Override
public String getFilename(){
return filename;
}
};
HttpHeaders imageHeaders = new HttpHeaders();
imageHeaders.setContentType(MediaType.IMAGE_PNG);
HttpEntity<Resource> imageEntity = new HttpEntity<Resource>(contentsAsResource, imageHeaders);
params.add("file", imageEntity);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.ALL));
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String,Object>> requestEntity =new HttpEntity<>(params,headers);
try {
ResponseEntity<String> responseEntity = restTemplate.exchange(url,HttpMethod.POST, requestEntity, String.class);
return responseEntity.getBody();
} catch (final HttpClientErrorException httpClientErrorException) {
return httpClientErrorException.getResponseBodyAsString();
} catch (Exception exception) {
return exception.getMessage();
}
The above request throws a HttpClientErrorException and this what the response body looks like
{"error": {"message": "Expected multipart/form-data; boundary=<..> content but got multipart/form-data;boundary=x6G0xWVxdZX4n8pYNU8ihGAnCg4Twj3DgMARYDs.", "code": "WRONG_CONTENT_TYPE"}}
I have also tried using FileSystemResource, but it throws the same exception. The problem probably lies in formatting the data in multipart content-type.
If it can help, this is the code template generated by Postman on a successful request using Okhttp.
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW");
RequestBody body = RequestBody.create(mediaType,
"------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n
Content-Disposition: form-data; name=\"item\"\r\n\r\n/api/v0/item/3d8dcdd1daa54bcfafd8d1c6a58249b5/\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\n
Content-Disposition: form-data; name=\"file\"; filename=\"times_logo.png\"\r\nContent-Type: image/png\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--");
Request request = new Request.Builder()
.url("https://my.craftar.net/api/v0/image/?api_key=c6d4750c7368806fab27294fba8d0f93d48e1e11")
.post(body)
.addHeader("content-type", "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW")
.addHeader("cache-control", "no-cache")
.addHeader("Postman-Token", "cf09a989-338e-4d68-8968-b30a43384e5f")
.build();
Response response = client.newCall(request).execute();
just add the Resource to params instead of creating a HttpEntity
params.add("file", contentsAsResource);
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()));