OCP  
line decor
  Начало  ::  
line decor
   
 
UTL_SMTP на кирилица Март 2006

Oracle предлагат пакета за изпращане на e-mail съобщения от няколко вресии насам. Работата с него е доста лесна. Всичко необходимо може да изглежда така:

procedure SendMail(pSender      in varchar2,
 pRecipient   in varchar2,
 pMailHost    in varchar2,
 pPort        in number := 25,
 pCcRecipient in varchar2 := null,
 pSubject     in varchar2 := null,
 pMessage     in varchar2 := null) is

conn utl_smtp.connection;

begin
 conn := UTL_smtp.open_connection(pMailHost, pPort);
 UTL_smtp.helo(conn, pMailHost);
 UTL_smtp.open_data(conn);
 UTL_smtp.write_data(conn, 'From: ' || pSender || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'To: ' || pRecipient || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'Subject: ' || pSubject || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, pMessage);
 UTL_smtp.close_data(conn);
 UTL_smtp.quit(conn);
end SendMail; 

Само 11 реда код. Чисто и спретнато. Обаче ако искаме да поглезим потребителите може да опитаме да изпратим съобщение на български. Проблеми не би трябвало да има - базата е с правилен charset, всичко е настроено, но... Не се получава. Трябва да се постави съобщение в header за charset-a. И за да направим всичко красиво, ще изпращаме съобщението във формат html (това няма общо с проблема с кирилицата). Според мен първото (кирилицата) си е просто задължително - въпрос на уважение към получателя, ако е в България. Аз лично изобщо не се радвам когато получа нещо на "шльокавица". HTML, от друга страна, се имплементира безкрайно лесно.

След промяната нещата изглеждат по следния начин:

procedure SendHTML(pSender      in varchar2,
 pRecipient   in varchar2,
 pMailHost    in varchar2,
 pPort        in number := 25,
 pCcRecipient in varchar2 := null,
 pSubject     in varchar2 := null,
 pMessage     in varchar2 := null) is

conn utl_smtp.connection;

begin
 conn := UTL_smtp.open_connection(pMailHost, pPort);
 UTL_smtp.helo(conn, pMailHost);
 UTL_smtp.open_data(conn);
 UTL_smtp.write_data(conn, 'From: ' || pSender || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'To: ' || pRecipient || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'Subject: ' || pSubject || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'MIME-Version: ' || '1.0' || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'Content-Type: ' || 'text/html; charset=windows-1251' || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, pMessage);
 UTL_smtp.close_data(conn);
 UTL_smtp.quit(conn);
end SendHTML; 

Тук се натъкваме на 2 проблема. Единия нама нищо общо с кирилицата - дължи се на стандарта за SMTP съобщения. В стандарта е записано, че всеки ред трябва да завършва с CL/RF, недопустимо е да има само LF. Човека, който е подготвял body-то не се е съобразявал с това, SMTP сървъра се сърди. Това се оправя лесно:

procedure SendHTML(pSender      in varchar2,
 pRecipient   in varchar2,
 pMailHost    in varchar2,
 pPort        in number := 25,
 pCcRecipient in varchar2 := null,
 pSubject     in varchar2 := null,
 pMessage     in varchar2 := null) is

conn utl_smtp.connection;
mesgBody varchar2(32000);
begin
 conn := UTL_smtp.open_connection(pMailHost, pPort);
 UTL_smtp.ehlo(conn, pMailHost);
 UTL_smtp.open_data(conn);
 UTL_smtp.write_data(conn, 'From: ' || pSender || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'To: ' || pRecipient || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'Subject: ' || pSubject || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'MIME-Version: ' || '1.0' || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'Content-Type: ' || 'text/html; charset=windows-1251' || UTL_tcp.CRLF);
 mesgBody := replace(pMessage, CHR(10), CHR(13)||CHR(10));
 mesgBody := replace(mesgBody, CHR(13)||CHR(13)||CHR(10), CHR(13)||CHR(10));
 UTL_smtp.write_data(conn, mesgBody);
 UTL_smtp.close_data(conn);
 UTL_smtp.quit(conn);
end SendHTML; 

Вторият проблем е, че все още нямаме кирилица. Тук започва голямата борба. Явно на текста му се случва някакв конвертиране, но защо? След болезнени опити, промяна на NLS параметри, експерименти с convert и т.н. сe потвърждава правилото, че няколко часа борба с oracle могат да спестят няколко минути четене на документация... В документацията на UTL_SMTP, точно последния параграф, най-долу разкрива мистериозното конвертиране:

"Text (VARCHAR2) data sent using WRITE_DATA is converted to US7ASCII before it is sent. If the text contains multibyte characters, each multibyte character in the text that cannot be converted to US7ASCII is replaced by a '?' character. If 8BITMIME extension is negotiated with the SMTP server using the EHLO subprogram, multibyte VARCHAR2 data can be sent by first converting the text to RAW using the UTL_RAW package, and then sending the RAW data using WRITE_RAW_DATA."

Чиста работа, идеална реализация за всеки американец. Няма параметри, няма хакове - просто на всеки текст, изпратен с write_data му се случва конвертиране до US7ASCII. Дори е загатнато евентуалното решение: да се използва WRITE_RAW_DATA. Тази процедура по принцип е замислена за изпращане на raw данни, но каква разлика има от гледна точка на една TCP комуникация? Така достигаме до работещо решение:

procedure SendHTML(pSender      in varchar2,
 pRecipient   in varchar2,
 pMailHost    in varchar2,
 pPort        in number := 25,
 pCcRecipient in varchar2 := null,
 pSubject     in varchar2 := null,
 pMessage     in varchar2 := null) is

conn utl_smtp.connection;
mesgBody varchar2(3200);

begin
 conn := UTL_smtp.open_connection(pMailHost, pPort);
 UTL_smtp.ehlo(conn, pMailHost);
 UTL_smtp.open_data(conn);
 UTL_smtp.write_data(conn, 'From: ' || pSender || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'To: ' || pRecipient || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'Subject: ' || pSubject || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'MIME-Version: ' || '1.0' || UTL_tcp.CRLF);
 UTL_smtp.write_data(conn, 'Content-Type: ' || 'text/html; charset=windows-1251' || UTL_tcp.CRLF);
 mesgBody := replace(pMessage, CHR(10), CHR(13)||CHR(10));
 mesgBody := replace(mesgBody, CHR(13)||CHR(13)||CHR(10), CHR(13)||CHR(10));
 mesgBody := utl_raw.cast_to_raw(mesgBody);
 UTL_smtp.write_raw_data(conn, mesgBody);
 UTL_smtp.close_data(conn);
 UTL_smtp.quit(conn);
end SendHTML;

Решението, погледната готово, е безкрайно простичко. Просто казавме на Oracle "това не е текст, не го 'оправяй', моля" (-:

Явор

Пълния изходен код на процедурата, представена на пролетния семинар на БГПО в Пампорово се намира тук. Презентацията може да изтеглите от тук.