제나름 대로 사용하기 위해서 만들어 본것입니다. 오역이 많습니다. 참고하거나 틀린점이 있으면 댓글 부탁 드립니다.

원본은 다음과 같습니다.


Using process

Overview

인터넷을 통해 서버로 접속하는 것처럼 Twisted 는 로컬 프로세스에 같은 API로 접속 할수 있다. API 는 다음과 같다

Running Another Process

reacotr를 통해 프로세스를 실행하려면 reactor.spawnProcess를 사용해야한다. Pipe는 child process에 만들어 지고, reactor 에 추가된 어플레키에션은 블록되지 않은 상태로 데이터를 새로운 프로세스에 주고 받을 수 있다. reactor.spawnProcesss는 두개의 인자값인 processProtocol executable 이 필요하다. 추가적으로 arg, enviroment, path, userID, groupID, usePTY, and childFDS를 사용 할수 있다. 이 모든것이 Windows에서 사용 할수 있는건 아니다.

from twisted.internet import reactor

processProtocol = MyProcessProtocol()
reactor.spawnProcess(processProtocol, executable, args=[program, arg1, arg2],
 env={'HOME': os.environ['HOME']}, path,
 uid, gid, usePTY, childFDs)
  • processProtocol : tiwsted.internet.protocol.ProcessProtocol 의 하위 클래스 여야 하며 인터페이스는 하단에 설명 한다.
  • executable : 프로그램이 실행 할 절대경로 이것은 processProtocol 이 접속 될것이다.
  • args : 프로세스를 실행하기 위하여 넘겨지는 argument 이고 arg[0]은 항상 프로레스 이름이 되어야 한다.
  • env : 프로세스에게 넘겨줄 환경변수 type은 dictinaory 이다.
  • path : 프로세스가 실행될 위치. 프로세스는 현재 디렉토리로 주어지기 전까지 사용하고 path를 사용한다면 디렉토리는 변경 될 것이다.
  • uid, gid : 유저 ID와 그룹 ID 이며 기입시 해당 유저와 그룹 ID 로 실행된다.
  • userPTY : 프로세스가 pty 를 사용할 것인지 아니면 pipe의 쌍을 사용할건지 명시한다. Python PTY
  • childFDs : 하위 프로세스의 descriptor 를 사용할지 정한다. 각 키는 file descriptor 의 숫자(Integer)이며 하위 프로세스에서 보여질 것이다. 0, 1, 2 FD는 stdin, stdout, stderror 이지면 몇 프로그램은 추가적인 fds를 커맨드 라인이나 환경 변수로 설정한다. 각 integer 값은 부모의 file descriptor 이고 r 은 부모로가 읽을 수 있고 w는 부모가 쓸수 있는 파이프를 만드는 문자열이다. 만약 childFDS가 제공되어 지지 않는 다면 기본적으로 stdin-write, stdout-reader, stderr-reader 파이프로 기본적으로 생성 된다.

args 와 env 는 기본적으로 비어 있는 값이다. args[0]은 executable과 같아야 한다. 만약 env에 os.environ 이 주어진다면 하위 프로세스는 현재 프로세스로부터 상속 받는다. reactor.spawnProcess IProcessTransport를 상속받아 구현된다.

Writing a ProcessProtocol

ProcessProtocol 은 spawnProcess와 상호작용을 한다. 일반 프로토콜과 비슷 하지만 몇가지 특정 이벤트를 처리를 추가하는 방법이 있다. 우리의 예제는 wc를 사용하여 예제를 구현한다. 첫번째로는 ProcessProtocol을 초기화 한다.

from twisted.internet import protocol
class WCProcessProtocol(protocol.ProcessProtocol):

    def __init__(self, text):
        self.text = text

ProcessProtocol이 연결이 되었을대 connectionMade 메소드를 호출한다. 우리의 프로토콜에선 텍스트를 표준 입력으로 쓰고 표준 입력을 닫아서 프로세스가 우리가 표준 입력을 마쳤다는것을 알릴것이다.

...
def connectionMade(self):
    self.transport.write(self.text)
    self.transport.closeStdin()

이 시점에서 프로세는 데이터를 받고 결과를 읽을 시간이다. dataReceived 로 데이터가 수신되는 대신에 표준 출력데이터는 outReceived로 수신되어진다. 이것은 표준 오류에 대한 데이터와 구별하기 위한 것이다.

...
def outReceived(self, data):
    fieldLength = len(data) / 3
    lines = int(data[:fieldLength])
    words = int(data[fieldLength:fieldLength*2])
    chars = int(data[fieldLength*2:])
    self.transport.loseConnection()
    self.receiveCounts(lines, words, chars)

이제 프로세스가 출력을 파싱하여 프로세스 연결을 종료 하였다. 그런 다음 결과를 마지막 메서드로 결과를 보낸다. 이것은 클래스의 사용자가 데이터를 사용하여 다른 작업을 수행하도록 재지정 할 수 있다. 이 예제에서는 단지 결과를 출력 하는것 뿐이다.

...
def receiveCounts(self, lines, words, chars):
    print('Received counts from wc.')
    print('Lines:', lines)
    print('Words:', words)
    print('Characters:', chars)

작업을 다하였다 WCProcessProtocol을 사용하기 위해서 우리는 spawnProcess를 사용하여 인스턴서를 넘겨 준다.

from twisted.internet import reactor
wcProcess = WCProcessProtocol("accessing protocols through Twisted is fun!\n")
reactor.spawnProcess(wcProcess, 'wc', ['wc'])
reactor.run()

Things that can happen to your ProcessProtocol

ProcessProtocol 을 상속 받아서 오버라이드 해야하는 함수에 대해 설명

  • connectionMade() : 프로그램이 시작될때 호출 되어지고 stdin pipe 를 통해 데이터를 쓰기 가장 좋은 위치다. (using self.transport.write)
  • outReceived(data) : 하위 프로세스 에서 데이터를 받을때 호출 되어진다. 파이프는 socket보다 큰(보통 1kb) 사이즈를 제공하려는 경향이 있어서 네트워크 소켓의 전형적인 랜덤 드립과 드랍 행동을 경험하지 않을 수 있다 하지만 한번에 데이터를 어지 못한다면 대처할 준비가 되어 있어야 한다. 제대로 수행하려면 수신자가 데이터를 축적하고 프로세스가 완료 될때까지 모든 작업을 연기해야 한다.
  • errReceived(data) : outReceived 처럼 동작하며 프로세스의 stderr pipe 로부터 호출 되어진다.
  • inConnectionLost : 프로세스의 stdin pipe가 닫힘을 reactor 로부터 통지 받는다. 프로그램은 일반적으로 자신의 stdin을 닫지 않으며self.transport.loseConnection을 ProcessProtocol 에서 닫을때 호출 될 것이다.
  • outConnectionLost : 프로그램이 stdout pipe를 닫을때 호출 되어진다. 대게 프로그램이 종료 될때 호출되어진다.
  • processExited(status) : 이 호출은 하위 프로세스가 회수 되거나 프로세스의 종료 정보를 받으면 호출 되어진다. 이 상태는 프로세스가 정상적으로 종료된 경우 (signal을 수신하지 않고 자연적인 원인으로 사망하고 종료 코드가 0인 경우), ProcessTerminated (.exitCode 속성을 가진경우) 를 보유하는 .value 로 생선된 Failure인스턴스의 형태로 전달된다.
  • processEnded(status) : file desciptor와 연관된 하위 프로세스가 닫히거나 프로세스가 회수 되어질때 호출 되어진다. 이것은 ProcessProtocol의 마지막으로 콜백할것을 의미한다. status 파라메터는 processExited 와 같은 의미이다.

이러한 함수의 대부분에 대한 기본 클래스 정의는 no-ops입니다. 이것은 모든 튼튼하고 찌르레기가 버려지게 할 것이다. 파이프를 읽지 않으면 전체 파이프에 쓰기 위해 어린이 프로세스가 결국 차단됩니다. 즉, 파이프를 읽지 않아도됩니다.

Things you can do from your ProcessProtocol

하위 프로세스를 다루는 기본적인 방법 입니다.

  • self.transport.write(data) : data는 stdin pipe 입니다. write 메소드는 즉시 작성할 수 없는 데이터는 queue가 될것이다. 파이프가 다시쓰게 된다면 쓰기가 다시 시작될 것이다.
  • self.transport.closeStdin : stdin 파이프를 닫는다. 프로그램은 필터 역할을 한다 ( stdin 으로부터 읽기, data 수정, stdout 으로 데이터 쓰기) 보통 해당 작업이나 프로세스 종료 하는것으로 받아 들인다. 프로그램들이 다끝나면 stdin 을 닫는것이 중요하다. 그렇지 않으면 하위 프로세스는 절대로 끝나지 않을 것이다.
  • self.transport.closeStdout : 대게 호출 되어지지 않는다. stdout 에 쓰려고 하면 SIGPIPE 오류가 발생될 것이다.이 과정은 훌륭한 과정은 아니다.
  • self.transport.closeStderr : 대게 호출 되어지지 않는다. self.transport.closeStdout 과 같은 이유로
  • self.transport.loseConnection : 3개의 모든 파이프를 닫는다.
  • self.transport.signalProcess('KILL') : 하위 프로세스를 죽인다. 보통 processEnded가 호출 될 것이다.

Verbose Example

wc 프로그렘 작성

process.py
---
#!/usr/bin/env python

# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.

from __future__ import print_function

from twisted.internet import protocol
from twisted.internet import reactor
import re

class MyPP(protocol.ProcessProtocol):
    def __init__(self, verses):
        self.verses = verses
        self.data = ""

    def connectionMade(self):
        print("connectionMade!")
        for i in range(self.verses):
        self.transport.write("Aleph-null bottles of beer on the wall,\n" +
         "Aleph-null bottles of beer,\n" +
         "Take one down and pass it around,\n" +
         "Aleph-null bottles of beer on the wall.\n")
        self.transport.closeStdin() # tell them we're done

    def outReceived(self, data):
        print("outReceived! with %d bytes!" % len(data))
        self.data = self.data + data

    def errReceived(self, data):
        print("errReceived! with %d bytes!" % len(data))

    def inConnectionLost(self):
        print("inConnectionLost! stdin is closed! (we probably did it)")

    def outConnectionLost(self):
        print("outConnectionLost! The child closed their stdout!")
# now is the time to examine what they wrote
#print("I saw them write:", self.data)
        (dummy, lines, words, chars, file) = re.split(r'\s+', self.data)
        print("I saw %s lines" % lines)

    def errConnectionLost(self):
        print("errConnectionLost! The child closed their stderr.")

    def processExited(self, reason):
        print("processExited, status %d" % (reason.value.exitCode,))

    def processEnded(self, reason):
        print("processEnded, status %d" % (reason.value.exitCode,))
        print("quitting")
        reactor.stop()

pp = MyPP(10)
reactor.spawnProcess(pp, "wc", ["wc"], {})
reactor.run()

이 프로그램의 정확한 출력은 비동기 이벤트 타이밍에 따라 다릅니다. 특히 프로그램은 하위 프로세스가 stderr 파이프가 닫히기 전이나 stdout 파이프로부터 읽은 후를 관찬 할 수 있다. 가능한 아래와 같을 것이다.

% ./process.py
connectionMade!
inConnectionLost! stdin is closed! (we probably did it)
errConnectionLost! The child closed their stderr.
outReceived! with 24 bytes!
outConnectionLost! The child closed their stdout!
I saw 40 lines
processEnded, status 0
quitting
Main loop terminated. 

Mapping File Descripotrs

stdin, stdout, stderr 은 단지 컨벤션이다. 프로그램은 일반적으로 input 은 fd0, 외부로 출력인 fd1, 에러를 발생하는 fd2 이다. 이것은 표준 C 라이브러리 매크로와 같다. stdin은 fd0 을 의미한다 그리고 쉘에선 파이프는 |을 의미하고 fd1으로 부터 명령을 다음 커맨드의 fd0 으로 리다이렉션 과 같다. 그러나 이것은 단지 컨벤션이며 프로그램은 자유롭게 file descriptors 를 추가 할수 있으며 표준 3개의 fd 를 무시 할 수도있다. childFDsargument는 자식 프로세스를 제공해야 하는 file descripotrs 를 정확히 지정 할 수 잇다.

각 childFD 는 3개중에 한개만 넣을 수 있다.

  • 부모 FD를 매핑 : 부모와 같은 읽기 쓰기와 같은 것을 할 수 있다
  • 부모로 부터 읽을 수 있는 파이프를 넣는다.
  • 파이프로 부터 부모에게 쓸수 있도록 넣는다.

childFDs를 매핑하는 것은 대게 하위 프로세스의 stderr 를 부모와 같은 곳에 출력 하기 위해 사용된다. shell 로부터 프로그램을 실행 시킬때 대게 fds 0, 1, 2는 쉘의 0,1,2와 매핑 되어 진 상태로 하위 프로그램의 출력을 같은 터미널에서 볼수 있도록 한다. 마찬가지로, inetd는 일반적으로 stdin 과 stdout 을 네트워크 소켓에 매핑하고 stderr를 동일한 소켓 또는 일종의 로깅 메커니즘에 매핑 할 수 있다. 이는 하위 프로그램이 네트워크에 대한 지식 없이 실행될수 있도록 한다.

부모의 읽기 파이프는 child의 output을 가지기 위해 사용되어지고 하위 프로세스와 interactive 를 하기위한 공통 공통된 방법이다.

부모의 쓰기 파이프를 하위 프로세스에 주면 하위 프로세스를 통제 할 수 있다. FTP와 BC 같은 프로그램은 명령을 stdin 스트림에 작성하여 이러한 방식으로 제어 할 수 있다.

childFDs dictionary는 file descriptor 숫자를 위의 조건 3가지의 상태중 1개를 할 수있다. 부모의 fd들중 한개를 맵핑하고 싶다면 간단하게 fd의 숫자를 값처럼 넣어주면 된다. read 파이프를 매핑 하려면 r을 값처럼 사용하면 된다. write 파이프를 매핑 하고 싶다면 w를 사용하면 된다.

아래 예제는 기본적으로 stdin/stdout/stderr 파이프를 매핑하는 것이다. 이것은 dictionary 타입으로 되어있다.

childFDs = {0: "w", 1: "r", 2: "r"}

부모의 파이썬 프로그램이 하는 것과 동일한 장소에 읽고 쓰는 프로세스를 시작하려면 다음을 사용한다.

childFDs = {0: 0, 1: 1, 2: 2}

fd를 추가하려면 다음과 같이 한다.

childFDs = {0: "w", 1: "r", 2: "r", 4: "w"}

ProcessProtocol with extra file descriptors

childFDs dictionary 를 보통 3개의 fd보다 더 제공 되어질때 이런 파이프를 접근 하기위해 추가적인 메소드가 필요하다. 이러한 방법은 위에서 설명한 outReceived 방법보다 더 일반적이다. 사실 이런 메소드(outReceived and errReceived) 는 오래된 코드를 호환하기위해 래핑되어있다. 새로운 ProcessProtocol 은 다음을 따르고 있다.

  • connectionMaded : 프로그램이 시작되어질때 호출 되어진다.
  • childDataReceived(childFD, data) :프로세스의 output pipe 들중 데이터를 받을때 호출되어진다 (예를 들어 childFDs 값이 r 이면 실질적인 숫자는(하위 프로세스의 관점에서) childFD에 있다. 호환을 위해 childFD가 1또는 2일때 childDataReceived outReceived errReceived로 디스패치하게 되어있다.)
  • childConnectionLost(childFD) : 이것은 reactor 로부터 프로세스의 파이프가 닫힐때 통지 도이ㅓ진다. 이것은 단지 부모의 파이프가 닫힌것인지( self.transport.closeChildFD과 같은), 하위 프로세스에서 명시적으로 파이프로 닫을때(때때로 EOF를 가르킴), 하위 프로세스가 종료 되거나 커널이 모든 파이프를 닫을때를 의미한다. childFD argument는 파이프가 닫혔다고 말한다. 파이프에 매핑된 파일 설명자에 대해서만 알수있다 기존 FD에 매핑 될때 부모는 그들이 언제 닫혔는지 알 수 있는 방법이 없습니다. 호환을 위해 기본적으로 inConnectonLost,outConnectionLost, errConnectionLost 디스패치 되어있다.
  • processEnded(status) : 하위 프로세스가 종료되어질때 나 파이프가 종료 되어질때 호출 되어진다. 이것은 종료 되기전에 모든 데이터들이processEnded가 실행되기 전에 쓰여진다.

이러한 것 이외에도 하위 프로세스에게 사용 할 수있는 메소드는 다음과 같다.

  • self.transport.writeToChild(childFD, data) : input 파이프로 데이터를 넣을때 간단하게 write는 childFD = 0 에쓰는 것과 같다.
  • self.transport.closeChildFD(childFD) : 하위 프로세스들중 하나를 닫아라. input 파이프를 다는것은 공통적으로 EOF 를 하위 프로세스에게 가르킨다. 출력 파이프를 다는것은 매우 유용하지도 않다.

'개발 > Python' 카테고리의 다른 글

Twisted/Using Process 번역  (0) 2019.03.07
Twisted/ Static File server file listing disable  (0) 2019.02.10

Twisted 를 사용하면서 웹의 static한 파일들을 사용하려면 Resource를 만들어서 serve해야 한다

위와 같이 serve할 path를 지정해주고 사용을 하면 되는 것인데, 이렇게 사용한다면 단점이 있다..

단점이라 함은 static.File Class가 자체적으로 해당 위치에 접속을 하게 되면 해당 파일 내용들이 나오게 되어 난감한 상황을 발생시키게 된다.

이것은 기본적으로 Twisted에서 directory listing을 off 하게 해주면 좋을듯 한데. 그것까진 지원이 되는것 같지 않아 아쉽다.

[기본적은 file serve하고 해당 폴더를 입력시 위와 같이 Directory Listing이 나온다]


해당 문제라기 보다는 Twisted의 기본적인 API문서를 보면 알게 되는데 해당 API는 아래와 같이 기술되어있다.


Twisted Static File API[링크]


위와같이 위치를 설정하면 해당 내용들이 자동적으로 HTML형식으로 리스팅 되어서 내온다는 말이다. 자 대충의 내용은 알았으니 실질적인 File Listing을 Disable을 해보자.


처리 방식은 다음과 같다.


해당 API문서를 보면 directoryListing이란 method 가 있는데 이 메소드의 반환값을보면 다음과 같다.



여기에서 DirecotryListeter라는 클래스가 반환이 되어 지는데 이는 위에 기본적으로 file serve direcotryListing 모습의 클래스를 반환한다 안에는 CSS/HTML 태그등 다양한 템플릿 형태로 저장 되어있다.


그렇다면 DirecotryLister는 무엇일까 이것도 또한 Resource의 일부임으로 DirecotryLister 대신에 다른 리소스를 리턴해 준다면 해당 파일 리스트는 생성되지 않을 것이다. 가장 해결하기 편한 방법은 간단히 해당 file Class를 상속받아서 direcotryListing을 오버라이딩해서 Resource만 return 해주면 된다.

그래서 나는 다음과 같이 해당 클래스를 생성해서 사용하기로 했다.

이렇게 정의 하고 이것을 Resource로 사용한다면 해당 디렉터리 목록은 나오지 않게 될 것이다.



[설정 적용된 모습]


이렇게 한다면 해당 디렉터리 목록은 나오지 않지만 해당 static 파일들은 내오게 된다. 이런식으로 사용한다면 조금이나마 보안 설정을 할 수 있게 되지 않을까? 선택은 개인의 몫이며 해당 관련된 내용중 틀린 내용이 있거나 좋은 내용이 있다면 답글 달아주면 감사 하겠습니다.





'개발 > Python' 카테고리의 다른 글

Twisted/Using Process 번역  (0) 2019.03.07
Twisted/ Static File server file listing disable  (0) 2019.02.10

+ Recent posts