Estoy escribiendo una página cgi en Python. Digamos que un cliente envía una solicitud a mi página cgi. Mi página cgi realiza el cálculo y, tan pronto como tiene la primera salida, envía esa salida al cliente, pero CONTINUARÁ para hacer el cálculo y enviar otras respuestas DESPUÉS se envía la primera respuesta.

¿Es posible lo que he presentado aquí? Hago esta pregunta porque, en mi conocimiento limitado, en una página cgi las respuestas se envían de vuelta una vez, una vez que se envía una respuesta, la página cgi deja de ejecutarse. Esto se hace en el lado del servidor o del lado del cliente, y ¿cómo lo implemento?

Mi servidor está ejecutando Apache. Muchas gracias.

He intentado un código de cliente de "dbr" en este foro (gracias a él tuve la idea de cómo funciona la votación larga).

<html>
<head>
    <title>BargePoller</title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript" charset="utf-8"></script>

    <style type="text/css" media="screen">
      body{ background:#000;color:#fff;font-size:.9em; }
      .msg{ background:#aaa;padding:.2em; border-bottom:1px #000 solid}
      .old{ background-color:#246499;}
      .new{ background-color:#3B9957;}
    .error{ background-color:#992E36;}
    </style>

    <script type="text/javascript" charset="utf-8">
    function addmsg(type, msg){
        /* Simple helper to add a div.
        type is the name of a CSS class (old/new/error).
        msg is the contents of the div */
        $("#messages").append(
            "<div class='msg "+ type +"'>"+ msg +"</div>"
        );
    }

    function waitForMsg(){
        /* This requests the url "msgsrv.php"
        When it complete (or errors)*/
        $.ajax({
            type: "GET",
            url: "msgsrv.php",

            async: true, /* If set to non-async, browser shows page as "Loading.."*/
            cache: false,
            timeout:50000, /* Timeout in ms */

            success: function(data){ /* called when request to barge.php completes */
                addmsg("new", data); /* Add response to a .msg div (with the "new" class)*/
                setTimeout(
                    'waitForMsg()', /* Request next message */
                    1000 /* ..after 1 seconds */
                );
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                addmsg("error", textStatus + " (" + errorThrown + ")");
                setTimeout(
                    'waitForMsg()', /* Try again after.. */
                    "15000"); /* milliseconds (15seconds) */
            },
        });
    };

    $(document).ready(function(){
        waitForMsg(); /* Start the inital request */
    });
    </script>
</head>
<body>
    <div id="messages">
        <div class="msg old">
            BargePoll message requester!
        </div>
    </div>
</body>
</html>

Y aquí está mi código de servidor:

import sys
if __name__ == "__main__":
    sys.stdout.write("Content-Type: text/html\r\n\r\n")
    print "<html><body>"
    for i in range(10):
        print "<div>%s</div>" % i
        sys.stdout.flush()
    print "</body></html>"

Espero que mi página de cliente muestre 1 número a la vez (0,1,2, ...), pero los datos siempre salen todos a la vez (01234 ...). Por favor, ayúdame a resolverlo. Muchas gracias chicos.

Solo un poco fuera de pista, estoy tratando de usar el complemento jquery comet, pero no pude encontrar suficiente documentación. Las ayudas serían muy apreciadas. Gracias de nuevo: D

[editar] Ok chicos, finalmente gracias a sus guías he logrado que funcione. Tienes razón cuando predices que mod_deflate es la fuente de todo esto.

En resumen, lo que he hecho aquí:

  • Para el cliente, haga una página de encuesta larga como el código html anterior

  • Para el servidor, deshabilite mod_deflate: editando el archivo /etc/apache2/mods-available/deflate.conf, comente la línea con la parte text / html y reinicie el servidor. Para asegurarse de que Python no almacena el resultado en sí, incluya #! / Usr / bin / python -u al comienzo de la página. Recuerde usar sys.stdout.flush () después de cada impresión que desea que aparezca en el cliente. El efecto puede no ser transparente, debe incluir time.sleep (1) para probar. :RE

Muchas gracias a todos por apoyar y ayudar a resolver esto: D

8
wakandan 9 dic. 2009 a las 16:00

4 respuestas

La mejor respuesta

Seguro.

Existe un enfoque tradicional dirigido por el servidor, donde el script se ejecuta solo una vez, pero lleva mucho tiempo completarlo, escupiendo trozos de página a medida que avanza:

import sys, time

sys.stdout.write('Content-Type: text/html;charset=utf-8\r\n\r\n')

print '<html><body>'
for i in range(10):
    print '<div>%i</div>'%i
    sys.stdout.flush()
    time.sleep(1)

Al escribir una aplicación en WSGI, esto se hace haciendo que la aplicación devuelva un iterable que genera cada bloque que desea que se envíe por separado. Realmente recomiendo escribir a WSGI; puede implementarlo a través de CGI ahora, pero en el futuro cuando su aplicación necesite un mejor rendimiento, puede implementarlo a través de un servidor / interfaz más rápido sin tener que volver a escribirlo.

Ejemplo de WSGI sobre CGI:

import time, wsgiref.handlers

class MyApplication(object):
    def __call__(self, environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
        return self.page()

    def page(self):
        yield '<html><body>'
        for i in range(10):
            yield '<div>%i</div>'%i
            time.sleep(1)

application= MyApplication()
if __name__=='__main__':
    wsgiref.handlers.CGIHandler().run(application)

Tenga en cuenta que su servidor web puede frustrar este enfoque (para CGI o WSGI) agregando su propio almacenamiento en búfer. Esto suele suceder si está utilizando filtros de transformación de salida como mod_deflate para comprimir automáticamente la salida de la aplicación web. Deberá desactivar la compresión para los scripts que generan respuestas parciales.

Esto lo limita a renderizar la página bit a bit a medida que ingresan nuevos datos. Puede hacerlo más bonito haciendo que el lado del cliente se encargue de alterar la página a medida que ingresan nuevos datos, por ejemplo:

def page(self):
    yield (
        '<html><body><div id="counter">-</div>'
        '<script type="text/javascript">'
        '    function update(n) {'
        '        document.getElementById("counter").firstChild.data= n;'
        '    }'
        '</script>'
    )
    for i in range(10):
        yield '<script type="text/javascript">update(%i);</script>'%i
        time.sleep(1)

Esto se basa en las secuencias de comandos del lado del cliente, por lo que podría ser una buena idea incluir una salida final de respaldo no basada en secuencias de comandos al final.

Mientras hace esto, la página parecerá que todavía se está cargando. Si no quiere eso, entonces necesitaría dividir el script en una primera solicitud que solo escupe el contenido estático, incluido el script del lado del cliente que verifica nuevamente con el servidor usando una solicitud XMLHttpRequest que sondea para obtener nuevos datos a través de, o, para los casos de larga duración, muchas XMLHttpRequests, cada una de las cuales devuelve el estado y cualquier dato nuevo. Este enfoque es mucho más complicado, ya que significa que debe ejecutar su proceso de trabajo como un proceso de demonio en segundo plano aparte del servidor web, y pasar datos entre el demonio y la solicitud de CGI / WSGI front-end utilizando, por ejemplo. tuberías o una base de datos.

9
bobince 9 dic. 2009 a las 14:04

Sí, eso es posible y no tiene que hacer mucho, ya que imprime los datos, el servidor lo enviará, solo para asegurarse de seguir limpiando stdout

1
Anurag Uniyal 9 dic. 2009 a las 13:17

El truco en los antiguos programas CGI es usar el Transfer-Encoding: chunked encabezado HTTP:

3.6.1 Codificación de transferencia fragmentada

La codificación fragmentada modifica el cuerpo de un mensaje para transferirlo como una serie de fragmentos, cada uno con su propio indicador de tamaño, seguido de un avance OPCIONAL que contiene campos de encabezado de entidad. Esto permite transferir contenido producido dinámicamente junto con la información necesaria para que el destinatario verifique que ha recibido el mensaje completo.

Cuando haya un resultado disponible, envíelo como un fragmento separado: el navegador mostrará este mensaje HTTP autónomo. Cuando llega otro fragmento más tarde, se muestra una PÁGINA NUEVA .

Tendrá que producir los encabezados correctos para cada fragmento dentro del programa CGI. Además, recuerde enjuagar la salida CGI al final de cada fragmento. En Python, esto se hace con sys.stdout .flush()

1
Community 20 jun. 2020 a las 09:12

Hay algunas técnicas.

La forma tradicional es continuar transmitiendo datos y hacer que el navegador continúe procesándolos mediante el procesamiento progresivo. Entonces, como un CGI antiguo, simplemente haga sys.stdout.flush(). Esto muestra una página parcial a la que puede seguir agregando, pero se ve torpe en el navegador porque el throbber seguirá girando y se parece mucho a que el servidor está colgado o sobrecargado.

Algunos navegadores admiten un tipo MIME multiparte especial multipart/x-mixed-replace que le permite hacer el mismo truco para mantener la conexión abierta, pero el navegador reemplazará la página por completo cuando envíe el siguiente fragmento multiparte (que debe estar formateado en MIME). No sé si eso es utilizable: Internet Explorer no lo admite y puede que tampoco funcione bien en otro navegador.

La siguiente forma más moderna es sondear el servidor para obtener resultados con Javascript XMLHttpRequest. Esto requiere que pueda verificar los resultados de la operación desde un hilo o proceso de servidor web diferente, que puede ser un poco más difícil de lograr en el código del lado del servidor. Sin embargo, le permite crear una página web mucho más agradable.

Si desea complicarse aún más, consulte el modelo "Cometa" o "Web Sockets".

1
Mauve 9 dic. 2009 a las 13:35