Реализация REST в twisted.protocols.ftp.FTP? - PullRequest
1 голос
/ 29 ноября 2010

Кому-нибудь удалось реализовать команду REST на FTP-сервере twisted? Моя текущая попытка:

from twisted.protocols import ftp
from twisted.internet import defer

class MyFTP(ftp.FTP):
    def ftp_REST(self, pos):
        try:
            pos = int(pos)
        except ValueError:
            return defer.fail(CmdSyntaxError('Bad argument for REST'))

        def all_ok(result):
            return ftp.REQ_FILE_ACTN_PENDING_FURTHER_INFO # 350

        return self.shell.restart(pos).addCallback(all_ok)

class MyShell(ftp.FTPShell):
    def __init__(self, host, auth):
        self.position = 0
        ...

    def restart(self, pos):
        self.position = pos
        print "Restarting at %s"%pos
        return defer.succeed(pos)

Когда клиент отправляет команду REST, требуется несколько секунд, прежде чем я увижу это в выводе скрипта:

Traceback (most recent call last):
Failure: twisted.protocols.ftp.PortConnectionError: DTPFactory timeout
Restarting at <pos>

Что я делаю не так? Мне кажется, что ответ должен следовать немедленно из команды REST, почему время ожидания сокета истекло?

Обновление:

После включения ведения журнала, как это было предложено Жаном-Полем Кальдероне, похоже, что команда REST даже не попадает в мой класс FTP до истечения времени соединения DTP из-за отсутствия соединения (для краткости временные метки сокращены до MM: SS) :

09:53 [TrafficLoggingProtocol,1,127.0.0.1] cleanupDTP
09:53 [TrafficLoggingProtocol,1,127.0.0.1] <<class 'twisted.internet.tcp.Port'> of twisted.protocols.ftp.DTPFactory on 37298>
09:53 [TrafficLoggingProtocol,1,127.0.0.1] dtpFactory.stopFactory
09:53 [-] (Port 37298 Closed)
09:53 [-] Stopping factory <twisted.protocols.ftp.DTPFactory instance at 0x8a792ec>
09:53 [-] dtpFactory.stopFactory
10:31 [-] timed out waiting for DTP connection
10:31 [-] Unexpected FTP error
10:31 [-] Unhandled Error
        Traceback (most recent call last):
        Failure: twisted.protocols.ftp.PortConnectionError: DTPFactory timeout

10:31 [TrafficLoggingProtocol,2,127.0.0.1] Restarting at 1024

Команда ftp_PASV возвращает DTPFactory.deferred, который описывается как «отложенный [который] будет срабатывать при подключении экземпляра». Команды RETR проходят нормально (в противном случае ftp.FTP был бы бесполезен).

Это наводит меня на мысль, что здесь есть какая-то операция блокировки, которая не позволит чему-либо еще произойти, пока не будет установлено соединение DTP; тогда и только тогда мы сможем принять дальнейшие команды. К сожалению, похоже, что некоторые (все?) Клиенты (в частности, я тестирую с FileZilla) отправляют команду REST перед подключением при попытке возобновить загрузку.

Ответы [ 2 ]

2 голосов
/ 29 ноября 2010

Убедитесь, что клиент ведет себя так, как вы ожидаете.Захват всего необходимого трафика с помощью tcpdump или wireshark - это хороший способ сделать это, хотя вы также можете включить вход на FTP-сервер на основе Twisted несколькими способами (например, с помощью фабричной оболочки twisted.protocols.policies.TrafficLoggingFactory).

Из-за ошибки тайм-аута, за которой следует сообщение журнала «Restarting ...», я бы предположил , что клиент отправляет RETR «сначала», а затем REST.Время ожидания RETR истекло, потому что клиент не пытается подключиться к каналу данных, пока не получит ответ на REST, а сервер Twisted даже не обработает REST, пока клиент не подключится к каналу данных (и не загрузит его).весь файл).Исправление может потребовать изменения способа ftp.FTP обработки команд от клиентов, так что REST, следующий за RETR, может быть интерпретирован правильно (или, возможно, используемый вами FTP-клиент просто глючит, из документации по протоколу, которую я могу найти, RETRдолжен следовать за REST, а не наоборот).

Это всего лишь предположение, и вы должны посмотреть на захват трафика, чтобы подтвердить или отклонить его.

1 голос
/ 19 января 2011

После долгих поисков в источнике и размышлений над идеями я остановился на этом решении:

class MyFTP(ftp.FTP):
  dtpTimeout = 30

  def ftp_PASV(self):
    # FTP.lineReceived calls pauseProducing(), and doesn't allow
    # resuming until the Deferred that the called function returns
    # is called or errored.  If the client sends a REST command
    # after PASV, they will not connect to our DTP connection
    # (and fire our Deferred) until they receive a response.
    # Therefore, we will turn on producing again before returning
    # our DTP's deferred response, allowing the REST to come
    # through, our response to the REST to go out, the client to
    # connect, and everyone to be happy.
    resumer = reactor.callLater(0.25, self.resumeProducing)
    def cancel_resume(_):
      if not resumer.called:
        resumer.cancel()
      return _
    return ftp.FTP.ftp_PASV(self).addBoth(cancel_resume)
  def ftp_REST(self, pos):
    # Of course, allowing a REST command to come in does us no
    # good if we can't handle it.
    try:
      pos = int(pos)
    except ValueError:
      return defer.fail(CmdSyntaxError('Bad argument for REST'))

    def all_ok(result):
      return ftp.REQ_FILE_ACTN_PENDING_FURTHER_INFO

    return self.shell.restart(pos).addCallback(all_ok)

class MyFTPShell(ftp.FTPShell):
  def __init__(self, host, auth):
    self.position = 0

  def restart(self, pos):
    self.position = pos
    return defer.succeed(pos)

Подход callLater иногда может быть нестабильным, но он работает большую часть времени.Используйте на свой страх и риск, очевидно.

...