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.

33 comments

  1. Rich Liu says:

    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?

  2. Rich Liu says:

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

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

  3. jpotts says:

    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

  4. 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)

  5. Bruno Navert says:

    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.

  6. jpotts says:

    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

  7. Andrea says:

    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.

  8. jpotts says:

    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

  9. Guy Roberts says:

    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.

  10. Krista says:

    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

  11. jpotts says:

    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

  12. Krista says:

    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

  13. jpotts says:

    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

  14. Bill says:

    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.

  15. jpotts says:

    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

  16. ecandino says:

    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?

  17. jpotts says:

    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

  18. Jan Pfitzner says:

    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

  19. encorearthur says:

    Jeff,

    First thanks for your book and tutorials !

    I’m using Alfresco from a PHP web app.

    I found the php cmis client really helpfull … except for checkouts and checkins …

    Since it’s not implemented yet, i decided to use libcurl to use the checkout/in CMIS webscripts that are native in Alfresco.

    I’m using the “getting started with CMIS tutorial” to do my first libcurl checkin … and i can’t get it to work.

    There’s also something sad : as i don’t know any other way, i write a file with my xmlatom string to send it via curl.

    // Code ATOM pour réaliser le checkout
    $checkOutCode = '

    workspace://SpacesStore/f06f7a69-f700-4f82-8414-8609ec532f7d

    ';

    $filePath = '/var/www/html/cmis_alfresco/tototemp.atom.xml';

    $temp = fopen($filePath, wb);
    fwrite( $temp, $checkOutCode, strlen($checkOutCode) );
    fclose($temp);

    $ch = curl_init();
    $url_search = "http://localhost:8080/alfresco/service/cmis/checkedout?alf_ticket=".$ticket;
    curl_setopt($ch, CURLOPT_URL, $url_search);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, array ("Content-Type: application/atom+xml;type=entry;charset=UTF-8") );

    curl_setopt($ch, CURLOPT_POSTFIELDS, array ( 'file'=> '@'.$filePath ) );

    // grab URL and pass it to the browser
    $toto = curl_exec($ch);
    print_r($toto);

    The answer i got this ways is :
    […]
    08230257 Wrapped Exception (with status template): com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character ‘-‘ (code 45) in prolog; expected ‘<' at [row,col {unknown-source}]: [1,1]
    […]

    AND for some reason, i tried to change the content type this way : "Content-Type: application/atom+xml;type=atomentry;charset=UTF-8"

    And with this type, i'm receiving this message :
    […]
    08230258 Cannot locate template processor for template org/alfresco/cmis/checkedout.post.atomentry
    […]

    Any idea what's wrong ?

    Thanks anyway!

    Arthur

  20. jpotts says:

    What version of specific edition and version of Alfresco are you running and have you made any customizations or extensions other than your PHP app?

    Jeff

  21. encorearthur says:

    Installed Schema 4113
    Installed Version 3.4.0 (d 3370)
    Server Schema 4113
    Server Version 3.4.0 (d 3370)

    I got to work list files, upload (new files), delete files … with the php-cmis-client.

    I don’t think there are any customizations on the server.

    i only installed a few webscripts present in the “opsoro” project.

    The only extension i got in the wescript extension dir is the hello wold script ; )

    Arthur

  22. encorearthur says:

    oops, here is the xml checkout code file :


    $checkOutCode = '

    workspace://SpacesStore/f06f7a69-f700-4f82-8414-8609ec532f7d

    ';

  23. encorearthur says:

    Jeff,

    Sorry to come again, but i still can’t find a solution for the checkout via php curl !

    I’ve just tried POSTing a file via a firefox plugin to the address :
    http://locahost:8080/alfresco/service/cmis/checkedout?alf_ticket=TICKET_02c1adf8da224c548a0728c699092205a7fbb32c

    Here is a copy of the XML file (i replace the tag char by [] so that it would display) :
    [code]
    [?xml version=”1.0″ encoding=”utf-8″?]
    [atom:entry xmlns=”http://www.w3.org/2005/Atom” xmlns:cmisra=”http://docs.oasis-open.org/ns/cmis/restatom/200908/” xmlns:cmis=”http://docs.oasis-open.org/ns/cmis/core/200908/”]
    [cmisra:object]
    [cmis:properties]
    [cmis:propertyId propertyDefinitionId=”cmis:objectId”]
    [cmis:value]
    workspace://SpacesStore/f06f7a69-f700-4f82-8414-8609ec532f7d
    [/cmis:value]
    [/cmis:propertyId]
    [/cmis:properties]
    [/cmisra:object]
    [/atom:entry]
    [/code]

    And the error returned is still one that i already had :

    500 Description: An error inside the HTTP server which prevented it from fulfilling the request.

    08280272 Cannot locate template processor for template org/alfresco/cmis/checkedout.post.atomentry

    Any idea is welcome,

    Arthur

  24. jpotts says:

    To check out a document via CMIS, you post an atom entry for it to the “checkedout” collection. I know it works. What’s tricky for people new to the API who are rolling their own XML and making posts instead of using a client library is the syntax of the XML that gets posted. I can’t do it right this second, but when I get a chance I’ll respond to the forum post you pointed me to.

    Until then, maybe the checkedout collection hint will help you.

    Jeff

  25. jpotts says:

    Sorry it took me so long to get back to you on this. Hopefully you figured it out but just in case, to do a checkout on a document via the restful ATOM PUB binding, you simply post an entry to the checkedout collection. It looks like you already have the checkedout collection URL correct and your Atom entry looks fine. Are you setting the mimetype appropriately? It should be ‘application/atom+xml;type=entry’. I notice that when I do not include ‘type=entry’ I get the same template processor error you are reporting.

    Jeff

  26. phileas says:

    It’s an old article but it help me a lot for develloping a web interface with php curl and a lot of jquery.
    I have two things about creating an objet in alfresco:
    1 – If I try to create a text document and if I encode it in base64 as you says in your article with base64_encode from php, the webscript work but when I download it from the repository I have a base64 encode string and not the original document text.
    So I have to test the mimetype and If I found the term “text” in it , I pass the content by the fonction htmlentities in php and If It’s not I use the function base64_encode. And this work fine.
    2- About mimetype I’ved notice that if the term “xml” is present in the content type attribute(text/xml,image/svg+xml …), the web script works whitout error but there’s no content, contentStreamLength is 0 octet and i have an error when I try to download the document It don’t exist on the server. So I have to change the mimetype for exemple I replace text/xml by text/plain and image/svg+xml by image/svg. But It’s not a good solution in the first case I’ve the wrong type in the second it’s an unknown type. I must connect after to alfresco share to change the mime type, because i didn’t find how change it with curl.
    I make test too in cmd line with curl an it’s the same result
    So If you have a few minutes free for some explanations, thanks a lot ..and thanks too for all your tutorial and articles. they are THE reference of Alfresco
    I use alfresco community 5.0.a with mariadb on debian wheezy
    Thanks a lot
    Sorry for my english !!

Comments are closed.