run Python script from PHP <-- PHP <-- JavaScript (3)

run Python script from PHP (2)

Pomyślałem sobie, skoro umiem testować to i wywołać ze strony www poprzez PHP, to może pójść krok dalej? JavaScript?

Da to większe możliwości operacyjności stroną, dzięki temu JavaScript – strona www zyska moc Pythona. No oczywiście – można to zrobić poprzez Django. Ale nie zawsze mamy server Django, czasem po prostu PHP/Apache, który można w ten sposób nomen-omen ubogacić…

Skoro wiem już jak to hula w środku, usunąłem wyświetlanie komentarzy z Pythona i wewnętrznego PHP.

skrypt wewnętrzny Pythona mógłby wyglądać tak:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers

log_main = logging.getLogger('main')
formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                       datefmt='%Y-%m-%d %H:%M:%S')
file_handler = logging.handlers.RotatingFileHandler(os.path.join(
                                          os.path.dirname(os.path.abspath(__file__)),
                                          'log.txt'
                                       ),
                                       maxBytes=1000000,
                                       backupCount=3)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
log_main.setLevel(logging.DEBUG)
log_main.addHandler(file_handler)
log_main.addHandler(stream_handler)

log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

args = {}
if sys.argv:
    for pair in sys.argv:
        one = pair.split('=', 1)
        if one and len(one) == 2 and one[0]:
            args[one[0].decode('utf-8')] = one[1].decode('utf-8')
args[u'pdt'] = unicode(datetime.now())
args[u'p3g'] = u'Pythona 3 grosze'
sleep(3)
print(args)

log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')

Wprowadzone zmiany to:

– dołożony log w lokalnym pliku <code>log.txt</code> dzięki czemu wiemy co się wewnątrz dzieje, -tutaj jedynie wpisuję czasy startu i stopu, ale można cokolwiek 🙂

– Python dokłada swoje dane do otrzymanych data wejściowych -tutaj pola ‚p3g’ i ‚pdt’

– zakładam, że procedura Pythona będzie trwała chwilę, zanim zwróci wynik do JavyScript (Python –> PHP –> JavaScript) -tutaj 3 sekundy stąd dodane sleep(3)

Tworzenie logu, wiąże się z koniecznością dostępu do pliku logu (folderu w którym jest/będzie utworzony plik logu) – trzeba więc zadbać o uprawnienia do tego katalogu ( chown/chmod )

Właściwie, aby było to (jeszcze) ładniej, obuduję to w klasę:

#!/usr/bin/env python
# coding=utf-8

import os, sys
from datetime import datetime
from time import sleep
import logging.handlers


class PyMain:
    """

    """

    def __init__(self):
        """

        """

        self.log_main = logging.getLogger('main')
        formatter = logging.Formatter('%(asctime)s.%(msecs)03d  %(levelname)-8s  %(name)-15s  %(message)s',
                                      datefmt='%Y-%m-%d %H:%M:%S')
        file_handler = logging.handlers.RotatingFileHandler(os.path.join(
            os.path.dirname(os.path.abspath(__file__)),
            'log.txt'
        ),
            maxBytes=1000000,
            backupCount=3)
        file_handler.setFormatter(formatter)
        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(formatter)
        self.log_main.setLevel(logging.DEBUG)
        self.log_main.addHandler(file_handler)
        self.log_main.addHandler(stream_handler)
        self.log_main.info(u'init ' + unicode(datetime.now()))

    def clue(self):
        """
        return dict inomming plus some new fields
        :return:
        """

        self.log_main.info(u'start ' + unicode(datetime.now()) + u' ...')

        args = {}
        if sys.argv:
            for pair in sys.argv:
                one = pair.split('=', 1)
                if one and len(one) == 2 and one[0]:
                    args[one[0].decode('utf-8')] = one[1].decode('utf-8')
        args[u'pdt'] = unicode(datetime.now())
        args[u'p3g'] = u'Pythona 3 grosze'
        sleep(3)
        self.log_main.info(u'stop ' + unicode(datetime.now()) + u' ...')
        # print(args)
        return args


if __name__ == "__main__":
    c = PyMain()
    print(c.clue())

Musiałem zmienić z print(args) na return args, print zaś dopiero po wyjściu z metody.

Teraz pora na PHP odpalające …i też odrobina zmian:

<?php
header("Access-Control-Allow-Origin: *");

$log = 'log.txt';
$tolog = 'start';
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

$python_filename = 'php_fired2.py';
$python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';

$raw_data = file_get_contents('php://input'); 
$data = json_decode( $raw_data, true ); 
$cli_data = ''; 
if($data) { 
    foreach($data as $key => $value) { 
        $cli_data .= '"'.$key.'='.$value.'" '; 
    } 
} 
$command = $python_dir_path.'/'.$python_filename.' '.$cli_data; 
$output_from_python = shell_exec($command); 
echo $output_from_python; 
$tolog = print_r($output_from_python, true); 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX); 
$tolog = 'stop'; 
file_put_contents($log, (new DateTime())->format('Y-m-d H:i:s').' '.$tolog.PHP_EOL, FILE_APPEND | LOCK_EX);

Zmiany:

– dodałem log zamiast echo, tak więc interesujące momenty, wartości i zachowania będą umieszczone w pliku log.txt;

– linijka header("Access-Control-Allow-Origin: *"); umożliwiająca dostęp skryptowi JavaScript do tego pliku. Wcześniej dostęp nie był potrzebny, bo usyskiwał go skrypt PHP będący na tym samym serwerze. Teraz dostęp uzyskuje JavaScript – działająca gdziekolwiek na komputerze użytkownika…

 

Właściwie 🙂 , aby było to (jeszcze) ładniej, obuduję to w klasę:

<?php
header("Access-Control-Allow-Origin: *");

class PhpFiredPython {
/**
 *
 */

/** ustala plik logu, zapisuje moment startu
 * ustala zmienne dla klasy
 */
public function __construct() {

    $this->filelog = 'log.txt';
    $this->log('>start');
    $this->python_filename = 'php_fired2.py';
    $this->python_dir_path = '/home/wiks/Dokumenty/projects/weather/python2';
}

/** prowadzi plik logu
 *
 * @param type $tolog
 */
private function log($tolog) {

    file_put_contents($this->filelog,
            (new DateTime())->format('Y-m-d H:i:s').' '.
            $tolog.
            PHP_EOL,
            FILE_APPEND | LOCK_EX);
}

/** wykonuje właściwą pracę - odpala skrypt Pythona
 *
 * @return type
 */
public function get_post_and_fire_python() {

    $raw_data = file_get_contents('php://input');
    $data = json_decode( $raw_data, true );
    $cli_data = '';
    if($data) {
        foreach($data as $key => $value) {
            $cli_data .= '"'.$key.'='.$value.'" ';
        }
    }
    $command = $this->python_dir_path.'/'.$this->python_filename.' '.$cli_data;
    $output_from_python = shell_exec($command);
    $this->log(print_r($output_from_python, true));
    $this->log('>stop');
    //echo $output_from_python;
    return $output_from_python;
    }

}

$c = new PhpFiredPython();
echo $c->get_post_and_fire_python();

Podobnie jak z Pythonem zamieniłem echo na return.

Do szczęścia tego etapu potrzeba jeszcze JavaScript. która to wszystko potrząśnie:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="resp">miejsce na odpowiedź</div>
<?php
require 'url_and_data.php';
$dataj = json_encode( $data );
?>
<script>
    var d1 = new Date();
    console.log('JS start: ' + d1.toLocaleTimeString());
    var xhr = new XMLHttpRequest();
    xhr.open('POST', '<?php echo $url; ?>', true);
    xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
    xhr.onload = function () {
        var resp = this.responseText;
        console.log(resp);
        var element = document.getElementById("resp");
        if (resp) {
            element.innerHTML = resp;
            var d2 = new Date();
            console.log('JS stop: ' + d2.toLocaleTimeString());
        }
    };
    xhr.send('<?php echo $dataj; ?>');
</script>
</body>
</html>

Co my tu mamy:

Miejsce w HTML, do którego JS wpisze wynik: <div id="resp">miejsce na odpowiedź</div>, czyli DIV identyfikowany poprzez id=”resp” ,  oraz skrypt, który wywoła XMLHttpRequest metodą POST, na adres -nieładnie wdrukowany przez PHP: <?php echo $url; ?>  i wyśle tam dane w postaci JSON -również niełądnie wdrukowane przez PHP.

Po otrzymaniu zaś odpowiedzi (a będzie to czas który ustaliliśmy na 3 sekundy w skrypcie Pythona) w element DIV o id-„resp” wpisze otrzymaną odpowiedź. Ta sama odpowiedź pojawi się w konsoli przeglądarki (te miejsca, w których wpisujemy console.log(… ).

Na konsoli Firefoxa wygląda to podobnie do:

Albo nawet:

Gdzie ładnie widać co JS wysyła (dane formularza) i co otrzymuje (przedostatnia linijka).

Jak teraz wygląda nasz schemat?

A jak działa?

Otwieramy stronę PHP/HTML, przeglądarka tworzy element DIV z zawartością ‚miejsce na odpowiedź’.

Po wczytaniu skryptu JavaScript wykonuje go, czyli: wysyła dane JSON utworzone przez PHP na adres również utworzony przez PHP.

Dane trafiają do servera i wywołują skrypt PHP. Skrypt wywołuje skrypt Pythona, ten zaś po 3 skundach zwraca odpowiedź -też w formie JSON.

Odpowiedź trafia do PHP a następnie jest zwracana do JavaScript, który po jej odebraniu wykonuje
xhr.onload = function () { odszukuje element DOM o ID=”resp” i wpisuje otrzymaną odpowiedź do jego wnętrza jako HTML.

…to właściwie został mały kroczek do AngularJS, kiedyś to opiszę. Początek będzie zapewne wyglądał tak…

Loading Disqus Comments ...
Loading Facebook Comments ...

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *