eXist-db Запрос REST GET для динамического pdf - не удается прочитать исходный файл

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

};

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


person jbrehr    schedule 20.12.2018    source источник
comment
Вы пробовали response: stream-binary ($ pdf, media-type = application / pdf, $ filename) с использованием первого / оставшегося URL   -  person Kevin Brown    schedule 24.12.2018
comment
Большинство заголовков http (которые, как мне кажется, устанавливаются) будут иметь имя и значение, разделенные двоеточием. Просто приложение / pdf в этом поле выглядит неправильно, и, возможно, оно рассматривается как что-то еще, а не как PDF.   -  person Kevin Brown    schedule 24.12.2018
comment
Отправляется ли запрос с веб-страницы из базы данных? Или это какая-то статическая страница, с которой вы пытаетесь вызвать? Возможно, вы хотите обрабатывать HTTP-ответ как двоичные данные в Javascript. Ниже я опубликую ответ о том, как я это делаю на наших веб-сайтах.   -  person Kevin Brown    schedule 24.12.2018


Ответы (2)


Поскольку вы заявляете, что 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")

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

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

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

person adamretter    schedule 23.12.2018
comment
Я предполагаю, что под «это работает с шаблонами eXist-db» вы имеете в виду часть: $node as node(), $model as map(*)? Если это так, я уже пытался удалить это и получил ту же проблему. Кроме того, это единственная функция в этом модуле. Он вызывает 4 или 5 других функций в других модулях для подготовки преобразования. Я отредактировал свой пост, указав все содержимое функции download: download, чтобы вы могли видеть, куда должен обрабатываться REST. - person jbrehr; 23.12.2018