- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
查看这篇文章的底部,了解最终的工作代码。
这是一个工作的 Python/CGI 脚本,它可以通过调用另一个脚本然后通过本地套接字发送它的命令来获取用户对 CGI 脚本的输入。
原帖:
据我所知,没有任何方法可以将用户输入直接发送到已经发送了它的 header 的 Python/CGI 脚本。比如,在特定情况下警告用户并等待确认。我也没有找到任何已发布的解决方案。
如果我错了,请指正。
我目前有一个 Python 脚本,可以连接到服务器、上传固件、重启、重新连接、更改一些配置文件等。
有时,它可以帮助很多用户将输入发送到脚本,而无需重新启动脚本并从头开始执行。通过 2G 网络重新连接需要很长时间。
我在想,一定有可能将用户输入发送到另一个脚本,然后将其发布到一个文件中,第一个/主脚本正在监视该文件,直到它收到输入。如果能够使用停止/终止输入命令停止脚本的执行,那也很好。至于 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 个 header ( session ID 和输入)的 AJAX 调用第二个脚本
第二个脚本
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"
我已经为连接、上传文件和运行命令准备了一个脚本。但是,它无法接收用户输入。
我真的需要一些好的帮助,或者有人告诉我如何处理这个问题。
当然,每当脚本完成后,我都会将其张贴在这里或在 pastebin 上并链接到它,以供其他人使用。 :)
最终代码
在下面帖子的帮助下,我终于有了工作代码。它可以使用线程,但停止/取消进程对我来说似乎更容易理解。
客户端 - 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> <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 脚本是一种非常原始的操作。它的工作原理与您从命令 shell 运行的任何普通脚本基本相同。向 Web 服务器发出 http 请求。服务器启动一个新进程并通过标准输入将参数传递给脚本。在这一点上,它就像一个普通的脚本。
除非通过某种方式寻找输入,否则脚本无法获得更多输入,因此您假设一旦发送 header ,Web 客户端就无法再直接发送更多输入,因为请求已经正在进行中,响应也已经在进行中。
监视文件的线程是向脚本引入控制循环的一种方式。另一种是打开UNIX socket到基于每个实例的唯一 ID 的路径。然后让线程坐在套接字上进行输入。然后您需要做的是将 ID 传回 Web 客户端。并且客户端可以调用带有 ID 的第二个脚本,然后它会知道将控制命令发送到的正确 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 向您的第二个脚本发送命令时,伪代码可能如下所示:
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 毫秒这样的小值。它循环遍历每个任务并启动它(最终在一个线程中),然后尝试循环等待套接字连接。如果100ms内没有连接,会超时继续循环,同时检查任务是否完成。在任何时候,客户端都可以连接并发出“CANCEL”或“QUIT”命令。套接字将接受连接、读取它并使用react。
您可以在这里看到如何不需要多线程来解决这个问题。您唯一需要的线程或子进程就是运行任务。
关于Python - 用户通过 CGI 输入。线程和读取文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11893922/
我是一名优秀的程序员,十分优秀!