10th Apr, 2009

Curl up with a good web script

Curl is a useful tool for all sorts of things. One specific example of when it comes in handy is when you are developing Alfresco web scripts. On a Surf project, for example, you might divide into a “Surf tier” team and a “Repository tier” team. Once you’ve agreed on the interface, including both the URLs and the format of the data that goes back-and-forth between the tiers, the two teams can start cranking out code in parallel.

If you’re on the repo team, you need a way to test your API, and you probably don’t have a UI to test it with (that’s what the other team’s working on). There are lots of solutions to this but curl is really handy and it runs everywhere (on Windows, use Cygwin).

This post isn’t intended to be a full reference or how-to for curl, and obviously, you can use curl for a lot of tasks that involve HTTP, not just Alfresco web scripts. Here are some quick examples of using curl with Alfresco web scripts to get you going.

Get a ticket

It’s highly likely that your web script will require authentication. So the first thing you do is call the login web script to get a ticket.

curl -v "http://localhost:8080/alfresco/service/api/login?u=admin&pw=somepassword"

Alfresco will respond with something like:

<?xml version="1.0" encoding="UTF-8"?>
<ticket>TICKET_e46107058fdd2760441b44481a22e7498e7dbf66</ticket>

Now you can take that ticket and append it to your subsequent web script calls.

Any web script you’ve got that accepts GET can be tested using the same simple syntax.

Post JSON to your custom web script

If all you had were GETs you’d probably just test them in your browser. POSTs, PUTs and DELETEs require a little more doing to test. You’re going to want to test those web scripts so that when the front-end team has their stuff ready, it all comes together without a lot of fuss.

So let’s say you’ve got a web script that the front-end will be POSTing JSON to. To test it out, create a file with some test JSON, then post it to the web script using curl, like this:

curl -v -X POST "http://localhost:8080/alfresco/service/someco/someScript?alf_ticket=TICKET_e46107058fdd2760441b44481a22e7498e7dbf66" -H "Content-Type: application/json" -d @/Users/jpotts/test.json

By the way, did you know that starting with 3.0, if you name your controller with “.json” before the “.js” the JSON will be sitting in a root variable called “json”?  So in this case instead of naming my controller “someScript.post.js” I’d name it “someScript.post.json.js” and then in my JavaScript, I can just eval the “json” variable that got created for me automatically and start working with the object,  like this:

var postedObject = eval('(' + json + ')');
logger.log("Customer name:" + postedObject.customerName);

Run a CMIS query

With 3.0 Alfresco added an implementation of the proposed CMIS spec to the product. CMIS gives you a Web Services API, a RESTful API, and a SQL-like query language. Once you figure out the syntax, it’s easy to post CMIS queries to the repository. You can wrap the CMIS query in XML:

<cmis:query xmlns:cmis="http://www.cmis.org/2008/05" >
<cmis:statement><![CDATA[select * from cm_content where cm_name like '%Foo%']]></cmis:statement>
</cmis:query>

Then post it using the same syntax as you saw previously, but with a different Content-Type in the header, like this:

curl -v -X POST "http://localhost:8080/alfresco/service/api/query?alf_ticket=TICKET_e46107058fdd2760441b44481a22e7498e7dbf66" -H "Content-Type: application/cmisquery+xml" -d @/Users/jpotts/cmis-query.xml

Alfresco will respond with ATOM, but it’s a little verbose so I won’t take up space here to show you the result. Also, I noticed this bombed when I ran it against 3.1 Enterprise but I haven’t drilled down on why yet.

Create a new object using CMIS ATOM

Issuing a GET against a CMIS URL returns ATOM. But CMIS URLs can also accept POSTed ATOM to do things like create new objects. For example, to create a new content object you would first create the ATOM XML:

<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:cmis="http://www.cmis.org/2008/05">
<title>Test Plain Text Content</title>
<summary>Plain text content created via CMIS POST</summary>
<content type="text/plain">SGVyZSBpcyBzb21lIHBsYWluIHRleHQgY29udGVudC4K</content>
<cmis:object>
<cmis:properties>
<cmis:propertyString cmis:name="ObjectTypeId"><cmis:value>document</cmis:value></cmis:propertyString>
</cmis:properties>
</cmis:object>
</entry>

Note that the content has to be Base64 encoded. In this case, the content is plain text that reads, “Here is some plain text content.” One way to encode it is to use OpenSSL like “openssl base64 -in <infile> -out <outfile>”. The exact syntax of ATOM XML with CMIS is the subject for another post.

Once you’ve got the XML ready to go, post it using the same syntax shown previously, with a different Content-Type in the header:

curl -v -X POST "http://localhost:8080/alfresco/service/api/node/workspace/SpacesStore/18fd9821-42a5-4c6a-86d3-3f252679cf7d/children?alf_ticket=TICKET_e46107058fdd2760441b44481a22e7498e7dbf66" -H "Content-Type: application/atom+xml" -d @/Users/jpotts/testCreate.atom.xml

The node reference in the URL above is a reference to the folder in which this new child will be created. There’s also a similar URL that uses the path instead of a node ref if that’s more your thing.

Refreshing Web Scripts from Ant

One of the things you do quite frequently when you develop web scripts is tell Alfresco to refresh its list of web scripts. There are lots of ways to automate this, but one is to create an Ant task that uses curl to invoke the web script refresh URL. This lets you deploy your changes and tell Alfresco to refresh the list in one step (and makes sure you and your teammates never forget to do the refresh).

<target name="deploy-webscripts" depends="deploy" description="Refreshes the list of webscripts">
<exec executable="curl">
<arg value="-d"/>
<arg value="reset=on"/>
<arg value="http://${alfresco.web.url}/service/index"/>
</exec>
</target>

In this example, the “deploy” ant task this task depends on is responsible for copying the web scripts to the appropriate place in the exploded Alfresco WAR. (Thanks to my colleague Eric Shea (http://www.eshea.net/2009/01/30/alfresco-dev-survivors-kit-part-1/) for this tip).

So there you go. It’s not Earth-shattering but it might give you a productivity boost if you don’t already have curl or an alternative already in your bag of tricks.

Responses

If one is developing custom JSPs and using the out of box (or custom) login JSP, you can get the ticket with the use of scriptlet:

Jeff, is this a common (or even a recommended) practice?

Sorry, my earlier response didn’t accept the tags. Here’s what I was referring to:

((User)session.getAttribute(“_alfAuthTicket”)).getTicket()

Rich,

I think maybe you posted some code that got stripped out?

In any case, there are a few different ways to get a ticket. The trickiest thing with tickets seems to be figuring out when they expire.

Jeff

Why are you using a ticket for authentication? Can’t you just use HTTP Basic authentication? (that’s a rhetoric, of course you can; my opinion is that you should)

@Thomas,

Yes, you could use basic auth instead of a ticket, like this:

curl -v http://admin:somepassword@localhost:8080/alfresco/service/api/repository

Jeff

A bit off topic maybe, but isn’t it a bit risky, from a security standpoint, to eval() a posted json string?
I thought the recommendation was to use a true parser (e.g. json.org) for this, as otherwise the posted content could, if eval()’ed, execute arbitrary code.

Bruno,

You are right on both counts. I’ve used the json.org parser successfully in web scripts so for people who need to do that rather than an eval() it works great.

In server-side JavaScript, like what you might use for a web script controller, you can do an import of that library like:

<import resource="classpath:alfresco/site-webscripts/someco/lib/json2.js">

Then you can use JSON.parse instead of eval which, as you point out, is safer, and sometimes faster.

Jeff

Is there someway to have Alfresco respond using a custom content-type header?

How can I query using CMIS REST API a custom content type?

I defined a custom type extending the file contentModel.xml. Whenever I do a query like:

….
SELECT ObjectId, Name FROM MyCustomType
….
Is there actually a support for custom types within Alfresco 3.0?
Should I extend directly the model file cmsModel.xml instead? I’m confused about this.
Thanks for any advice.
Andrea

an exception is thrown from app server.

Andrea,

Running a CMIS query against a custom type was not possible in 3.0. I believe it may have been added recently, but to be honest, I don’t know in which version it was added. If I get some time today/tonight I can try your query starting with 3.2 Community and work backwards until it stops working. You might see if any of the Alfresco wiki pages on CMIS shed light on this.

Jeff

After a RESTful day spent writing Web Scripts for docasu I woke up this morning and thought, there has to be an easier way to refresh the service/index than clicking in a web browser.

I might have know you would have already blogged about this.

Thanks. Practical as ever.

Jeff,

I have a Custom Content Model that defines a property as multiple. I want to enter multiple values into that property using a JavaScript in a Web Script.

I’m using: file.properties[propertyName] = propertyValue.

However, only the last value I apply is shown. Basically, the original values are being overwritten by the new values.

How do I use Javascript to add multiple values to a property?

Thanks,

Krista

Krista,

Try using a list instead of a string, like this:

multiPropVal = Array();
multiPropVal[0] = "first value";
multiPropVal[1] = "second value";
file.properties[propertyName] = multiPropVal;

Jeff

Jeff,

Thank you so much! I really appreciate the help and the quick response. This worked immediately.

One more question – just to verify. Does this mean that if I assign 2 values. Then later I want to add a third I have to read out the existing 2 values, put them in an array with the third, and then assign them?

Thanks!

Krista

Yes, but it isn’t as bad as it sounds. When you get the property value of a multi-value property in JavaScript, Alfresco gives it to you as an Array. So if you want to append a new value, you just grab the existing props as an array, append the new value, and then set the property to the updated array.

Jeff

Thanks!

Jeff,
In your book you do an upload example (starting page 261).
What I’m trying to do is POST directly (eliminating the GET).
In my situation, I already KNOW the file to be uploaded. I’ve tried every combination of curl I can think of with no success. I always get errors with ‘formdata’ not defined. Very frustrating.

How can I POST a file directly? (without the user interaction part of the GET).

Thanks in advance.

Guys, anybody figured out how to find out whether ticket is expired or not.

You call a web script by http://localhost:8080/alfresco/service/someservice?alf_ticket=xxxxxxxxxxxxxx

It works perfectly when the ticket is valid, but once it is expired, alfresco authentication dialog pops up. Any way to find out whether the ticket is expired?

Hi Bill, just check my Curl based shell script to upload content to Alfresco repository: http://louise.hu/poet/?p=4529 (works with HTTP and HTTPS connections too)

The only way I know of is to try to use it and check the HTTP response code. I also do not know how to reliably predict (or control) when the ticket will expire. So if you decide to use tickets you need to be in the habit of checking the response code and re-authenticating if the authenticated call fails.

Jeff

Jeff,

When I use your command line to create a new object using CMIS ATOM it works perfectly. However I am now attempting to use it in PHP either by shell_exec or libcurl. I seem to be getting the 500- Internal Error each time I do either method. Any advice?

ecandino,

Make sure you are doing a POST and check your content-type header. To debug further I’d need to see any stack trace that’s showing up in your Alfresco server log.

You might also be interested in my “Getting Started with CMIS Tutorial“. And, depending on what you need to do, you might want to take a look at the PHP CMIS client Richard McKnight wrote and recently contributed to Apache Chemistry.

Hope that helps,

Jeff

hi,
to refresh surf webscripts in Alfresco 3.3 use (add accept-charset & -language headers):
curl -s -d “reset=on” –header “Accept-Charset:ISO-8859-1,utf-8″ –header “Accept-Language:en” -u admin:admin http://localhost:8080/share/service/index

cheers, jan

PS: thanks to mike for his tweet

Leave a response

Your response:

Categories