HTTP Client Samples

Machine

Here are several examples of use of the HTTP Client extension. They are either in XSLT or XQuery. XQuery samples have been written for and tested with eXist, MarkLogic Server and Saxon 9. XSLT samples have been written for and tested with Saxon 9. Details on how to use the extension with either product can be found on the implementations page (especially details on namespace URIs and how to import and/or install modules may vary.)

You can also have a look at the presentation I gave at XML Prague 2009 about this extension among other things (there are also the slides, the video and other related info on my website.) The presentation was about EXSLT 2.0, but the extension presented there is the same.

Besides the samples here, there are a WSDL compiler (compiling WSDL files to XSLT and XQuery modules providing functions for each operation, using this HTTP Client) and a library of XSLT and XQuery modules to support the Google GData protocol, based on REST, and providing convenient functions for several Google services (based on GDAta or not.) The WSDL compiler has not been released yet, but the Google APIs library can be found in the project xlibs.

This page presents the following examples:

Accessing the eXist REST API

The eXist XML Database provides a REST API, accessible through a URI of the form: http://localhost:8080/exist/rest/db/coll/doc.xml. Among other things, one can retrieve a document by sending a GET request, or upload a document with a PUT request. The stylesheet hereafter retrieves the following document from eXist (document /db/tmp/in.xml):

<who>world</who>

then transform it to the following document:

<hello>world</hello>

and eventually put it in eXist as the document /db/tmp/out.xml. The complete stylesheet is:

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

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:http="http://expath.org/ns/http-client"
                xmlns:impl="urn:X-EXPath:httpclient:samples:exist:impl"
                exclude-result-prefixes="#all"
                version="2.0">

   <xsl:import href="http://expath.org/ns/http-client.xsl"/>

   <xsl:output indent="yes"/>

   <!-- credentials -->
   <xsl:param name="username" select="'guest'"/>
   <xsl:param name="password" select="'guest'"/>

   <!-- document to retrieve, and document to upload -->
   <xsl:param name="in"  select="'/db/tmp/in.xml'"/>
   <xsl:param name="out" select="'/db/tmp/out.xml'"/>

   <!-- URI of the REST interface of eXist instance -->
   <xsl:param name="rest" select="'http://localhost:8080/exist/rest'"/>

   <!-- main template: retrieve a doc, transform it and
        upload the result -->
   <xsl:template name="main">
      <xsl:variable name="doc">
         <xsl:apply-templates select="impl:get-in()"/>
      </xsl:variable>
      <xsl:sequence select="impl:put-out($doc)"/>
   </xsl:template>

   <!-- the transform rules -->
   <xsl:template match="who">
      <hello>
         <xsl:value-of select="."/>
      </hello>
   </xsl:template>

   <!-- retrieve the input doc -->
   <xsl:function name="impl:get-in" as="document-node()">
      <xsl:variable name="req" as="element()">
         <http:request href="{ $rest }{ $in }"
                       method="get"
                       username="{ $username }"
                       password="{ $password }"
                       auth-method="basic"
                       send-authorization="true"/>
      </xsl:variable>
      <!-- error checking is left as an exercise -->
      <xsl:sequence select="http:send-request($req)[2]"/>
   </xsl:function>

   <!-- upload the output doc -->
   <xsl:function name="impl:put-out" as="item()*">
      <xsl:param name="doc" as="document-node()"/>
      <xsl:variable name="req" as="element()">
         <http:request href="{ $rest }{ $out }"
                       method="put"
                       username="{ $username }"
                       password="{ $password }"
                       auth-method="basic"
                       send-authorization="true">
            <http:body media-type="application/xml"/>
         </http:request>
      </xsl:variable>
      <!-- error checking is left as an exercise -->
      <xsl:sequence select="http:send-request($req, (), $doc)"/>
   </xsl:function>

</xsl:stylesheet>

The main result of the stylesheet is a side effect (the output document has been uploaded to eXist) but technically the result of the stylesheet itself (its main result tree, i.e. what you get on the screen when you run it from the command line) is the result of the PUT (not really useful, but I left it there for educational purpose):

<?xml version="1.0" encoding="UTF-8"?>
<http:response xmlns:http="http://www.expath.org/mod/http-client" status="201" message="Created">
   <http:header name="Date" value="Mon, 13 Apr 2009 18:03:57 GMT"/>
   <http:header name="Server" value="Jetty/5.1.12 (Mac OS X/10.5.6 i386 java/1.5.0_16"/>
   <http:header name="Content-Length" value="0"/>
   <http:header name="Connection" value="keep-alive"/>
</http:response>

The equivalent XQuery module is as follows (this is just a simple translation of the above stylesheet into XQuery):

import module namespace http = "http://expath.org/ns/http-client";

(: credentials :)
declare variable $username := 'guest';
declare variable $password := 'guest';

(: document to retrieve, and document to upload :)
declare variable $in  := '/db/tmp/in.xml';
declare variable $out := '/db/tmp/out.xml';

(: URI of the REST interface of eXist instance :)
declare variable $rest := 'http://localhost:8080/exist/rest';

(: the transform rule :)
declare function local:transform($doc as document-node()) as document-node()
{
  document {
    <hello> {
      $doc//who/string(.)
    }
    </hello>
  }
};

(: retrieve the input doc :)
declare function local:get-in() as document-node()
{
  let $req := <http:request href="{ $rest }{ $in }"
                            method="get"
                            username="{ $username }"
                            password="{ $password }"
                            auth-method="basic"
                            send-authorization="true"/>
    return
      (: error checking is left as an exercise :)
      http:send-request($req)[2]
};

(: upload the output doc :)
declare function local:put-out($doc as document-node()) as item()*
{
  let $req := <http:request href="{ $rest }{ $out }"
                            method="put"
                            username="{ $username }"
                            password="{ $password }"
                            auth-method="basic"
                            send-authorization="true">
                 <http:body media-type="application/xml"/>
              </http:request>
    return
      (: error checking is left as an exercise :)
      http:send-request($req, (), $doc)
};

(: retrieve a doc, transform it and upload the result :)
let $doc         := local:get-in()
let $transformed := local:transform($doc)
  return
    local:put-out($transformed)

Google Contacts API

This sample uses the Google APIs to access your contact information, aka your address book, on your GMail or Google Apps account.

The Google APIs provide a simple REST API: you just need to send an HTTP POST request with parameters encoded in application/x-www-form-urlencoded (that means the request body looks like: param1=value1&param2=value2, with a bit of escaping). You first need to use the Authentication API to get an authentication token, that you'll pass to every call of other APIs. Then you can use the Contact API to get the data of all your contacts, then a second call to get the data of all the groups your contacts belong to.

Before showing the whole stylesheet, here are what the three request should look like (more exactly what the elements representing the three HTTP request should look like). Here is the authentication call (indented for readability, but there shouldn't be any carriage return):

<http-request method="post" mime-type="application/x-www-form-urlencoded">
   <body>Email=your.email%40gmail.com&amp;Passwd=xxx&amp;service=cp
       &amp;source=fgeorges.org-contacts-1&amp;accountType=GOOGLE</body>
</http-request>

The get feed call (for either contacts or groups, but the endpoint URI is different in both cases):

<http-request method="get">
   <header name="Authorization">GoogleLogin auth=xxx</header>
</http-request>

Finally, this is the whole stylesheet. Run it by applying it to any XML document or with the initial template main, and setting both parameters account and pwd.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:http="http://expath.org/ns/http-client"
                xmlns:goog="http://fgeorges.org/ns/test/google"
                exclude-result-prefixes="#all"
                version="2.0">

   <!--
       Google information about authentication and contacts API:
       http://code.google.com/apis/accounts/docs/AuthForInstalledApps.html
       http://code.google.com/apis/contacts/developers_guide_protocol.html
   -->

   <xsl:import href="http://expath.org/ns/http-client.xsl"/>

   <xsl:output indent="yes"/>

   <!-- The account to use (the address email) -->
   <xsl:param name="account" as="xs:string" required="yes"/>
   <!-- The associated password, required -->
   <xsl:param name="pwd"     as="xs:string" required="yes"/>

   <!--
       Utility: check for error in HTTP response.
   -->
   <xsl:function name="goog:check-error">
      <!-- the HTTP response element -->
      <xsl:param name="response" as="element(http:response)"/>
      <!-- message in case of error -->
      <xsl:param name="message" as="xs:string"/>
      <xsl:variable name="status" select="xs:integer($response/@status)"/>
      <xsl:if test="not($status ge 200 or $status le 299)">
         <xsl:sequence select="
             error((), concat($message, ': ', $response/@message))"/>
      </xsl:if>
   </xsl:function>

   <!--
       The authentication parameters, as simple param elements.
   -->
   <xsl:function name="goog:auth-params" as="element(param)+">
      <!-- the email (user account) -->
      <xsl:param name="email" as="xs:string"/>
      <!-- the password -->
      <xsl:param name="pwd" as="xs:string"/>
      <!-- $email can be abbreviated if @gmail.com -->
      <xsl:variable name="full-email" select="
          if ( contains($email, '@') ) then
            $email
          else
            concat($email, '@gmail.com')"/>
      <!-- the param elements -->
      <param name="Email">
         <xsl:value-of select="$full-email"/>
      </param>
      <param name="Passwd">
         <xsl:value-of select="$pwd"/>
      </param>
      <param name="source">fgeorges.org-contacts-1</param>
      <param name="service">cp</param>
      <param name="accountType">
         <xsl:value-of select="
             if ( ends-with($full-email, '@gmail.com') ) then
               'GOOGLE'
             else
               'HOSTED_OR_GOOGLE'"/>
      </param>
   </xsl:function>

   <!--
       Authenticates to the Google server, and returns the
       authentication token.
   -->
   <xsl:function name="goog:auth-token" as="xs:string">
      <!-- the email (user account) -->
      <xsl:param name="email" as="xs:string"/>
      <!-- the password -->
      <xsl:param name="pwd" as="xs:string"/>
      <!-- the endpoint -->
      <xsl:variable name="endpoint" as="xs:string" select="
          'https://www.google.com/accounts/ClientLogin'"/>
      <!-- the http request element -->
      <xsl:variable name="request" as="element()">
         <http:request method="post" href="{ $endpoint }">
            <http:body media-type="application/x-www-form-urlencoded">
               <xsl:for-each select="goog:auth-params($email, $pwd)">
                  <xsl:value-of select="@name"/>
                  <xsl:text>=</xsl:text>
                  <xsl:value-of select="encode-for-uri(.)"/>
                  <xsl:if test="position() ne last()">
                     <xsl:text>&amp;</xsl:text>
                  </xsl:if>
               </xsl:for-each>
            </http:body>
         </http:request>
      </xsl:variable>
      <!-- send the request and get the response -->
      <xsl:variable name="response" select="http:send-request($request)"/>
      <!-- was the request ok? -->
      <xsl:sequence select="goog:check-error($response[1], 'Error while login')"/>
      <!-- get the auth token in the response -->
      <xsl:sequence select="
          substring-after(
            tokenize($response[2], '&#10;')
              [substring-before(., '=') eq 'Auth'],
            '=')"/>
   </xsl:function>

   <!--
       Get a simple feed content.
       
       Send to the right endpoint (regarding the feed), a simple HTTP
       GET, with the right HTTP header for authorization (defined by
       Google).  Then check the response, and if everything was ok,
       parse the XML result.
   -->
   <xsl:function name="goog:get-feed" as="element()">
      <!-- the authentication token -->
      <xsl:param name="auth" as="xs:string"/>
      <!-- the feed name -->
      <xsl:param name="feed" as="xs:string"/>
      <!-- the endpoint -->
      <xsl:variable name="endpoint" as="xs:string" select="
          concat('https://www.google.com/m8/feeds/',
                 $feed,
                 '/default/full?max-results=1000')"/>
      <!-- the http request element -->
      <xsl:variable name="request" as="element()">
         <http:request method="get" href="{ $endpoint }">
            <http:header name="Authorization" value="GoogleLogin auth={ $auth }"/>
         </http:request>
      </xsl:variable>
      <!-- send the request and get the response -->
      <xsl:variable name="response" select="http:send-request($request)"/>
      <!-- was the request ok? -->
      <xsl:sequence select="goog:check-error($response[1], 'Error while getting groups')"/>
      <!-- get the response as an xml element -->
      <xsl:sequence select="$response[2]/*"/>
   </xsl:function>

   <!--
       Main template: authenticates, then gets contacts and groups.
   -->
   <xsl:template name="main">
      <contacts-and-groups>
         <!-- the authentication token -->
         <xsl:variable name="auth" select="goog:auth-token($account, $pwd)"/>
         <!-- the contacts -->
         <xsl:sequence select="goog:get-feed($auth, 'contacts')"/>
         <!-- the groups -->
         <xsl:sequence select="goog:get-feed($auth, 'groups')"/>
      </contacts-and-groups>
   </xsl:template>

</xsl:stylesheet>

SOAP request to a Web service

Here is a complete stylesheet showing a sample of use of this extension that call a Web service by sending it a SOAP message:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
                xmlns:wsx="http://www.webservicex.net"
                xmlns:http="http://expath.org/ns/http-client"
                exclude-result-prefixes="#all"
                version="2.0">

   <xsl:import href="http://expath.org/ns/http-client.xsl"/>

   <!-- The result is text -->
   <xsl:output method="text"/>

   <!-- To serialize with saxon:serialize() -->
   <xsl:output name="default" indent="yes" omit-xml-declaration="yes"/>

   <!-- The Web service endpoint -->
   <xsl:param name="endpoint" as="xs:string" select="
       'http://www.webservicex.net/WeatherForecast.asmx'"/>

   <!-- The element representing the HTTP request (with the SOAP envelope) -->
   <xsl:variable name="request" as="element()">
      <http:request method="post">
         <http:header name="SOAPAction" value="http://www.webservicex.net/GetWeatherByPlaceName"/>
         <http:body media-type="text/xml">
            <soap:Envelope>
               <soap:Header/>
               <soap:Body>
                  <wsx:GetWeatherByPlaceName>
                     <wsx:PlaceName>NEW YORK</wsx:PlaceName>
                  </wsx:GetWeatherByPlaceName>
               </soap:Body>
            </soap:Envelope>
         </http:body>
      </http:request>
   </xsl:variable>

   <!-- The main template -->
   <xsl:template match="/" name="main">
      <!-- Send the HTTP request and get the result back -->
      <xsl:variable name="response" select="http:send-request($request, $endpoint)"/>
      <!-- Check for error in the HTTP layer -->
      <xsl:if test="$response[1]/xs:integer(@status) ne 200">
         <xsl:sequence select="
             error((), $response[1]/concat('HTTP error: ', @status, ' ', @message))"/>
      </xsl:if>
      <!-- Apply templates to the SOAP's payload -->
      <xsl:apply-templates select="$response[2]/soap:Envelope/soap:Body/*/*"/>
   </xsl:template>

   <!-- Handle the payload -->
   <xsl:template match="wsx:GetWeatherByPlaceNameResult">
      <xsl:text>Place: </xsl:text>
      <xsl:value-of select="wsx:PlaceName"/>
      <xsl:text>&#10;</xsl:text>
      <xsl:apply-templates select="wsx:Details/*"/>
   </xsl:template>

   <!-- Handle a single forecast -->
   <xsl:template match="wsx:WeatherData[*]">
      <xsl:text>  - </xsl:text>
      <xsl:value-of select="wsx:Day"/>
      <xsl:text>:&#09;</xsl:text>
      <xsl:value-of select="wsx:MinTemperatureC"/>
      <xsl:text> - </xsl:text>
      <xsl:value-of select="wsx:MaxTemperatureC"/>
      <xsl:text>&#10;</xsl:text>
   </xsl:template>

</xsl:stylesheet>

When you run the above stylesheet, you should get the following result:

Place: NEW YORK
  - Sunday, March 30, 2008:     2 - 11
  - Monday, March 31, 2008:     9 - 19
  - Tuesday, April 01, 2008:    7 - 13
  - Wednesday, April 02, 2008:  2 - 11
  - Thursday, April 03, 2008:   2 - 12
  - Friday, April 04, 2008:     6 - 13
  - Saturday, April 05, 2008:   3 - 14

This is a real, complete example that formats the result besides preparing the request, but the interesting parts are really the global variable $request and the call to http:send-request().