Python-用户输入CGI的方式.线程读取文件 [英] Python - User input to CGI via. Threading and reading file

查看:95
本文介绍了Python-用户输入CGI的方式.线程读取文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

查看这篇文章的底部,以获取最终的工作代码.

这是一个有效的Python/CGI脚本,可以通过调用另一个脚本,然后通过本地套接字发送命令来获取用户对CGI脚本的输入.

It's a working Python/CGI script which can get user-input to a CGI-script by calling another script which then sends it's commands through a local socket.

原始帖子:

据我所知,没有任何方法可以将用户输入直接发送到已经发送了标头的Python/CGI脚本.例如,在特定情况下警告用户并等待确认. 我也找不到任何已发布的解决方案.

As far as I know, there isn't any way to send user input directly to a Python/CGI script which has allready sent it's header. Like, warning the user under specific circumstances and waiting for a confirmation. Neither have I been able to find any published solutions to this.

如果我错了,请纠正我.

If I'm wrong, please correct me.

我目前有一个Python脚本,可以连接到服务器,上传固件,重新启动,重新连接,更改一些配置文件等等.

I currently have a Python script which can connect to servers, upload firmware, reboot, re-connect, change a few configuration files and such.

有时候,这将帮助很多用户将输入发送到脚本,而不必重新启动脚本并从头开始执行它.通过2G网络重新连接花费的时间太长.

Sometimes, it would help alot of the user could send input to script, without having to re-launch the script and execute it from the beginning. Re-connecting over a 2G network takes too long.

我认为必须将用户输入发送到另一个脚本,然后再将其发布到第一个/主脚本正在监视的文件中,直到它接收到输入. 如果能够使用stop/kill输入命令停止脚本的执行,那也很好. 至于stop/kill命令,主脚本将需要具有2个线程.如果没有,它将知道应该停止脚本,如果正在执行诸如大文件上载之类的过程,则在完成上载之前.

I'm thinking that it must be possible to send user input to another script, which then posts it to a file, which the first/main script is watching, until it recieves the input. It would also be nice, if the was able to stop the execution of the script, with a stop/kill input command. As for the stop/kill command, the main script would need to have 2 threads. If it did not, it would know it should stop the script, if a process such as a large file upload is being executed, before the upload is completed.

同时,我认为multipe用户应该能够同时使用脚本.因此,每次启动主脚本时都必须生成一个唯一的ID.

At the same time, I think multipe users should be able to use the script at the same time. Therefore, a unique ID must be generated every time the main script launches.

这是我认为可以做到的方式:

Here's how I think it could be made:

主脚本被调用

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个标头(会话ID和输入)的AJAX调用第二个脚本

Second script gets called by the user through AJAX with 2 headers (session ID & input)

第二个脚本

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"

我已经准备好用于连接,上传文件和运行命令的脚本. 但是,它无法接收用户输入.

I have made a script allready for connecting, uploading files, and running commands. However, it cannot recieve user-input.

我真的可以使用一些很好的帮助,或者有人告诉我如何解决这个问题.

I could really use some good help, or someone to tell me how to approach this.

当然,只要脚本完成,我都会在此处或粘贴框上发布并链接到该脚本,以供其他人使用. :)

Of course, whenever the script has been completed, I will post it here or on pastebin and link to it, for other people to use. :)

最终代码

在下面的文章的帮助下,我终于有了工作代码. 它可以使用Threads,但是停止/取消进程对我来说似乎更容易解决.

With help from the post below, I have finally have the working code. It could use Threads, but stopping/cancelling processes appeared to be way easier for me to figure out.

客户端-cgi_send.py

Client - 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()

推荐答案

CGI脚本是一种非常原始的操作.它的工作原理与从命令外壳运行的任何普通脚本基本相同.向Web服务器发出了http请求.服务器启动一个新进程,并将参数通过stdin传递给脚本.在这一点上,它就像一个普通的脚本.

A CGI script is a pretty primitive operation. It works basically the same as any normal script you run from your command shell. An http request is made to the web server. The server starts a new process and passes the arguments in via stdin to the script. At this point, it's like a normal script.

除非脚本正在通过某种方式寻找输入,否则脚本将无法获得任何更多输入,因此您可以正确地假设一旦发送标头后,Web客户端就无法再直接发送更多输入,因为请求已经存在正在进行中,响应也已经在进行中.

A script can't get any more input unless it's looking for input by some means, so you are correct in assuming that once the headers are sent, the web client can no longer directly send more input, because the request is already in progress, and the response is already in progress as well.

监视文件的线程是向脚本引入控制循环的一种方法.另一个方法是将UNIX 套接字打开到基于您的路径每个实例的唯一ID.然后将线程放在套接字上进行输入.然后,您要做的就是将ID传递回Web客户端.然后客户端可以使用ID调用第二个脚本,该脚本随后将知道将控制命令发送到的正确的UNIX套接字路径:

A thread watching a file is one way to introduce a control loop to the script. Another is to open a UNIX socket to a path based on your unique ID for each instance. Then have the thread sitting on the socket for input. What you would then have to do is pass the ID back to the web client. And the client could make a call to the second script with the ID, which would then know the proper UNIX socket path to send control commands to: ie.

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

您实际上可能只需要1个线程.您的主线程可以简单地循环检查套接字上的信息,并监视线程或子进程中正在运行的当前操作.伪代码中可能是这样的:

You actually might only need 1 thread. You main thread could simply loop over checking for information on the socket, and monitoring the current operation being run in a thread or subprocess. It might be like this in pseudocode:

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向您的第二个脚本发送命令时,它的伪代码可能如下所示:

When the web client sends a command via ajax to your second script, it might look like this in pseudocode:

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()

更新

根据您的代码更新,这是我建议的有效示例:

Based on your code update, here is a working example of what I was suggesting:

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()

您可以将其设置为100ms之类的小数值,而不是使用10秒的全局超时.它遍历每个任务并启动它(最终在线程中),然后尝试遍历等待套接字连接.如果在100毫秒内没有连接,它将超时并继续循环,同时检查任务是否完成.客户端可以随时连接并发出取消"或退出"命令.套接字将接受连接,读取连接并做出反应.

Instead of using a 10 second global timeout, you set it to something small like 100ms. It loops over each task and starts it (eventually in a thread), and then tries to loop over waiting for a socket connection. If there is no connection within 100ms, it will timeout and continue to loop, while checking if the task is done. At any point, a client can connect and issue either a "CANCEL" or "QUIT" command. The socket will accept the connection, read it, and react.

您可以在这里看到不需要多个线程的解决方案.您唯一需要的线程或子流程就是运行任务.

You can see how you do not need multiple threads here for the solution. The only threading or subprocess you need is to run the task.

这篇关于Python-用户输入CGI的方式.线程读取文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆