我正在使用HTTPServer作为使用SSL的基本HTTP服务器.我想记录客户端何时启动SSL握手(或者可能是任何时候接受套接字?)以及任何相关的错误.我想我需要扩展一些类或覆盖一些方法,但我不确定哪个或如何正确地实现它.我非常感谢任何帮助.提前致谢!
修剪示例代码:
from http.server import BaseHTTPRequestHandler, HTTPServer from socketserver import ThreadingMixIn from threading import Thread import ssl import logging import sys class MyHTTPHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): logger.info("%s - - %s" % (self.address_string(), format%args)) def do_GET(self): self.send_response(200) self.end_headers() self.wfile.write('test'.encode("utf-8")) class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): pass logger = logging.getLogger('myserver') handler = logging.FileHandler('server.log') formatter = logging.Formatter('[%(asctime)s] %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.DEBUG) server = ThreadedHTTPServer(('', 443), MyHTTPHandler) server.socket = ssl.wrap_socket (server.socket, keyfile='server.key', certfile='server.crt', server_side=True, cert_reqs=ssl.CERT_REQUIRED, ca_certs='client.crt') Thread(target=server.serve_forever).start() try: quitcheck = input("Type 'quit' at any time to quit.\n") if quitcheck == "quit": server.shutdown() except (KeyboardInterrupt) as error: server.shutdown()
Lukas Graf.. 8
从ssl
模块看,大多数相关的魔法发生在SSLSocket
课堂上.
ssl.wrap_socket()
只是一个很小的便利功能,基本上作为SSLSocket
具有一些合理默认值的工厂,并包装现有的套接字.
不幸的是,SSLSocket
它似乎没有自己的任何日志记录,因此没有简单的方法来打开日志记录级别,设置debug
标志或注册任何处理程序.
所以你可以做的就是子类化 SSLSocket
,用你自己的方法覆盖你感兴趣的方法来做一些日志记录,并创建和使用你自己的wrap_socket
帮助函数.
SSLSocket
首先,将ssl.wrap_socket()
Python 复制.../lib/python2.7/ssl.py
到代码中.(确保您复制和修改的任何代码实际上来自您正在使用的Python安装 - 代码可能在不同的Python版本之间发生了变化).
现在适应你的副本wrap_socket()
,以
创建一个实例LoggingSSLSocket
(我们将在下面实现)而不是SSLSocket
和使用常量从ssl
模块在必要时(ssl.CERT_NONE
以及ssl.PROTOCOL_SSLv23
在此实例中)
def wrap_socket(sock, keyfile=None, certfile=None, server_side=False, cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_SSLv23, ca_certs=None, do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None): return LoggingSSLSocket(sock=sock, keyfile=keyfile, certfile=certfile, server_side=server_side, cert_reqs=cert_reqs, ssl_version=ssl_version, ca_certs=ca_certs, do_handshake_on_connect=do_handshake_on_connect, suppress_ragged_eofs=suppress_ragged_eofs, ciphers=ciphers)
现在改变你的路线
server.socket = ssl.wrap_socket (server.socket, ...)
至
server.socket = wrap_socket(server.socket, ...)
为了使用你自己的wrap_socket()
.
现在进行子类化SSLSocket
.通过在代码中添加以下内容来创建一个LoggingSSLSocket
子类SSLSocket
:
class LoggingSSLSocket(ssl.SSLSocket): def accept(self, *args, **kwargs): logger.debug('Accepting connection...') result = super(LoggingSSLSocket, self).accept(*args, **kwargs) logger.debug('Done accepting connection.') return result def do_handshake(self, *args, **kwargs): logger.debug('Starting handshake...') result = super(LoggingSSLSocket, self).do_handshake(*args, **kwargs) logger.debug('Done with handshake.') return result
在这里我们重写accept()
和do_handshake()
方法ssl.SSLSocket
- 其他一切保持不变,因为类继承自SSLSocket
.
我使用特定模式来覆盖这些方法,以便更容易应用于您将覆盖的几乎任何方法:
def methodname(self, *args, **kwargs):
*args, **kwargs
确保我们的方法接受任意数量的位置和关键字参数,如果有的话.accept
实际上并没有采取任何这些,但它仍然有效,因为Python的参数列表的打包/解包.
logger.debug('Before call to superclass method')
在调用超类方法之前,你有机会做自己的事情.
result = super(LoggingSSLSocket, self).methodname(*args, **kwargs)
这是对超类'方法的实际调用.有关其工作原理的详细信息,请参阅文档super()
,但它基本上调用.methodname()
了LoggingSSLSocket
超类(SSLSocket
).因为我们传递*args, **kwargs
给方法,我们只传递我们的方法得到的任何位置和关键字参数 - 我们甚至不需要知道它们是什么,方法签名将始终匹配.
因为某些方法(比如accept()
)将返回一个结果,我们将result
它存储起来并在我们的方法结束时返回它,就在我们的调用后工作之前:
logger.debug('After call.') return result
如果要在日志记录语句中包含更多信息,则可能必须完全覆盖相应的方法.因此,将它们复制并根据需要进行修改,并确保满足任何缺少的导入.
以下是一个示例,accept()
其中包括尝试连接的客户端的IP地址和本地端口:
def accept(self): """Accepts a new connection from a remote client, and returns a tuple containing that new connection wrapped with a server-side SSL channel, and the address of the remote client.""" newsock, addr = socket.accept(self) logger.debug("Accepting connection from '%s'..." % (addr, )) newsock = self.context.wrap_socket(newsock, do_handshake_on_connect=self.do_handshake_on_connect, suppress_ragged_eofs=self.suppress_ragged_eofs, server_side=True) logger.debug('Done accepting connection.') return newsock, addr
(确保from socket import socket
在代码顶部的导入中包含- 参考ssl
模块的导入以确定在需要时从哪里导入丢失的名称NameError
.PyFlakes
配置好的文本编辑器非常有助于指出丢失的导入给你).
此方法将导致日志输出如下:
[2014-10-24 22:01:40,299] Accepting connection from '('127.0.0.1', 64152)'... [2014-10-24 22:01:40,300] Done accepting connection. [2014-10-24 22:01:40,301] Accepting connection from '('127.0.0.1', 64153)'... [2014-10-24 22:01:40,302] Done accepting connection. [2014-10-24 22:01:40,306] Accepting connection from '('127.0.0.1', 64155)'... [2014-10-24 22:01:40,307] Done accepting connection. [2014-10-24 22:01:40,308] 127.0.0.1 - - "GET / HTTP/1.1" 200 -
因为它涉及到遍布各处的相当多的变化,所以这里是包含示例代码的所有更改的要点.