Jump to content

Recommended Posts

Okay, so I have a neat little website and I'm having some issue with some quality-of-life improvements.

 

Namely, the user clicks a button which starts a server-side operation that can take up to 20 to 30 seconds.

 

I want a little message to appear below the button that says, "Operation started. This may take upwards of 20 to 30 seconds depending on traffic."

 

As of now, I have the typical 

<form action="post" action="<?php echo $_SERVER['PHP_SELF'];?>">
    ...
    <input type="submit" name="submit" value="Make PDF" />
</form>
<?php   
    if (isset($_POST["submit"]))
    {
        ...
    }
?>

The only problem is, part of my PHP code must communicate with a Java server that I have running on the server itself.

 

So how the site works is, there's the computer I'm renting out and it's running Apache and a custom Java server I wrote myself. Apache handles the web request and upon form submission, PHP opens a socket with the Java server and begins the task. PHP then waits for the connection to close.

 
This code looks like :
            fwrite($sock, $data);

            $pdf_to_download = "";

            // wait for write-back from Java server
            while (!feof($sock)) 
                $pdf_to_download = fgets($sock, 4096);

            fclose($sock);
The only problem is, I can't seem to get PHP to write a message until the entire script is done running so if put an echo statement at the beginning of the script (the "This takes a long time" message), it's not generated until the entire script is done running.
 
How do I make it so that when the user clicks the submit button, an initial PHP message is echoed back and the task I need accomplished is started? Do I need multiple threads for this? Do I need PHP to make two independent socket requests with Apache? One request to echo the message at the proper location in the HTML and then another socket connection to begin my time-intensive task?

 

 

 

 

 

 

Link to comment
https://forums.phpfreaks.com/topic/294185-questions-about-php-and-form-submission/
Share on other sites

I think what you want is an "asynchronous" server request, so you can send the data to the task and it executes independently...and you don't have to wait for a response before continuing on so the user doesn't have to wait.

 

I've used something like this.

Interesting.

 

What about pcntl_fork()?

 

This seems like something that I might want.

 

<?php


    $pid = pcntl_fork();


    if ($pid == -1)
    {
        die("Could not fork process");
    }
    else if ($pid)
    {
        echo "Parent process is waiting...\n";
        pcntl_wait($status);
        echo "Alright, parent's done waiting. Let's go home.\n";
    }
    else
    {
        echo "Child process!\n";
        sleep(10);
    }
?>

Haven't needed to use forks, so not sure. But a quick glance at the documentation states it shouldn't be used in a web platform (apache), only the CLI.

http://php.net/manual/en/intro.pcntl.php

 

 

 Process Control should not be enabled within a web server environment and unexpected results may happen if any Process Control functions are used within a web server environment.

Haven't needed to use forks, so not sure. But a quick glance at the documentation states it shouldn't be used in a web platform (apache), only the CLI.

http://php.net/manual/en/intro.pcntl.php

Lol I literally just tried with my page and you're right, it definitely does not work.

Oh, I meant executing the JavaScript and the PHP code in the same action field.

 

But I did talk about with my boss and the idea was suggested that we just put a disclaimer on the page that the PDF generation can take a chunk of time. This is a good workaround but it's exactly that, a work around.

 

I'll continue looking at the links posted in this thread though.

Not sure why you'd need to do that.

if (isset($_POST["submit"]))
{
  //use the asynchronous code I linked to to send data to the script that will create the PDF

  //this will immediately show since using the async method doesn't wait for the PDF generating script to be completed
  echo 'Your PDF is being generated and will be emailed to you when completed. Please check your email in about 5 minutes. Note: some PDF's will take longer to generate depending on the requested information.';
}

Then the user can continue on with their business, go to a different page, etc.

Edited by CroNiX

Keep in mind that if your script takes too long, it might hit the script execution time limit which is set to 30 seconds by default.

 

Another solution is to use a job scheduler such as Gearman. This would allow you to spin off a background task which will be non-blocking. But with that solution you would have to use polling to check the status of the operation.

Not sure why you'd need to do that.

if (isset($_POST["submit"]))
{
  //use the asynchronous code I linked to to send data to the script that will create the PDF

  //this will immediately show since using the async method doesn't wait for the PDF generating script to be completed
  echo 'Your PDF is being generated and will be emailed to you when completed. Please check your email in about 5 minutes. Note: some PDF's will take longer to generate depending on the requested information.';
}

Then the user can continue on with their business, go to a different page, etc.

That code looked complicated and it scared me lol T_T

Because it's a full fledged working example, with a proof of concept to show you it's working. All you really need is the curl_post_async() function from that code.

 

Then in your code, you'd just:

curl_post_async('http://yoursite.com/path/to/pdf/processing/script.php', array(parameters needed by script.php));

the "parameters needed by script.php" would probably just be your $_POST array.

I appreciate your advice.

 

Aright, I want to decode this function line-by-line to make sure that I'm understanding this all correctly.

function curl_post_async($url, $params = array()){

// declare simple array to store $_POST parameters (makes sense)
$post_params = array();

// iterate the parameter array, using $val as a 
// reference for each element
foreach ($params as $key => &$val) 
{
    // if $val is an array type, implode it into one argument
    if (is_array($val)) 
        $val = implode(',', $val);

    // not sure about the syntax for this
    // my guess is that this is similar to C++'s push_back feature
    // and we are pushing back the index of the array and 
    // and encodes the argument as a url?
    $post_params[] = $key.'='.urlencode($val);
}
// the post string is then the collapse of all parameters, 
// using & as a delim
$post_string = implode('&', $post_params);

// we then parse the url but I'm not sure why we want this
$parts=parse_url($url);

// we open up a socket

// $hostname = ??? where does the 'host' come from?
// is that a side-effect of the parse_url()?

// $port : if 'port' or parts is set, we return the port else 80

// $errno, $errstr, $timeout : default variables for error and timeout

$fp = fsockopen($parts['host'],
                           isset($parts['port'])?$parts['port']:80,
                           $errno, 
                           $errstr, 
                           30);

// we then create a string $out full of what seems to me
// to be random info required for some reason

$out = "POST ".$parts['path']." HTTP/1.1\r\n";
$out.= "Host: ".$parts['host']."\r\n";
$out.= "Content-Type: application/x-www-form-urlencoded\r\n";
$out.= "Content-Length: ".strlen($post_string)."\r\n";
$out.= "Connection: Close\r\n\r\n";
if (isset($post_string)) $out.= $post_string;

// we then write the $out string to the socket resource
fwrite($fp, $out);

// we then close the socket but won't this function
// cause the socket to immediately close?
fclose($fp);
}

Yes, you've pretty much got it.

 

// not sure about the syntax for this

// my guess is that this is similar to C++'s push_back feature

// and we are pushing back the index of the array and

// and encodes the argument as a url?

$post_params[] = $key.'='.urlencode($val);

This is shorthand for array_push.

 

// we then parse the url but I'm not sure why we want this

$parts=parse_url($url);

Because, parse_url will give us the parts of the URL so that we can use them in the socket connection.

 

// $hostname = ??? where does the 'host' come from?

// is that a side-effect of the parse_url()?

Yes, parse_url parses a URL and returns an array of the components that make it up.

 

// we then create a string $out full of what seems to me

// to be random info required for some reason

Not random info, these are request headers which describe the request.

 

// we then close the socket but won't this function

// cause the socket to immediately close?

Yes, which is what makes this non-blocking.

 

 

My only problem with this approach is that your script can only assume that everything went as planned. You cannot get any response data, so you have no idea if the script had errors or not.

Edited by scootstah

It's simply making a custom POST request to the given url, and using the array() (2nd parameter) as the POST data. In essence, making a 2nd request to a different URL and not waiting for a response back before returning control back to your app. Then the url to the script that you called in curl_post_async() gets processed on it's own behind the scenes, as a totally separate web request (because that's what it is)

 

All of the "we then create a string $out full of what seems to me to be random info required for some reason" you refer to is not random at all. It's a header request. All requests to your server, and the response back, contain them behind the scenes. Like if you click on a link, or request a url. It sends a header to the server, and the server sends back a response.

 

There's a nice plugin for firefox, called "HTTP Live Headers", which allows you to easily examine all of that raw header/response data flying back and forth between your browser and a server behind the scenes. Other browsers might have a similar plugin but that's what I mainly use. It's quite helpful to learn this stuff and how it works.

Edited by CroNiX

I gotta admit, PHP is pretty cool. Alright, I'm gonna research this method some more. Thanks for the help, you guys!

 

Edit : And I have heard a little bit about headers and web servers. Basically, when the web server receives a request, headers come before all else which is why you can't have any output if you're going to be forwarding headers. Is there anything else I should know or read up on?

Edited by MutantJohn

Okay, quick question : Am I feeding this code the wrong URL?

 

<html>
    <head />
    <body>
<?php
    function index()
    {
        echo "PHP async test...<br />";
 
        $params = array("one"   => "1111",
                        "two"   => "2222",
                        "three" => "3333",
                        "four"  => "4444");
 
        // is this part wrong?
        curl_post_async("http://127.0.0.1/longone", $params);
    }
 
    function longone()
    {
        $one   = $_POST["one"];
        $two   = $_POST["two"];
        $three = $_POST["three"];
        $four  = $_POST["four"];
 
        echo uniqid("You won't see this because your PHP script isn't waiting to read any response");
 
        sleep(5);
 
        $fp = fopen("data.txt", "w");
        fwrite($fp, $one);
        fwrite($fp, $two);
        fwrite($fp, $three);
        fwrtie($fp, $four);
        fclose($fp);
    }
 
    function curl_post_async($url, $params = array())
    {
        // declare array for $_POST parameters
        $post_params = array();
 
        // iterate the parameters passed to this function
        foreach($params as $key => &$val)
        {
            // if the parameter is an array...
            if (is_array($val))
                $val = implode(",", $val);
 
            // push back the key-value pair
            $key_val = $key."=".urlencode($val);
            array_push($post_params, $key_val);
 
            var_dump($key_val); echo "<br />";
        }
 
        // create one string to hold all parameters
        $post_string = implode("&", $post_params);
 
        var_dump($post_string); echo "<br />";
 
        $parts = parse_url($url);
        var_dump($parts); echo "<br />";
 
        $fp = fsockopen($parts["host"],                       // hostname
                        $parts["port"] ? $parts["port"] : 80, // port
                        $errno,
                        $errstr,
                        30);
 
        $out  = "POST ".$parts["path"]." HTTP/1.1\r\n";
        $out .= "Host: ".$parts["host"]."\r\n";
        $out .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $out .= "Content-Length: ".strlen($post_string)."\r\n";
        $out .= "Connection: Close\r\n\r\n";
        if (isset($post_string)) 
            $out .= $post_string;
 
        var_dump($out); echo "<br />";
 
        if (fwrite($fp, $out) === false)
            echo "Failed to write to socket<br />";
        fclose($fp);
    }
 
    index();
?>
    </body>
</html>

This is my directory info :

$ ls -l
total 16
-rw-rw-rw- 1 www-data www-data  2429 Jan 23 15:33 async.php
-rw-r--r-- 1 www-data www-data     0 Jan 23 14:49 data.txt
-rw-r--r-- 1 www-data www-data 11510 Jan 23 14:04 index.html

This is all done on the localhost, btw, which for me on Ubuntu is /var/www/html

How is longone() supposed to be getting executed?

Heck if I know. That's kind of how the link I followed did it. In the sample code posted at http://blog.markturansky.com/archives/205 the author used

$this->curl_post_async("http://127.0.0.1/sq/scratch/longone", $params);

so I assumed I was supposed to use something similar. I get that the first part is my localhost IP and then I assume sq and scratch were directories and then longone matched the function declared in the class.

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.