multipartentity that seems to be multiple levels deep? - java

I am having an issue with a client that is using some crazy (it likely isn't crazy, I really mean unfamiliar to myself ;) ) ruby rails plugin to handle the uploading of images with data attached. The way they say the server is looking for the information is
{
"objectname"{
"field"=>"value"
"field"=>"value"
"photo"=>#<ActionDispatch::Http::UploadedFile:0x00000004864fd0 #original_filename="avatar.png", #content_type="image/png",
#headers="Content-Disposition: form-data; name=\"aPhoto\"; filename=\"avatar.png\"\r\nContent-Type: image/png\r\n",
#tempfile=#<File:/tmp/RackMultipart20140211-8-1456165191981>
}
}
the problem is that with MultiPartEntity (which is what they say to use) all I can do is get the server to see this
{
"field"=>"value"
"field"=>"value"
"photo"=>#<ActionDispatch::Http::UploadedFile:0x00000004864fd0 #original_filename="avatar.png", #content_type="image/png",
#headers="Content-Disposition: form-data; name=\"aPhoto\"; filename=\"avatar.png\"\r\nContent-Type: image/png\r\n",
#tempfile=#<File:/tmp/RackMultipart20140211-8-1456165191981>
}
that extra layer of wrapping is proving difficult. here is how I have it, how can I achieve that extral layer under that key "objectname"?
MultipartEntity multiPartEntity = new MultipartEntity(HttpMultipartMode.STRICT, null, Charset.forName("UTF-8"));
ByteArrayBody imageByteArrayBody = new ByteArrayBody(imageBytes, "image/png", "avatar.png");
FormBodyPart imageFormBodyPart = new FormBodyPart("photo", imageByteArrayBody);
// sanity checks to make sure the headers are right
imageFormBodyPart.addField("Content-Disposition", "form-data; name=\"aPhoto\"");
imageFormBodyPart.addField("name", "aPhoto");
imageFormBodyPart.addField("filename", "avatar.png");
imageFormBodyPart.addField("Content-Type", "image/png");
// add the image body part to the multipart entity
multiPartEntity.addPart(imageFormBodyPart);
// add customer data
Map<String, String> customerMap = user.getUserMap();
// loop through the fields
for (Map.Entry<String, String> entry : customerMap.entrySet())
{
// create a new form part with the key as the name and the value as the value
FormBodyPart body = new FormBodyPart(entry.getKey(), new StringBody(entry.getValue()));
// sanity checking the headers are right
body.addField("Content-Disposition", "form-data; name=\"" + entry.getKey() + "\"");
body.addField(entry.getKey(), entry.getValue());
// add the part to the multipart entity
multiPartEntity.addPart(body);
}
postRequest.setEntity(multiPartEntity);
postRequest.setHeader(KEY_AUTHORIZATION, VALUE_AUTHORIZATION);
HttpResponse response = httpClient.execute(postRequest);

I know this is kind of late. But I've stumbled upon a similar issue recently. The solution that worked for me is adjusting the code on the rails end to remove that extra param wrapping. As per my research nested parts are not possible with MultipartEntity

Related

Basic Auth Authorization header and base 64 encoding

I have a vendor that I wish to exchange data with. They want me to take the username and password that they gave me and use it on an Authorization header for a get request.
And now my dirty little secret. I've never created an Authorization header before.
So I do a bunch of research and figure out the following code.
public static final String AUTH_SEPARATOR = ":";
private static final String AUTH_TYPE = "Basic ";
public static final String HEADER_AUTHORIZATION = "Authorization";
public static void addAuthHeader(Map<String, String> headers, String user, String password) {
String secretKey = user
+ AUTH_SEPARATOR
+ password;
byte[] tokenBytes = secretKey.getBytes();
String token64 = Base64.getEncoder().encodeToString(tokenBytes);
String auth = AUTH_TYPE + token64;
headers.put(HEADER_AUTHORIZATION, auth);
}
I run it and I get no response back. Hmmm. So I go to open a support request with them and I want to create an example, so I open postman and use the APIs they gave me for postman. First, I run the one for the API I'm replicating and it works. Hmm.
So then I modify that API and use my username and password instead of the one included in the example and it works fine. Crikey!
So I bang around a bit and notice that the Base64 string in the auth created by postman is slightly different at the end than the one I created.
So, back to the research and all the code I find looks a lot like mine, although I had to update it some because of version differences. The string is still different and now I'm asking for help. Surely someone has solved this problem.
String from postman "Basic THVKZ...FvTg=="
String from code above "Basic THVKZ...FvTiA="
How did I do something wrong and end up with only a three byte difference?
Tg== is base64 for N.
TiA= is base64 for N (as in, N, then a space).
Sooo, it sounds like postman is sticking a space up there and you aren't. Hopefully, if you add a space at the very end of whatever you are base64 encoding, you should get the exact same string as postman is giving you, and, hopefully, it'll all just work out at that point :)
Postman using UTF-8 for basic auth encoding, check from https://github.com/postmanlabs/postman-app-support/issues/4070
change your code like this
secretKey.getBytes(StandardCharsets.UTF_8);
https://www.base64encode.org/ for test
The problem is caused by padding. I still don't understand exactly why, but the string I'm encoding is 49 bytes long, which is not evenly divisible by 3, which means that padding comes into play.
When I changed my code to the following:
String token64 = Base64.getEncoder().withoutPadding().encodeToString(tokenBytes);
and then ran it, I got the same string minus the two == at the end that base64 uses as a pad character. Sending that to the server got the answer I was looking for.
Why do they call it software when it's so damned hard?
Esteemed developer, are they not running on OAuth2? If so, kindly make use of the code blocks below and kill it
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Bearer " + token you are either generating or getting from Eureka discovery service);
return headers;
}
AuthResponseObjectType getAuthorization() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
String auth = basicAuthUsername + ":" + basicAuthPassword;
byte[] encodedAuth = org.apache.commons.net.util.Base64.encodeBase64(auth.getBytes(StandardCharsets.US_ASCII));
headers.add("Authorization", "Basic " + new String(encodedAuth));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", grantType);
map.add("username", username);
map.add("password", password);
map.add("scope", scope);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
return restTemplate.exchange(authUrl, HttpMethod.POST, request, AuthResponseObjectType.class).getBody();
}
Let me know if you are facing any challenges then we can do it together

How to define multiple parameters for a POST request using Java 11 HTTP Client

I have a code that makes a POST request for a specific endpoint. This code is using Apache's HttpClient and I would like to start using the native HttpClient from Java (JDK11). But I didn't understand how to specify the parameters of my request.
This is my code using Apache Httpclient:
var path = Path.of("file.txt");
var entity = MultipartEntityBuilder.create()
.addPart("file", new FileBody(path.toFile()))
.addTextBody("token", "<any-token>")
.build();
And the code using HttpClient:
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://myendpoint.com/"))
.POST( /* How can I set the parameters here? */ );
How can I set file and token parameters?
Unfortunately the Java 11 HTTP client does not provide any convenient support for multipart kind of body. But we can build custom implementation on top of it:
Map<Object, Object> data = new LinkedHashMap<>();
data.put("token", "some-token-value");
data.put("file", File.createTempFile("temp", "txt").toPath());
// add extra parameters if needed
// Random 256 length string is used as multipart boundary
String boundary = new BigInteger(256, new Random()).toString();
HttpRequest.newBuilder()
.uri(URI.create("http://example.com"))
.header("Content-Type", "multipart/form-data;boundary=" + boundary)
.POST(ofMimeMultipartData(data, boundary))
.build();
public HttpRequest.BodyPublisher ofMimeMultipartData(Map<Object, Object> data,
String boundary) throws IOException {
// Result request body
List<byte[]> byteArrays = new ArrayList<>();
// Separator with boundary
byte[] separator = ("--" + boundary + "\r\nContent-Disposition: form-data; name=").getBytes(StandardCharsets.UTF_8);
// Iterating over data parts
for (Map.Entry<Object, Object> entry : data.entrySet()) {
// Opening boundary
byteArrays.add(separator);
// If value is type of Path (file) append content type with file name and file binaries, otherwise simply append key=value
if (entry.getValue() instanceof Path) {
var path = (Path) entry.getValue();
String mimeType = Files.probeContentType(path);
byteArrays.add(("\"" + entry.getKey() + "\"; filename=\"" + path.getFileName()
+ "\"\r\nContent-Type: " + mimeType + "\r\n\r\n").getBytes(StandardCharsets.UTF_8));
byteArrays.add(Files.readAllBytes(path));
byteArrays.add("\r\n".getBytes(StandardCharsets.UTF_8));
} else {
byteArrays.add(("\"" + entry.getKey() + "\"\r\n\r\n" + entry.getValue() + "\r\n")
.getBytes(StandardCharsets.UTF_8));
}
}
// Closing boundary
byteArrays.add(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
// Serializing as byte array
return HttpRequest.BodyPublishers.ofByteArrays(byteArrays);
}
Here's working example on Github (you need to change VirusTotal API key)
The HttpClient doesn't provide any high level API to compose or format data in POST requests. You could either compose and format your post data manually, and then use either one of BodyPublishers.ofString(), BodyPublishers.ofInputStream(), or BodyPublishers.ofByteArrays() etc... to send it, or write your own implementation of BodyPublisher.

JAX-WS sending empty value after explicitly adding SOAPAction header

I have made webservice client with NetBeans 8.2 wizard from an existing WSDL document. Java8 and JAX-WS 2.2.9 is used.
As far as I understand everything works with the wizard created code as expected but the query is missing value from "SOAPAction" header which is requirement to have value for the query to work. The header key exists but value is empty string: SOAPAction: "" when it should be SOAPAction = "SendReports"
I have tried using this:
Map<String, List<String>> requestHeaders = new HashMap<>();
requestHeaders.put("SOAPAction", Arrays.asList("sendReports"));
sourceDispatch.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, requestHeaders);
-> which results also in empty value. If I put "SOAPAction_2" & "sendReports", that header works correct but obviously the header key is wrong and wont solve my problem. Something overwrites my value afterwards?
The webservice method has annotation:
#WebMethod(operationName = "SendReports", action = "SendReports")
Any tips on what could I try next?
I saw many posts suggesting using BindingProvider but I cannot use com.sun.* packages for reasons left unexplained.
Finally found a working solution.
I created a Web service Dispatch client with Netbeans (as originally) and had to add SOAPACTION_USE_PROPERTY and SOAPACTION_URI_PROPERTY.
These I had tried before as System properties but seemed not to work that way.
Here is the working snippet:
public void sendReports() throws IOException {
ReportService service = new ReportService();
QName portQName = new QName("http://URL/ReportService", "ReportService");
req = readFile("C:/temp/myFile.xml", "UTF-8");;
try {
// Call Web Service Operation
Dispatch<Source> sourceDispatch = null;
sourceDispatch = service.createDispatch(portQName, Source.class, Service.Mode.PAYLOAD);
Map<String, Object> map = sourceDispatch.getRequestContext();
map.put(BindingProvider.SOAPACTION_USE_PROPERTY, Boolean.TRUE);
map.put(BindingProvider.SOAPACTION_URI_PROPERTY, "SendReports");
Source result = sourceDispatch.invoke(new StreamSource(new StringReader(req)));
} catch (Exception ex) {
// TODO handle custom exceptions here
}
}

Mailgun API: Sending Inline Image with Spring's RestTemplate

The goal is to send an email with inline image. Everything is working well, except the image is not appearing in the email.
My approach is based on this Jersey-example of Mailgun's User Guide.
public static ClientResponse SendInlineImage() {
Client client = Client.create();
client.addFilter(new HTTPBasicAuthFilter("api",
"YOUR_API_KEY"));
WebResource webResource =
client.resource("https://api.mailgun.net/v3/YOUR_DOMAIN_NAME" +
"/messages");
FormDataMultiPart form = new FormDataMultiPart();
form.field("from", "Excited User <YOU#YOUR_DOMAIN_NAME>");
form.field("to", "baz#example.com");
form.field("subject", "Hello");
form.field("text", "Testing some Mailgun awesomness!");
form.field("html", "<html>Inline image here: <img src=\"cid:test.jpg\"></html>");
File jpgFile = new File("files/test.jpg");
form.bodyPart(new FileDataBodyPart("inline",jpgFile,
MediaType.APPLICATION_OCTET_STREAM_TYPE));
return webResource.type(MediaType.MULTIPART_FORM_DATA_TYPE).
post(ClientResponse.class, form);
}
However, I need to use Spring's RestTemplate.
This is what I've got so far:
RestTemplate template = new RestTemplate();
MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
// ... put all strings in map (from, to, subject, html)
HttpHeaders headers = new HttpHeaders();
// ... put auth credentials on header, and content type multipart/form-data
template.exchange(MAILGUN_API_BASE_URL + "/messages", HttpMethod.POST,
new HttpEntity<>(map, headers), String.class);
The remaining part is to put the *.png file into the map. Not sure how to do that. Have tried reading all its bytes via ServletContextResource#getInputStream, but without success: Image is not appearing in the resulting e-mail.
Am I missing something here?
This turned out to be a case where everything was set up correctly, but only a small detail prevented it from working.
map.add("inline", new ServletContextResource(this.servletContext,
"/resources/images/email-banner.png"));
For Mailgun you need to use the map-key "inline". Also, the ServletContextResource has a method getFilename(), which is used to resolve against the image tag. Thus, the image tag should have the following content id:
<img src="cid:email-banner.png"/>

How do I send JSON to an external API using RestEasy?

I need to send a JSON body to https://mandrillapp.com/api/1.0//messages/send-template.json . How do I do this using RestEasy in Java? This is what I have so far:
ResteasyClient client = new ResteasyClientBuilder().build();
ResteasyWebTarget target = client.target("https://mandrillapp.com/api/1.0//messages/send-template.json");
How do I actually send the JSON?
Once you have the ResteasyWebTarget, you need to get the Invocation
Invocation.Builder invocationBuilder = target.request("text/plain").header("some", "header");
Invocation incovation = invocationBuilder.buildPost(someEntity);
invocation.invoke();
where someEntity is some instance of Entity<?>. Create one with
Entity<String> someEntity = Entity.entity(someJsonString, MediaType.APPLICATION_JSON);
Read this javadoc.
This is for 3.0 beta 4.
This is a bit old question, but I found it looking for something similar on Google, so this is my solution, using RestEasy client 3.0.16:
I'll use a Map object to be sent but you can use whatever JavaBean that Jackson provider can convert to JSON.
BTW, you'll need add as dependency the resteasy-jackson2-provider lib.
ResteasyClient client = new ResteasyClientBuilder().build();
ResteasyWebTarget target = client.target("http://server:port/api/service1");
Map<String, Object> data = new HashMap<>();
data.put("field1", "this is a test");
data.put("num_field2", 125);
Response r = target.request().post( Entity.entity(data, MediaType.APPLICATION_JSON));
if (r.getStatus() == 200) {
// Ok
} else {
// Error on request
System.err.println("Error, response: " + r.getStatus() + " - "+ r.getStatusInfo().getReasonPhrase());
}
I've never used this framework, but according to an example at this url, you should be able to make a call like this:
Client client = ClientBuilder.newBuilder().build();
WebTarget target = client.target("http://foo.com/resource");
Response response = target.request().get();
String value = response.readEntity(String.class);
response.close(); // You should close connections!
The 3rd line seems to be the answer you're looking for.

Categories