跳转到内容

XQuery/REST 接口定义

来自维基教科书,开放的书籍,开放的世界

REST 接口,特别是用于调用服务的 URL 语言,没有等同于 SOAP 的 WSDL。这个例子探讨了创建用于定义此类接口的简单 XML 模式,以及使用 XQuery 脚本根据接口定义创建通用的网站接口。

示例 REST 定义

[编辑 | 编辑源代码]

以下是使用自制模式对 del.icio.us 接口进行的部分定义。参数使用每个参数的唯一本地名称定义,然后接口支持的服务使用模板定义,大括号用于分隔参数的名称,以便用其实际值替换。

<?xml version="1.0" encoding="UTF-8"?>
<interface>
    <name>del.icio.us</name>
    <description><p>An almost complete description of the REST interface of the del.icio.us social
    bookmark site, excluding services requiring login</p>
        <p> Chris Wallace May 2009</p></description>
    <endpoint>http://del.icio.us/</endpoint>
    <parameters>
        <parameter>
            <name>user-id</name>
            <purpose>User Identifier</purpose>
            <default>morelysq</default>
            <tag>model</tag>
        </parameter>
        <parameter>
            <name>tag</name>
            <purpose>a group of bookmarks</purpose>
            <default>xml</default>
            <tag>model</tag>
        </parameter>
        <parameter>
            <name>url</name>
            <purpose>bookmark</purpose>
            <default>http://xml.com/</default>
            <tag>model</tag>
        </parameter>
        <parameter>
            <name>tagview</name>
            <purpose>the tag list appearance</purpose>
            <options>
                <option>list</option>
                <option>cloud</option>
            </options>
            <default>list</default>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>tagsort</name>
            <purpose>the order of tags in the tag list</purpose>
            <options>
                <option>alpha</option>
                <option>freq</option>
            </options>
            <default>list</default>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>minfreq</name>
            <purpose>the minimum frequency of a tag to appear in the tag list</purpose>
            <options>
                <option>1</option>
                <option>2</option>
                <option>5</option>
            </options>
            <default>1</default>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>bundleview</name>
            <purpose>whether bundles are shown</purpose>
            <options>
                <option>show</option>
                <option>hide</option>
            </options>
            <default>show</default>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>pageno</name>
            <purpose>the page of the bookmark list</purpose>
            <format>[0-9]+</format>
            <default>1</default>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>count</name>
            <purpose>the number of bookmarks to shown per page</purpose>
            <options>
                <option>10</option>
                <option>25</option>
                <option>50</option>
                <option>100</option>
            </options>
            <default>10</default>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>search</name>
            <purpose>search string</purpose>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>scope</name>
            <purpose>search scope</purpose>
            <options>
                <option>user</option>
                <option>all</option>
                <option>web</option>
            </options>
            <default>all</default>
            <tag>ui</tag>
        </parameter>
        <parameter>
            <name>helptopic</name>
            <purpose>a page of the help manual</purpose>
            <default>urlhistory</default>
            <tag>help</tag>
        </parameter>
    </parameters>
    <services>
        <service>
            <template/>
            <purpose>Home Page</purpose>
            <tag>home</tag>
        </service>
        <service>
            <template>{user-id}</template>
            <purpose>View a user's public bookmarks</purpose>
            <tag>user</tag>
        </service>
        <service>
            <template>{user-id}?settagview={tagview}&amp;settagsort={tagsort}&amp;setminfreq={minfreq}&amp;setbundleview={bundleview}&amp;page={pageno}&amp;setcount={count}</template>
            <purpose>View a user's public bookmarks, controlling its appearance</purpose>
            <tag>user</tag>
        </service>
        <service>
            <template>rss/{user-id}</template>
            <purpose>Get an RSS feed of a user's bookmarks - limited to the latest 20 items</purpose>
            <tag>user</tag>
            <tag>RSS</tag>
        </service>
        <service>
            <template>{user-id}/{tag}</template>
            <purpose>View a user's bookmarks by tag</purpose>
            <tag>user</tag>
            <tag>tag</tag>
        </service>
        <service>
            <template>tag/{tag}</template>
            <purpose>View tagged bookmarks</purpose>
            <tag>tag</tag>
        </service>
        <service>
            <template>network/{user-id}</template>
            <purpose>View a user's network and their tags</purpose>
            <tag>user</tag>
            <tag>network</tag>
        </service>
        <service>
            <template>subscriptions/{user-id}</template>
            <purpose>View a user's subscriptions - i.e.watched bookmarks</purpose>
            <tag>user</tag>
            <tag>subscriptions</tag>
        </service>
        <service>
            <template>for/{user-id}</template>
            <purpose>View links suggested to a user</purpose>
            <tag>user</tag>
            <tag>links</tag>
        </service>
        <service>
            <template>rss/tag/{tag}</template>
            <purpose>Get an RSS feed of tagged bookmarks</purpose>
            <tag>tag</tag>
            <tag>RSS</tag>
        </service>
        <service>
            <template>popular/{tag}</template>
            <purpose>View popular tagged bookmarks</purpose>
            <tag>tag</tag>
        </service>
        <service>
            <template>popular/</template>
            <purpose>View today's popular bookmarks</purpose>
            <tag>current</tag>
        </service>
        <service>
            <template>popular/?new</template>
            <purpose>View today's new popular bookmarks</purpose>
            <tag>current</tag>
        </service>
        <service>
            <template>url?url={url}</template>
            <purpose>View Bookmarks for a URL</purpose>
            <tag>url</tag>
        </service>
        <service>
            <template>help/</template>
            <purpose>Help index</purpose>
            <tag>help</tag>
        </service>
        <service>
            <template>help/{helptopic}</template>
            <purpose>View a page of the help manual</purpose>
            <tag>help</tag>
        </service>
        <service>
            <template>search/?fr=del_icio_us&amp;p={search}&amp;searchtype={scope}</template>
            <purpose>Search for string in different scopes</purpose>
            <tag>search</tag>
        </service>
        <service>
            <template>rss/</template>
            <purpose>RSS hotlist feed</purpose>
            <tag>current</tag>
            <tag>RSS</tag>
        </service>
        <service>
            <template>rss/tag/{tag}</template>
            <purpose>RSS feed for a tag</purpose>
            <tag>tag</tag>
            <tag>RSS</tag>
        </service>
        <service>
            <template>html/{user-id}/</template>
            <purpose>Get a contolled HTML extract of a user's tag</purpose>
            <tag>user</tag>
            <tag>html</tag>
        </service>
    </services>
</interface>

生成接口

[编辑 | 编辑源代码]

XQuery 脚本接受一个名为 uri 的参数,即 XML 接口描述的 uri。该脚本根据此定义创建通用接口,在表单中的值更改并刷新表单时重新生成服务 URL。

del.icio.us 接口

declare namespace rest  = "http://ww.cems.uwe.ac.uk/xmlwiki/rest";


(: declare global variables :)

declare variable $uri := request:get-parameter("_uri",());
declare variable $index := request:get-parameter("_index","tag");
declare variable $interface :=  doc(concat($uri,"?r=",math:random()))/interface;

declare function rest:template-parameters($template as xs:string) as xs:string* {
(: parse the template to get the parameters :) 
   distinct-values(
    for $p in  subsequence(tokenize($template,"\{"),2)
    return substring-before($p,"}")
   )
};

declare function rest:parameter-value($name as xs:string)  as xs:string? {
  let $parameter := $interface/parameters/parameter[name=$name]
  return  (request:get-parameter($name,$parameter/default),"")[1]
};

declare function rest:replace-template-parameters($template as xs:string, $names as xs:string* ) as xs:string {
(: recursively replace the tempate paramters by their current values :)
  if (empty($names))
  then $template
  else 
       let $name := $names[1]
       let $value := rest:parameter-value($name)
       let $templatex :=
           if (exists($value))
           then replace($template, concat("\{",$name,"\}"),$value)
           else $template
       return  rest:replace-template-parameters( $templatex,subsequence($names,2))
};

(:  interface generation  :)

declare function rest:parameter-input-field( $parameter as element(parameter) ) as element(span)? {
(: create a  parameter field in the  parameter input form :) 
        let $name := $parameter/name
        let $value := rest:parameter-value($name)
        return        
              <span class="input">
                    <label for="{$name}">             
                    {if ($index = "parameter")    (: if  it the index is by parameter, generate a link to that part of the index :)
                    then <a href="#{$name}">  {string($name)}</a>
                    else  $name
                    }
                    </label>
                    {if ($parameter/options)
                      then   
                          <select name="{$name}" title="{$parameter/purpose}" > 
                                 {for $option in $parameter/options/option
                                 return
                                        <option value="{$option}"  title="{$option/@label}">
                                            { if ($option= $value)
                                               then  attribute selected {"true"}
                                               else ()
                                            }
                                            {string($option)} 
                                       </option>    
                                 }
                           </select>
                      else 
                        <input type="text" name="{$name}" title="{$parameter/purpose}"  value="{$value}"  size="{string-length($value)+1}"/>
                   }
             </span>
};

declare function rest:parameter-form() {
     <form method="post"  action="interface.xq">
            <div class="subhead"> interface
                        <div class="group">
                          <label for="_uri" > uri </label> 
                          <input type="text" name="_uri" value="{$uri}" size="80"/>                     
                      </div>
           </div>
        {for $tag in distinct-values($interface/parameters/parameter/tag)
        return
         <div>
            <div class="subhead">{$tag} </div>
            <div class="group">
             { for $parameter in $interface/parameters/parameter[tag=$tag]
               return
                  rest:parameter-input-field($parameter)
             }
             </div>
          </div>
       }
      <hr/>
      Index services by  <select name="_index">
         {for $index in ("parameter","tag")
          return
                if ($index = request:get-parameter("index","tag"))
                then <option value="{$index}" selected="true"> {$index} </option>
                else <option value="{$index}" > {$index}  </option>
          }                
      </select>
      <hr/><input type="submit" value="refresh"/>
    </form>
};



declare function rest:service-link ($service as element(service) )as element(tr) {
  <div>
      <div class="label">{string($service/purpose)}</div>
       {
        let $names := rest:template-parameters($service/template)
        let $filledTemplate := rest:replace-template-parameters($service/template,$names)
        let $uri := 
            if (starts-with($service/template,"http://"))
            then  $filledTemplate
            else concat($interface/endpoint,$filledTemplate)
        return 
           <div class="link"><a href="{$uri}">../{$filledTemplate}</a> </div>
       }
   </div>
};


declare  function rest:parameter-index() {
<div id="index"> 
    <h2>Parameter index </h2>
     {for $parameter in $interface/parameters/parameter
       let $name := $parameter/name
       let $match := concat("{",$name,"}")
       order by lower-case($name)
       return 
          <div>
            <div class="subhead"><a name="{$name}">{string($name)} </a> </div>
            <div class="group">  
                      {for $service in $interface//service[contains(template,$match)]
                        return  rest:service-link($service)
                      }
              </div>
          </div>
      }
</div>
};

declare function rest:tag-index()  {
<div id="index">
    <h2>Tag index </h2>
      {for $tag in distinct-values($interface//service/tag)
       order by lower-case($tag)
       return 
         <div>
            <div class="subhead" >{$tag}</div>
            <div class="group">
                    {for $service in $interface//service[tag=$tag]
                     return   rest:service-link($service)
                    }
            </div>
       </div>
      } 
 </div>
};

declare option exist:serialize "method=xhtml media-type=text/html omit-xml-declaration=no indent=yes 
        doctype-public=-//W3C//DTD&#160;XHTML&#160;1.0&#160;Transitional//EN
        doctype-system=http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";
        

<html>
  <head>
       <title>Infterface for {string($interface/name)}</title>
      <link rel="stylesheet" type="text/css" href="screen.css" />
  </head>
  <body>
        <h1>{string($interface/name)} interface</h1>
          {$interface/description/(text(),*)}  
          <div id="parameters">            
             {rest:parameter-form()}
         </div>
    {if (exists($interface))
      then 
       <div id="services">
               <h2> Interface properties </h2>
                    <div class="group">
                          <div class="label">Interface definition 
                             <div class="link"><a href="{$uri}">{$uri} </a> </div>
                          </div>
                           <div class="label">Service endpoint 
                              <div class="link"><a href="{$interface/endpoint}"> {string($interface/endpoint)} </a> </div>
                            </div>                        
                    </div>
             {if ($index = "parameter")
              then  rest:parameter-index()
              else if ($index= "tag")
              then rest:tag-index()
              else ()
              }
         </div>
       else ()
   }
 </body>
</html>

该脚本使用常见的层状架构,其中低级函数对基本数据模型进行操作,这些函数又用于生成用户界面的函数。最后,生成的 XHTML 中的类和 id 钩子与 CSS 链接起来以设置页面样式。确定使用多少层以及层如何交互是 XQuery 应用程序中的一个核心设计决策,就像其他技术一样。有几个替代方案值得考虑:脚本生成一个中间 XML 结构,该结构在服务器端或客户端使用 XSLT 进行转换;脚本生成一个 XForm 代替 HTML 表单;整个任务由客户端的 JavaScript 处理;客户端 AJAX 与基本 XQuery 脚本交互。处理此设计空间是 Web 开发的挑战之一。

缓存清除

[编辑 | 编辑源代码]

对于在代理服务器内运行的脚本,由于这些脚本位于 UWE 服务器上,因此多次访问 doc() 函数中的同一 URL 将返回缓存的文件。为了打破缓存,会在 URL 中添加一个随机数。

全局变量

[编辑 | 编辑源代码]

该脚本使用变量声明来定义脚本函数中使用的一些全局变量。全局变量感觉像是回到了 Fortran COMMON 和类似的恐怖,除了这些变量一旦定义就是常量。尽管如此,对这些变量的依赖关系并不显式。另一种方法是显式地将这些数据通过函数传递下去。使用此样式的替代脚本,将构成数据的单个节点传递到单个“对象”中,执行速度慢了几倍,更冗长,而且可以说没有更易理解。

在模板中替换多个参数是一个递归函数,它依次在整个模板中依次替换每个参数。

其他接口

[编辑 | 编辑源代码]
华夏公益教科书