10

Alright, I managed to get it working how I want (although some/most might disagree with the method). I used Flask as recommended below. The part that might be considered "wrong" is the 404 check loop. It isn't proper design and results in a "stuck script" error in some browsers if it goes on for too long. However, the script I want to run doesn't last long enough for that to be a problem.

Thanks for the help and please let me know if you have any other suggestions.

Flask App:

import threading
import subprocess
import os
import sys
from flask import Flask
from flask import render_template, abort
app = Flask(__name__)
app.debug = True

def run_script():
    theproc = subprocess.Popen([sys.executable, "run_me.py"])
    theproc.communicate()

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/generate')
def generate():
    threading.Thread(target=lambda: run_script()).start()
    return render_template('processing.html')

@app.route('/is_done')
def is_done():
    hfile = "templates\\itworked.html"
    if os.path.isfile(hfile):
        return render_template('itworked.html')
    else:
        abort(404)

if __name__ == "__main__":
    app.run()

processing.html:

<!DOCTYPE html>
<html>
<head>
    <script src="/static/jquery.min.js"></script>
</head>

<body>
<p>Processing...</p>
<script>
    setInterval(function()
    {
        var http = new XMLHttpRequest();
        http.open('HEAD', "is_done", false);
        http.send();
        if (http.status==200)
            window.location.replace("is_done");
    },2000);
</script>
</body>
</html>

Original question:

I am a beginner/intermediate Python programmer and a beginning web developer so please be gentle. What I want: A user clicks a hyperlink and is taken to an intermediate web page that says something like "Processing...". In the background (on the server) this link triggers a python script that processes a file and creates a new web page. On completion of the script, the user is redirected to the newly created page. What I have: I have a script that does the processing and spits out a new page. What I haven't figured out is how to trigger the python script and then trigger the redirect on script completion. I've looked at web frameworks like django, and cgi scripting. But I haven't found anything I feel fits the bill. It's very possible I'm just missing something completely obvious so I'd really appreciate any help. Thanks.

4

4 回答 4

8

解决这个问题的一个简单方法是使用像 Flask 这样的简单 Web 框架来构建系统的 Web 部件。在魔术链接的请求处理程序中,您需要生成脚本并跟踪它。查看脚本是否完成并将其转发给用户的一种简单方法是定期发送 ajax 请求以检查是否完成。

例如,Flask 网站可能如下所示:

import threading
import subprocess
import uuid
from flask import Flask
from flask import render_template, url_for, abort, jsonify, request
app = Flask(__name__)

background_scripts = {}

def run_script(id):
    subprocess.call(["/path/to/yourscript.py", "argument1", "argument2"])
    background_scripts[id] = True

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/generate')
def generate():
    id = str(uuid.uuid4())
    background_scripts[id] = False
    threading.Thread(target=lambda: run_script(id)).start()
    return render_template('processing.html', id=id)

@app.route('/is_done')
def is_done():
    id = request.args.get('id', None)
    if id not in background_scripts:
        abort(404)
    return jsonify(done=background_scripts[id])

index.html

<a href="{{ url_for('generate') }}">click me</a>

并且processing.html

<html>
<head>
<script src="/static/jquery.js"></script>
<script>
    function ajaxCallback(data) {
        if (data.done)
            window.location.replace("http://YOUR_GENERATED_PAGE_URL");
        else
            window.setTimeout(function() {
                $.getJSON('{{ url_for('is_done') }}', {id: {{ id }} }, ajaxCallback);
            }, 3000);
    }
    $(document).ready(function(){
        ajaxCallback({done=false});
    });
</script>
</head>
<body>
    Processing...
</body></html>

目前这都是未经测试的代码,但我希望你对如何解决这个问题有所了解。另外请记住,这仅在您从一个进程提供页面时才有效,因此如果您设置 Apache 和 mod_wsgi,请确保进程组中只有一个进程。

如果您需要更复杂的解决方案,您可能需要查看消息队列等。

于 2012-06-05T21:14:25.397 回答
2

这对您来说可能是一个过于繁重的解决方案,但假设您无法取消“在服务器上创建文件”部分,这被认为是 Web 开发中的“最佳实践”解决方案:

使用 Flask 或 Django 之类的 Web 框架来提供页面。当用户请求通过请求(最好是 POST 请求,因为 GET 请求不会引起明显的副作用)启动作业时,框架会向 Celery 等延迟任务系统发送消息。然后向用户显示一个页面,说明作业已提交。

此时,您可以使用 ajax 轮询服务器并查看作业何时完成,但通常您不希望这是查看作业是否完成的唯一方法。如果连接因任何原因而中断,例如,如果用户错误地关闭了浏览器或选项卡(非常常见),那么努力将被浪费,用户将不得不重新开始该过程。如果可能的话,你应该有一种与用户交流的备用方式;这可能是出现在网站其他地方的快速消息、自动“工作完成”电子邮件、主页中显示最近完成的工作的框等。这有多实用和重要取决于用户是否登录以及如何登录工作需要多长时间/除了创建文件之外是否有副作用。

最后,您可以允许用户通过对该用户的工作结果唯一的 url 访问该页面。如果该 url 不会无限期地有效,请务必通知用户,例如,如果文件将在计时器上被删除。

于 2012-06-06T15:49:41.357 回答
0

你想要什么是可能的,但这实际上是关于 3 个问题。

第一个问题是,如何从 PHP 创建一个新文件。我真的无法回答这个问题。您需要做什么,可能取决于您要创建的文件类型以及原因。

第二个问题是,如何让用户知道正在发生这种情况,第三个问题是如何重定向到新页面。

就个人而言,我认为通往胜利的最短途径可能是让您的客户端 javascript 向创建新内容的服务器页面或 CGI 模块发出 AJAX 请求。

  1. 单击按钮时,向生成新内容页面的 PHP 页面发出 ajax 请求。
  2. 展示您的忙碌指示器。
  3. 让请求的页面返回新页面的 URL 作为其内容。
  4. 当请求完成时,通过 javascript 重定向到返回的 URL。

作为 Web 开发的新手,最容易做的事情可能是研究 jQuery 和它的 AJAX 功能。那里的包装器使上述操作变得非常容易。当然,您也可以对任何其他主要的 javascript 框架或直接 javascript 执行相同的操作。

以供参考:

于 2012-06-05T19:54:25.643 回答
0

两种简单的方法来做到这一点:

1)使用ajax轮询服务器。每 10 秒或您认为合适的任何间隔检查一次是否完成。例如,当脚本正在处理时,它会写入文件或数据库,无论它可能是指示完成百分比以及以某种方式识别进程(如果您有多个脚本同时运行,您会想知道哪个是Bob 的,哪一个是 Suzy 的)。

2)禁用输出缓冲并将输出流式传输到客户端。这比第一个选项简单。基本上,运行脚本并在处理过程中您可以输出 javascript 代码来更新进度指示器。您需要禁用输出缓冲,或手动刷新缓冲区,否则您的客户端可能不会立即收到输出(指标更新可能仅在达到 100% 时对客户端可见)。谷歌如何在您运行的任何设置(例如 PHP、Django)上执行此操作。

于 2012-06-05T19:48:17.207 回答