我正在编写一个将使用Python的HTTPServer和BaseHTTPRequest的应用程序。在某些时候,我认为由于用户可能想要发送的数据的敏感性,SOCKS的实现将是有用的。问题是 - 应用程序计划在非标准端口上运行,因此如果它可以与纯文本和SSL连接进行通信,那将非常有用。我发现there是一种让HTTPServer使用SSL的方法:
import BaseHTTPServer, SimpleHTTPServer
import ssl
httpd = BaseHTTPServer.HTTPServer(('localhost', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler)
httpd.socket = ssl.wrap_socket (httpd.socket, certfile='path/to/localhost.pem', server_side=True)
httpd.serve_forever()
有没有办法创建一个可以处理SSL和纯文本连接的套接字类?一种检测SSL的简洁方法(即一些魔术字节)?另一种方法是分配两个端口,但这样做不太酷。
答案 0 :(得分:4)
我已经对这个问题进行了一些调查。 很容易使套接字表现得像两个不同的服务器(取决于收到的数据类型)。这里有什么不好的是python的_ssl库直接从socket._socket读取,这是一个原生的python对象,因此不能正常挂钩。
一种方法是编写一个将挂钩本机python套接字的C模块。 另一种解决方案是拥有1个前端和2个后端(https和http)。前端侦听4443并决定是否应该与https后端或http后端进行换向连接。您可以向两台服务器添加相同的处理程序,它们的行为方式也相同。另一个问题是在后端我们不知道客户端的ip,但是有一些解决方法(比如dict {(后端源端口号的前端):前端将保留的后端客户端IP和后端将会查看)
与C解决方案相比,第二个看起来很脏,但现在是。
import BaseHTTPServer, SimpleHTTPServer
import ssl
import socket
import select
import threading
FRONTEND_PORT = 4443
BACKEND_PORT_SSL = 44431
BACKEND_PORT_HTTP = 44432
HOST = 'localhost'
httpd_ssl = BaseHTTPServer.HTTPServer((HOST, BACKEND_PORT_SSL), SimpleHTTPServer.SimpleHTTPRequestHandler)
httpd_ssl.socket = ssl.wrap_socket (httpd_ssl.socket, certfile='key.pem', server_side=True)
httpd_direct = BaseHTTPServer.HTTPServer((HOST, BACKEND_PORT_HTTP), SimpleHTTPServer.SimpleHTTPRequestHandler)
def serve_forever(http_server):
http_server.serve_forever()
def categorize(sock, addr):
data = sock.recv(1)
if data == '\x16':
port = BACKEND_PORT_SSL
else:
port = BACKEND_PORT_HTTP
other_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
other_sock.connect((HOST, port))
other_sock.send(data)
inp = [sock, other_sock]
select_timeout = 1.0
try:
while 1:
r,w,x = select.select(inp,[],[],select_timeout)
if not r:
continue
for s in r:
o_s = inp[1] if inp[0]==s else inp[0]
buf = s.recv(4096)
if not buf:
raise socket.error
o_s.send(buf)
except socket.error:
pass
finally:
for s in inp:
s.close()
threading.Thread(target=serve_forever, args=(httpd_ssl,)).start()
threading.Thread(target=serve_forever, args=(httpd_direct,)).start()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((HOST, FRONTEND_PORT))
sock.listen(10)
while True:
conn, addr = sock.accept()
threading.Thread(target=categorize, args=(conn, addr)).start()
答案 1 :(得分:2)
我用一个帮助器类解决了问题,该类隐藏了accept()
上的接收数据,然后返回一个包裹或裸插槽:
class SmartServerSocket:
def __init__( self, sock ):
self.sock = sock
# delegate methods as needed
_delegate_methods = [ "fileno" ]
for method in _delegate_methods:
setattr( self, method, getattr( sock, method ) )
def accept( self ):
(conn, addr) = self.sock.accept()
if conn.recv( 1, socket.MSG_PEEK ) == "\x16":
return (ssl.wrap_socket( conn, certfile='path/to/localhost.pem', server_side=True ), addr)
else:
return (conn, addr)
httpd = BaseHTTPServer.HTTPServer( ('localhost', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler )
httpd.socket = SmartServerSocket( httpd.socket )
httpd.serve_forever()
如果您愿意,可以将服务器对象提供给SmartServerSocket
构造函数,并让accept()
在其中设置一个特殊的成员变量来检测协议,这样您就可以在{{1}中检查这一点。实例。
答案 2 :(得分:0)
但我不知道任何实现HTTP的STARTLS的客户端或服务器。它通常用于IMAP和SMTP,但不幸的是,对于HTTP似乎没有任何实现,在HTTPS之后的其他端口上提供HTTP仍然是常见做法。