Page content suddenly cut off - PHP

And there are no errors of course.

There is an obscure problem that occurs on the web that is very hard to describe. Because it is hard to describe, it is even harder to find an answer on the stackoverflow.

Scenario

I deployed my website and everything worked just fine. It worked locally, on staging and on production. One day, I deployed on production and some JavaScript simply failed to load. I checked the Network tab on the browser and there wasn't even an attempt to download the file. I checked my code and I did not forget to include it on the page.

The next thing I did was look at the generated page source. It was cutoff right in the middle of the script tag declaration:

<script src="http://cdn.jq

uery.

Just like that the file ended. I would check different pages and they all ended in different abrupt places. Some other pages ended where they were supposed to.

This was a weird bug because there were no errors at all. It wasn't a variable I used that I forget to define, or there wasn't a method I called that didn't exist. The file simply ended.

Debugging

I didn't know if this was a php problem, the webserver, or if it was the hosting company messing with the content. But before blaming anyone, it is always better to assume you made a mistake yourself. At least if I blame myself, I can ask the right questions that will lead me to a solution.

The first thing I did was look at the place where the content is being printed on the page. I use a framework and there are multiple scopes in different parts of the page. For example, the first scope is the layout of the page. It contains the header the footer and some navigation links. This is handled by capturing the output buffer of the layout template. The second scope is another template inside the layout, this template is determined by the current page you are visiting. It also contains it's own output buffer.

Since the only template that is cut off is the layout one, the problem must be on the first scope. So the first thing I did was look at when this template is printed on the page. The content of the page is saved in a variable $content before it is echoed on the page.

echo $content;

So I started by printing text before and after it to see if somehow there was an error in it. The text before appeared, but not the text after. The other unusual thing was that the text was cut off in a different place:

<script src="http:

As if the content length was limited by the a specific amount of bytes. I thought to blame the hosting company, but let's leave that as a last resort.

I counted the byte size of the document and it was 29688 bytes. I checked a different page and it was a 36876 bytes. So the content was not limited to a specific amount. I had to be the one who somehow cut off the size.

So I started commenting out code one line at a time and suddenly it worked. The problem of course was caused by me.

The Problem

A few month ago, I started receiving HEAD requests on my website. What these request do is check the validity of a page and its size without looking at the content.

I upgraded my framework to handle those requests. It went from this:

$content = $response->render();
$response->processCookies();
echo $content;

To this:

$content = $response->render();
$response->processCookies();
$response->setHeader("Content-length",mb_strlen($content)); 
$headers = $response->commitHeaders();

if ($realRequestMethod !== "HEAD"){
    echo $content;
}

Basically, I added a new line to set the content length a head of time and only printed the content if the request was different then a HEAD request.

It worked fine. Except when the content length was incorrect.

mb_strlen() takes multi-byte characters into consideration. It is the correct way to find the length of a string. Except in this case what I really needed was the byte size not the string length. When the page contained foreign characters my content length was incorrect hence it was cut off.

The solution

All I had to do was switch mb_strlen to just strlen. Now multi-byte characters are counted for the real length thus making the content length correct.

$content = $response->render();
$response->processCookies();
$response->setHeader("Content-length",strlen($content)); // <---- strlen
$headers = $response->commitHeaders();

if ($realRequestMethod !== "HEAD"){
    echo $content;
}

When you set the Content-Length header, make sure that the content is actually the right length. If you set the number to smaller then the actual size, that's all the browser is going to download.

I thought it would be good to share this here because I couldn't find the solution elsewhere. I hope someone benefits from it.


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only