动态 pdf 的 eXist-db REST GET 请求 - 无法读取源文件

eXist-db REST GET request for dynamic pdf - cannot read source file

(eXist 4.4,XQuery 3.1)

我为用户提供下载请求时动态创建的 PDF 文档的能力。该请求有两个参数:文档名称(即doc=MS609-0002.pdf)和文档语言版本(即lang=EN)。

输出的函数在download.xql:

declare function download:download($node as node(), $model as map(*), $doc as xs:string, $lang as xs:string)
 { 
    ...
    return response:stream-binary($pdf,"application/pdf", $filename)
 }

无论是直接调用 IDE 还是通过 eXist HTML 模板调用该函数,它都可以正常输出 PDF,例如:

http://localhost:8081/exist/apps/deheresi/download?doc=MS609-0002.pdf&lang=EN

但是,使用HTML意味着打开另一个浏览器window。

相反,我想通过按钮请求 REST GET。我查看了 eXist REST documentation,但无法正常工作。

根据文档,我应该发出一个 GET 结构如下:

 http://localhost:8081/exist/rest/db/deheresi/download.xql?doc=MS609-0002.pdf&lang=EN

但是当提出这个请求时,我得到:

HTTP ERROR 404
Problem accessing /exist/rest/db/deheresi/download.xql. 
Reason: Document /db/deheresi/download.xql not found

/exist/rest/apps/ 的变体:http://localhost:8081/exist/rest/apps/deheresi/download.xql?doc=MS609-0002.pdf&lang=EN

Returns 带有空白树的以下消息:

This XML file does not appear to have any style information associated with it. The document tree is shown below.

以及 /exist/db/apps/ 的变体:http://localhost:8081/exist/db/apps/deheresi/download.xql?doc=MS609-0002.pdf&lang=EN

Returns:

XQueryServlet Error
Error found
Message: Cannot read source file
/Applications/eXist-db.app/Contents/Resources/eXist-db/webapp/db/apps/deheresi/download.xql

我测试了文件权限,好像没有问题。虽然可能有一个我不知道的 REST permission/configuration 要求? localhost 上的 REST 有问题吗?

编辑:这是应该处理 REST 请求的完整函数:

xquery version "3.1";

module namespace get="/db/apps/deheresi/modules/download”;
declare namespace templates="http://exist-db.org/xquery/templates";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare namespace xsl = "http://www.w3.org/1999/XSL/Transform";
import module namespace xslfo = "http://exist-db.org/xquery/xslfo";

import module namespace document="/db/apps/deheresi/modules/document" at "/db/apps/deheresi/modules/document.xql";
import module namespace document-view="/db/apps/deheresi/modules/document-view" at "/db/apps/deheresi/modules/document-view.xql";
import module namespace document-preprint="/db/apps/deheresi/modules/document-preprint" at "/db/apps/deheresi/modules/document-preprint.xql";
import module namespace document-print="/db/apps/deheresi/modules/document-print" at "/db/apps/deheresi/modules/document-print.xql";
import module namespace functx="http://www.functx.com" at "/db/apps/deheresi/modules/functx.xql";
import module namespace globalvar="/db/apps/deheresi/modules/globalvar" at "/db/apps/deheresi/modules/globalvar.xqm";


declare function download:download($doc as xs:string?, $lang as xs:string?)
{   (: parse $doc to get name of XML to transform, send back pdf with same name :)

    let $docset := upper-case(substring-before($doc,"."))

    let $filename := concat($docset,".pdf")

    let $document := doc(concat($globalvar:URIdata,concat($docset,".xml")))

    let $language := if (lower-case($lang) = "fr")
                     then lower-case($lang)
                     else "en"

    let $filename := concat($docset,".pdf")

    (: get XSLT stylesheet :)
    let $fostylesheet := document-print:single-doc-fo-stylesheet($language)

     (: get XEP FO config:)
     let $config := util:expand(doc("/db/apps/deheresi/xep.xml")/*)

     (: get xml for transformation in correct language :)
     let $xml := document-preprint:single-doc-preprint($document, $language)

     (: create FO xml :)
     let $fo := util:expand(transform:transform($xml, $fostylesheet, ()))

     (: render pdf :)
     let $pdf := xslfo:render($fo, "application/pdf", (), $config)

     return  response:stream-binary($pdf,"application/pdf", $filename)

};

注意:我已经对此悬赏,希望收到一个遍历 REST 输入和输出函数的响应,并以获取自发生成的 PDF 为例。这包括可能影响 REST 请求的任何配置/权限问题。

您的 download:download 函数是以与 eXist-db 模板一起工作的方式编写的。我建议将实际的下载逻辑抽象为单独的库模块中的单独函数。

然后您可以让 download:download 函数调用抽象的下载逻辑函数,您还可以创建一个新的主模块,例如 direct-download.xq 或任何只处理 URL 和然后调用你抽象的下载逻辑函数。

由于您声明在调用此函数时会返回 PDF:

http://localhost:8081/exist/apps/deheresi/download?doc=MS609-0002.pdf&lang=EN

也许,您应该做的是处理该响应。一个简单的例子就是在 jQuery 中使用 FileSaver.js。 (您可以 google FileSaver.js 并使用 jQuery 下载并将其包含在您的页面中):

function preview_cover(path){
    var pdffilename="cover.pdf";    
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
            saveAs(this.response, pdffilename);
        }}
    xhr.open('GET', 'cover-formatter.xq?cover=' + path + '&page_width=' + page_width + '&page_height=' + page_height);
    xhr.setRequestHeader('Authorization','Basic ' + sessinfo);
    xhr.responseType = 'blob';
    xhr.send();
}

以上示例将使用现代浏览器(Chrome、Firefox、Edge)下载 PDF。

后面的代码是这样的(我剪掉了所有其他的东西,只留下了格式化部分):

let $fo := if ($territory = 'WALES') then util:expand(transform:transform($doc, doc("/db/EIDO/data/edit/xsl/EIDOcoverbilingual.xsl"), $parameters))
else util:expand(transform:transform($doc, doc("/db/EIDO/data/edit/xsl/EIDOcover.xsl"), $parameters))
let $pdf := xslfo:render($fo, "application/pdf", (), $config)
let $headers := response:set-header("Content-Disposition", "attachment;filename=document.pdf")
return
response:stream-binary($pdf, "media-type:application/pdf","document.pdf")

下面是一个更冗长的 jQuery Javascript 代码,它试图在 Javascript 端处理响应。有几个技巧需要注意,我先提一下,以便理解。一种 hack 是 iOS 或 IE9 浏览器无法处理浏览器中的二进制下载。所以服务器端代码实际上有一个创建 PDF 的 hack,如果浏览器是 iE9 或 iOS,它将结果存储在数据库(或 AWS S3)和 returns a link 添加到该 PDF,以便可以 "clicked" 查看。如果操作正确,其他常见浏览器可以自动处理发回的二进制数据。为此,我们使用 FileSaver.js 插件 Javascript 来下载 PDF。

其他部分可以直接忽略。与向 Google Analytics 发送事件的 logEvent 一样,totformats 变量跟踪用户下载并在任何一个会话中限制他们。 Chrome 下载的 hack 可能不需要,因为那是 Chrome for Android 中的错误。添加和加载 'loader' 类 用于 GUI。 iE9,iOS解决方案使用IP作为设置的变量,这是因为数据库在许多国家被复制和负载平衡并且因为数据写入数据库是为了这次调用,我们需要IP地址具有结果的那个确切的服务器。这将随着 S3 集成而消失。

本质上关键是调用与您相同的 URL 并使用以下方法保存响应:

saveAs(this.response, pdffilename);

这是对 FileSaver.js 的调用,它负责保存来自 XHR GET 的二进制数据并为您下载。我从一个更大的代码中删除了这个,它处理所有的下载,包括像你一样从 RenderX 动态生成的下载,还有静态 PDF。

调用很简单,只是一个 GET 到 customer-formatter.xq,这在我的情况下与调用 http://localhost/customer-formatter.xq 相同(因为我删除了 /exist 和我的 post for码头是 80):

 xhr.open('GET', 'customer-formatter.xq?masterlang=' + masterlang + '&doclang=' + doclang + '&specialty='+ specialty + '&article=' + docnum + '&user_name=' + loggedInUser + '&territory=' + territory + '&expiry=' + expiry + '&page_width=' + page_width + '&page_height=' + page_height + '&column_count=' + column_count + '&phrasechange=' + phrasechange + '&genlink=' + genlink + '&access=' + access + '&scalefont=' + scalefont + '&skin=' + skin + '&watermark=' + watermarkmsg +'&timestamp=' + timestamp);


        totformats++;
        if (totformats > maxformats)
            window.location.href = '/user?logout=logout';
        var docfilename = ((doclang) ? doclang : '') + ((doctype) ? doctype : '');
        var pdffilename = docnum + '-' + docfilename + '.pdf';    
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
            if (this.readyState == 4 && this.status == 200){
                // Do IE9 stuff or iPhone/iPad
                if (version == 9) {
                    var ip = this.responseText;
                    var a = document.createElement("a");
                    a.style = "cursor: pointer;";
                    document.body.appendChild(a);
                    var url = 'http://' + ip + '/IE9/' + loggedInUser + '-' + docnum + '-English.pdf';
                    a.href = url;
                    $(a).attr('target','_blank');
                    a.click();
                    $(a).remove();
                    $(doc).removeClass('loader');
                    $(doc).prop('disabled',false);
                }
                else if (isiOS) {
                    var ip = this.responseText.trim();
                    ioswindow.location.href = 'http://' + ip + '/IE9/' + loggedInUser + '-' + docnum + '-English.pdf';
                    $(doc).removeClass('loader');
                    $(doc).prop('disabled',false);
                }
                // Hack to partially fix Chrome error, file is now in Chrome downloads
                else if (Math.max(document.documentElement.clientWidth, window.innerWidth || 0) <= 1024 && window.chrome) {
                    var blob = new Blob([this.response], {type: 'application/pdf'});
                    var a = document.createElement("a");
                    a.style = "display: none";
                    document.body.appendChild(a);
                    var url = window.URL.createObjectURL(blob);
                    a.href = url;
                    a.download = pdffilename;
                    a.click();
                    window.URL.revokeObjectURL(url);
                    $(doc).removeClass('loader');
                    $(doc).prop('disabled',false);
                }
                else {
                    saveAs(this.response, pdffilename);
                    $(doc).removeClass('loader');
                    $(doc).prop('disabled',false);
                }
            }
        }
        xhr.open('GET', 'customer-formatter.xq?masterlang=' + masterlang + '&doclang=' + doclang + '&specialty='+ specialty + '&article=' + docnum + '&user_name=' + loggedInUser + '&territory=' + territory + '&expiry=' + expiry + '&page_width=' + page_width + '&page_height=' + page_height + '&column_count=' + column_count + '&phrasechange=' + phrasechange + '&genlink=' + genlink + '&access=' + access + '&scalefont=' + scalefont + '&skin=' + skin + '&watermark=' + watermarkmsg +'&timestamp=' + timestamp);
        xhr.setRequestHeader('Authorization','Basic ' + sessinfo);
        if (isiOS) 
            xhr.responseType = 'text';
        else
            xhr.responseType = 'blob';
        xhr.send();
        logEvent(docnum, doclang, 'format', specialty, source, docname);