SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
Ruby提供了 Net::SMTP 来发送邮件,并提供了两个方法 new 和 start:
成功或错误的状态码如下所示:
‘*************************
‘* 邮件服务返回代码含义
‘* 500 格式错误,命令不可识别(此错误也包括命令行过长)
‘* 501 参数格式错误
‘* 502 命令不可实现
‘* 503 错误的命令序列
‘* 504 命令参数不可实现
‘* 211 系统状态或系统帮助响应
‘* 214 帮助信息
‘* 220 服务就绪
‘* 221 服务关闭传输信道
‘* 421 服务未就绪,关闭传输信道(当必须关闭时,此应答可以作为对任何命令的响应)
‘* 250 要求的邮件操作完成 OK
‘* 251 用户非本地,将转发向
‘* 450 要求的邮件操作未完成,邮箱不可用(例如,邮箱忙)
‘* 550 要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问)
‘* 451 放弃要求的操作;处理过程中出错
‘* 551 用户非本地,请尝试
‘* 452 系统存储不足,要求的操作未执行
‘* 552 过量的存储分配,要求的操作未执行
‘* 553 邮箱名不可用,要求的操作未执行(例如邮箱格式错误)
‘* 354 开始邮件输入,以.结束
‘* 554 操作失败
‘* 535 用户验证失败
‘* 235 用户验证成功
‘* 334 等待用户输入验证信息
SMTP 对象实例化方法调用了 sendmail, 参数如下:
以下提供了简单的Ruby脚本来发送非SSL的邮件(sina的是非SSL,QQ的是SSL):
首先打开新浪邮箱,进入设置,开启SMTP服务,如图所示:
代码如下:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "net/smtp"
message = <
To: beyond <***@qq.com>
Subject: 这是邮件Subject
这是邮件正文,#{Time.now}
MESSAGE_END
Net::SMTP.start('smtp.sina.cn',25,'localhost',
'username','password', :plain) do |smtp|
smtp.send_message message,'***@sina.cn','***@qq.com'
end
运行效果如下:
打开邮件后的效果:
在以上实例中,你已经设置了一个基本的电子邮件消息,以及正确的标题格式。
一个电子邮件要要From,To和Subject,
注意: 邮件头部 与 邮件正文之间需要一个空行。
使用Net::SMTP连接到本地机器上的SMTP服务器,
使用send_message方法来发送邮件,
方法参数为发送者邮箱 与 接收者邮箱。
如果你没有运行在本机上的SMTP服务器,您可以使用Net::SMTP与远程SMTP服务器进行通信。
如果使用网络邮件服务(如新浪或QQ邮件),您的电子邮件提供者会为您提供发送邮件服务器的详细信息:
Net::SMTP.start('smtp.sina.cn')
以上代码将连接主机为 mail.sina.cn,端口号为 25的邮件服务器,
如果需要填写用户名密码,则代码如下:
Net::SMTP.start('smtp.sina.cn',
25,
'localhost',
'username', 'password', :plain)
以上实例使用了指定的用户名密码连接到主机为 mail.sina.cn,端口号为 25的邮件服务器。
Net::SMTP同样提供了支持发送 HTML 格式的邮件。
发送电子邮件时你可以设置MIME版本,文档类型,字符集来发送HTML格式的邮件。
以下实例用于发送 HTML 格式的邮件:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "net/smtp"
message = <
To: beyond <***@qq.com>
MIME-Version: 1.0
Content-type: text/html
Subject: 这是HTML邮件Subject
这是HMTL邮件正文,#{Time.now}
hello beyond
Copyright © 2018 Powered by beyond
MESSAGE_BLOCK
Net::SMTP.start('smtp.sina.cn',25,'localhost',
'username','passsword', :plain) do |smtp|
puts smtp.send_message(message,'***@sina.cn','***@qq.com').message
end
运行效果如下:
如果需要发送混合内容的电子邮件,需要设置Content-type为multipart/mixed。
这样就可以在邮件中添加附件内容。
附件在传输前需要使用 pack("m") 函数将其内容转为 base64 格式。
以下实例将发送附件为 /Users/beyond/sg_ruby/anime.txt 的邮件:
其中,anime.txt内容如下:
代码如下:(注意格式千万不要写错,血的教训)
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'net/smtp'
require 'base64'
filenPath = "/Users/beyond/sg_ruby/anime.txt"
filename = "anime.txt"
#读取文件并编码成base64格式
fileContent = File.read(filenPath)
fileContent_encode = [fileContent].pack("m")
#定义分割线
separateLine = "THIS_IS_SEPARATE_LINE"
#定义头部信息
part1_head =<
To: <***@qq.com>
Subject: 使用SMTP发送带附件的邮件Subject
MIME-Version: 1.0
Content-Type: multipart/mixd; boundary=\"#{separateLine}\";charset=UTF-8
Content-Transfer-Encoding: 7bit
--#{separateLine}
HEAD_BLOCK
#定义body部分
body =< e
print "发送失败:",e
end
注意:你可以指定多个发送的目的地址,但需要使用逗号隔开。
打印结果如下:
Last login: Sat Mar 3 00:46:45 on ttys003
bogon:~ beyond$ ruby /Users/beyond/sg_ruby/ruby_132.rb
From: <***@sina.cn>
To: <***@qq.com>
Subject: 使用SMTP发送带附件的邮件Subject
MIME-Version: 1.0
Content-Type: multipart/mixd; boundary="THIS_IS_SEPARATE_LINE";charset=UTF-8
Content-Transfer-Encoding: 7bit
--THIS_IS_SEPARATE_LINE
Content-Type: text/plain;charset=UTF-8
Content-Transfer-Encoding: base64
5oiR5Lus5LuN5pyq55+l6YGT6YKj5bm05aSP5aSp5omA55yL6KeB55qE6Iqx
55qE5ZCN5a2XCg==
--THIS_IS_SEPARATE_LINE
Content-Type: text/plain; filename=anime.txt;charset=UTF-8
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=anime.txt
5Zyo5oiR5Lus6LWw6L+H55qE5a2j6IqC6YeM77yMCui3r+aXgeebm+W8gOea
hOiKseacteS5n+WcqOS4jeaWreWPmOWMlu+8jArpgqPkuKrlraPoioLnm5vl
vIDnmoToirHmmK/lj6vku4DkuYjmnaXnnYDvvJ8K6L276L275pGH5puz552A
77yM5LiA56Kw5Lya5b6u5b6u5Yi655eb77yMCumdoOi/keS4gOmXu++8jOma
kOe6puacieiCoemdkua2qeeahOmYs+WFieeahOawlOaBr+OAggrpgqPmsJTm
ga/muJDmuJDlnLDmt6HljrvvvIzmiJHku6zkuZ/lnKjmhaLmhaLplb/lpKfj
gIIK5Y+v5piv77yM6YKj5py16Iqx5LiA5a6a6L+Y5Zyo5p+Q5Liq5Zyw5pa5
55ub5byA552A4oCm4oCmCuWvue+8jOaIkeS7rOawuOi/nOmDveS8mue7p+e7
reWunueOsOmCo+acteiKseeahOaEv+acm+OAggrigJTigJTigJTigJTigJTi
gJTigJTigJQ85pyq6Ze76Iqx5ZCNPg==
--THIS_IS_SEPARATE_LINE--
250 ok queue id 52545997012
bogon:~ beyond$
邮件效果如下:
附件预览如下:
其他发送邮件附件的方法:
使用mail进行发送邮件
1.先安装mail
sudo gem install mail
然后代码如下:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "mail"
smtp = {:address => 'smtp.sina.cn',
:pot => 25,
:domain => "sina.cn",
:user_name => "***@sina.cn",
:password => "***",
:enable_starttls_auto => true,
:openssl_verify_mode => "none"}
Mail.defaults{delivery_method :smtp,smtp}
mail = Mail.new do
from "***@sina.cn"
to "***@qq.com"
subject "邮件使用mail发送"
body "我们仍未知道那年夏天所看见的花的名字"
add_file File.expand_path("/Users/beyond/sg_ruby/anime.txt")
# add_file File.expand_path("/Users/beyond/sg_ruby/anohana.jpg")
end
puts mail.deliver!
运行效果如下:
Last login: Sat Mar 3 00:16:48 on ttys001
bogon:~ beyond$ ruby /Users/beyond/sg_ruby/ruby_133.rb
Non US-ASCII detected and no charset defined.
Defaulting to UTF-8, set your own if this is incorrect.
Date: Sat, 03 Mar 2018 00:24:32 +0800
From: ***@sina.cn
To: ***@qq.com
Message-ID: <[email protected]>
Subject: =?UTF-8?Q?=E9=82=AE=E4=BB=B6=E4=BD=BF=E7=94=A8mail=E5=8F=91=E9=80=81?=
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="--==_mimepart_5a997abf3765d_9d923fd48245244c39716";
charset=UTF-8
Content-Transfer-Encoding: 7bit
----==_mimepart_5a997abf3765d_9d923fd48245244c39716
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: base64
5oiR5Lus5LuN5pyq55+l6YGT6YKj5bm05aSP5aSp5omA55yL6KeB55qE6Iqx
55qE5ZCN5a2X
----==_mimepart_5a997abf3765d_9d923fd48245244c39716
Content-Type: text/plain;
charset=UTF-8;
filename=anime.txt
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename=anime.txt
Content-ID: <[email protected]>
在我们走过的季节里,
路旁盛开的花朵也在不断变化,
那个季节盛开的花是叫什么来着?
轻轻摇曳着,一碰会微微刺痛,
靠近一闻,隐约有股青涩的阳光的气息。
那气息渐渐地淡去,我们也在慢慢长大。
可是,那朵花一定还在某个地方盛开着……
对,我们永远都会继续实现那朵花的愿望。
————————<未闻花名>
----==_mimepart_5a997abf3765d_9d923fd48245244c39716--
bogon:~ beyond$
收到的邮件如下:
预览附件如下:
Ruby提供了两个级别访问网络的服务,在底层你可以访问操作系统,它可以让你实现客户端和服务器为面向连接和无连接协议的基本套接字支持。
Ruby 统一支持应用程的网络协议,如FTP、HTTP等。
不管是高层的还是底层的。ruby提供了一些基本类,让你可以使用TCP,UDP,SOCKS等很多协议交互,而不必拘泥在网络层。这些类也提供了辅助类,让你可以轻松的对服务器进行读写。
接下来就让我们来学习如何进行 Ruby Socket 编程
应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要 通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字 (Socket)的接口,区分不同应用程序进程间的网络通信和连接。
生成套接字,主要有3个参数:通信的目的IP地址、使用的传输 层协议(TCP或UDP)和使用的端口号。Socket原意是"插座"。通过将这3个参数结合起来,与一个"插座"Socket绑定,应用层就可以和传输 层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
Sockets 词汇解析:
选项 | 描述 |
---|---|
domain | 指明所使用的协议族,通常为 PF_INET, PF_UNIX, PF_X25, 等等。 |
type | 指定socket的类型:SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议 |
protocol | 通常赋值0。 |
hostname | 网络接口的标识符:
|
port | port是端口的编号,每个服务器都会监听客户端连接的一个或多个端口号,一个端口号可以是 Fixnum 的端口号, 包含了服务器名和端口。 |
以下我们通过给定的主机和端口编写了一个简单的客户端实例,Ruby TCPSocket 类提供了 open 方法来打开一个 socke。
TCPSocket.open(hosname, port ) 打开一个 TCP 连接。
一旦你打开一个 Socket 连接,你可以像 IO 对象一样读取它,完成后,你需要像关闭文件一样关闭该连接。
以下实例ruby_134.rb的代码演示了如何连接到一个指定的主机,并从 socket 中读取数据,最后关闭socket:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'socket'
hostname = 'localhost'
port = 5267
s = TCPSocket.open(hostname,port)
#阻塞循环等待读取内容
while line = s.gets
#输入到终端
puts line.chop
end
#最后记得关闭
s.close
现在还没有Server服务端,所以ruby_134.rb这个客户端请求到5267端口的时候悲剧了
Ruby 中可以使用 TCPServer 类来写个简单的服务。
TCPServer 对象是 TCPSocket 的工厂对象。
现在我们使用 TCPServer.open(hostname, port) 来创建一个 TCPServer 对象。
接下来调用 TCPServer 的 accept 方法,该方法会等到一个客户端连接到指定的端口,然后返回一个的TCPSocket对象,表示连接到该客户端。
require 'socket' # 获取socket标准库
server = TCPServer.open(5267) # Socket 监听端口为 5267
loop { # 永久运行服务
client = server.accept # 等待客户端连接
client.puts(Time.now.ctime) # 发送时间到客户端
client.puts "Closing the connection,farewell"
client.close # 关闭客户端连接
}
先运行ruby_135.rb这个server端,然后再运行上面写的ruby_134.rb这个客户端,效果如下:
互联网上,大多服务都同时有大量的客户端连接。
Ruby的Thread类可以很容易地创建多线程服务,服务端的某一个子线程执行客户端的连接,而主线程则在等待更多的来自客户端的连接。
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'socket'
server = TCPServer.open(5267)
loop {
Thread.start(server.accept) do |client|
client.puts("Hi client , I'am Server, welcome!")
client.puts(Time.now)
client.puts("Closing the connection,Farewell~")
client.close
end
}
在这个例子中,socket将会永久运行,而当server.accept接收到客户端的连接时,一个新的线程被创建并立即开始处理请求。
而主程序则立即循环回,并等待一个全新的来自客户端的连接。
我们可以使用socket库来实现任何的 Internet 协议。
以下代码展示了如何获取网页的内容:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'socket'
host = 'localhost'
port = 80
path = "/index.html"
#连接服务器
socket = TCPSocket.open(host,port)
#指定请求头
requesHeader = "GET #{path} HTTP/1.0\r\n\r\n"
socket.print(requesHeader)
#读取结果
response = socket.read
#根据\r\n\r\n分割成返回头和返回的正文
headers,body = response.split("\r\n\r\n",2)
puts headers
puts ""
puts ""
puts body
如果是想要实现一个类似 web 的客户端,你同样也可以使用为 HTTP 预先构建的库如Net::HTTP。
以下代码与先前代码是等效的:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'net/http'
host = 'localhost'
filePath = '/index.html'
http = Net::HTTP.new(host,80)
response = http.get(filePath)
if response.code == "200" && response.message == "OK"
puts response.body
else
puts "#{response.code},#{response.message}"
end
如果文件存在,运行结果如下:
如果文件不存在,报错信息如下:
下面的代码也是一样的效果:
您还可以把get到的网页源码写到文件中,ruby_138.rb的代码如下所示:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "net/http"
host = "localhost"
filePath = "/index.html"
port = "80"
http = Net::HTTP.new(host,port)
#写到文件
targetFilePath = "/Users/beyond/sg_ruby/httpSource_01.txt"
File.open(targetFilePath, "w") { |file|
http.get(filePath) do |response|
file.write response
end
}
运行效果如下:
您还可以通过URI.parse(urlStr)方法,ruby_139.rb代码如下:
或者,您直接Net::HTTP.get_response(host,filePath)也可以,ruby_140.rb的代码如下:
下面将演示如何抓取网页上的所有图片,网站的样子如下:
打印输出该网页的源码,发现所有的高清图片的格式如下:
故使用正则如下,ruby_141.rb的代码:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "net/http"
host = "www.xxx.com"
port = '80'
filePath = "/yyy/zzz.html"
http = Net::HTTP.new(host,port)
response = http.get(filePath)
puts response.body.scan(/"imgUrl":"(.*?)","name":/m).uniq
运行效果如下:
您还可以使用open-uri抓取该页面的所有高清动漫图片,ruby_142.rb的代码如下:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
#能自动处理302
require 'open-uri'
urlStr = "http://xxx.com/yyy/zzz.html"
open(urlStr) do |f|
puts f.read.scan(/"imgUrl":"(.*?)","name":/m).uniq
end
运行效果如下:
您还可以使用ruby来发送带参数的POST请求,如下面代码所示:
ruby_143.rb是发送一个post请求:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'net/http'
#设定对象的运行方式
Net::HTTP.version_1_2
Net::HTTP.start('localhost',80) {|http|
response = http.post("/cgi-bin/cgi_9.rb",
"id=1&name=beyond")
print response.code," ",response.message,"\n"
puts response.body
}
而cgi_9.rb是一个CGI程序:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'cgi'
cgi = CGI.new("html4")
# 获取传递过来的POST参数
id = cgi['id']
name = cgi['name']
cgi.out{
cgi.html{
cgi.head{ "\n" +'' + cgi.title{"Ruby CGI"}}+
cgi.body{ "hello #{name},id is #{id}\n"
}
}
}
它收到ruby_143.rb发来的post请求中的参数id=1&name=beyond后,再输出一个html页面
注意,在运行CGI之前,先把权限设置一下 ,否则会报服务器内部500错误
sudo chmod 755 /Users/beyond/sg_python/cgi-bin/cgi_9.rb
然后,就可以运行ruby_143.rb发送POST请求了
运行效果如下:
关于open-uri简单说明一下用法:
像打开普通文件那样打开http/ftp的URL
open("http://www.baidu.com/") {|f|
f.each_line {|line| p line}
}
打开的文件对象已经被OpenURI::Meta所扩展, 因此,您可以方便地获取meta信息。
require 'open-uri'
open("http://www.baidu.com") {|f|}
代码如下:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'open-uri'
urlStr = "http://localhost"
open(urlStr){|f|
f.each_line{|line|
p line
}
p f.base_uri
p f.content_type
p f.charset
p f.content_encoding
p f.last_modified
}
运行效果如下:
如果在参数中 使用哈希表参数,您还可以添加指定的头字段。
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require 'open-uri'
urlStr = "http://localhost"
open(urlStr,
"User-Agent" => "Ruby/#{RUBY_VERSION}",
"From" => "***@sina.cn",
"Referer" => "http://www.baidu.com/") {|f|
f.each_line {|line| p line}
}
运行效果如下:
在默认情况下,http_proxy以及ftp_proxy这些环境变量都是有效的.
若想禁用代理,可以这样 :proxy => nil 。
open("http://www.baidu.com/index.html",}
}
您还可以直接读取URI对象。
返回的字符串已经被OpenURI::Meta所扩展。
str = uri.read另一种写法,其效果也是一样的:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "net/https"
require "uri"
urlStr = "http://localhost"
uri = URI.parse(urlStr)
http = Net::HTTP.new(uri.host,uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
request.initialize_http_header("User-Agent" => "RUBY/#{RUBY_VERSION}")
response = http.request(request)
print response.code,",",response.message,",",response.body
下面另外演示是一个POST请求的示例代码:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "net/https"
require "uri"
urlStr = "http://localhost/cgi-bin/cgi_9.rb"
uri = URI.parse(urlStr)
#发送post请求
response = Net::HTTP.post_form(uri,{"id" => "1",
"name" => "beyond"})
puts response.body
更加完整的一个Post请求:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
#coding=utf-8
require "net/https"
require "uri"
urlStr = "http://localhost/cgi-bin/cgi_9.rb"
uri = URI.parse(urlStr)
#更完整post请求
http = Net::HTTP.new(uri.host,uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data({"id" => "1",
"name" => "beyond"})
response = http.request(request)
puts response.body
下面是带Cookie的POST请求示例代码片断:
# 带cookie的post请求
http = Net::HTTP.new(uri.host,uri.port)
filePath = "/login.html"
cookie = response["set-cookie"]
data = "key1=value1&key2=value2...."
headers = {"cookie"=>cookie,"content-type"=>"..."}
response = http.post(filePath,data,headers)
#或者:
request = NET::HTTP::Post.new(filePath)
request.set_form_data({"key1"=>"value1","key2"=>"value2"...."})
request.initialize_http_header({"cookie"=>cookie})
response = http.request(request)
下面是设置连接超时:
#设置请求的连接超时 时间
http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = 5 # in seconds
http.read_timeout = 5 # in seconds
所有HTTP 1.0的请求方式列表如下:
# 所有的HTTP 1.1的请求method列表如下
Net::HTTP::Get
Net::HTTP::Post
Net::HTTP::Put
Net::HTTP::Delete
Net::HTTP::Head
Net::HTTP::Options
未完待续,下一章节,つづく