Play Framework: Post image to imageshack using WS - java

I am trying to POST an image to imageshack using their API and Play Framework's WSRequest object.
My code is as follows:
public static void upload( Picture picture ) throws Exception {
//set file parameter - in this case the image
WS.FileParam fp = new WS.FileParam( picture.asFile, "fileupload");
//set other parameters
Map<String,Object> params = new HashMap<String, Object>();
params.put( "optsize", "resample" );
params.put( "rembar", "yes" );
params.put( "public", "no" );
params.put( "a_username", username );
params.put( "a_password", password );
params.put( "key", a_key );
//POST request
Document doc = WS.url( "http://www.imageshack.us/upload_api.php" )
.setHeader( "Content-Type", picture.contentType )
.mimeType( "multipart/form-data" )
.params( params )
.files( fp )
.post()
.getXml();
}
However, I always reveive the following response from imageshack:
Sorry, but we've detected that unexpected data is received. Required parameter 'fileupload' is missing or your post is not multipart/form-data.
I have tried sending the file as a parameter using a byte array:
params.put( "fileupload", Base64.encode( picture.asBytes ) )
But this also results in the same response from Imageshack.
This is driving me mad. Can anyone point out where I am going wrong or possibly point me in the direction of a better solution? Thanks.
The cause
After a bit of research I found that I had neglected a bit of important information from this question....I am including the Google App Engine module within my app.
According to the Play Framework Google Group the code associated with attaching Files to a WS request when using GAE is actually just commented out. Hence the reason it just doesn't work. So no error thrown for you and no indication why it doesn't work...you just have to work it out.
I have accepted #Gary's answer as it is the correct way to upload an image to imageshack using WS - just not when using GAE.

I don't think you need to specify the content type or mime type directly.
I used the following code to upload successfully.
WS.FileParam fp = new WS.FileParam(
new File("d:\\workspace\\ImageShackTest\\sample_picture.png"), "fileupload");
Map<String,Object> params = new HashMap<String, Object>();
params.put( "optsize", "resample" );
params.put( "rembar", "yes" );
params.put( "public", "yes" );
//params.put( "a_username", username );
//params.put( "a_password", password );
params.put( "key", API_KEY );
//POST request
Document doc = WS.url( "http://www.imageshack.us/upload_api.php" )
.params( params )
.files( fp )
.post()
.getXml();
I think when you attach a file to a request it automatically decides its going to be multipart/form-data.
This is my entire controller (except for the API Key)
package controllers;
import play.*;
import play.mvc.*;
import java.util.*;
import models.*;
import play.libs.*;
import java.io.File;
public class Application extends Controller {
public static void index() { render(); }
private static final String API_KEY = "API KEY REMOVED TO PROTECT THE INNOCENT";
public static void tryUpload() {
WS.FileParam fp = new WS.FileParam( new File("d:\\workspace\\ImageShackTest\\sample_picture.png"), "fileupload");
Map<String,Object> params = new HashMap<String, Object>();
params.put( "optsize", "resample" );
params.put( "rembar", "yes" );
params.put( "public", "yes" );
params.put( "key", API_KEY );
String doc = WS.url( "http://www.imageshack.us/upload_api.php" )
.params( params )
.files( fp )
.post()
.getString();
System.out.println(doc);
index();
}
}
and this is the application.conf file
# This is the main configuration file for the application.
# ~~~~~
application.name=ImageShackTest
application.mode=dev
%prod.application.mode=prod
application.secret=JIVQE8y3y1lCzXRGprFJvoXBdi8Jpa8qE1U1mBIooLLOOYk5yyhAI5cxbEf4q4pl
date.format=yyyy-MM-dd
attachments.path=data/attachments
mail.smtp=mock
I didn't make any other changes. Just browsed to http://localhost:9000/Application.tryUpload and could see the success XML on the play console.

You are setting the content type header incorrectly.
Instead of this:
.setHeader( "Content-Type", picture.contentType )
Try this:
.setHeader( "Content-Type", "multipart/form-data" )

Related

RestAssured with duplicate query parameters

Working on an automated test pack that uses REST-assured rest-assured-3.3.0. Have an endpoint to test that has a number of duplicate Query parameters used to drive it's search function:
/count?taskTypes=EAS&taskTypes=MBO&taskTypes=OTHER&taskAges=NEW&taskAges=EXISTING&ageSelection=ALL
REST-assured provides a params(Map<String, ?> var1) in it's request specification to pass in parameters. However this uses Map which can not contain duplicate keys. Therefore in order to construct the query in test code I am setting up the parameters and associated values in a Multimap:
import com.google.common.collect.Multimap;
Multimap<String, String> searchParams = ArrayListMultimap.create();
searchParams.put("taskTypes", "EAS");
searchParams.put("taskTypes", "MBO");
searchParams.put("taskTypes", "OTHER");
searchParams.put("taskAges", "NEW");
searchParams.put("taskAges", "EXISTING");
searchParams.put("ageSelection", "ALL");
The Multimap is then passed into a function to format the query and set up a GET request using REST-assured:
import org.apache.http.client.utils.URIBuilder;
import io.restassured.http.Method;
import io.restassured.response.ExtractableResponse;
import io.restassured.specification.RequestSpecification;
protected ExtractableResponse request(Method method, Multimap<String, ?> params, String url)
{
URIBuilder builder = new URIBuilder();
for (Map.Entry<String, ?> entry : params.entries()) {
builder.addParameter(entry.getKey(), entry.getValue().toString());
}
try {
url = url + builder.build().toString();
} catch(URISyntaxException e) {}
return getRequestSpec()
.when()
.headers("Content-Type", "application/json")
.request(method, url)
.then()
.spec(getResponseSpec(method))
.extract();
}
Running this code results in a request with the following parameters:
/count?taskTypes=OTHER&taskAges=EXISTING&ageSelection=ALL
The problem is that REST-assured appears to remove the duplicate query parameters passed to it in the url parameter. Interestingly REST-assured offers the following interface given().queryParam("param", 1,2,3)
Check that for loop here:
URIBuilder builder = new URIBuilder();
for (Map.Entry<String, ?> entry : params.entries()) {
builder.addParameter(entry.getKey(), entry.getValue().toString());
}
looks like despite using Multimap for params you are still ending with Map for the builder. Here is where you got rid of duplicated keys anyway.
queryParam(String var1, Collection<?> var2)
should work. I have used it in the past for rest-assured-3.*.
Example:
List<String> multiQuery = new ArrayList<>();
multiquery.add("EAS");
multiquery.add("MBO");
queryParam("taskTypes", multiQuery);

Check if tokens have been substituted

I use Apache Velocity to substitute tokens in a templates which looks like this:
<batch>
#set( $appName = "helloworld" )
#set( $appDisplayName = "Hello World App")
#set( $appVersion = "1.0")
<registerapplication>
<appkey>$appkey</appkey>
<appname>$appName</appname>
<appversion>$appVersion</appversion>
<appdescriptiondefault>$appDisplayName</appdescriptiondefault>
</registerapplication>
</batch>
The following three lines of code do the trick:
Velocity.init();
StringWriter w = new StringWriter();
Velocity.evaluate(new VelocityContext(), w, "", new InputStreamReader(inputStream);
It works perfect, but I have an additional requirement. If some substitutions has really been made, the user should get a notification. Otherwise, i.e. if a template looks like
<batch>
#set( $appName = "helloworld" )
#set( $appDisplayName = "Hello World App")
#set( $appVersion = "1.0")
<registerapplication>
<appkey>$appkey</appkey>
<appname>helloworld</appname>
<appdescriptiondefault>Hello World App</appdescriptiondefault>
</registerapplication>
</batch>
no actions should be taken.
Any idea how to achieve that?
upd Thanks to Claude Brisson. My solution looks as follows:
VelocityContext velocityContext = new VelocityContext();
EventCartridge eventCartridge = new EventCartridge();
velocityContext.attachEventCartridge(eventCartridge);
eventCartridge.addReferenceInsertionEventHandler(new ReferenceInsertionEventHandler()
{
#Override
public Object referenceInsert(String reference, Object value)
{
if (!varSubsituted && value != null)
{
varSubsituted = true;
}
return value;
}
});
You can use a reference insertion handler.
First, create your handler class:
package mypackage;
import org.apache.velocity.app.event.ReferenceInsertionEventHandler;
public class MyHandler implements ReferenceInsertionEventHandler
{
public Object referenceInsert( String reference, Object value )
{
// ...notify user...
}
}
You can then register your handler in your velocity.properties file, with:
eventhandler.referenceinsertion.class = mypackage.UserNotifyHandler
In a multi-threaded application, since Velocity is single-threaded, you can use a global thread specific variable to hold involved the user's info, since you'll need it from within your handler.

Post multipart form with google-http-java-client

It's not clear from the google-http-java-client* docs how you would go about posting a form that has a file field.
For example I'm trying to print a document using the Google Cloud Print API:
HttpRequestFactory httpRequestFactory = getHttpRequestFactory();
Map<String, Object> parameters = Maps.newHashMap();
parameters.put("printerId", printRequest.getPrinterId());
parameters.put("title", printRequest.getTitle());
parameters.put("contentType", printRequest.getContentType());
parameters.put("ticket", new Gson().toJson(printRequest.getOptions()));
MultipartContent content = new MultipartContent();
content.addPart(new MultipartContent.Part(new UrlEncodedContent(parameters)));
content.addPart(new MultipartContent.Part(
new FileContent(printRequest.getContentType(), printRequest.getFile())));
try {
HttpResponse response = httpRequestFactory.buildPostRequest(
SubmitUrl, content).execute();
System.out.println(IOUtils.toString(response.getContent()));
} catch (IOException e) {
String message = String.format();
System.out.println("Error submitting print job: " + e.getMessage());
}
Unfortunately this doesn't work. The API returns the error "Printer Id required for this request." which seems to me like the request isn't properly formed.
What am I doing wrong?
* I'm specifically using the google-http-java-client as it handles automatic refreshing of OAuth tokens etc for me. Please don't reply with solutions that involve using other HTTP clients.
So it looks like I misunderstood how form fields are added to multipart messages. The working code now looks like this
HttpRequestFactory httpRequestFactory = getHttpRequestFactory(username);
Map<String, String> parameters = Maps.newHashMap();
parameters.put("printerid", printRequest.getPrinterId());
parameters.put("title", printRequest.getTitle());
parameters.put("contentType", printRequest.getContentType());
// Map print options into CJT structure
Map<String, Object> options = Maps.newHashMap();
options.put("version", "1.0");
options.put("print", printRequest.getOptions());
parameters.put("ticket", new Gson().toJson(options));
// Add parameters
MultipartContent content = new MultipartContent().setMediaType(
new HttpMediaType("multipart/form-data")
.setParameter("boundary", "__END_OF_PART__"));
for (String name : parameters.keySet()) {
MultipartContent.Part part = new MultipartContent.Part(
new ByteArrayContent(null, parameters.get(name).getBytes()));
part.setHeaders(new HttpHeaders().set(
"Content-Disposition", String.format("form-data; name=\"%s\"", name)));
content.addPart(part);
}
// Add file
FileContent fileContent = new FileContent(
printRequest.getContentType(), printRequest.getFile());
MultipartContent.Part part = new MultipartContent.Part(fileContent);
part.setHeaders(new HttpHeaders().set(
"Content-Disposition",
String.format("form-data; name=\"content\"; filename=\"%s\"", printRequest.getFile().getName())));
content.addPart(part);
try {
HttpResponse response = httpRequestFactory.buildPostRequest(
SubmitUrl, content).execute();
System.out.println(IOUtils.toString(response.getContent()));
} catch (IOException e) {
...
}
The most important parts above were overriding the default HttpMediaType to specify "multipart/form-data" and adding each field as its own part with a "Content-Disposition" header to designate the form field name.

How to multithread in Grizzly

I'm implementing a Java RESTful webservice on Heroku. Currently my entry point looks like
public class Main
{
public static final String BASE_URI = getBaseURI();
public static void main( String[] args ) throws Exception
{
final Map<String, String> initParams = new HashMap<String, String>();
initParams.put( "com.sun.jersey.config.property.packages", "services.contracts" );
initParams.put( "com.sun.jersey.api.json.POJOMappingFeature", "true" );
System.out.println( "Starting grizzly..." );
SelectorThread threadSelector =
GrizzlyWebContainerFactory.create( BASE_URI, initParams );
System.out.println( String.format( "Jersey started with WADL available at %sapplication.wadl.", BASE_URI,
BASE_URI ) );
}
private static String getBaseURI()
{
return "http://localhost:" + ( System.getenv( "PORT" ) != null ? System.getenv( "PORT" ) : "9998" ) + "/";
}
}
where the initParms contains RESTful services being hosted. I understand that GrizzlyWebContainerFactory.create() returns an instance of ServletContainer (SelectorThread), but how would I multithread the returned threadSelector such that multiple SelectorThread's can handle the incoming requests under one process (aka web dyno)? The reason is to increase performance of a single dyno in handling requests.
Any suggestions appreciated! Thanks!
You can call:
SelectorThread.setSelectorReadThreadsCount(int)

WSSE (with digest) in WCF\.Net\C# - An easy way?

Disclaimer: .Net N00b
I've been beating my head against the wall for a couple of days now trying to get the security work with this external vendors web service to no avail. It turns out that they use WSSE digest security, which, in short, adds something like this to the SOAP header:
<wsse:UsernameToken wsu:Id="Example-1">
<wsse:Username> ... </wsse:Username>
<wsse:Password Type="..."> ... </wsse:Password>
<wsse:Nonce EncodingType="..."> ... </wsse:Nonce>
<wsu:Created> ... </wsu:Created>
</wsse:UsernameToken>
I started out by adding the service reference, and through many, many blog posts, stackoverflow questions fiddling with the app.config and the code. I just couldn't seem to get it right. Maybe it isn't easily possible? Maybe I just don't know Visual Studio 2010 and .Net that well, I'm not sure.
Here is what I stopped with in my app.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="ServiceHttpBinding" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="TransportWithMessageCredential" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://vendorurl"
binding="basicHttpBinding" bindingConfiguration="ServiceHttpBinding"
contract="ContractName"
name="ServiceHttpPort">
</endpoint>
</client>
</system.serviceModel>
And the C#:
var someService = new ServiceClient();
someService.ClientCredentials.UserName.UserName = "username";
someService.ClientCredentials.UserName.Password = "passwordgobbletygook/somemorebase64stuff=";
#region Begin Magic
var elements = someService.Endpoint.Binding.CreateBindingElements();
var securityBindingElement = elements.Find<SecurityBindingElement>();
securityBindingElement.IncludeTimestamp = false;
someService.Endpoint.Binding = new CustomBinding(elements);
#endregion
var response = someService.webMethod(param1, param2, param3, param4);
Console.WriteLine(response);
The funny thing is, in the vendors spec, I found that they encourage the use of WSSJ, so I tried it out (in java) and I GOT IT TO WORK IN 2 HOURS
Here is what that looks like:
public class Test implements CallbackHandler {
/**
* #param args
*/
public static void main( final String[] args ) throws Throwable {
SomeService_Service someService_Service = new SomeService_Service();
SomeService someService = someService_Service.getSomeServiceHttpPort();
BindingProvider bindingProvider = (BindingProvider)someService;
Map< String, Object > requestContext = bindingProvider.getRequestContext();
requestContext.put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://vendorurl" );
Client client = ClientProxy.getClient( someService );
Endpoint endpoint = client.getEndpoint();
Map< String, Object > outProps = new HashMap< String, Object >();
outProps.put( WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN );
outProps.put( WSHandlerConstants.USER, "username" );
outProps.put( WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_DIGEST );
outProps.put( WSHandlerConstants.PW_CALLBACK_REF, new Test() );
WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor( outProps );
endpoint.getOutInterceptors().add( wssOut );
System.out.println( someService.webMethod(param1, param2, param3, param4) );
}
public void handle( final Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
WSPasswordCallback pc = (WSPasswordCallback)callbacks[ 0 ];
// set the password for our message.
pc.setPassword( "passwordgobbletygook/somemorebase64stuff=" );
}
}
Has anyone out there in stackoverflow land got this to work in .Net\C#? Is there something obvious I'm missing here?
We've run into this problem before when trying to connect a .NET based component to a JAVA based SOAP service. Our solution doesn't involve any XML construction and is IMHO a bit cleaner than anything else I've seen.
The downside is that you need to download and include an older optional .NET DLL to make it work. The upside is that the code is quite clean and fits naturally into WCF.
The basic implementation looks something like this:
using (OperationContextScope scope = new OperationContextScope(client.InnerChannel))
{
//Class from WSE 3.0
UsernameToken token = new UsernameToken("MY_USERNAME", "MY_PASSWORD", PasswordOption.SendHashed);
//Add Auth to SOAP Header
MessageHeader header
= MessageHeader.CreateHeader(
"Security",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
token.GetXml(new XmlDocument())
);
OperationContext.Current.OutgoingMessageHeaders.Add(header);
//Build Request
OrgWS.OrganizationDetailsRequest request = new OrgWS.OrganizationDetailsRequest()
{
ID = 1
};
//Send Request
OrgWS.OrganizationDetail[] response = client.getOrganizationDetail(request);
//Do something with response
}
A full explanation can be found here: http://cxdeveloper.com/article/implementing-ws-security-digest-password-nonce-net-40-wcf
Andy's answer is spot on! Spent most of the say on this, there is a lot out there but this is the ONLY answer that worked for me. Perfect for adding nonce with passwordDigest in SOAP wsse headers. Agree with Nick V, this answer should get more recognition.
BasicHttpBinding myBinding = new BasicHttpBinding();
myBinding.Security.Mode = BasicHttpSecurityMode.Transport;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
EndpointAddress ea = new EndpointAddress("****");
WebServiceServiceClient cc = new WebServiceServiceClient(myBinding, ea);
cc.Open();
using (OperationContextScope scope = new OperationContextScope(cc.InnerChannel))
{
//Class from WSE 3.0
UsernameToken token = new UsernameToken("userid", "password", PasswordOption.SendHashed);
//Add Auth to SOAP Header
MessageHeader header
= MessageHeader.CreateHeader(
"Security",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
token.GetXml(new XmlDocument())
);
OperationContext.Current.OutgoingMessageHeaders.Add(header);
try
{
//call SOAP methos
}
catch (Exception ex)
{
//catch any errors
}
}

Categories