Seeking in remote flv files using PHP - php

I'm string to seek in a remotely hosted FLV file and have it stream locally. Streaming from start works, but when I try to 'seek', player stops.
I'm using this script to seek to remote file
$fp = fsockopen($host, 80, $errno, $errstr, 30);
$out = "GET $path_to_flv HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Range: bytes=$pos-$end\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
$content = false;
while (!feof($fp))
{
$data = fgets($fp, 1024);
if($content) echo $data;
if($data == "\r\n")
{
$content = true;
header("Content-Type: video/x-flv");
header("Content-Length: " . (urlfilesize($file) - $pos));
if($pos > 0)
{
print("FLV");
print(pack('C', 1));
print(pack('C', 1));
print(pack('N', 9));
print(pack('N', 9));
}
}
}
fclose($fp);
Any ideas ?

UPDATE
so apparently, even though the server signals it accepts range requests (with the Accept-Ranges: bytes), it does not actually do so. to see if there is another way to make the flv seekable, let's have a look at the communication between flash player and server (i use wireshark for this):
the request when starting the player is:
GET /files/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/ HTTP/1.1
Host: xxxxxx.megavideo.com
<some more headers>
<no range header>
this is answered with a response like that:
HTTP/1.0 200 OK
Server: Apache/1.3.37 (Debian GNU/Linux) PHP/4.4.7
Content-Type: video/flv
ETag: "<video-id>"
Content-Length: <length of complete video>
<some more headers>
<the flv content>
now when i seek in the flash player, another request is sent. it is almost the same as the initial one, with the following difference:
GET /files/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/8800968 HTTP/1.1
<same headers as first request>
which gets answered with a response almost the same as the initial one, with a difference only in the Content-Length header.
which lets me assume that the 8800968 at the end of the request url is the "seek range" (the byte offset in the file after seeking) we are looking for, and the second response Content-Length is the initial Content-Length (the length of the whole file) minus this range. which is the case indeed.
with this information, it should be possible to get what you want. good luck!
UPDATE END
this will only work if the server supports HTTP RANGE requests. if it does, it will return a 206 Partial Content response code with a Content-Range header and your requested range of bytes. check for these in the response to your request.

Related

Serverside Websocket Handshake fails with 'Sec-WebSocket-Accept mismatch'

I tried to create a Websocket Server in PHP which uses the current WebSocket Protocol 13.
I implemented the header creation according to the RFC but in Google Chrome (Version 31) it still fails with the Error message Error during WebSocket handshake: Sec-WebSocket-Accept mismatch. Firefox shows at least the response but it also doesn't fire the clientside onOpen Event.
Below is a minimal testserver in PHP with the code I use to create the header:
<?php
$srv = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($srv, '0.0.0.0', 50500);
socket_listen($srv, 10);
for(;;){
$sock = socket_accept($srv);
// get header
$header = socket_read($sock, 1024);
// extract key
$keys = array();
preg_match_all("/Sec-WebSocket-Key:\s*(.*)\s*/", $header, $keys);
if(count($keys) < 1){
// Not a valid Websocket Handshake
socket_close($sock);
continue;
}
// create hash according to RFC
$key = $keys[1][0];
$accept = base64_encode(SHA1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
// create header
$h = "HTTP/1.1 101 Switching Protocols\r\n";
$h .= "Upgrade: websocket\r\n";
$h .= "Connection: Upgrade\r\n";
$h .= "Sec-WebSocket-Accept: $accept\r\n";
$h .= "Sec-WebSocket-Protocol: 13\r\n\r\n";
echo $h;
// send header to client
socket_write($sock,$h,strlen($h));
}
?>
Am I missing something here?
Thanks in advance!
The regexp /Sec-WebSocket-Key:\s*(.*)\s*/ will include the \r characters in the matched content (the . matches \r but not \n) so your $key variable will have the wrong value. You need to figure out a regexp that matches everything but the \r\n sequence at the end.
Also, the server should only include the Sec-WebSocket-Protocol attribute if the client included it in the request, and the value must be one of the protocols requested in the client Sec-WebSocket-Protocol attribute. Otherwise the client will give an error, see RFC 6455.

Raw HTTP - Using full URL in the GET line?

<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
header('content-type: text/plain');
$host='www.google.com';
$fp = fsockopen($host, 80);
$out = "GET http://www.google.com/ HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
?>
this just works.
is it standards compliant?
Yes it is.
From the specification:
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
...and...
Request-URI = "*" | absoluteURI | abs_path | authority
Yes, according to 5.1.2 of RFC 2616:
An example Request-Line would be:
GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1
To allow for transition to absoluteURIs in all requests in future versions of HTTP, all HTTP/1.1 servers MUST accept the absoluteURI form in requests, even though HTTP/1.1 clients will only generate them in requests to proxies.

How to speed up file_get_contents?

Here's my code:
$language = $_GET['soundtype'];
$word = $_GET['sound'];
$word = urlencode($word);
if ($language == 'english') {
$url = "<the first url>";
} else if ($language == 'chinese') {
$url = "<the second url>";
}
$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"User-Agent: <my user agent>"
)
);
$context = stream_context_create($opts);
$page = file_get_contents($url, false, $context);
header('Content-Type: audio/mpeg');
echo $page;
But I've found that this runs terribly slow.
Are there any possible methods of optimization?
Note: $url is a remote url.
It's slow because file_get_contents() reads the entire file into $page, PHP waits for the file to be received before outputting the content. So what you're doing is: downloading the entire file on the server side, then outputting it as a single huge string.
file_get_contents() does not support streaming or grabbing offsets of the remote file. An option is to create a raw socket with fsockopen(), do the HTTP request, and read the response in a loop, as you read each chunk, output it to the browser. This will be faster because the file will be streamed.
Example from the Manual:
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
header('Content-Type: audio/mpeg');
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.example.com\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
The above is looping while there is still content available, on each iteration it reads 128 bytes and then outputs it to the browser. The same principle will work for what you're doing. You'll need to make sure that you don't output the response HTTP headers which will be the first few lines, because since you are doing a raw request, you will get the raw response with headers included. If you output the response headers you will end up with a corrupt file.
Instead of downloading the whole file before outputting it, consider streaming it out like this:
$in = fopen($url, 'rb', false, $context);
$out = fopen('php://output', 'wb');
header('Content-Type: video/mpeg');
stream_copy_to_stream($in, $out);
If you're daring, you could even try (but that's definitely experimental):
header('Content-Type: video/mpeg');
copy($url, 'php://output');
Another option is using internal redirects and making your web server proxy the request for you. That would free up PHP to do something else. See also my post regarding X-Sendfile and friends.
As explained by #MrCode, first downloading the file to your server, then passing it on to the client will of course incur a doubled download time. If you want to pass the file on to the client directly, use readfile.
Alternatively, think about if you can't simply redirect the client to the file URL using a header("Location: $url") so the client can get the file directly from the source.

How do I make a POST request over HTTPS in PHP?

I trying to work with the YouTube API and its ClientLogin. And that means that I need to make a POST request to their servers.
The URL to which I need to make the request to https://www.google.com/accounts/ClientLogin. The variables I need to send are Email, Passwd, source and service. So far, so good.
I found this neat function to make POST calls (see below), but it does not use HTTPS, which I think I must use. It all works but I think my POST request is being forwarded to HTTPS and therefore it does not give me the proper callback. When I try to var_dump, the returned data web page reloads and I end up at https://www.google.com/accounts/ClientLogin where I get proper data. But of course I need this data as an array or string.
So how do I make a POST request using HTTPS?
Se my code (which I found at Jonas’ Snippet Library) below:
function post_request($url, $data, $referer='') {
$data = http_build_query($data);
$url = parse_url($url);
$host = $url['host'];
$path = $url['path'];
$fp = fsockopen($host, 80, $errno, $errstr, 30);
if ($fp){
fputs($fp, "POST $path HTTP/1.1\r\n");
fputs($fp, "Host: $host\r\n");
if ($referer != '')
fputs($fp, "Referer: $referer\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: ". strlen($data) ."\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $data);
$result = '';
while(!feof($fp)) {
$result .= fgets($fp, 128);
}
}
else {
return array(
'status' => 'err',
'error' => "$errstr ($errno)"
);
}
fclose($fp);
$result = explode("\r\n\r\n", $result, 2);
$header = isset($result[0]) ? $result[0] : '';
$content = isset($result[1]) ? $result[1] : '';
return array(
'status' => 'ok',
'header' => $header,
'content' => $content
);
}
This is the response headers:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Date: Tue, 03 May 2011 12:15:20 GMT
Expires: Tue, 03 May 2011 12:15:20 GMT
Cache-Control: private, max-age=0
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Length: 728
Server: GSE
Connection: close
The content I get back is some kind of form autosubmitted, which I think is because I use HTTP instead of HTTPS:
function autoSubmit() {
document.forms["hiddenpost"].submit();
}
Processing...
So, how do I do a HTTPS POST request?
As octopusgrabbus kindly pointed out, I need to use port 443 instead of 80. So I changed this, but now I get nothing back.
var_dump from function return:
array(3) {
["status"]=>
string(2) "ok"
["header"]=>
string(0) ""
["content"]=>
string(0) ""
}
I get no header and no content back. What is wrong?
I think you cannot talk directly HTTPS, as it is HTTP encrypted with the public certificate of the server you are connecting to. Maybe you can use some of the ssl functions in php. But, this will take you some time and frankly, there are easier things.
Just take a look at cURL (client URL), that has support for GET and POST requests, and also connecting to https servers.
You are opening your socket at port 80. The SSL port is 443.
If this is SSL, there is an official computer name tied to the secure cert that's present on that web server. You might need to connect using that official name.
When you open the socket, changing the port to 443 and the prepending the host with ssl:// should work. (I just had this issue with paypal and some third party code). This assumes you don't have a protocol in your host already.
So
$fp = fsockopen('ssl://' . $host, 443, $errno, $errstr, 30);
As Carlos pointed out cUrl is good for this sort of thing. But there's no need to completely change what you're using in this case, particularly when it's a single line change.

PHP: Connection: keep-alive problem reading socket data

Trying to write data to a socket and read the response.
$packet = "GET /get-database HTTP/1.1\r\n";
$packet .= "Host: 192.168.3.136:3689\r\n";
//$packet .= "Accept-Encoding: gzip\r\n";
$packet .= "Viewer-Only-Client: 1\r\n";
$packet .= "Connection: keep-alive\r\n\r\n";
socket_write($socket, $packet, strlen($packet));
do{
$buf = "";
$buf = socket_read($socket, 4096);
$data .= $buf;
}while($buf != "");
echo "$data\r\n\r\n";
If I set the Connection to close then it works and I'm able to read the response. The problem with that is, that after I read the data, I need to write back to the socket. The response contains an id that I need to send back for verification. If I write to the server on two separate sockets, it rejects the verification post back. So I can only assume, that I need to post on same "open connection" or "session".
Any thoughts?
I would like to figure out why I can't read from the socket with Connection: keep-alive
####### EDIT
There has been a little development on this.
I'm trying to make this very simple so I can pinpoint the problem:
Right now my code looks like this:
$fp = pfsockopen("192.168.3.136", "3689");
$content = "GET /login?id=a90347 HTTP/1.1\r\n";
$content .= "Connection: keep-alive\r\n\r\n";
fputs($fp, $content);
while (!feof($fp)) {
echo fgets($fp, 8192);
}
What happens is, as soon as I do my fputs, I get a response header from the server that looks like this:
HTTP/1.1 200 OK
Date: Fri, 05 Mar 2010 22:05:47 GMT
RIPT-Server: iTunesLib/3.0.2 (Mac OS X)
Content-Type: application/x-dmap-tagged
Content-Length: 32
And then my cursor just sits there. After anywhere from 15 seconds to a minute, I sometimes get the content, but I am still stuck in the while loop.
Does anyone know, if after the server has sent the response header, if I should be sending something back to it to let it know that I am ready for the content?
Again, I don't think this is the case, since when I look in the packets on the network, I can see the entire response. That and the fact that I do sometimes get the content of the response. It's really like PHP can't handle this or I am just way off base.
Still need help..... :(
Working Code
$fp = pfsockopen("192.168.3.136", "3689");
$header = "GET /login?id=5648349 HTTP/1.1\r\n";
$header .= "Connection: keep-alive\r\n\r\n";
fputs($fp, $header);
$headers = array();
while(true){
$line = fgets($fp, 8192);
if($line == "\r\n"){ break; }
$line_parts = explode(': ',$line);
echo $line_parts[1]."\r\n";
$headers[$line_parts[0]] = $line_parts[1];
}
$content = fread($fp,intval($headers['Content-Length']));
echo $content;
Now, I'll have to be wary of the "\r\n" test as I'm sure it's possible that some responses might only send a "\n" after the header.
Did you try setting the socket to nonblocking mode?
socket_set_nonblock($socket);
EDIT1: Ok. Just a hunch... try this...
$fp = pfsockopen("192.168.3.136", "3689");
$content = "GET /login?id=a90347 HTTP/1.1\r\n";
$content .= "Connection: keep-alive\r\n\r\n";
fputs($fp, $content);
$headers = array();
do
{
$line = fgets($fp, 8192);
$line_parts = ecplode(': ',$line);
$headers[$line_parts[0]] = $line_parts[1];
} while($line != '');
$content = fread($fp,intval($headers['Content-Length']));
echo $content;

Categories