15.4 使用内置的SSL
在多数系统中都包含内置SSL的Python。然而,编译不带SSL支持的Python也是有可能的。如果您得到“缺少SSL支持”的错误信息,您就需要重新编译您的Python或者取得一个更新的版本。
为了启动一个SSL session,首先您需要像平常那样连接一个socket,接着建立一个SSL对象,该对象可以在socket上通信。这时候,所有通信都会使用这个新的SSL对象。下面是一个简单的例子:
#!/usr/bin/env python
# Basic SSL example - Chapter 15 - basic.py
import socket, sys
def sendall(s, buf):
byteswritten = 0
while byteswritten < len(buf):
byteswritten += s.write(buf[byteswritten:])
print "Creating socket...",
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "done."
print "Connecting to remote host...",
s.connect(("www.openssl.org", 443))
print "done."
print "Establishing SSL...",
ssl = socket.ssl(s)
print "done."
print "Requesting document...",
sendall(ssl, "GET / HTTP/1.0\r\n\r\n")
print "done."
s.shutdown(1)
while 1:
try:
buf = ssl.read(1024)
except socket.sslerror, err:
if (err[0]) in [socket.SSL_ERROR_ZERO_RETURN, socket.SSL_ERROR_EOF]:
break
elif (err[0]) in [socket.SSL_ERROR_WANT_READ,
socket.SSL_ERROR_WANT_WRITE]:
continue
raise
if len(buf) == 0:
break
sys.stdout.write(buf)
s.close()
运行这个程序(它不需要参数),您会发现它连接到www.openssl.org站点。接着它建立起一个SSL连接,并像普通HTTP那样通信。它会打印出该站点的主页。
您会注意到这个程序里面的sendall()函数。SSL对象只提供两个方法:read()和write()。它们大致和socket的recv()和send()方法相对应。和send()方法一样,write()也不能保证会把所有请求的数据都写出。不幸的是,SSL对象不能提供一个和第一章中介绍的socket的sendall()类似的方法,所以您必须自己实现。这一版本的sendall()方法简单地确保整个字符串都得到传输,就像标准的sendall()方法。
请注意在read()周围处理异常的部分。Python内置的SSL可以在文件尾,或者即使是读数据时产生异常。这段代码可以确保当收到合适的文件尾标记时退出主循环,并忽略那些不是错误的异常。
当前很多网络协议都是面向行的。SSL对象并不提供一个readline()方法,这就在使用面向行的协议时比较麻烦。下面是一个包装SSL的对象,它加入了一些缺少的函数:
#!/usr/bin/env python
# Basic SSL example with wrapper - Chapter 15 - basic-wrap.py
import socket, sys
class sslwrapper:
def __init__(self, sslsock):
self.sslsock = sslsock
self.readbuf = ''
self.eof = 0
def write(self, buf):
byteswritten = 0
while byteswritten < len(buf):
byteswritten += self.sslsock.write(buf[byteswritten:])
def _read(self, n):
retval = ''
while not self.eof:
try:
retval = self.sslsock.read(n)
except socket.sslerror, err:
if (err[0]) in [socket.SSL_ERROR_ZERO_RETURN,
socket.SSL_ERROR_EOF]:
self.eof = 1
elif (err[0]) in [socket.SSL_ERROR_WANT_READ,
socket.SSL_ERROR_WANT_WRITE]:
continue
else:
raise
break
if len(retval) == 0:
self.eof = 1
return retval
def read(self, n):
if len(self.readbuf):
# Return the stuff in readbuf, even if less than n.
# It might contain the rest of the line, and if we try to
# read more, it might block waiting for data that is not
# coming to arrive.
bytesfrombuf = min(n, len(self.readbuf))
retval = self.readbuf[:bytesfrombuf]
self.readbuf = self.readbuf[bytesfrombuf:]
return retval
retval = self._read(n)
if len(retval) > n:
self.readbuf = retval[n:]
return retval[:n]
return retval
def readline(self, newlinestring = "\n"):
retval = ''
while 1:
linebuf = self.read(1024)
if not len(linebuf):
return retval
nlindex = linebuf.find(newlinestring)
if nlindex != -1:
retval += linebuf[:nlindex + len(newlinestring)]
self.readbuf = linebuf[nlindex + len(newlinestring):] \
+ self.readbuf
return retval
else:
retval += linebuf
print "Creating socket...",
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print "done."
print "Connecting to remote host...",
s.connect(("www.openssl.org", 443))
print "done."
print "Establishing SSL...",
ssl = socket.ssl(s)
print "done."
ssl = sslwrapper(ssl)
print "Requesting document...",
ssl.write("HEAD / HTTP/1.0\r\n\r\n")
print "done."
s.shutdown(1)
while 1:
line = ssl.readline("\r\n")
if not len(line):
break
print "Received line:", line.strip()
s.close()
尽管这个程序仅仅从服务器读取几行数据,您还是可以把sslwrapper类用在您自己的程序中。它可以在很多程序中替换标准socket对象。还请注意的是,您或许根本永远用不上它,有些Python模块,例如在第6章中讨论的urllib2,已经支持Python内置的SSL了。







