WebDAV pseudo-PUT request code w/ YQL

My previous post presented the YQL code required to handle a WebDAV GET request for a “file” in yql storage. To update the file, we need an additional table.

Prerequisites:

  • A sherpa record w/ this value in it: {“file1″:”content 1″, “file2″, “content 2″}. The keys and values w/in the JSON can be anything. If you haven’t worked w/ YQL storage before, check out the documentation.
  • The code below edited to use your storage record’s select address

Flow:

  • The WebDAV client issues a PUT/POST request with “path” set to the key of the value to change, and “contents” set to the updated value.
  • The YQL table will extract the stored record, decode it, update the value, encode it, and store it back

Code:

<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
    <meta>
        <author>Erik Eldridge</author>
        <description>
        </description>
        <sampleQuery></sampleQuery>
    </meta>
    <bindings>
        <insert produces="XML">
            <inputs>
                <value id="path" type="xs:string" paramType="variable"/>
                <value id="contents" type="xs:string" paramType="variable"/>

            </inputs>
            <execute><![CDATA[
                var execute = 'store://{your store val}',
                    select = 'store://{your store val}',
                    update = 'store://{your store val}';
                
                // http://www.json.org/json2.js
                y.include('http://{your domain}/json2.js');
                    
                //credit: http://javascript.crockford.com/remedial.html
                if (typeof String.prototype.supplant !== 'function') {
                    String.prototype.supplant = function (o) { 
                        return this.replace(/{([^{}]*)}/g, 
                            function (a, b) {  
                                var r = o[b];
                                return typeof r === 'string' ? r : a; 
                            }); 
                    };
                }
                
                response.object = function () {

                    //put queries and results in arrays so we can reuse the var w/o overwriting values
                    var queries = [],
                        results = [];

                    queries[0] = 'select * from yql.storage where name="{select}"'.supplant({'select':select}),
                    results[0] = y.xmlToJson( y.query( queries[0] ).results );
                    
                    if (results[0].results.result.value[path]) {
                        
                        results[0].results.result.value[path] = contents;
                        
                        queries[1] = "update yql.storage set value='{json}' where name='{update}'"
                            .supplant({
                                'json' : JSON.stringify(results[0].results.result.value),
                                'update' : update
                            });
                        results[1] = y.xmlToJson( y.query( queries[1] ).results );

                        return {
                           "headers" : {
                               "HTTP/1.1 status" : "204",
                               "Date" : new Date().getTime(),
                               "Location" : "/webdav/" + path,
                               "Content-Length" : contents.length,
                               "Connection" : "close",
                               "Content-Type" : "text/plain; charset=UTF-8"
                           }
                        }
                    }

                    results[0].results.result.value[path] = contents;

                    queries[1] = "update yql.storage set value='{json}' where name='{update}'"
                        .supplant({
                            'json' : JSON.stringify(results[0].results.result.value),
                            'update' : update
                        });
                    results[1] = y.xmlToJson( y.query( queries[1] ).results );

                    return {
                        headers : {
                            "HTTP/1.1 status" : "201",
                            "Date" : new Date().getTime(),
                            "Location" : '/webdav/' + path,
                            "Content-Length" : contents.length,
                            "Connection" : "close",
                            "Content-Type" : "text/plain; charset=UTF-8"
                        }
                    };
                }();
            ]]></execute>
        </insert>
    </bindings>
</table>

Notes:

  • We could use some ruby like this in a rack app to intermediate between YQL and the WebDAV client:
        request = Rack::Request.new(env)
        
        contents = '';
        while part = request.body.read(8192)
          contents += part
        end
        
        query = "use '{the uri for the YQL table file using the code above}/put.xml' as table;"+
          "insert into table (path, contents) values ('#{file}', '#{contents}')"
        host = 'http://query.yahooapis.com'
        path = '/v1/public/yql'
        
        response = Net::HTTP.post_form( URI.parse( "#{host}#{path}" ), {
          'q' => query,
          'debug' => true
        } )
        doc = REXML::Document.new(response.body)
        headers = REXML::XPath.first( doc, "///headers" )
        # you could dump out the headers here
        # p(headers[0].text)
        
        # return an empty success response
        [204, {}, '']

running a WebDav GET request against YQL

This builds off my previous post. Suppose you’ve got content in YQL that you’d like to GET (ha!) out. The table is super simple.  Ok, this is really just an unexciting GET request to YQL, but it’s cool because we’re starting to think of YQL as a file store accessible via WebDAV methods.

Prerequisites

  • A sherpa record w/ this value in it: {“file1″:”content 1”, “file2”, “content 2”}.  The keys and values w/in the JSON can be anything.  If you haven’t worked w/ YQL storage before, check out the documentation.
  • The code below edited to use your storage record’s select address

Flow

  1. You make a GET request to YQL w/ a query param path set the the value of one of your keys in the JSON object described above, eg path=’file1′
  2. YQL retrieves the storage record, converts it to JSON, and returns the value associated w/ the path you sent

Code

<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
    <meta>
        <author>Erik Eldridge</author>
        <description>
        </description>
        <sampleQuery></sampleQuery>
    </meta>
    <bindings>
        <select produces="XML">
            <inputs>
                <key id="path" type="xs:string" paramType="variable"/>
            </inputs>
            <execute><![CDATA[
                response.object = function () {

                    //fetch 'files'
                    var query = 'select * from yql.storage where name="store://{select store id}"',
                        results = y.xmlToJson(y.query(query).results);

                    return results.results.result.value[path];
                }();
            ]]></execute>
        </select>
    </bindings>
</table>

Notes

  • If you wanted to use a WebDAV client w/ this output, you could run something like this Ruby code in a Rack app, and point your WebDAV client at it:
    file = 'file1'
    query = "use 'http://example.com/get.xml' as table; select * from table where path='#{file}'"
    host = 'http://query.yahooapis.com'
    path = '/v1/public/yql'
    q = Rack::Utils.escape(query)
    
    # setting debug to true turns off YQL's caching, which is good when testing
    uri = "#{host}#{path}?q=#{q}&debug=true"
    
    res = Net::HTTP.get_response( URI.parse(uri) )
    doc = REXML::Document.new(res.body)
    
    # extract the 'results' element
    result = REXML::XPath.first( doc, "//results" )
    
    # return the flattened xml
    [200, {"Content-Type" => "application/xml"}, '<?xml version="1.0" encoding="utf-8"?>' + result.elements[1].to_s]
    

generating webdav propfind xml from yql

E4X support makes YQL is a great XML-generation engine. Here’s some code to create the response xml for a WebDAV PROPFIND request for a directory called webdav containing an empty file called foo.txt.

Note: to initially get a handle on what XML WebDAV outputs, I turned on WebDAV support in apache and made a curl request to it like this:
curl -X PROPFIND –header “Depth:1” {user}:{pass}@{your ip address}/webdav/

You can run the code below in the YQL console.

<?xml version="1.0" encoding="UTF-8"?>
<table xmlns="http://query.yahooapis.com/v1/schema/table.xsd">
    <meta>
        <author>Erik Eldridge</author>
        <description>
        </description>
        <sampleQuery></sampleQuery>
    </meta>
    <bindings>
        <select produces="XML">
            <inputs>
                <key id="method" type="xs:string" paramType="variable"/>
                <key id="path" type="xs:string" paramType="variable"/>
            </inputs>
            <execute><![CDATA[
                response.object = function () {
                    var xml = <D:multistatus xmlns:D="DAV:">
                        <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
                           <D:href>/webdav/</D:href>
                           <D:propstat>
                              <D:prop>
                                 <lp1:resourcetype>
                                    <D:collection/>
                                 </lp1:resourcetype>
                                 <lp1:creationdate>2010-01-02T19:43:01Z</lp1:creationdate>
                                 <lp1:getlastmodified>Sat, 02 Jan 2010 19:43:01 GMT</lp1:getlastmodified>
                                 <lp1:getetag>"19013d-1000-b2283b40"</lp1:getetag>
                                 <D:supportedlock>
                                    <D:lockentry>
                                       <D:lockscope>
                                          <D:exclusive/>
                                       </D:lockscope>
                                       <D:locktype>
                                          <D:write/>
                                       </D:locktype>
                                    </D:lockentry>
                                    <D:lockentry>
                                       <D:lockscope>
                                          <D:shared/>
                                       </D:lockscope>
                                       <D:locktype>
                                          <D:write/>
                                       </D:locktype>
                                    </D:lockentry>
                                 </D:supportedlock>
                                 <D:lockdiscovery/>
                                 <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
                              </D:prop>
                              <D:status>HTTP/1.1 200 OK</D:status>
                           </D:propstat>
                        </D:response>
                        <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
                           <D:href>/webdav/foo.txt</D:href>
                           <D:propstat>
                              <D:prop>
                                 <lp1:resourcetype/>
                                 <lp1:creationdate>2010-01-02T19:43:01Z</lp1:creationdate>
                                 <lp1:getcontentlength>0</lp1:getcontentlength>
                                 <lp1:getlastmodified>Sat, 02 Jan 2010 19:43:01 GMT</lp1:getlastmodified>
                                 <lp1:getetag>"19013f-0-b2283b40"</lp1:getetag>
                                 <lp2:executable>F</lp2:executable>
                                 <D:supportedlock>
                                    <D:lockentry>
                                       <D:lockscope>
                                          <D:exclusive/>
                                       </D:lockscope>
                                       <D:locktype>
                                          <D:write/>
                                       </D:locktype>
                                    </D:lockentry>
                                    <D:lockentry>
                                       <D:lockscope>
                                          <D:shared/>
                                       </D:lockscope>
                                       <D:locktype>
                                          <D:write/>
                                       </D:locktype>
                                    </D:lockentry>
                                 </D:supportedlock>
                                 <D:lockdiscovery/>
                                 <D:getcontenttype>text/plain</D:getcontenttype>
                              </D:prop>
                              <D:status>HTTP/1.1 200 OK</D:status>
                           </D:propstat>
                        </D:response>
                    </D:multistatus>;
                    return xml;
                }();
            ]]></execute>
        </select>
    </bindings>
</table>