MutantJohn Posted January 23, 2015 Share Posted January 23, 2015 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? Quote Link to comment Share on other sites More sharing options...
CroNiX Posted January 23, 2015 Share Posted January 23, 2015 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. Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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); } ?> Quote Link to comment Share on other sites More sharing options...
CroNiX Posted January 23, 2015 Share Posted January 23, 2015 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. Quote Link to comment Share on other sites More sharing options...
scootstah Posted January 23, 2015 Share Posted January 23, 2015 (edited) socket_set_nonblock might be what you're looking for. Edited January 23, 2015 by scootstah Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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. Quote Link to comment Share on other sites More sharing options...
ginerjm Posted January 23, 2015 Share Posted January 23, 2015 Why not an onclick event on the submit button that triggers a js function that outputs your message? Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 Why not an onclick event on the submit button that triggers a js function that outputs your message? Can I have both in the same action? Quote Link to comment Share on other sites More sharing options...
ginerjm Posted January 23, 2015 Share Posted January 23, 2015 Both what? Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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. Quote Link to comment Share on other sites More sharing options...
ginerjm Posted January 23, 2015 Share Posted January 23, 2015 Action field? If you have a submit button with an event action too, then yes, the event response will occur as well as the form submission. Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 Action field? If you have a submit button with an event action too, then yes, the event response will occur as well as the form submission. Oh ****, you are so right. Omg, I'm dumb. Omg. Okay, I'ma try that. Quote Link to comment Share on other sites More sharing options...
CroNiX Posted January 23, 2015 Share Posted January 23, 2015 (edited) 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 January 23, 2015 by CroNiX Quote Link to comment Share on other sites More sharing options...
scootstah Posted January 23, 2015 Share Posted January 23, 2015 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. Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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 Quote Link to comment Share on other sites More sharing options...
CroNiX Posted January 23, 2015 Share Posted January 23, 2015 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. Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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); } Quote Link to comment Share on other sites More sharing options...
scootstah Posted January 23, 2015 Share Posted January 23, 2015 (edited) 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 January 23, 2015 by scootstah Quote Link to comment Share on other sites More sharing options...
CroNiX Posted January 23, 2015 Share Posted January 23, 2015 (edited) 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 January 23, 2015 by CroNiX Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 (edited) 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 January 23, 2015 by MutantJohn Quote Link to comment Share on other sites More sharing options...
scootstah Posted January 23, 2015 Share Posted January 23, 2015 You can have output, you just need to output the headers first. Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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> Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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 Quote Link to comment Share on other sites More sharing options...
scootstah Posted January 23, 2015 Share Posted January 23, 2015 How is longone() supposed to be getting executed? Quote Link to comment Share on other sites More sharing options...
MutantJohn Posted January 23, 2015 Author Share Posted January 23, 2015 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. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.