I have a file uploaded in AWS s3 bucket and set that file to public permission . i want to share that file in my Facebook .. the thing is i can just copy that public link and share it . but i also want the count of the downloads to stored .. in other way i want to host a php file in my web hosting where there will be a tab like bar in which that file name,file size, download link and total download count will be there . Please help me with the code
I tried the following code which i got from google search but no use
<?php
$aws_key = '_YOUR_AWS_KEY_000000';
$aws_secret = '_your_aws_secret_00000000000000000000000';
$aws_bucket = 'anyexample-test'; // AWS bucket
$aws_object = 'test.png'; // AWS object name (file name)
if (strlen($aws_secret) != 40) die("$aws_secret should be exactly 40 bytes long");
$dt = gmdate('r'); // GMT based timestamp
// preparing string to sign
$string2sign = "GET
{$dt}
/{$aws_bucket}/{$aws_object}";
// preparing HTTP query
$query = "GET /{$aws_bucket}/{$aws_object} HTTP/1.1
Host: s3.amazonaws.com
Connection: close
Date: {$dt}
Authorization: AWS {$aws_key}:".amazon_hmac($string2sign)."\n\n";
echo "Downloading: http://s3.amazonaws.com/{$aws_bucket}/{$aws_object}\n";
list($header, $resp) = downloadREST($fp, $query);
echo "\n\n";
if (strpos($header, '200 OK') === false) // checking for error
die($header."\r\n\r\n".$resp);
$aws_object_fs = str_replace('/', '_', $aws_object);
// AWS object may contain slashes. We're replacing them with underscores
#$fh = fopen($aws_object_fs, 'wb');
if ($fh == false)
die("Can't open file {$aws_object_fs} for writing. Fatal error!\n");
echo "Saving data to {$aws_object_fs}...\n";
fwrite($fh, $resp);
fclose($fh);
// Sending HTTP query, without keep-alive support
function downloadREST($fp, $q)
{
// opening HTTP connection to Amazon S3
// since there is no keep-alive we open new connection for each request
$fp = fsockopen("s3.amazonaws.com", 80, $errno, $errstr, 30);
if (!$fp) die("$errstr ($errno)\n"); // connection failed, pity
fwrite($fp, $q); // sending query
$r = ''; // buffer for result
$check_header = true; // header check flag
$header_end = 0;
while (!feof($fp)) {
$r .= fgets($fp, 256); // reading response
if ($check_header) // checking for header
{
$header_end = strpos($r, "\r\n\r\n"); // this is HTTP header boundary
if ($header_end !== false)
$check_header = false; // We've found it, no more checking
}
}
fclose($fp);
$header_boundary = $header_end+4; // 4 is length of "\r\n\r\n"
return array(substr($r, 0, $header_boundary), substr($r, $header_boundary));
}
// hmac-sha1 code START
// hmac-sha1 function: assuming key is global $aws_secret 40 bytes long
// http://en.wikipedia.org/wiki/HMAC
// warning: key is padded to 64 bytes with 0x0 after first function call
// hmac-sha1 function
function amazon_hmac($stringToSign)
{
if (!function_exists('binsha1'))
{ // helper function binsha1 for amazon_hmac (returns binary value of sha1 hash)
if (version_compare(phpversion(), "5.0.0", ">=")) {
function binsha1($d) { return sha1($d, true); }
} else {
function binsha1($d) { return pack('H*', sha1($d)); }
}
}
global $aws_secret;
if (strlen($aws_secret) == 40)
$aws_secret = $aws_secret.str_repeat(chr(0), 24);
$ipad = str_repeat(chr(0x36), 64);
$opad = str_repeat(chr(0x5c), 64);
$hmac = binsha1(($aws_secret^$opad).binsha1(($aws_secret^$ipad).$stringToSign));
return base64_encode($hmac);
}
// hmac-sha1 code END
?>
I would suggest using the official AWS SDK for PHP, because it has all of the request signing and handling logic implemented for you. Here is an article by one of the SDK's developers that is relevant to what you are doing: Streaming Amazon S3 Objects From a Web Server
Infact if you just need to see the number of downloads, you can achieve this without running yourown server with php.
This info is already available in the S3 bucket logs, if you enable. This will be more accurate, since the in the PHP approach there is no way to track download, if the user take the S3 link directly and share/download.
These logs are little difficult to parse though, but the services like https://qloudstat.com and http://www.s3stat.com/ help here.
Another point: Downloads will be considerably faster, if you enable CDN - Cloudfront in front of the S3 bucket.
Related
I have a download server for files.
And I want my users to get the files from the download server.
My download server is Linux.
I want when the user clicks on the download button.
Get the file directly from the download server.
I do not want to use the stream to download ...
I want to connect to the download server via the link and then download it using PHP
My site is with mvc
Thank you, step by step to help me
thank you
Stream stream = null;
//This controls how many bytes to read at a time and send to the client
int bytesToRead = 10000;
// Buffer to read bytes in chunk size specified above
byte[] buffer = new Byte[bytesToRead];
// The number of bytes read
try
{
//Create a WebRequest to get the file
HttpWebRequest fileReq = (HttpWebRequest)HttpWebRequest.Create(Global.UrlVideoPrice + IdCourse + "//" + IdTopic+".rar");
//Create a response for this request
HttpWebResponse fileResp = (HttpWebResponse)fileReq.GetResponse();
if (fileReq.ContentLength > 0)
fileResp.ContentLength = fileReq.ContentLength;
//Get the Stream returned from the response
stream = fileResp.GetResponseStream();
// prepare the response to the client. resp is the client Response
var resp = System.Web.HttpContext.Current.Response;
//Indicate the type of data being sent
resp.ContentType = "application/octet-stream";
//Name the file
resp.AddHeader("Content-Disposition", "attachment; filename=\"" + Topic.fldName + ".rar\"");
resp.AddHeader("Content-Length", fileResp.ContentLength.ToString());
int length;
do
{
// Verify that the client is connected.
if (resp.IsClientConnected)
{
// Read data into the buffer.
length = stream.Read(buffer, 0, bytesToRead);
// and write it out to the response's output stream
resp.OutputStream.Write(buffer, 0, length);
// Flush the data
resp.Flush();
//Clear the buffer
buffer = new Byte[bytesToRead];
}
else
{
// cancel the download if client has disconnected
length = -1;
}
} while (length > 0); //Repeat until no data is read
}
finally
{
if (stream != null)
{
//Close the input stream
stream.Close();
}
}
This is my download code.
Now I want the user to get the files directly from the download server.
Do not use site traffic to download the file.
And use server download traffic
I'm using the API Client Library for PHP (Beta) to work with google drive api, So far I can authorize and and upload a file in chuncks.
According to the documentation, these three steps should be taken to upload a file:
Start a resumable session.
Save the resumable session URI.
Upload the file.
Which I think the Client Library handles.
Again, according to the documentation, if I want to show the progress or resume an interrupted upload, or to handle errors I need to capture the response and also be able to send requests like this:
> PUT {session_uri} HTTP/1.1 Content-Length: 0 Content-Range: bytes
> */2000000
But I have no idea how should I make such request and where can I get the response from, the php code I'm using to upload,like any other php code, only returns values when it is done executing, which is when the upload is done.
here is the function I'm using to upload files (Resumable):
function uploadFile($service,$client,$filetoUpload,$parentId){
$file = new Google_Service_Drive_DriveFile();
$file->title = $filetoUpload['name'];
$chunkSizeBytes = 1 * 1024 * 1024;
// Set the parent folder.
if ($parentId != null) {
$parent = new Google_Service_Drive_ParentReference();
$parent->setId($parentId);
$file->setParents(array($parent));
}
// Call the API with the media upload, defer so it doesn't immediately return.
$client->setDefer(true);
$request = $service->files->insert($file);
// Create a media file upload to represent our upload process.
$media = new Google_Http_MediaFileUpload(
$client,
$request,
$filetoUpload['type'],
null,
true,
$chunkSizeBytes
);
$media->setFileSize(filesize($filetoUpload['tmp_name']));
// Upload the various chunks. $status will be false until the process is
// complete.
$status = false;
$handle = fopen($filetoUpload['tmp_name'], "rb");
while (!$status && !feof($handle)) {
set_time_limit(120);
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
// The final value of $status will be the data from the API for the object
// that has been uploaded.
$result = false;
if($status != false) {
$result = $status;
set_time_limit(30);
echo "<pre>";
print_r($result);
}
fclose($handle);
// Reset to the client to execute requests immediately in the future.
$client->setDefer(false);
}
Should i make a separate php file to handle these requests?
if so, How should tell that which file's status I'm requesting for?
Thanks.
Apperantly, the BETA Client API simply doesn't support resuming Uploads.
Please see this issue on Github, which asks for fixing this. Of course it should be easy to modify the class (see below) and create a Pull-request to enable support for resuming existing uploads when the session-URL is supplied.
However, there's an easy way to get the progress after an chunk has been uploaded.
The Google_Http_MediaFileUpload-object ($media in your example) has a public method called 'getProgress' which can be called anytime.
(Please have a look at the source code of the API-client-library).
To get the upload status, I'd add a parameter to modify the progress precision by adjusting the chunk size. Since the more chunks are used, the more protocol overhead is generated, setting the precision as low as possible should be avoided.
Therefore, you could modify your source code as below to output the progress after each chunk:
function uploadFile($service,$client,$filetoUpload,$parentId,$progressPrecision = 1){
$file = new Google_Service_Drive_DriveFile();
$file->title = $filetoUpload['name'];
$filesize = filesize($filetoUpload['tmp_name']);
//minimum chunk size needs to be 256K
$chunkSizeBytes = min( $filesize / 100 * $progressPrecision, 262144);
// Set the parent folder.
if ($parentId != null) {
$parent = new Google_Service_Drive_ParentReference();
$parent->setId($parentId);
$file->setParents(array($parent));
}
// Call the API with the media upload, defer so it doesn't immediately return.
$client->setDefer(true);
$request = $service->files->insert($file);
// Create a media file upload to represent our upload process.
$media = new Google_Http_MediaFileUpload(
$client,
$request,
$filetoUpload['type'],
null,
true,
$chunkSizeBytes
);
$media->setFileSize($filesize);
…
while (!$status && !feof($handle)) {
set_time_limit(120);
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
if(!$status){ //nextChunk() returns 'false' whenever the upload is still in progress
echo 'sucessfully uploaded file up to byte ' . $media->getProgress() .
' which is ' . ( $media->getProgress() / $chunkSizeBytes ) . '% of the whole file';
}
}
Hope this helps. I'll see if I can find some time to add resume-support to the client Library.
EDIT: according to this doc, the chunks need to be at least 256KB big. Changed in code.
EDIT2: I just added a Pull request To add the Resume-Feature. If it gets rejected you could still decide whether it would be ok for you to modify/extend the client. If it gets accepted, just use store the return value of $media->getResumeUri() in a database, and later call $media->resume($previously_stored_return_value) after instantiation to resume the process.
On the first instance, make an upload call and get the resumeUri:
$chunk = fread($handle, 1*1024*1024);
$result = $media->nextChunk($chunk);
$resumeUri = $media->getResumeUri();
$offset = ftell($handle);
On the second instance use the same resumeUri to resume the upload from where we left out by calling resume() function before nextChunk() function:
if ($resumeUri) {
$media->resume($resumeUri);
}
fseek($handle, $offset);
$chunk = fread($handle, 1*1024*1024);
$result = $media->nextChunk($chunk);
Repeat the process until the $result variable has truthy value.
Original question
I'm serving an mp3 file from a ZF2 controller action. This works fine in all browsers except for Safari on OS X and iPhone/iPad.
The audio plays, but the duration is just displayed as NaN:NaN, whereas in every other browser the correct duration is being displayed.
I went over all the threads on SO talking about the same problem and it seems like it has something to do with the response headers and the Content-Range and Accept-Ranges headers in particular. I've tried all the different combinations but still to no avail - Safari still refuses to display the duration correctly.
The relevant code snippet looks like this:
$path = $teaserAudioPath . DIRECTORY_SEPARATOR . $teaserFile;
$fp = fopen($path, 'r');
$etag = md5(serialize(fstat($fp)));
fclose($fp);
$fsize = filesize($path);
$shortlen = $fsize - 1;
$response->setStatusCode(Response::STATUS_CODE_200);
$response->getHeaders()
->addHeaderLine('Pragma', 'public')
->addHeaderLine('Expires', -1)
->addHeaderLine('Content-Type', 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3')
->addHeaderLine('Content-Length', $fsize)
->addHeaderLine('Content-Disposition', 'attachment; filename="teaser.mp3"')
->addHeaderLine('Content-Transfer-Encoding', 'binary')
->addHeaderLine('Content-Range', 'bytes 0-' . $shortlen . '/' . $fsize)
->addHeaderLine('Accept-Ranges', 'bytes')
->addHeaderLine('X-Pad', 'avoid browser bug')
->addHeaderLine('Cache-Control', 'no-cache')
->addHeaderLine('Etag', $etag);
$response->setContent(file_get_contents($path));
return $response;
The player (I'm using mediaelementjs) looks like this in Safari:
I've also tried interpreting the HTTP_RANGE request header based on another example, like so:
$fileSize = filesize($path);
$fileTime = date('r', filemtime($path));
$fileHandle = fopen($path, 'r');
$rangeFrom = 0;
$rangeTo = $fileSize - 1;
$etag = md5(serialize(fstat($fileHandle)));
$cacheExpires = new \DateTime();
if (isset($_SERVER['HTTP_RANGE']))
{
if (!preg_match('/^bytes=\d*-\d*(,\d*-\d*)*$/i', $_SERVER['HTTP_RANGE']))
{
$statusCode = 416;
}
else
{
$ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));
foreach ($ranges as $range)
{
$parts = explode('-', $range);
$rangeFrom = intval($parts[0]); // If this is empty, this should be 0.
$rangeTo = intval($parts[1]); // If this is empty or greater than than filelength - 1, this should be filelength - 1.
if (empty($rangeTo)) $rangeTo = $fileSize - 1;
if (($rangeFrom > $rangeTo) || ($rangeTo > $fileSize - 1))
{
$statusCode = 416;
}
else
{
$statusCode = 206;
}
}
}
}
else
{
$statusCode = 200;
}
if ($statusCode == 416)
{
$response = $this->getResponse();
$response->setStatusCode(416); // HTTP/1.1 416 Requested Range Not Satisfiable
$response->addHeaderLine('Content-Range', "bytes */{$fileSize}"); // Required in 416.
}
else
{
fseek($fileHandle, $rangeFrom);
set_time_limit(0); // try to disable time limit
$response = new Stream();
$response->setStream($fileHandle);
$response->setStatusCode($statusCode);
$response->setStreamName(basename($path));
$headers = new Headers();
$headers->addHeaders(array(
'Pragma' => 'public',
'Expires' => $cacheExpires->format('Y/m/d H:i:s'),
'Cache-Control' => 'no-cache',
'Accept-Ranges' => 'bytes',
'Content-Description' => 'File Transfer',
'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename="' . basename($path) .'"',
'Content-Type' => 'audio/mpeg', // $media->getFileType(),
'Content-Length' => $fileSize,
'Last-Modified' => $fileTime,
'Etag' => $etag,
'X-Pad' => 'avoid browser bug',
));
if ($statusCode == 206)
{
$headers->addHeaderLine('Content-Range', "bytes {$rangeFrom}-{$rangeTo}/{$fileSize}");
}
$response->setHeaders($headers);
}
fclose($fileHandle);
This still gives me the same result in Safari. I even tried using core PHP functions instead of the ZF2 Response object to render a response, using header() calls and readfile(), but that doesn't work either.
Any ideas on how to solve this are welcome.
Edit
As suggested by #MarcB I compared the response headers of the two requests. The first request is to the PHP action serving the mp3 file data and the second is when I browse to the same mp3 file directly. At first the headers weren't completely the same, but I modified the PHP script to match the headers of the direct download, see Firebug screenshots below:
Response headers served by PHP:
Response headers direct download:
As you can see they are exactly the same except for the Date header, but that's because there was about a minute and a half in between the requests. Still Safari is claiming it is a live broadcast when I try to serve the file from the PHP script and so the audioplayer still shows NaN for the total time when I load it that way. Is there any way to tell Safari to just download the whole file and just trust me when I say this is not a live broadcast?
Also could it be that Safari sends different request headers and thus the response headers are also different? I usually do my debugging in Firefox with Firebug. When I open the mp3 file URL in Safari for instance I cannot open the Web Inspector dialog. Is there any other way to view what headers are being sent and received by Safari?
Edit 2
I'm now using a simple stream function implementing the range requests. This seems to work on my dev machine even in Safari, but not on the live VPS server where the site is running.
The function I use now (courtesy of another SO-er, don't remember the exact link):
private function stream($file, $content_type = 'application/octet-stream', $logger)
{
// Make sure the files exists, otherwise we are wasting our time
if (!file_exists($file))
{
$logger->debug('File not found');
header("HTTP/1.1 404 Not Found");
exit();
}
// Get file size
$filesize = sprintf("%u", filesize($file));
// Handle 'Range' header
if (isset($_SERVER['HTTP_RANGE']))
{
$range = $_SERVER['HTTP_RANGE'];
$logger->debug('Got Range: ' . $range);
}
elseif ($apache = apache_request_headers())
{
$logger->debug('Got Apache headers: ' . print_r($apache, 1));
$headers = array();
foreach ($apache as $header => $val)
{
$headers[strtolower($header)] = $val;
}
if (isset($headers['range']))
{
$range = $headers['range'];
}
else
$range = FALSE;
}
else
$range = FALSE;
// Is range
if ($range)
{
$partial = true;
list ($param, $range) = explode('=', $range);
// Bad request - range unit is not 'bytes'
if (strtolower(trim($param)) != 'bytes')
{
header("HTTP/1.1 400 Invalid Request");
exit();
}
// Get range values
$range = explode(',', $range);
$range = explode('-', $range[0]);
// Deal with range values
if ($range[0] === '')
{
$end = $filesize - 1;
$start = $end - intval($range[0]);
}
else
if ($range[1] === '')
{
$start = intval($range[0]);
$end = $filesize - 1;
}
else
{
// Both numbers present, return specific range
$start = intval($range[0]);
$end = intval($range[1]);
if ($end >= $filesize || (! $start && (! $end || $end == ($filesize - 1))))
$partial = false; // Invalid range/whole file specified, return whole file
}
$length = $end - $start + 1;
}
// No range requested
else
$partial = false;
// Send standard headers
header("Content-Type: $content_type");
header("Content-Length: $filesize");
header('X-Pad: avoid browser bug');
header('Accept-Ranges: bytes');
header('Connection: Keep-Alive"');
// send extra headers for range handling...
if ($partial)
{
header('HTTP/1.1 206 Partial Content');
header("Content-Range: bytes $start-$end/$filesize");
if (! $fp = fopen($file, 'rb'))
{
header("HTTP/1.1 500 Internal Server Error");
exit();
}
if ($start)
fseek($fp, $start);
while ($length)
{
set_time_limit(0);
$read = ($length > 8192) ? 8192 : $length;
$length -= $read;
print(fread($fp, $read));
}
fclose($fp);
}
// just send the whole file
else
readfile($file);
exit();
}
This is then called in the controller action:
$path = $teaserAudioPath . DIRECTORY_SEPARATOR . $teaserFile;
$fsize = filesize($path);
$this->stream($path, 'audio/mpeg', $logger);
I added some logging for debugging purposes and the difference seems to be in the request headers. On my local dev machine, where it works I get this in the log:
2014-03-09T18:01:17-07:00 DEBUG (7): Got Range: bytes=0-1
2014-03-09T18:01:18-07:00 DEBUG (7): Got Range: bytes=0-502423
2014-03-09T18:01:18-07:00 DEBUG (7): Got Range: bytes=131072-502423
On the VPS, where it doesn't work I get this:
2014-03-09T18:02:25-07:00 DEBUG (7): Got Range: bytes=0-1
2014-03-09T18:02:29-07:00 DEBUG (7): Got Range: bytes=0-1
2014-03-09T18:02:35-07:00 DEBUG (7): Got Apache headers: Array
(
[Accept] => */*
[Accept-Encoding] => identity
[Connection] => close
[Cookie] => __utma=71101845.663885222.1368064857.1368814780.1368818927.55; _nsz9=385E69DA4D1C04EEB22937B75731EFEF7F2445091454C0AEA12658A483606D07; PHPSESSID=c6745c6c8f61460747409fdd9643804c; _ga=GA1.2.663885222.1368064857
[Host] => <edited out>
[Icy-Metadata] => 1
[Referer] => <edited out>
[User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11
[X-Playback-Session-Id] => 04E79834-DEB5-47F6-AF22-CFDA0B45B99F
)
Somehow on the live server only the initial request for the first two bytes, which Safari uses to determine if a server supports range requests comes in (twice), but the range request for the actual data is never done. Instead I'm getting a bunch of strange request headers as returned by the apache_request_headers() call in the stream function. I'm not getting that on my local dev machine, which also runs Apache.
Any ideas would be greatly appreciated, really pulling my hair out here.
Tonight I spent a while on a similar problem - audio tags that work fine on most browsers, but don't deal with progress properly on Safari. I have found a solution that works for me, hopefully it works for you too.
I also read the other SO questions about similar issues, and they all spoke about dealing with the Range header. There are a few snippets floating around that aim to deal with the Range header. I found a short(ish) function on github that has been working for me. https://github.com/pomle/php-serveFilePartial
I did have to make one change to the file though. On line 38:
header(sprintf('Content-Range: bytes %d-%d/%d', $byteOffset, $byteLength - 1, $fileSize));
I made a small modification (removed a -1)
header(sprintf('Content-Range: bytes %d-%d/%d', $byteOffset, $byteLength, $fileSize));
I have posted an issue to the github just now explaining why I made this change. The way I found this issue is interesting: It appears that Safari doesn't trust a server when it says it can provide partial content: Safari (or technically Quicktime I think) requests bytes 0-1 of a file with a range header like this:
Range: bytes=0-1
as its first request to the file. If the server returns the whole file - it treats the file as a 'stream', which has no beginning or end. If the server responds with a single byte from that file, and the correct headers, it will then ask for a few different ranges of that file (which grossly overlap in what seems like a very inappropriate way). I see that you have already noticed this, and that you have experienced that Safari/Quicktime only makes the first ranged (0-1) request, and no subsequent 'real' ranged requests. It appears from my poking-around that this is happening because your server did not serve a 'satisfactory' ranged reply, so it gave up on the whole ranged request idea. I was experiencing this problem when I used the linked serverFilePartial function, before making my adjustment to it. However, after 'fixing' that line, Safari/Quicktime seems to be happy with the first response, and continues to make subsequent ranged requests, and the progress bar and everything appears, and we are all good.
So, long story short, give the linked library a go, and see if it works for you like it did for me :) I know you have already found a php solution that works on your dev machine but not on your production machine, but maybe my different solution will be different on your VPS machine? worth a try.
just as a complement of information, I believe this could be related to this bug https://bugs.webkit.org/show_bug.cgi?id=82672
There have been a few "workarounds" proposed like :
xhr.setRequestHeader("If-None-Match", "webkit-no-cache");
Hope this can help you or people with similar problems.
do you instantiate the player yourself via javascript? Just try to load your resource via
player.setSrc("http://www.xxx.de/controller/action");
and listen for the 'canplay' - Event, if you want to play directly:
player.addEventListener("canplay", function(){
this.play();
});
I'm trying to send a huge amount of data using SSL/TLS connection in PHP. It works pretty well if the data chunk isn't very big or if I don't use TLS, but what I need (near 2MiB), the fwrite function shows the warning:
Warning: fwrite(): SSL operation failed with code 1. OpenSSL Error messages: error: 1409F07F: SSL routines: SSL3_WRITE_PENDING: bad write retry
The relevant code I'm using to connect clients:
$cntxt = stream_context_create(array('ssl' => array('local_cert' => 'certificate.pem')));
$server = stream_socket_server('tls://127.0.0.1:8080', $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $cntxt);
// Wait for client connection //
$client = stream_socket_accept($server);
// Use non-blocking socket to allow answering many clients at a time
stream_set_blocking($client, 0);
$clients[] = $client;
When sending data, it's append to a buffer and, from time to time, this function is called for each client and linked buffer:
function trySend($client, &$buffer) {
if (strlen($buffer)) {
$len = fwrite($client, $buffer);
$buffer = substr($buffer, $len);
}
}
As I said, my code works for small ammount of data or for normal (non-TLS) connections. I've searched for this error and found http://www.openssl.org/docs/ssl/SSL_write.html:
SSL_write() will only return with success, when the complete contents of buf of length num has been written. This default behaviour can be changed with the SSL_MODE_ENABLE_PARTIAL_WRITE option of SSL_CTX_set_mode(3). When this flag is set, SSL_write() will also return with success, when a partial write has been successfully completed. In this case the SSL_write() operation is considered completed. The bytes are sent and a new SSL_write() operation with a new buffer (with the already sent bytes removed) must be started. A partial write is performed with the size of a message block, which is 16kB for SSLv3/TLSv1.
But how can I do this in PHP?
Any help appreciated :)
I have found I can get around this problem by restricting the length of the string passed to fwrite() to 8192, which prevents fwrite() warning.
So for the code in your example I would try changing the substr() call to:
$buffer = substr($buffer, $len, 8192);
The solution is:
$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
try {
$result = fwrite($fp, $msg, strlen($msg));
}
catch (Exception $ex) {
sleep(1); //sleep for 5 seconds
$result = fwrite($fp, $msg, strlen($msg));
}
I am working on a little project of mine and have built a UDP scraper that uses sockets to return data about a specific sha1 hash.
It works but is incredibly slow and wondered if any one knows how I could speed it up or improve the existing code.
The code is below;
// SCRAPE UDP
private function scrapeUDP($tracker, $hash) {
// GET TRACKER DETAILS
preg_match('%udp://([^:/]*)(?::([0-9]*))?(?:/)?%i', $tracker, $info);
// GENERATE TRANSACTION ID
$transID = mt_rand(0, 65535);
// PACKED TRANSACTION ID
$packedTransID = pack('N', $transID);
// ATTEMPT TO CREATE A SOCKET
if(!$socket = #fsockopen('udp://' . $info[1], $info[2], $errno, $errstr, 2)) {
return;
}
// SET STREAM TIMEOUT
stream_set_timeout($socket, 2);
// CONNECTION ID
$connID = "\x00\x00\x04\x17\x27\x10\x19\x80";
// BUILD CONNECTION REQUEST PACKET
$packet = $connID . pack('N', 0) . $packedTransID;
// SEND PACKET
fwrite($socket, $packet);
// CONNECTION RESPONSE
$response = fread($socket, 16);
// CHECK CONNECTION RESPONSE LENGTH
if(strlen($response) < 16) {
return;
}
// UNPACK CONNECTION RESPONSE
$returnData = unpack('Naction/NtransID', $response);
// CHECK CONNECTION RESPONSE DATA
if($returnData['action'] != 0 || $returnData['transID'] != $transID) {
return;
}
// GET CONNECTION ID
$connID = substr($response, 8, 8);
// BUILD SCRAPE PACKET
$packet = $connID . pack('N', 2) . $packedTransID . $hash;
// SEND SCRAPE PACKET
fwrite($socket, $packet);
// SCRAPE RESPONSE
$response = fread($socket, 20);
// CHECK SCRAPE RESPONSE LENGTH
if(strlen($response) < 20) {
return;
}
// UNPACK SCRAPE RESPONSE
$returnData = unpack('Naction/NtransID', $response);
// CHECK SCRAPE RESPONSE DATA
if($returnData['action'] != 2 || $returnData['transID'] != $transID) {
return;
}
// UNPACK SCRAPE INFORMATION
$returnData = unpack('Nseeders/Ncompleted/Nleechers', substr($response, 8, 12));
// RETURN TRACKER INFORMATION
return array('seeders' => $returnData['seeders'], 'leechers' => $returnData['leechers'],);
}
It is my first time I have ever created anything to do with sockets or UDP so forgive me if it is a mess!
Thanks...
You have to make parallel request using socket_select() and non-blocking sockets or forks, because you are spending a lot of time in waiting for the response. Additionally, it may be better to use low-level functions like socket_read() or similar to control connection and data transmission better.