eXist-db REST GET запрос для динамического PDF - не может прочитать исходный файл - PullRequest
0 голосов
/ 20 декабря 2018

(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)
 }

Она выводит штраф в формате PDF как при прямом вызове в IDE, так и при вызове функции через HTML-шаблон eXist,например:

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

Однако использование HTML означает открытие другого окна браузера.

Вместо этого я хотел бы запросить REST GET с кнопки.Я просмотрел документацию eXist REST и не могу заставить ее работать.

В соответствии с документацией, я должен выпустить 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

Возвращает следующее сообщение с пустым деревом:

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

Возвращает:

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

Я проверил права доступа к файлам, и, похоже, проблем нет.Хотя может быть требование разрешения / конфигурации REST, о котором я не знаю?Есть ли проблемы с REST для localhost?

РЕДАКТИРОВАТЬ: это полная функция, которая должна обрабатывать запрос 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)

};

Примечание: IМы вознаградили вас за это в надежде получить ответ, который проходит через функцию ввода и вывода REST с примером получения PDF, который спонтанно генерируется.Это включает в себя любые проблемы конфигурации / разрешения, которые могут повлиять на запрос REST.

Ответы [ 2 ]

0 голосов
/ 24 декабря 2018

Поскольку вы утверждаете, что 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();
}

Приведенный выше пример загрузит PDF-файл с использованием современных браузеров (Chrome, Firefox, Edge).

Код, стоящий за этим (я отбросил все остальные вещи, просто оставив часть форматирования):

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,Следует отметить несколько уловок, которые я упомяну в первую очередь, чтобы понять.Один из них заключается в том, что браузеры iOS или IE9 не могут обрабатывать двоичные загрузки в браузере.Таким образом, серверный код на самом деле имеет хак для создания PDF, и если браузер iE9 или iOS, он сохраняет результат в БД (или AWS S3) и возвращает ссылку на этот PDF, чтобы его можно было «щелкнуть»смотреть.Другие распространенные браузеры могут автоматически обрабатывать двоичные данные, отправленные обратно, если все сделано правильно.Для этого мы используем плагин FileSaver.js Javascript, который будет загружать PDF.

Другие части, которые вы можете откровенно игнорировать.Как и logEvent, который отправляет событие в Google Analytics, переменная totformats отслеживает загрузки пользователей и ограничивает их за один сеанс.Взлом для загрузки Chrome, вероятно, не требуется, поскольку это было ошибкой в ​​Chrome для Android.добавление и загрузка классов загрузчика для графического интерфейса.Решение iE9, iOS, использующее IP в качестве установленной переменной, это потому, что база данных реплицируется и сбалансирована нагрузка во многих странах, и поскольку данные записываются в БД для этого одного вызова, нам нужен IP-адрес этого точного сервера.в этом есть результат.С интеграцией S3 это исчезнет.

По сути, ключ заключается в том, что он вызывает тот же URL-адрес, что и при сохранении ответа, используя:

saveAs(this.response, pdffilename);

Это вызов в FileSaver.js, которыйпозволяет сохранять двоичные данные из XHR GET и загружать их для вас.Я извлек это из гораздо большего кода, который обрабатывает все загрузки, включая динамически сгенерированные из RenderX, как у вас, но также и статические PDF-файлы.

Вызов простой, просто GET для клиента-форматера.xq, который в моем случае совпадает с вызовом http://localhost/customer-formatter.xq (потому что я убираю / существую и мой пост для Jetty равен 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);
0 голосов
/ 23 декабря 2018

Ваша функция download:download написана таким образом, что она работает с шаблонами eXist-db.Я бы предложил абстрагировать действительную логику загрузки в отдельную функцию в отдельном библиотечном модуле.

Затем ваша функция download:download может вызывать вашу абстрактную функцию логики загрузки, а также вы можете создать новый основной модуль, такой какdirect-download.xq или что-то еще, что просто обрабатывает URL-адрес и затем вызывает вашу абстрактную логическую функцию загрузки.

...