← Архив: Common Lisp

cgi: передать файл через web

Author: · 27.07.2015 15:12
· original author: anaumov
Всем привет.
Приходилось кому-нибудь работать с cgi? У меня есть sbcl-скрипт, который в процессе своей работы создает файл. При его запуске в консоли файл просто создается в том же каталоге, где и сам скрипт. Я ищу пример кода, при помощи которого этот файл можно было бы сохранить, обращаясь к скрипту через web.
Поискал в сети. Информации немного. Рекомендуют lisp-cgi-utils, но в его примерах я разобраться не смог :/
Если кто-то уже работал с cgi, буду благодарен за любую помощь.
· original author: anaumov
В python для этого просто надо было добавить в конец что-то типа
(format t "Content-Type: application/vnd.ms-excel\n")
(format t "Content-Disposition: attachment; filename=\"~A\"\n\n" file)
CL этого явно недостаточно :)
· original author: m4
>CL этого явно недостаточно :)
Собственно, как и для python)  Где строка отвечающая за запуск интерпретатора?
ЗЫ Зачем вы отнимаете у perl его работу?
· original author: anaumov
> Собственно, как и для python)

Вот этот кусочек кода создает файл и, либо сохраняет его в каталоге, где запускается скрипт, либо передает через сеть, где на обратной стороне его благополучно можно сохранить при помощи того же браузера:
if (os.environ.get('QUERY_STRING')):
    sys.stdout.write("Content-Type: application/vnd.ms-excel\n")
    sys.stdout.write("Content-Disposition: attachment; filename=\""+FILENAME+"\"\n\n")
    book.save(sys.stdout)
else:
    book.save(FILENAME)
· original author: m4
Начинать нужно со строки отвечающей за запуск интерпретатора, без неё ваш скрипт не будет работать. Где она?
· original author: anaumov
Ок, поехали :)
#!/usr/bin/sbcl --script
· original author: m4
Теперь напишите код скрипта выводящего какую-нибудь простенькую html страничку. Закиньте его в cgi-bin (надеюсь, сервер у вас уже установлен и работает), и протестируйте его с помощью браузера.
· original author: anaumov
Рисовал страничку и не одну. Например код отсюда. Все работает без проблем.
Вопрос как передать созданный файл на браузер?
· original author: m4
Можете сделать так же как и на python. Или это был не ваш код?
· original author: andy128k
Вот здесь есть нужный вам кусок кода: Fast Bulk I/O
output-stream замените на t (он же *standard-output*), а input-stream создайте с помощью with-open-file.
· original author: anaumov
> Можете сделать так же как и на python. Или это был не ваш код?
Ну.... я уж догодался, что точно так же можно сделать :) Не работает. Что-то не так...
Python-код мой.
> output-stream замените на t (он же *standard-output*), а input-stream создайте с помощью with-open-file.
Я создаю файл 1:1 как в этом примере. Завтра я попробую снова (проект на работе), если получится, буду Вам очень благодарен. Я уже 2 дня топчусь на одном и том же месте. Все реализовал, осталось только дать пользователям возможность забирать файл через web.
· original author: m4
>Не работает. Что-то не так...
А где код? Я же не Ванга)
· original author: anaumov
> А где код? Я же не Ванга)
Если не получится завтра. я выложу код.
· original author: anaumov
Итак, что мы имеем.
Код, который создает excel-файл:
(let ((doc (excel:with-excel-workbook ()
...
(when file (write-doc-to-file doc file))
(values (document-to-string doc) doc)
)
)

Код, который по идее должен работать по аналогии с python:
(format t "Content-Type: application/vnd.ms-excel\n")
(format t "Content-Disposition: attachment; filename=\"~A\"\n\n" file)
(format t doc)
И наконец Fast Bulk I/O код, который стоит использовать по мнению andy128k:
(let ((buf (make-array 4096 :element-type (stream-element-type input-stream)))
  (loop for pos = (read-sequence input-stream)
        while (plusp pos)
        do (write-sequence buf t :end pos)
)
)
)

где input-stream нужно реализовать при помощи with-open-file...
· original author: anaumov
Запускается скрипт, никаких ошибок, никакого файла... использовать сниффер? Казалось бы тривиальная задача...
· original author: anaumov
Вот этот код создает файл если его вызвать локально (притом последюю строчку можно убрать) и не отбрасывает ошибок при вызове через web, НО браузер не подхватывает его вывод.
(when file (buildnode-excel::write-doc-to-file doc file)) (format t "Content-Type: application/vnd.ms-excel\n") (format t "Content-Disposition: attachment; filename=\"~A\"\n\n" file) (format t file)
Запустил wireshark. Вижу вывод "Connect-Type text/html..." (это самая первая строчка в моем коде), но нет "Connect-Type: application/vnd.ms-excel" и всего остального (но, как я уже сказал, этот код (эта функция) работает, если его запускать в консоли, т.е. новый файл создается).
· original author: anaumov
И еще маленький подвопрос: может ли sbcl не выводить сообщения от QL? Наверное это зависит от настроек самого QL.
· original author: anaumov
Думаю, что вряд ли мне кто-то сможет подсказать что-то конкретное пока я показываю лишь куски кода. Вот код полностью. Я убрал лишь технические детали, касающиеся содержания самого файла.
#!/usr/bin/sbcl --script (format t "Content-type: text/html~%~%") (setq customer "BRL") (setq VERSION "0.3") (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init))) (ql:quickload "buildnode-excel") (ql:quickload "cl-mysql") (ql:quickload "cl-string-match") (in-package :buildnode-excel) (defun filename () (if (equal common-lisp-user::customer "ALL") (return-from filename "SERVICES.xls") (return-from filename (format nil "SERVICES-~A.xls" common-lisp-user::customer)))) (defun state (x) (if (equal x 1) (return-from state "Enabled") (return-from state "Disabled"))) (defun sql () (cl-mysql:connect :host "IP" :database "db_name" :user "user_name" :password "password") (if (equal common-lisp-user::customer "ALL") (setq list_s (car (car (cl-mysql:query "SELECT")))) (setq list_s (car (car (cl-mysql:query (format nil "SELECT")))))) (setq list_h (car (car (cl-mysql:query "SELECT")))) (setq list_g (car (car (cl-mysql:query "SELECT")))) (setq list_p (car (car (cl-mysql:query "SELECT")))) (setq list_c (car (car (cl-mysql:query "SELECT")))) (setq relation_h_p (car (car (cl-mysql:query "SELECT")))) (setq relation_s_c (car (car (cl-mysql:query "SELECT")))) (setq relation_s_g (car (car (cl-mysql:query "SELECT")))) (setq relation_s_h (car (car (cl-mysql:query "SELECT"))))) (defun contact-hash-relation (hash-relation list-relation) (dolist (i list-relation) (if (gethash (nth 0 i) hash-relation) (progn (if (consp (gethash (nth 0 i) hash-relation)) (setf a (gethash (nth 0 i) hash-relation)) (setf a (list (gethash (nth 0 i) hash-relation)))) (setf (gethash (nth 0 i) hash-relation) (append a (list (nth 1 i))))) (setf (gethash (nth 0 i) hash-relation) (nth 1 i))))) (defun data () (setf hash-relation-s-h (make-hash-table)) (dolist (i relation_s_h) (setf (gethash (nth 0 i) hash-relation-s-h) (nth 1 i))) (setf hash-host (make-hash-table)) (dolist (i list_h) (setf (gethash (car i) hash-host) (cdr i))) (setf hash-relation-s-g (make-hash-table)) (contact-hash-relation hash-relation-s-g relation_s_g) (setf hash-group (make-hash-table)) (dolist (i list_g) (setf (gethash (car i) hash-group) (cdr i))) (setf hash-relation-h-p (make-hash-table)) (dolist (i relation_h_p) (setf (gethash (nth 0 i) hash-relation-h-p) (nth 1 i))) (setf hash-poller (make-hash-table)) (dolist (i list_p) (setf (gethash (car i) hash-poller) (cdr i))) (setf hash-relation-s-c (make-hash-table)) (contact-hash-relation hash-relation-s-c relation_s_c) (setf hash-contact (make-hash-table)) (dolist (i list_c) (setf (gethash (car i) hash-contact) (cdr i)))) (defun contact-string (list-contact hash) (setf msg (car (list '()))) (dolist (x list-contact) (setf msg (append msg '(", ") (gethash x hash)))) (return-from contact-string (cdr msg))) (defun make-str (i) (return-from make-str (ss:row () (excel:set-index 0 (ss:string-cell (nth 0 i))) (ss:string-cell (nth 1 i)) (ss:string-cell (nth 0 (gethash (gethash (car i) hash-relation-s-h) hash-host))) (ss:string-cell (nth 1 (gethash (gethash (car i) hash-relation-s-h) hash-host))) (ss:string-cell (nth 2 (gethash (gethash (car i) hash-relation-s-h) hash-host))) (ss:string-cell ;(if (nth 3 (gethash (gethash (car i) hash-relation-s-h) hash-host)) (state (parse-integer (nth 3 (gethash (gethash (car i) hash-relation-s-h) hash-host))))) ;'unknown) (ss:string-cell (state (parse-integer (nth 4 i)))) (if (consp (gethash (car i) hash-relation-s-c)) (ss:string-cell (contact-string (gethash (car i) hash-relation-s-c) hash-contact)) (if (numberp (gethash (car i) hash-relation-s-c)) (ss:string-cell (nth 0 (gethash (gethash (car i) hash-relation-s-c) hash-contact))) (ss:string-cell "< NO ALERTING >"))) (if (consp (gethash (car i) hash-relation-s-g)) (ss:string-cell (contact-string (gethash (car i) hash-relation-s-g) hash-group)) (if (numberp (gethash (car i) hash-relation-s-g)) (ss:string-cell (nth 0 (gethash (gethash (car i) hash-relation-s-g) hash-group))) (ss:string-cell "< NO ALERTING >"))) (ss:string-cell (nth 0 (gethash (gethash (gethash (car i) hash-relation-s-h) hash-relation-h-p) hash-poller))) (ss:string-cell (nth 1 (gethash (gethash (gethash (car i) hash-relation-s-h) hash-relation-h-p) hash-poller))) (ss:string-cell (nth 3 i)) (ss:string-cell (nth 2 i))))) (defun make-table (&optional file) (let ((doc (excel:with-excel-workbook () (ss:worksheet `("ss:Name" "services") (ss:table () (ss:row () (excel:set-index 0 ;number of columns (ss:header-cell "1")) (ss:header-cell "2") (ss:header-cell "3") (ss:header-cell "4") (ss:header-cell "5") (ss:header-cell "6") (ss:header-cell "7") (ss:header-cell "8") (ss:header-cell "9") (ss:header-cell "10") (ss:header-cell "11") (ss:header-cell "12") (ss:header-cell "13")) (iter (for i from 0 to (- (list-length list_s) 1)) (collect (if (not (let ((idx (cl-string-match::initialize-bmh "ST-"))) (cl-string-match::search-bmh idx (nth 1 (nth i list_s))))) (make-str (nth i list_s)))))))))) (when file (buildnode-excel::write-doc-to-file doc file)) (format t "Content-Type: application/vnd.ms-excel\n") (format t "Content-Disposition: attachment; filename=\"~A\"\n\n" file) (format t file)
)) (sql) (data) (make-table (filename))
· original author: andy128k
В первой же строке два переноса строки. Значит что весь остальной вывод будет трактоваться как тело и заголовки работать не будут.
"\n" не даст переноса строки.
(format t file) выведет имя файла, а не содержимое.
· original author: anaumov
> В первой же строке два переноса строки. Значит что весь остальной вывод будет трактоваться как тело и заголовки работать не будут.
А сколько надо? Изменения приводят к ошибке 500: Internal Server Error.
> (format t file) выведет имя файла, а не содержимое.
Без этой строчки я все равно получу файл, если запущу скрипт в консоли. Выбрасываем эту строку.
Наверное надо изменить второй параметр write-doc-to-file функции. Но со *standard-output* она работать не хочет. Пишет The value NIL is not of type PATHNAME.
> "\n" не даст переноса строки.
Поменял на ~%
· original author: zh17
Не знаю почему andy128k посоветовал английский кукбук, на сайте есть статья http://lisper.ru/wiki/cookbook:input-and-output с готовым кодом.
Обрати внимание на строчку
(let ((output-stream (hunchentoot:send-headers))То есть, тебе надо сформировать заголовки в хунчетуте до вызова (hunchentoot:send-headers)
Подробности в доке по хунчу http://weitz.de/hunchentoot/#send-headers
· original author: anaumov
По поводу двух переносов в начале. Получаю Premature end of script headers: script-name.lisp если использую только один перенос.
Изменил на
(format t "Content-type: text/html ~C~C~C~C" #\return #\linefeed #\return #\linefeed) работает дальше, но сниффер показывает, что именно после этой строки ничего не приходит больше.
· original author: anaumov
zh17, спасибо. Думаю, на это у меня уйдет еще пару дней.
Кстати, а почему нельзя сразу из
write-doc-to-file записать информацию в *standard-output*?
· original author: m4
Можете оставить "~%", т.к. браузерам обычно на это пофиг. Тут проблема в том, что у вас бинарный файл, а *standard-output* - символьный поток, поэтому потребуется преобразование байтов в символы. Но как это сделать правильно? Можно попробовать открыть файл как текстовый с ansi форматом.
· original author: andy128k
> Не знаю почему andy128k посоветовал английский кукбук
Первое что нагуглилось по запросу "common lisp copy stream" :)
Hunchentoot тут не в тему, ИМХО. Это ж CGI.
· original author: m4
Проверьте на работоспособность:
(with-open-file (input-stream       file
                 :direction         :input
                 :external-format   :ascii
                 :if-does-not-exist :error
)

    (let ((buf (make-array (* 32 1024) :element-type (stream-element-type input-stream))))
      (loop for pos = (read-sequence buf input-stream)
            while (plusp pos)
            do (write-sequence buf *standard-output* :end pos)
)
)
)
· original author: zh17
Мне казалось что ты участвовал в переводе кукбука. Да и как "старичок" должен знать об этом разделе сайта. ;)
CGI всё равно же за каким-нибудь сервером, и надо разбираться как его сервер формирует заголовки. Возможно, сервер неявно дописывает что-либо, тогда _весь_ вывод трактуется как тело ответа. Хунч только для примера.
· original author: anaumov
> Подробности в доке по хунчу http://weitz.de/hunchentoot/#send-headers

Спасибо конечно, но этих 3х предложений явно не достаточно, чтобы понять как сформировать заголовки...

· original author: anaumov
> Проверьте на работоспособность:
Скрипт вывел на консоль, где я его сначала протестировал, весь excel файл ячейка за ячейкой.
Вот так теперь выглядит код:
(when file (buildnode-excel::write-doc-to-file doc file))

(format t "Content-Type: application/vnd.ms-excel~%")
(format t "Content-Disposition: attachment; filename=\"~A\"~%~%" file)


(with-open-file (input-stream   file
           :direction         :input
           :external-format   :ascii
           :if-does-not-exist :error)

   (let ((buf (make-array (* 32 1024) :element-type (stream-element-type input-stream))))
       (loop for pos = (read-sequence buf input-stream)
           while (plusp pos)
           do (write-sequence buf *standard-output* :end pos))))
Вот что отвечает сервер:
16:40:28.325940 IP (tos 0x0, ttl 64, id 14224, offset 0, flags [DF], proto TCP (6), length 244)
   10.52.1.7.80 > 172.16.102.114.53103: Flags [P.], cksum 0x1ea4 (incorrect -> 0x18f8), seq 493744840:493745032, ack 1258463031, win 235, options [nop,nop,TS val 1514325348 ecr 27039921], length 192
       0x0000:  4500 00f4 3790 4000 4006 e4b6 0a34 0107  E...7.@.@....4..
       0x0010:  ac10 6672 0050 cf6f 1d6d f2c8 4b02 9f37  ..fr.P.o.m..K..7
       0x0020:  8018 00eb 1ea4 0000 0101 080a 5a42 c564  ............ZB.d
       0x0030:  019c 98b1 4854 5450 2f31 2e31 2032 3030  ....HTTP/1.1.200
       0x0040:  204f 4b0d 0a44 6174 653a 2057 6564 2c20  .OK..Date:.Wed,.
       0x0050:  3239 204a 756c 2032 3031 3520 3134 3a34  29.Jul.2015.14:4
       0x0060:  303a 3238 2047 4d54 0d0a 5365 7276 6572  0:28.GMT..Server
       0x0070:  3a20 4170 6163 6865 2f32 2e34 2e36 2028  :.Apache/2.4.6.(
       0x0080:  4c69 6e75 782f 5355 5345 290d 0a43 6f6e  Linux/SUSE)..Con
       0x0090:  7465 6e74 2d4c 656e 6774 683a 2030 0d0a  tent-Length:.0..
       0x00a0:  4b65 6570 2d41 6c69 7665 3a20 7469 6d65  Keep-Alive:.time
       0x00b0:  6f75 743d 3135 2c20 6d61 783d 3130 300d  out=15,.max=100.
       0x00c0:  0a43 6f6e 6e65 6374 696f 6e3a 204b 6565  .Connection:.Kee
       0x00d0:  702d 416c 6976 650d 0a43 6f6e 7465 6e74  p-Alive..Content
       0x00e0:  2d54 7970 653a 2074 6578 742f 6874 6d6c  -Type:.text/html
       0x00f0:  0d0a 0d0a
Я не могу понять, даже если скрипт не может передать файл, почему мы не видим в выводе Content-Type: application/vnd.ms-excel и Content-Disposition: attachment; filename="SERVICES-BRL.xls" строк? При запуске скрипта в консоли они есть.
· original author: andy128k
В этом пакете не выводит, а в другом может и выводит... Кто его знает, как там этот TCP работает?
У вас же CGI, его же можно потестировать и без сервера. Запустите скрипт в терминале и посмотрите, что выводится.
· original author: anaumov
> У вас же CGI, его же можно потестировать и без сервера. Запустите скрипт в терминале и посмотрите, что выводится.
Ну я же говорю, в консоли все отлично. В консоли я раньше получал просто файл (buildnode-excel::write-doc-to-file doc file), теперь и файл создается после вызова этой функции, да еще и его содержание самого файла ячейка за ячейкой (после добавления кода от m4). Консольная версия работала с самого начала без проблем, но тему эту я создал, потому что всего 3 строчки python-кода могут "раздавать этот файл по сети". Я хочу получить точно такой же функционал в CL скрипте.
· original author: m4
Создайте у себя простой тестовый скрипт:
(format t "Content-Type:application/vnd.ms-excel~%")
(format t "Content-Disposition:attachment;filename=file.xls~%~%")
(princ "Test. Ok!")
Если уж и он не выдаст вам файлик, то вас реально какие-то проблемы)
· original author: anaumov
> Если уж и он не выдаст вам файлик, то вас реально какие-то проблемы
Проблем быть не может, потому что на той же самой машинке, в том же самом каталоге, лежит python-скрипт, который делате тоже самое и делает это без каких-либо проблем.
> Создайте у себя простой тестовый скрипт:

Скрипт отработал без проблем. Я смог сохранить этот файл на другой стороне.
· original author: anaumov
А вот этот скриптик уже не работает. Не работает через HTTP, но работает в консоли.
#!/usr/bin/sbcl --script (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init))) (ql:quickload "buildnode-excel") (in-package :buildnode-excel) (defun make-table (&optional file) (let ((doc (excel:with-excel-workbook () (ss:worksheet `("ss:Name" "test") (ss:table () (excel:set-index 5 (ss:row () (excel:set-index 0 (ss:header-cell "Test")) (ss:header-cell "OK") (ss:header-cell "Yahoooo"))) ))))) (when file (write-doc-to-file doc file)) (values (document-to-string doc) doc)) (format t "Content-Type:application/vnd.ms-excel~%") (format t "Content-Disposition:attachment;filename=file.xls~%~%") (princ "Test. Ok!") ) (make-table "aaa.xls")
· original author: anaumov
Вот этот код работает.
#!/usr/bin/sbcl --script (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init))) (format t "Content-Type:application/vnd.ms-excel~%") (format t "Content-Disposition:attachment;filename=file.xls~%~%") (princ "Test. Ok!")
Вот этот уже не работает.
#!/usr/bin/sbcl --script (let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))) (when (probe-file quicklisp-init) (load quicklisp-init))) (ql:quickload "buildnode-excel") (format t "Content-Type:application/vnd.ms-excel~%") (format t "Content-Disposition:attachment;filename=file.xls~%~%") (princ "Test. Ok!")
Я там выше спрашивал - как отключить сообщения QL? Почему-то мне кажется, что cgi не нравится весь этот QL-вывод.
· original author: anaumov
Решение "заткнуть" QL тут
Но этот код не работает все равно...
#!/usr/bin/sbcl --script
(let ((quicklisp-init (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname))))
  (when (probe-file quicklisp-init)
    (load quicklisp-init)
)
)

(with-open-file (*standard-output* "/dev/null" :direction :output
                                   :if-exists :supersede
)

                (ql:quickload "buildnode-excel")
)

(format t "Content-Type:application/vnd.ms-excel~%")
(format t "Content-Disposition:attachment;filename=file.xls~%~%")
(princ "Test. Ok!")
Использование этого языка доставляет столько удовольствия :)
· original author: m4
Перенаправьте *standard-output* и *error-output* в файл(ы) для дальнейшего анализа. А то вы как слепыш тот)
· original author: m4
Использовать quicklisp для cgi мягко выражаясь опасно! Ведь кто может гарантировать, что в случае сбоя quicklisp на полезет в интернет, или еще куда-нибудь?
Может обойдетесь asdf? И, кстати, сколько ваш скрипт потребляет оперативной памяти, просто интересно на обслуживание скольких одновременных запросов у вас хватит ресурсов?
· original author: flyamt
Конкретно quickload можно "утихомирить" с :silent t. Все же иногда полезно смотреть на сигнатуры :)
· original author: flyamt
Если хочется заниматься страныым с "/dev/null" то есть переносимый uiop:with-null-output. Но не стоит забывать что есть еще Variable *DEBUG-IO*, *ERROR-OUTPUT*, *QUERY-IO*,  *STANDARD-OUTPUT*, *TRACE-OUTPUT*
· original author: anaumov
> Перенаправьте *standard-output* и *error-output* в файл(ы) для дальнейшего анализа. А то вы как слепыш тот)
Я запускаю скрипт в консоли и вижу, что он ничего дурного и не помышляет.
Как перенаправить выводы в *standard-output* и *error-output*?


> Может обойдетесь asdf?
Это лучше, т.е. проще? Как подключить библиотеку через asdf, если я её уже скачал и установил при помощи QL?


> Использовать quicklisp для cgi мягко выражаясь опасно! Ведь кто может гарантировать, что в случае сбоя
quicklisp на полезет в интернет, или еще куда-нибудь?
Гарантировать должен программист, который грамотно обрабатывает ошибки. Покажите код, который каким-то образом манипулирует QL :)
Я должен буду реализовать передачу переменной в скипт, да, но эта переменная сначала идет в скрипт через html-меню, т.е. переменная достаточно "статическая".
> И, кстати, сколько ваш скрипт потребляет оперативной памяти, просто интересно на обслуживание скольких одновременных запросов у вас хватит ресурсов?
Скриптик будет использоваться командой из 4х человек, т.е. вряд ли будет запущен одновременно больше... 2х раз.
> Если хочется заниматься страныым с "/dev/null" то есть переносимый uiop:with-null-output. Но не стоит забывать что есть еще Variable *DEBUG-IO*, *ERROR-OUTPUT*, *QUERY-IO*,  *STANDARD-OUTPUT*, *TRACE-OUTPUT*
Мне просто надо передать файл в браузер на клиентской стороне, который я и так получаю, если запускаю скрипт в консоли. Я очень надеюсь, что для этого мне не придется перенастраивать или перепрограммировать QL или sbcl
...хотя не удивлюсь уже, если выясниться, что по-другому это не работает...
· original author: EO
А на самом деле какая задача решается? Точно нужно именно выхлоп скрипта из каталога забирать? Может поднять FTP-сервер, скриптом на него закидывать, а клиентам собирать?
Зачем каждый раз тянуть библиотеки? Сохранить в образ один раз и его запускать не проще будет?
· original author: EO
А вобще, если положить файл в известную папку, потом поставить hunchentoot и запустить что-то типа
(hunchentoot:define-easy-handler (download :uri "/download") (filename)
  (setf (hunchentoot:header-out :content-disposition)
        (concatenate 'string "attachment; filename=" filename)
)

  (hunchentoot:handle-static-file filename)
)

(hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242))
то в браузере, открыв http://127.0.0.1:4242/download?filename=~/CommonLisp/12.xls, можно загрузить Excel (который лежит по указанному пути)
· original author: anaumov
> А на самом деле какая задача решается?
Генереируется xls-файл, который надо отдавать по HTTP, т.е. cgi-скрипт вызывается с html страницы и должен (после SQL-запросов, обработки полученных данных и генерирования xls файла) на выход передавать этот самый xls файл.
Я реализовал все, кроме передачи этого файла на вывод, т.е. в моей версии он сохраняется в каталог.
Вообще, было бы здорово, если бы скрипт сам мог определять из какого окружения его вызывают и на основании этой информации принимал решение о способе вывода xls, т.е. в текуший каталог, если вызвается из консоли, или на браузер клиента, если вызвается как cgi. В 4 сообщении я показал как я это реализовал в python.
Как я сейчас начинаю понимать, возможно я все релизовал правильно до создания этой темы, но из-за некорректного поведения QL, подумал, что проблема в моем коде и решил создать тему.

> Точно нужно именно выхлоп скрипта из каталога забирать?

Нет, файл сохранять в каталоге не нужно. Его нужно сразу передавать в браузер клиенту.
· original author: anaumov
> А вобще, если положить файл в известную папку, потом поставить hunchentoot и запустить что-то типа
Ребят, если для этой элементарной операции перенаправления вывода надо запускать второй web-сервер, то... вы извините конечно, но мне нафиг такой язык не нужен. И в python, и в perl вообще голову не надо ломать над типом файла и передачей его на вывод. Тут мы этот файл уже переконвертировали, и теперь "осталось только" загрузить hunchentoot в cgi-скрипт, создать новый сокет на 4242 порту и забирать созданый файл из ФС сервера... Т.е. cgi-скрипт постепенно превращается в web-сервер? :)
· original author: EO
Зачем второй? Hunchentoot'а и одного хватит. И дебажить удобнее в силу особенностей CL.
Про перенаправление *standard-output* - как и любое изменение динамических переменных. Можно взять уже готовое - в кукбуке же пишут:
If the output of the program should go to a file, you can do the following: (with-open-file (*standard-output* "somefile.dat" :direction :output :if-exists :supersede) ...)
>Это лучше, т.е. проще? Как подключить библиотеку через asdf, если я её уже скачал и установил при помощи QL?
Это правильнее, QL - это в первую очередь пакетный менеджер, а не система сборки (построения образа в случае CL).
По идее QL положит её в известное asdf место, потом можно начинать знакомиться по ссылке http://lisper.ru/wiki/cookbook:asdf
В вариантах выше для выхлопа в веб я бы попробовал только content-type и content-disposition в одном вызове format передать и посмотреть, что сниффер покажет.
· original author: anaumov
> Про перенаправление *standard-output* - как и любое изменение динамических переменных. Можно взять уже готовое - в кукбуке же пишут.
Так я же так и сделал.
> В вариантах выше для выхлопа в веб я бы попробовал только content-type и content-disposition в одном вызове format передать и посмотреть, что сниффер покажет.
Если не затрудит, покажите пожалуйста код.
· original author: EO
stdio - я имел ввиду предложение выше перенаправить выхлоп самого скрипта в файл, используя динамические переменные явно, а не глушить выхлоп QL. Был вопрос про то, что напишет в файл скрипт, если туда направить вывод. Думаю, что напишет всё нормально (иначе копали не там и баги в коде) и перейдём ко второму вопросу:
(format t "Content-Type:application/vnd.ms-excel~%Content-Disposition:attachment;filename=file.xls~%~%")
А в целом - сделать with-output-to-string всему, что надо отдать клиенту и полученную строку уже в одном вызове формата кинуть.
Подозреваю проблему, которая была в подфоруме по RESTAS - если форматом писать сразу в поток, то браузер понимает только последнюю запись.
· original author: anaumov
>> Перенаправьте *standard-output* и *error-output* в файл(ы) для дальнейшего анализа. А то вы как слепыш тот)
>
>Я запускаю скрипт в консоли и вижу, что он ничего дурного и не помышляет.
>Как перенаправить выводы в *standard-output* и *error-output*?
В *error-output* пусто.
В *standart-output* куча сообщений от QL и biuldnode-excel.
· original author: EO
Код в студию. Вызовы формата в with-open-file попали? В конце выхлопа ничего про контент не написано?