Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Отправка файлов через UTL_SMTP

401 views
Skip to first unread message

Деев Илья

unread,
Feb 14, 2003, 3:37:24 AM2/14/03
to
Добрый день! Тут спрашивали по поводу отсылки файлов из базы.
Кое что получилось - комментарии внутри процедуры.
Вообще-то, отправлять большие файлы через такую процедуру - это
форменное насилие над базой. Наверное, UTL_SMTP для этого не
предназначен или требуются какие-то дополнительные настройки.
Небольшие файлы отсылаются довольно быстро. Такое впечатление, что
основные тормоза после отсылки крупных файлов идут уже после отсылки
письма на сервер. Что-то там чистится, что ли. 300K отсылались у меня
за 4 с чем-то (!!!) минуты. Небольшие файлы - в считанные секунды.
Но все равно, массовую рассылку, например, делать через такую процедуру
можно только с большого горя.

Описание стандарта MIME, которое использовалось при создании процедуры
http://www.sauron.kiev.ua/doc/mail/mime/index.php
Вариант процедуры черновой. Лучше это было бы через package сделать,
кое-что добавив при этом. После сравнения с JavaMail станет ясно, стоит
ли дорабатывать.

Буду рад любым замечаниям.

С уважением,
Деев Илья.

Итак:

CREATE OR REPLACE PROCEDURE send_file(sender IN
VARCHAR2:='de...@pskov.mts.ru',
recipient IN
VARCHAR2:='de...@pskov.mts.ru',
subject IN VARCHAR2:='Заголовок',
message IN VARCHAR2:='Сообщение',
file_name IN
VARCHAR2:='F8945/Doc1.doc') IS
-- Описание стандарта MIME
http://www.sauron.kiev.ua/doc/mail/mime/index.php

mailhost VARCHAR2(30) := 'polina'; -- Ваш сервер
mail_conn utl_smtp.connection;
lr_reply utl_smtp.reply;

src_lob BLOB; -- переменная LOB
buffer RAW(3); -- буфер для чтения
amt BINARY_INTEGER := 3; -- длина буфера
pos INTEGER := 1; -- позиция, с которой начинается чтение

TYPE rec_table IS TABLE OF CHAR(1) INDEX BY BINARY_INTEGER;
t rec_table; -- таблица перекодировки

b3 CHAR(3); -- буфер на 3 символа
summ BINARY_INTEGER; -- суммарный код - трехбайтовое слово

c1 NUMBER(2); -- код 1-го символа
c2 NUMBER(2); -- код 2-го символа
c3 NUMBER(2); -- код 3-го символа
c4 NUMBER(2); -- код 4-го символа

send_buff VARCHAR2(4096); -- буфер данных
message_koi8 VARCHAR2(32767); -- сообщение в КОИ8
raw_message RAW(32767); -- сообщение в RAW

-- чистое название файла без префикса-псевдокаталога(для уникальности
добавляется по умолчанию
-- при upload'e документа в таблицу), т.е.
превращаем 'F8945/Doc1.doc' в 'Doc1.doc'
file_name_pure VARCHAR2(255):=substr(file_name,instr(file_name,'/')
+1);
mime_type_v VARCHAR2(128); -- тип содержимого,
например 'application/msword'

BEGIN
-- Заполнение таблицы перекодировки
t(1):='A';
t(2):='B';
t(3):='C';
t(4):='D';
t(5):='E';
t(6):='F';
t(7):='G';
t(8):='H';
t(9):='I';
t(10):='J';
t(11):='K';
t(12):='L';
t(13):='M';
t(14):='N';
t(15):='O';
t(16):='P';
t(17):='Q';
t(18):='R';
t(19):='S';
t(20):='T';
t(21):='U';
t(22):='V';
t(23):='W';
t(24):='X';
t(25):='Y';
t(26):='Z';
t(27):='a';
t(28):='b';
t(29):='c';
t(30):='d';
t(31):='e';
t(32):='f';
t(33):='g';
t(34):='h';
t(35):='i';
t(36):='j';
t(37):='k';
t(38):='l';
t(39):='m';
t(40):='n';
t(41):='o';
t(42):='p';
t(43):='q';
t(44):='r';
t(45):='s';
t(46):='t';
t(47):='u';
t(48):='v';
t(49):='w';
t(50):='x';
t(51):='y';
t(52):='z';
t(53):='0';
t(54):='1';
t(55):='2';
t(56):='3';
t(57):='4';
t(58):='5';
t(59):='6';
t(60):='7';
t(61):='8';
t(62):='9';
t(63):='+';
t(64):='/';

-- Выборка содержимого BLOB и определение типа содержимого

/* !!! Здесь должно быть название Вашей таблицы
Я использовал стандартную таблицу из WebDB для хранения
документов со следующей структурой:
name varchar2(256)
mime_type varchar2(128)
doc_size number
dad_charset varchar2(128)
last_updated date
content_type varchar2(128)
content long raw
blob_content blob
!!! */
BEGIN
SELECT blob_content, mime_type
INTO src_lob, mime_type_v
FROM my_docs
WHERE NAME = file_name;
EXCEPTION
WHEN no_data_found
THEN RAISE_application_error(-20001,'No such file!');
END;

send_buff:='Subject: '||subject||'
MIME-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_NextPart_000_00CB_01C2CDF5.B57328E0"
This is a multi-part message in MIME format.

------=_NextPart_000_00CB_01C2CDF5.B57328E0
Content-Type: text/plain;
charset="koi8-r"
Content-Transfer-Encoding: base64

';

-- начинаем пересылку
mail_conn := utl_smtp.open_connection(mailhost, 25);
lr_reply := utl_smtp.helo(mail_conn, mailhost);
lr_reply := utl_smtp.mail(mail_conn, sender);
lr_reply := utl_smtp.rcpt(mail_conn, recipient);
lr_reply := utl_smtp.open_data(mail_conn);

-- отправим заголовок
raw_message:=utl_raw.cast_to_raw(convert(send_buff,'CL8KOI8R'));
utl_smtp.write_raw_data(mail_conn,raw_message);
send_buff:='';

-- обработаем сообщение
message_koi8:=CONVERT(message,'CL8KOI8R');
BEGIN
FOR i IN 1..round(length(message_koi8),3)+1 LOOP
b3:=substr(message_koi8,(i-1)*3+1,3);
summ:=ascii(substr(b3,1,1))*65536+ascii(substr(b3,2,1))*256+ascii
(substr(b3,3));
c1:=bitand(summ,16515072)/262144;
c2:=bitand(summ,258048)/4096;
c3:=bitand(summ,4032)/64;
c4:=bitand(summ,63);
send_buff:=send_buff||T(c1+1)||t(c2+1)||t(c3+1)||t(c4+1);
-- отправляем по частям по 2K
IF length(send_buff)>2045 THEN
raw_message:=utl_raw.cast_to_raw(send_buff);
utl_smtp.write_raw_data(mail_conn, raw_message);
send_buff:='';
END IF;
END LOOP;
EXCEPTION WHEN OTHERS THEN
NULL;
END;

-- если в конце обработки еще что-то не отправлено
IF send_buff IS NOT NULL THEN
raw_message:=utl_raw.cast_to_raw(send_buff);
utl_smtp.write_raw_data(mail_conn, raw_message);
END IF;

send_buff:='
------=_NextPart_000_00CB_01C2CDF5.B57328E0
Content-Type: '||mime_type_v||';
name="'||file_name_pure||'"
Content-Disposition: attachment;
filename="'||file_name_pure||'"
Content-Transfer-Encoding: base64

';

-- Перекодировка файла в base64
BEGIN

LOOP
dbms_lob.read (src_lob, amt, pos, buffer);
pos := pos + amt;
summ:=to_number(rawtohex(buffer),'xxxxxxxx');
c1:=bitand(summ,16515072)/262144;
c2:=bitand(summ,258048)/4096;
c3:=bitand(summ,4032)/64;
c4:=bitand(summ,63);
send_buff:=send_buff||T(c1+1)||t(c2+1)||t(c3+1)||t(c4+1);
IF length(send_buff)>2044 THEN
raw_message:=utl_raw.cast_to_raw(send_buff);
utl_smtp.write_raw_data(mail_conn, raw_message);
send_buff:='';
END IF;
END LOOP;

EXCEPTION
WHEN no_data_found
THEN -- передаем оставшиеся данные
raw_message:=utl_raw.cast_to_raw(send_buff);
utl_smtp.write_raw_data(mail_conn, raw_message);
END;

-- окончание этой секции файла - в принципе, возможно вложить
несколько файлов,
-- тогда необходимо их разделять
raw_message:=utl_raw.cast_to_raw('------
=_NextPart_000_00CB_01C2CDF5.B57328E0');
utl_smtp.write_raw_data(mail_conn, raw_message);

-- конец связи
lr_reply := utl_smtp.close_data(mail_conn);
lr_reply := utl_smtp.quit(mail_conn);

EXCEPTION
WHEN OTHERS
THEN dbms_output.put_line(SQLERRM);
-- конец связи
lr_reply := utl_smtp.close_data(mail_conn);
lr_reply := utl_smtp.quit(mail_conn);
END send_file;

--
Отправлено через сервер Форумы@mail.ru - http://talk.mail.ru

Vadim

unread,
Feb 14, 2003, 4:01:05 AM2/14/03
to
СПАСИБО !


Деев Илья пишет:

> Буду рад любым замечаниям.

> С уважением,
> Деев Илья.

> Итак:

> This is a multi-part message in MIME format.

> ------=_NextPart_000_00CB_01C2CDF5.B57328E0
> Content-Type: text/plain;
> charset="koi8-r"
> Content-Transfer-Encoding: base64

> ';

> -- начинаем пересылку
> mail_conn := utl_smtp.open_connection(mailhost,
> 25);
> lr_reply := utl_smtp.helo(mail_conn, mailhost);
> lr_reply := utl_smtp.mail(mail_conn, sender);
> lr_reply := utl_smtp.rcpt(mail_conn,
> recipient);
> lr_reply := utl_smtp.open_data(mail_conn);

> -- отправим заголовок

> utl_smtp.write_raw_data(mail_conn,raw_message);

> send_buff:='';

> -- обработаем сообщение
> message_koi8:=CONVERT(message,'CL8KOI8R');
> BEGIN
> FOR i IN 1..round(length(message_koi8),3)+1
> LOOP
> b3:=substr(message_koi8,(i-1)*3+1,3);

> (substr(b3,3));


> c1:=bitand(summ,16515072)/262144;
> c2:=bitand(summ,258048)/4096;
> c3:=bitand(summ,4032)/64;
> c4:=bitand(summ,63);

> -- отправляем по частям по 2K
> IF length(send_buff)>2045 THEN

> utl_smtp.write_raw_data(mail_conn,
> raw_message);
> END IF;

> ';

> IF length(send_buff)>2044 THEN

> END send_file;


> VARCHAR2(255):=substr(file_name,instr(file_name,'
> /')
> boundary="----=_NextPart_000_00CB_01C2CDF5.B57328
> E0"
> raw_message:=utl_raw.cast_to_raw(convert(send_buf
> f,'CL8KOI8R'));
> summ:=ascii(substr(b3,1,1))*65536+ascii(substr(b3
> ,2,1))*256+ascii


> send_buff:=send_buff||T(c1+1)||t(c2+1)||t(c3+1)||
> t(c4+1);
> send_buff:=send_buff||T(c1+1)||t(c2+1)||t(c3+1)||
> t(c4+1);
--

Vladimir M. Zakharychev

unread,
Feb 14, 2003, 4:17:30 AM2/14/03
to
>Небольшие файлы отсылаются довольно быстро. Такое впечатление, что
>основные тормоза после отсылки крупных файлов идут уже после отсылки
>письма на сервер. Что-то там чистится, что ли. 300K отсылались у меня
>за 4 с чем-то (!!!) минуты. Небольшие файлы - в считанные секунды.

Не, основные тормоза - в dbms_lob.read() по 3 байта за раз. Это ОЧЕНЬ
напрягает Оракл. Чтение (как и запись) LOBов буферизовать надо по
максимуму.


--
Vladimir Zakharychev (b...@dpsp-yes.com) http://www.dpsp-yes.com
Dynamic PSP(tm) - the first true RAD toolkit for Oracle-based internet applications.
All opinions are mine and do not necessarily go in line with those of my employer.


Деев Илья

unread,
Feb 14, 2003, 4:43:56 AM2/14/03
to
Vladimir M. Zakharychev пишет:
VM>>Небольшие файлы отсылаются довольно быстро.
VM> Такое впечатление, что
VM>>основные тормоза после отсылки крупных файлов
VM> идут уже после отсылки
VM>>письма на сервер. Что-то там чистится, что ли.
VM> 300K отсылались у меня
VM>>за 4 с чем-то (!!!) минуты. Небольшие файлы - в
VM> считанные секунды.

VM> Не, основные тормоза - в dbms_lob.read() по 3
VM> байта за раз. Это ОЧЕНЬ
VM> напрягает Оракл. Чтение (как и запись) LOBов
VM> буферизовать надо по
VM> максимуму.

Насчет буферизации - согласен. Хотя я в переменную ведь сначала читаю
BLOB, а не из таблицы или файла его вытаскиваю по три байта.

И что странно - я уже получил письмо от Oracle, а сообщение о том, что
процедура выполнилась, не появляется еще очень долго (в случае отсылки
больших файлов). Причем зависимость времени отклика от размера файла
нелинейная. Что-то здесь не так... Но что?

Файл 320K прочитался и перевелся в base64 (без отправки его на почтовый
сервер) за 3,687 сек.
С отправкой, как я писал - 4 с лишним минуты.
Так что основные тормоза идут в UTL_SMPT, а не при чтении и
перекодировке. Мне кажется, надо как-то настраивать что-то в Java.

С уважением,
Деев Илья.

Vladimir M. Zakharychev

unread,
Feb 14, 2003, 7:21:59 AM2/14/03
to
> Насчет буферизации - согласен. Хотя я в переменную ведь сначала читаю
> BLOB, а не из таблицы или файла его вытаскиваю по три байта.

В переменную ты читаешь BLOB locator. А потом по этому локатору
читаешь из LOB-сегмента.

> С отправкой, как я писал - 4 с лишним минуты.
> Так что основные тормоза идут в UTL_SMPT, а не при чтении и
> перекодировке. Мне кажется, надо как-то настраивать что-то в Java.

Ну тогда не знаю. Странно. Попробуй попрофилировать.

И, кстати, последнюю boundary надо выводить в таком виде:

--boundary--

(обрати внимание на два последних символа.) У тебя она такая
же, как все остальные, что вообще не есть правильно.

Деев Илья

unread,
Feb 14, 2003, 8:26:16 AM2/14/03
to
Vladimir M. Zakharychev пишет:
VM>> Насчет буферизации - согласен. Хотя я в
VM> переменную ведь сначала читаю
VM>> BLOB, а не из таблицы или файла его вытаскиваю
VM> по три байта.

VM> В переменную ты читаешь BLOB locator. А потом по
VM> этому локатору
VM> читаешь из LOB-сегмента.

Понятно. Я раньше с BLOB не работал.

VM>> С отправкой, как я писал - 4 с лишним минуты.
VM>> Так что основные тормоза идут в UTL_SMPT, а не
VM> при чтении и
VM>> перекодировке. Мне кажется, надо как-то
VM> настраивать что-то в Java.

VM> Ну тогда не знаю. Странно. Попробуй
VM> попрофилировать.

Надо заняться будет. Попробовал на версии 8.1.7 для Solaris, там еще
грустнее - вместо 4 минут - 10 минут все длится при пересылке 320K.

VM> И, кстати, последнюю boundary надо выводить в
VM> таком виде:

VM> --boundary--

VM> (обрати внимание на два последних символа.) У
VM> тебя она такая
VM> же, как все остальные, что вообще не есть
VM> правильно.

Точно. Спасибо за замечание!
Еще по стандарту в конце перекодирования, если вместо трех остаются 2
или 1 байт, то для дополнения числа символов до 3 нужно использовать
символы заполнения '='. Этого я тоже не сделал.

0 new messages