代码,发送电子邮件(工作正常):
#!/usr/bin/perl
use utf8;
use strict;
use warnings;
use Email::Sender::Simple qw(sendmail);
use Email::Sender::Transport::SMTP ();
use Email::Simple ();
use open ':std', ':encoding(UTF-8)';
sub send_email
{
my $email_from = shift;
my $email_to = shift;
my $subject = shift;
my $message = shift;
my $smtpserver = 'smtp.gmail.com';
my $smtpport = 465;
my $smtpuser = 'user@gmail.com';
my $password = 'secret';
my $transport = Email::Sender::Transport::SMTP->new({
host => $smtpserver,
port => $smtpport,
sasl_username => $email_from,
sasl_password => $password,
debug => 1,
ssl => 1,
});
my $email = Email::Simple->create(
header => [
To => $email_to,
From => $email_from,
Subject => $subject,
],
body => $message,
);
$email->header_set( 'Content-Type' => 'text/html' );
$email->header_set( 'charset' => 'UTF-8' );
sendmail($email, { transport => $transport });
}
send_email('user@gmail.com', 'user@gmail.com', 'Hello', 'test email');
一旦我向身体添加非ascii字符:
send_email('user@gmail.com', 'user@gmail.com', 'Hello', 'test email. Русский текст');
它与调试输出中的最后一条消息挂起:
Net::SMTP::_SSL=GLOB(0x8d41fa0)>>> charset: UTF-8
Net::SMTP::_SSL=GLOB(0x8d41fa0)>>>
Net::SMTP::_SSL=GLOB(0x8d41fa0)>>> test email. Русский текст
Net::SMTP::_SSL=GLOB(0x8d41fa0)>>> .
如何解决?
答案 0 :(得分:2)
TL; TR:修复很简单,但问题本身很复杂。要解决此问题,请添加:
$email = Encode::encode('utf-8',$email->as_string)
在将邮件发送给sendmail(...)
之前。但请注意这个答案结尾处的警告,说明在邮件中首先发送8位数据时可能出现的问题。
要真正理解问题,修复者必须深入研究Perl中套接字中字符与八位字节的处理:
Email::Sender::Transport::SMTP
使用Net::SMTP
,它本身使用基础syswrite
或IO::Socket::SSL
(或IO::Socket::IP
)套接字的IO::Socket::INET
方法,具体取决于是否使用了SSL。 syswrite
期望八位字节,并且它期望写入套接字的八位字节数。 Email::Simple
构造的邮件不返回八位字节,而是返回设置了UTF8标志的字符串。在此字符串中,字符数与八位字节数不同,因为俄语текст
被视为5个字符,而使用UTF-8转换为10个八位字节。Email::Sender::Transport::SMTP
只是将电子邮件的UTF8字符串转发给Net::SMTP
,syswrite
在length
内使用它。使用ий\r\n.\r\n
计算长度,它给出的字符数与这种情况下的八位字节数不同。但是在套接字站点上,它将占用八位字节而不是字符串中的字符,并将给定长度视为八位字节数。作为一个例子,请使用仅包含两个俄语字符'йй'的邮件。对于行尾和邮件结束标记,它由7个字符组成:
и й \r \n . \r \n
d0 b8 d0 b9 0d 0a 2e 0d 0a
但是,这7个字符实际上是9个八位字节,因为前2个字符分别是两个八位字节
syswrite($fd,"ий\r\n.\r\n",7)
现在,и й \r \n .
d0 b8 d0 b9 0d 0a 2e
只会写出7个字符的前7个八位字节,但是9个八位字节长的字符串:
syswrite
这意味着邮件结束标记不完整。这意味着邮件服务器将等待更多数据,而邮件客户端不知道需要发送更多数据。这实际上导致应用程序挂起。
现在,谁对此太过责备?
有人可能会争辩说IO :: Socket :: SSL :: syswrite应该以理智的方式处理UTF8数据,这是RT#98732所要求的。但是,IO :: Socket :: SSL中的Wide character in syswrite ...
文档清楚地表明它适用于字节。因为在考虑非阻塞套接字时,实际上不可能创建一个理智的基于字符的行为,这个错误被拒绝了。此外,非SSL套接字也会出现UTF8字符串问题:如果您不首先使用SSL,则程序不会挂起,而是会因Net::SMTP
而崩溃。
下一层将是期望Email::Transport
正确处理此类UTF8字符串。只是,在documentation of Net::SMTP::data:
DATA可以是对列表或列表的引用,并且必须由调用者编码为八位字节所需的任何编码,例如,通过使用Encode模块的encode()函数。
现在有人可能认为Email::Simple::as_string
应该正确处理UTF8字符串,或者Email::MIME
不应该首先返回UTF8字符串。
但是,人们甚至可以进入另一层:开发者本身。邮件传统上只是ASCII,在邮件中发送非ASCII字符是一个坏主意,因为它只能与具有8BITMIME扩展名的邮件服务器可靠地工作。如果涉及不支持此扩展的邮件服务器,则结果是不可预测的,即邮件可以被转换(可能破坏签名),可以被改变为不可读或可能在某处丢失。因此,最好使用更复杂的模块,如UILabel
,并设置适当的内容传输编码。