I have a PHP script that serves portions of a PDF file by byte ranges.
If an HTTP HEAD request is received, it should send back headers (including the PDF file size) but not the actual file contents. I have tried this:
header('HTTP/1.1 200 OK');
header('Content-Type: application/pdf');
header('Accept-Ranges: bytes');
header('Content-Length: '.filesize($Pathname));
die;
The problem is that something (I assume the web server == LiteSpeed) replaces the Content-Length header with Content-Length: 0 - which defeats the whole purpose.
Can anyone suggest what I should be doing? Thanks
From w3c Hypertext Transfer Protocol -- HTTP/1.1:
When a Content-Length is given in a message where a message-body is
allowed, its field value MUST exactly match the number of OCTETs in
the message-body. HTTP/1.1 user agents MUST notify the user when an
invalid length is received and detected.
And:
The Content-Length entity-header field indicates the size of the
entity-body, in decimal number of OCTETs, sent to the recipient or, in
the case of the HEAD method, the size of the entity-body that would
have been sent had the request been a GET.
So, I suppose, your code will properly work if you send real HEAD request to your server.
It's the webserver job, not yours.
In my case I left everything to the Apache webserver and nothing changed in my php code except of how the requests is being parsed
For example things like
if($_SERVER['REQUEST_METHOD'] === "GET"){
//ok
}else{
//send 400 Bad Request
}
are changed to
if($_SERVER['REQUEST_METHOD'] === "GET" || $_SERVER['REQUEST_METHOD'] === "HEAD"){
//ok
}else{
//send 400 Bad Request
}
and Apache did all the heavy lifting (striped the response body).
(don't try to ob_clean() or die("") or things like this).
related resources:
http://hc.apache.org/httpclient-3.x/methods/head.html
https://security.stackexchange.com/questions/62811/should-i-disable-http-head-requests
Apache 2.2.2 response on HEAD requests
As Lurii mentioned, the content length is affected by your request type.
With GET requests, a non-matching content length may result in a hanging client, so LiteSpeed will verify the content length before sending the header to the client.
Using a HEAD request should return the content length as expected.
Related
I have a symfony2 application where I am using the Guzzle http client to send a GET request to a server in order to retrieve the contents of a json file. The Guzzle response gets transformed into a Symfony2 response to the browser.
The Guzzle response comes back with the following headers:
Content-Encoding: gzip
Content-Length: 2255
Content-Type: application/json
When outputting the data to the UI/browser I notice that it gets cut off because the Content-Length is incorrect. The size of the file is closer to 4905 bytes, not 2255. 2255 is the exact length of the data up to the cut-off point. I suspect that the 2255 is the size of the gzipped data and it gets uncompressed at some point without updating the content-length. Now I did verify that I get all of the data back, however the content-length header is honored which is why the data gets cut off when I forward it to the browser. Interestingly, hitting the url to the json file directly yields the full contents even though the content-length is 2255 which means it gets ignored by Chrome when hitting the file directly. Same if I use the POSTman REST client to make the GET request - full contents get displayed.
By default, Guzzle has a request option decode_content = true for how the responses should be handled. I set it to false when submitting the request but that didn't seem to resolve the issue.
Before converting the Guzzle response to a Symfony response I removed the content-length header and that seems to solve the problem however I am not sure that's the best approach since RFC protocol states that a content-length header should be present unless a transfer-encoding header is present, which it isn't. https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
Another alternative is, since this is a streamed response, to get the size of the stream and correct the content-length, however the Guzzle implementation uses strlen() for this which has the undesirable affect of reading the whole stream.
What possible issues might I run into if I choose to omit the content-length header? And alternatively, is there a way to get the TRUE length of the contents without reading the whole stream and simply update the content-length header with the correct amount?
if curl --head http://.... includes "301" or "404" then ...
if curl --head http://.... has content-Length > 1000000 then ...
how to be put the least possible load on the destination server
when doing many of such requests?
is it possible to only request the first line of the http answer or even only the 3 digits of the status code(/content length) but nothing else, to minimize traffic?
(actually, is there any alternative http field next to content-length indicating file size?)
thanks!
If you cannot modify server to return something small and useful - then the only option is HEAD http method to request only headers of response, all methods can be found here.
Content-length is standard way to tell the length of HTTP body, there are no other ways or they are very server specific, check http rfc
I use Libsyn for several podcasts, and this is a new issue that I've never had before. I publish my own RSS feed, and redirect the audio file requests through my own server so I can do my own logging. iTunes is rejecting my submission of the feed with the message "There is a problem with your feed. Your episode are hosted on a server which doesn't support byte-range requests. Enable byte-range requests and try your submission again."
Obviously, Libsyn DOES support byte-range requests, so the problem appears to be in the way I'm redirecting the requests. This is what has always worked on my existing podcasts:
$id = 12345; // (the episode number)
$url = 'http://traffic.libsyn.com/myshow/myfile-'.$id.'.mp3';
header("Location: $url");
So I've tried adding some headers to convince iTunes that I actually do support byte-range requests. I've tried just about every combination of these that I can think of:
$id = 12345; // (the episode number)
$filesize = 12345678; // (the size of my file on Libsyn)
$url = 'http://traffic.libsyn.com/myshow/myfile-'.$id.'.mp3';
header('HTTP/1.1 206 Partial Content');
header('Content-Type: audio/x-mp3');
header('Accept-Ranges: bytes');
header("Content-Length: $filesize");
header("Location: $url");
I still get the error trying to submit the show to iTunes, and am out of ideas. Any suggestions?
You can't return make a single HTTP response be both a success (206) and a redirect (Location header, implying a 30x). You'll need to either serve the content yourself, or give up on logging.
I encourage the many others who have posted about this question to look for unrelated typos and something silly like my problem was. The error returned by iTunes does not necessarily mean what it says.
Shoutcast servers not generate valid HTTP resource. Our mobile player link (Android) requires a valid HTTP resource. How can i use php script for shoutcast stream? This script provide valid http resource? Any one knows?
<?php
$track = "shoutcaststream.mp3";
if (file_exists($track)) {
header("Content-Type: audio/mpeg");
header('Content-Length: ' . filesize($track));
header('Content-Disposition: inline; filename="shoutcaststream.mp3"');
header('X-Pad: avoid browser bug');
header('Cache-Control: no-cache');
readfile($track);
exit;
} else {
header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found', true, 404);
echo "no file";
}
Update 2
Everything now fine asper "Brad" instructions. We use set_time_limit(0) But still my stream closed after 3 mins to 5 mins? How to fix? Kindly help me.. :)
Yes, what you have is valid.
The invalid part about SHOUTcast streams is that they return ICY 200 OK instead of HTTP/1.0 200 OK in their responses. If you wish to change your script to proxy the SHOUTcast request, you will likely need to connect to your SHOUTcast server via a normal TCP connection (see fsockopen()), and send the raw request data.
Once you have a connection and are receiving data, echo this data out to your client.
Also note that you will want to call set_time_limit(0) so that your script doesn't time out in 30 seconds or so. Also make sure to not send a Content-Length header, and set your response to be HTTP/1.0 so that you don't have to send the data as chunked. (Chunked encoding playback only works on Android 2.3 or later.)
Finally, if you find that hacking something in place with PHP is too much of a hassle, I have a stream hosting service available for testing that does exactly what you are asking, which works fine for the built-in audio player on Android.
I was wondering if there are any problems or difference between sending normal headers before or after sending cookie headers. Do some browsers prefer a certain order to headers? If the cookie header is to large would subsequent headers never be parsed?
setcookie("TestCookie", $value);
header("Content-type: text/javascript");
or
header('Location: http://www.example.com/');
setcookie("TestCookie", $value);
or
setcookie("SuperLargeCookie", $massive_value);
setcookie("TinyCookie", $small_value);
header("Status: 404 Not Found");
There is no difference. The Http protocol does not specify that headers are to be in a certain order. Browsers do not differentiate based on the order of headers either.
The total length of Http headers does have a limit. This limit is imposed by the server and not the browser. Typically between 8K and 16K. However this is configurable.
It really doesn't matter as long as the other HTTP headers have not been sent. setcookie() actually writes a header itself:
Set-Cookie: SuperLargeCookie=whatever; Max-Age=3600; Version=1
similar to a header() call:
Location: http://www.example.com/redirect
HTTP messages span packets all the time, so you'd be hard-pressed to overfill one unless you're jamming tons of kilobytes in there. If you need to do that, consider a better design. Browsers don't care about the order of headers since different servers (and applications) append headers all the time. Cookies are implemented as HTTP headers, so they should appear like so in the HTTP request:
Cookie: TestCookie=value\r\n
Content-type: text/javascript\r\n
\r\n
I'm not sure what the Status header is supposed to do in your example, but I don't think it's right since the webserver will set a 200 OK response code if the code executes correctly... The header function page has this examaple:
<?php
header("HTTP/1.0 404 Not Found");
?>
With the PHP header function, just make sure you're not writing any text out before issuing it. Otherwise, you could mess everything up.