twisted.protocols.ftp.FTP에서 REST를 구현하시겠습니까? (Implementing REST in twisted.protocols.ftp.FTP?)


문제 설명

twisted.protocols.ftp.FTP에서 REST를 구현하시겠습니까? (Implementing REST in twisted.protocols.ftp.FTP?)

twisted의 FTP 서버에서 REST 명령을 구현한 사람이 있습니까? 현재 시도:

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 명령에서 즉시 응답이 따라와야 하는 것 같습니다. 소켓 시간이 초과되는 이유는 무엇입니까?

업데이트:

제안된 대로 로깅을 활성화한 후 Jean‑Paul Calderone, 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 명령을 보내는 것 같습니다.


참조 솔루션

방법 1:

Verify that the client is behaving as you expect. Capturing all of the relevant traffic with tcpdump or wireshark is a good way to do this, although you can also enable logging in your Twisted‑based FTP server in a number of ways (for example, by using the factory wrapper twisted.protocols.policies.TrafficLoggingFactory).

From the timeout error followed by the "Restarting ..." log message, I would guess that the client is sending a RETR ''first'' and then a REST. The RETR times out because the client doesn't try to connect to the data channel until after it receives a response to the REST, and the Twisted server doesn't even process the REST until after the client connects to the data channel (and downloads the entire file). Fixing this may require changing the way ftp.FTP processes commands from clients, so that a REST which follows a RETR can be interpreted properly (or perhaps the FTP client you are using is merely buggy, from the protocol documentation I can find, RETR is supposed to follow REST, not the other way around).

This is just a guess, though, and you should look at the traffic capture to confirm or reject it.

방법 2:

After much digging into the source and fiddling with ideas, this is the solution I settled on:

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)

The callLater approach can be flaky at times, but it works the majority of the time. Use at your own risk, obviously.

(by eternicodeJean‑Paul Calderoneeternicode)

참조 문서

  1. Implementing REST in twisted.protocols.ftp.FTP? (CC BY‑SA 3.0/4.0)

#FTP #twisted






관련 질문

사용자 입력 경로 psftp 스크립트 배치 파일 (User input path psftp script batch file)

Android의 비동기 클래스에서 FTP 다운로드에 진행률 표시줄을 표시하는 방법은 무엇입니까? (How to show the progress bar in FTP download in async class in Android?)

Excel VBA - WinSCP 명령줄에 공백이 있는 변수 경로 전달 (Excel VBA - Passing a variable path with spaces to WinSCP command-line)

PHP에서 ftp를 사용하여 서버에 파일 업로드 (upload the file into the server using ftp in php)

VSFTP 연결 오류 (VSFTP Error Connecting)

ftp를 통해 게시할 때 액세스가 거부되었지만 로컬 IIS(.NET 응용 프로그램)에 게시할 때는 제대로 작동합니다. (Access denied on publish via ftp but works fine on publish to local IIS (.NET Application))

FTP 바이너리 전송 후의 Charset (Charset after FTP binary transfer)

FTP4J를 사용하여 FTP에 연결할 수 없음 (Unable to connect to FTP using FTP4J)

twisted.protocols.ftp.FTP에서 REST를 구현하시겠습니까? (Implementing REST in twisted.protocols.ftp.FTP?)

루트 액세스 없이(설치용) 프로젝트에서 libfuse를 사용하시겠습니까? FTP 마운트 및 inotify/kqueue/FSEvents (Use libfuse in a project, without root access (for installation)? FTP mounts & inotify/kqueue/FSEvents)

사용자 이름: root, 호스트: <host_ip>로 로그인할 수 없습니다. (Could not login with username: root, host: <host_ip>)

FTP Python 550 매개변수 잘못된 파일 이름 오류 (FTP Python 550 Parameter Incorrect Filename Error)







코멘트