3

最終的な作業コードについては、この投稿の下部をご覧ください。

これは、ローカルソケットを介してコマンドを送信する別のスクリプトを呼び出すことにより、CGI スクリプトへのユーザー入力を取得できる、動作する Python/CGI スクリプトです。


元の投稿:

私の知る限り、すでにヘッダーを送信している Python/CGI スクリプトにユーザー入力を直接送信する方法はありません。同様に、特定の状況下でユーザーに警告し、確認を待ちます。これに対する公開された解決策を見つけることもできませんでした。

私が間違っている場合は、私を修正してください。

現在、サーバーへの接続、ファームウェアのアップロード、再起動、再接続、いくつかの構成ファイルの変更などを行うことができる Python スクリプトがあります。

場合によっては、スクリプトを再起動して最初から実行しなくても、多くのユーザーがスクリプトに入力を送信できると便利です。2G ネットワークでの再接続に時間がかかりすぎます。

ユーザー入力を別のスクリプトに送信し、最初の/メインスクリプトが監視しているファイルに投稿して、入力を受信することが可能でなければならないと考えています。stop/kill 入力コマンドを使用してスクリプトの実行を停止できれば、それもいいでしょう。stop/kill コマンドに関しては、メイン スクリプトに 2 つのスレッドが必要です。そうでない場合、大きなファイルのアップロードなどのプロセスが実行されている場合、アップロードが完了する前に、スクリプトを停止する必要があることがわかります。

同時に、複数のユーザーが同時にスクリプトを使用できるようにする必要があると思います。したがって、メイン スクリプトが起動するたびに一意の ID を生成する必要があります。

これが私がそれを作ることができると思う方法です:

メインスクリプトが呼び出される

Global variable with a unique session ID is generated and sent to client.

Thread 1
    pexpect spawns a "tail -F /var/www/cgi/tmp_cmd.log"

Thread 2
    Thread status "Busy"
    Connects to network element
    Does its usual stuff until it reaches a point where the user needs to interact.
    Prints the message to user and waits for Thread 1 with a timeout of x seconds.
    Thread status "Ready"

2 番目のスクリプトは、2 つのヘッダー (セッション ID と入力) を使用して AJAX を介してユーザーによって呼び出されます。

2 番目のスクリプト

Session ID and user input is saved to "/var/www/cgi/tmp_cmd.log"
Execution of the input script ends

メインスクリプト

Thread 1 
    User input recieved.
    Wait for Thread 2 status to become "Ready" or ignore status if command is equals to "kill" ect.
    Send user input (single line) and start Thread 1 from the beginning
Thread 2 
    Thread 2 status "Busy"
    Input recieved and process stops/continues.
    Thread 2 status "Ready"

接続、ファイルのアップロード、およびコマンドの実行のためのスクリプトを作成しました。ただし、ユーザー入力を受け取ることはできません。

私は本当に良い助けを借りたり、誰かがこれにアプローチする方法を教えてくれたりすることができます.

もちろん、スクリプトが完成したら、ここまたはペーストビンに投稿し、他の人が使用できるようにリンクします。:)


最終コード

以下の投稿の助けを借りて、ようやく動作するコードを手に入れました。スレッドを使用することもできますが、プロセスを停止/キャンセルする方が簡単に理解できるようです。

クライアント- cgi_send.py

#!/usr/bin/python
import sys, cgi, cgitb, socket

cgitb.enable()
TASKS_DIR = "/var/www/cgi-bin/tmp"

def main():
    global TASKS_DIR

    url = cgi.FieldStorage()
    cmd = str(url.getvalue('cmd'))
    sessionId = str(url.getvalue('session'))
    socketLocation = TASKS_DIR + '/%s.socket' % sessionId

    print '<a href="?session='+sessionId+'&cmd=QUIT">End script</a>&nbsp;<a href="?session='+sessionId+'&cmd=CANCEL">Cancel task</a>'
    print '<form action=""><input type="hidden" name="session" id="session" value="'+sessionId+'" /><input type="text" name="cmd" id="cmd" value="" /><input type="submit" value="Fun!" />'

    try:

        sock = socket.socket(socket.AF_UNIX)
        sock.setblocking(0)
        sock.connect(socketLocation)
        sock.send(cmd)
        sock.close()
        print '<br />Command sent: '+ cmd;

    except IOError:
        print '<br /><b>Operation failed.</b><br /> Could not write to socket: '+ socketLocation
        pass

    sock.close()

    sys.exit();

if __name__ == '__main__':
    sys.stdout.write("Content-type:text/html;charset=utf-8\r\n\r\n")
    sys.stdout.write('<!DOCTYPE html>\n<html><head><title>Test</title></head><body>')

    main()

    print '</body></html>'
    sys.exit()

サーバ

#!/usr/bin/python
import sys, os, socket, uuid, time, multiprocessing

# Options
TASKS_DIR = "/var/www/cgi-bin/tmp/"

def main():

    sessionId = str(uuid.uuid4())

    print 'Session ID: '+ sessionId
    sys.stdout.write ('<br /><a href="cgi_send.py?cmd=test&session=' + sessionId +'" target="cmd_window">Send test command</a>')
    sys.stdout.flush()

    address = os.path.join(TASKS_DIR, '%s.socket' % sessionId)

    sock = socket.socket(socket.AF_UNIX)
    sock.setblocking(0)
    sock.settimeout(.1)
    sock.bind(address)
    sock.listen(1)

    taskList = [foo_task, foo_task, foo_task]

    try:
        for task in taskList:

            print "<br />Starting new task"
            runningTask = multiprocessing.Process(target=task)
            runningTask.daemon = True # Needed to make KeyboardInterrupt possible when testing in shell
            runningTask.start()

            while runningTask.is_alive():
                conn = None
                try:
                    conn, addr = sock.accept()
                    data = conn.recv(100).strip()

                except socket.timeout:
                    # nothing ready from a client
                    continue

                except socket.error, e:
                    print "<br />Connection Error from client"

                else:
                    print "<br />"+ data
                    sys.stdout.flush()
                    conn.close()

                    if data == "CANCEL":
                        # temp way to cancel our task
                        print "<br />Cancelling current task."
                        runningTask.terminate()

                    elif data == "QUIT":
                        print "<br />Quitting entire process." 
                        runningTask.terminate()
                        taskList[:] = []

                finally:
                    if conn:
                        conn.close()

    except (KeyboardInterrupt, SystemExit):
        print '\nReceived keyboard interrupt, quitting threads.'

    finally:
        sock.close()
        os.remove(address)

def foo_task():
    i = 1
    while 10 >= i:
        print "<br />Wating for work... "+ str(i)
        sys.stdout.flush()
        i = i + 1
        time.sleep(1)


if __name__ == '__main__':
    sys.stdout.write("Content-type:text/html;charset=utf-8\r\n\r\n")
    sys.stdout.write('<!DOCTYPE html>\n<html><head><title>Test</title></head><body>')

    main()

    print '</body></html>'
    sys.exit()
4

1 に答える 1

2

CGI スクリプトはかなり原始的な操作です。コマンド シェルから実行する通常のスクリプトと基本的に同じように機能します。Web サーバーに対して http 要求が行われます。サーバーは新しいプロセスを開始し、stdin を介して引数をスクリプトに渡します。この時点で、通常のスクリプトのようになります。

何らかの手段で入力を探していない限り、スクリプトはこれ以上入力を取得できません。そのため、ヘッダーが送信されると、Web クライアントはそれ以上の入力を直接送信できなくなると仮定するのは正しいことです。対応もすでに進行中です。

ファイルを監視するスレッドは、スクリプトに制御ループを導入する 1 つの方法です。もう 1 つは、各インスタンスの一意の ID に基づくパスへの UNIXソケットを開くことです。次に、入力用のソケットにスレッドを配置します。次に行う必要があるのは、ID を Web クライアントに戻すことです。クライアントは、ID を使用して 2 番目のスクリプトを呼び出すことができます。これにより、制御コマンドを送信する適切な UNIX ソケット パスが認識されます。

/tmp/script-foo/control/<id>.socket

実際には、必要なスレッドは 1 つだけかもしれません。メインスレッドは、ソケットに関する情報のチェックと、スレッドまたはサブプロセスで実行されている現在の操作の監視を単純にループできます。擬似コードでは次のようになります。

uid = generate_unique_id()
sock = socket.socket(AF_UNIX)
sock.bind('/tmp/script-foo/control/%s.socket' % uid)
# and set other sock options like timeout

taskList = [a,b,c]
for task in taskList:
    runningTask = start task in thread/process
    while runningTask is running:
        if new data on socket, with timeout N ms
            if command == restart:
                kill runningTask
                taskList = [a,b,c]
                break
            else:
                process command

Web クライアントが ajax 経由で 2 番目のスクリプトにコマンドを送信すると、擬似コードで次のようになります。

jobid = request.get('id')
cmd = request.get('cmd')
sock = socket.socket(socket.AF_UNIX)
sock.connect('/tmp/script-foo/control/%s.socket' % jobid)
sock.sendall(cmd)
sock.close()

アップデート

あなたのコードの更新に基づいて、私が提案していたものの実際の例を次に示します。

import sys
import os
import socket
import uuid 
import time 

# Options
TASKS_DIR = "."

def main():

    sessionId = str(uuid.uuid4())

    print 'Session ID: '+ sessionId
    sys.stdout.write ('<br /><a href="cgi_send.py?cmd=test&session=' + sessionId +'" target="_blank">Send test command</a>')
    sys.stdout.flush()

    address = os.path.join(TASKS_DIR, '%s.socket' % sessionId)

    sock = socket.socket(socket.AF_UNIX)
    sock.setblocking(0)
    sock.settimeout(.1)
    sock.bind(address)
    sock.listen(1)


    fakeTasks = [foo_task, foo_task, foo_task]

    try:
        for task in fakeTasks:

            # pretend we started a task
            runningTask = task()
            # runningTask = Thread(target=task) 
            # runningTask.start()

            # while runningTask.is_alive():   
            while runningTask:
                conn = None
                try:
                    conn, addr = sock.accept()
                    data = conn.recv(100).strip()

                except socket.timeout:
                    # nothing ready from a client
                    continue

                except socket.error, e:
                    print "<br />Connection Error from client"

                else:
                    print "<br />"+ data
                    sys.stdout.flush()
                    conn.close()

                    # for the thread version, you will need some 
                    # approach to kill or interrupt it. 
                    # This is just simulating. 
                    if data == "CANCEL":
                        # temp way to cancel our task
                        print "<br />Cancelling current task." 
                        runningTask = False

                    elif data == "QUIT":
                        print "<br />Quitting entire process." 
                        runningTask = False 
                        fakeTasks[:] = []

                finally:
                    if conn:
                        conn.close()

    finally:
        sock.close()
        os.remove(address)



def foo_task():
    print 'foo task'
    return True


if __name__ == '__main__':
    sys.stdout.write("Content-type:text/html;charset=utf-8\r\n\r\n")
    sys.stdout.write('<!DOCTYPE html>\n<html><head><title>Test</title></head><body>')

    main()

    print '</body></html>'
    sys.exit()

10 秒のグローバル タイムアウトを使用する代わりに、100 ミリ秒などの小さな値に設定します。各タスクをループして (最終的にはスレッドで) 開始し、ソケット接続を待ってループを試みます。100 ミリ秒以内に接続がない場合、タイムアウトになり、タスクが完了したかどうかを確認しながらループを続けます。いつでも、クライアントは接続して「CANCEL」または「QUIT」コマンドを発行できます。ソケットは接続を受け入れ、読み取り、反応します。

ここでは、ソリューションに複数のスレッドが必要ないことを確認できます。必要な唯一のスレッドまたはサブプロセスは、タスクを実行することです。

于 2012-08-10T01:32:15.193 に答える