栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

Python FTP

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Python FTP

FileZilla Server从1.0版本开始,FTPS默认会强制采用TLS_RESUME。采用Python的FTP_TLS模块的话,需要自己继承一个子类,并进行修改实现TLS_RESUME, 并skip掉SSLSocket的unwrap。

#If you met the problem of 450 TLS session of data connection has not resumed or the session does not match the control connection
#I met this problem on filezilla server, it is because python FTP_TLS can't support resume session
#there is a workaround to solve this problem
#reference: https://stackoverflow.com/questions/46633536/getting-a-oserror-when-trying-to-list-ftp-directories-in-python
class ReusedSslSocket(SSLSocket):
    def unwrap(self):
        pass


#MyFTP_TLS is derieved to support TLS_RESUME(filezilla server)
class MyFTP_TLS(ftplib.FTP_TLS):
    """Explicit FTPS, with shared TLS session"""

    def ntransfercmd(self, cmd, rest=None):
        conn, size =ftplib.FTP.ntransfercmd(self,cmd, rest)
        if self._prot_p:
            conn = self.context.wrap_socket(conn,
                                            server_hostname=self.host,
                                            session=self.sock.session)  # reuses TLS session     

            conn.__class__ = ReusedSslSocket  # we should not close reused ssl socket when file transfers finish

        return conn,size

如果是TLS_RESUME模式,就可以用自己的子类来处理。

ftp_host='192.168.1.100'
ftp_port=990

# ftps = ftplib.FTP_TLS(host=ftp_host,timeout=15)
ftps = MyFTP_TLS(host=ftp_host,timeout=15)
ftps.auth()
ftps.port=ftp_port

print(ftps.login('sample','xxxxxxx'))


ftps.prot_p()
ftps.set_pasv(1)

print(ftps.welcome)
print(ftps.cwd("/test"))
print(ftps.retrlines('LIST'))

但是当用storbinary上传文件的时候,就会收到一个425错误信息。425信息看上去,就是当你从命令session拿服务器回应消息时,服务器端发现数据连接异常。

ftplib.error_temp: 425 Error while transfering data: ECONNABORTED - Connection aborted

同时,还发现一个有趣的现象,服务器端有文件上传,但是大小是0. 尝试客户端用time.sleep(3)等待3秒,即便还是拿到425信息,但是文件上传却是正常的。

搜索一些论坛的讨论,讨论了产生这个问题的原因,但是并没有找到一个直接的Python的解决方法。基本意见认为直接忽略425。

In this PR, the SSL Stream gets closed first. Also, if the method is available, the SSL Stream gets shutdown and will not be disposed in a hard way, which can lead to an error on FileZilla servers.
The socket will also send a shutdown signal, so that the other endpoint knows, that the connection will be closed.

结合上面文件字节是0的现象,同时从服务器端同时看到错误信息如下,server端会认为传送数据的TLS连接没有被适当的关闭。

基于这些,我做了一个假设,FileZilla Server采用一个并不寻常的方式。

1)服务器端不会同List一样做同步响应,收到传送的文件字节流后,不会直接响应客户端的(从等待后文件能正常写入推测)。

2) 传送文件结束后,要求客户端自己管理session关闭,而不能不关闭。

基于这个推测,继承改写了storbinary函数。主要是把conn的基类在overwrite到正常的SSLSocket,从而调用unwrap的时候能正常触发shutdown。

def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
            """Store a file in binary mode.  A new port is created for you.

            Args:
            cmd: A STOR command.
            fp: A file-like object with a read(num_bytes) method.
            blocksize: The maximum data size to read from fp and send over
                        the connection at once.  [default: 8192]
            callback: An optional single parameter callable that is called on
                        each block of data after it is sent.  [default: None]
            rest: Passed to transfercmd().  [default: None]

            Returns:
            The response code.
            """
            self.voidcmd('TYPE I')

            with self.transfercmd(cmd, rest) as conn:
                
                while 1:
                    buf = fp.read(blocksize)
                    if not buf:
                        break
                    conn.sendall(buf)
                    if callback:
                        callback(buf)
                # shutdown ssl layer
                if ftplib._SSLSocket is not None and isinstance(conn, ftplib._SSLSocket):
                    conn.__class__=SSLSocket
                    conn.unwrap()
                    
            return self.voidresp()

这个时候,发现果然起了作用,但是客户端会挂住,直到15秒的timeout后触发一个异常(socket.timeout: The read operation timed out)。

此时,文件被正常上传,同时服务器端向客户端发出226的成功指令。如下图,226是在15秒后,就是客户端timeout异常触发的时候,但是服务器端认为连接此时被客户端正常地关闭了。

根据这个情况,我的假设有效,可以进一步推测客户端挂住的原因很大可能是Filezilla的服务器端也是采用同样的策略,完全不会理会客户端关闭的请求,不做任何回应,在收到客户端的shutdown通知后,就直接单方面处理关闭数据连接。由此,想到了一个折中的方法,客户端捕获并忽略timeout的异常。

代码修改:

# shutdown ssl layer
if ftplib._SSLSocket is not None and isinstance(conn, ftplib._SSLSocket):
      conn.__class__=SSLSocket
      conn.setblocking(False)
      try:
          conn.unwrap()
      except Exception as re:
          print("met error '{0}', but it is harmless".format(re))

此时,客户端采取不需要blocking的模式,捕获的timeout异常认为正常。

经过这个修改后,个人觉得是一个不错的结果,至少比忽略425要好,服务器端也不会有异常信息。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/979812.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号