Search This Blog

Thursday, December 17, 2009

$dom2mootools: MooTools JavaScript Element Construction / Adoption Just Got Easier!

$dom2mootools

Overview: $dom2mootools drastically speeds up the process of JavaScript element construction when using the MooTools framework. Given any element node, it produces MooTools element constructors and should perform nearly seamless parent-child adoption (including a final adoption at the end and a reminder to reassign to your own root node).

Accreditation: Idea suggested by Jessie Yeagle and implemented by Eric Knickerbocker, both developers at Intuitive Web Solutions.

Creation Date: November 20, 2009. Last updated June 23, 2010.

URL: http://mrrena.blogspot.com/2009/12/dom2mootools.html.

Simply paste the HTML source code you want
to convert into the text box below and click Submit.

Input

Check to flatten div HTML.

Output

Monday, October 19, 2009

"Failed To Find Flength File": Uber-Uploader Error SOLVED


"Failed to find flength file."

I moved a new application we're developing from the development server onto the one that we hope to release live soon. As part of this package, we make use of Uber-Uploader and experienced no problems with the dev server, which was set up identically to the soon-to-be-live configuration. However, I kept getting a "Failed to find flength file" error on the new server.

I first consulted the page from Uber Uploader entitled Flength File Help "Failed To Find Flength File." That was clearly not the problem—I did not even have the mod_security module for Apache configured. I then ended up in a discussion forum related to the XUpload solution. Still no joy, though looking back, some people may have been on the right track, just not able to clearly communicate the solution.

In short, it was a file permissions issue. As most of you reading these words probably know, Linux has file permission settings and your Perl/CGI uploader script needs to chmod-ed to 755 or similar—it needs to be fully executable, in other words. No doubt the same issue would be true of an MS server with permissions set as well.

Don't know how to set script permissions? Most FTP programs offer that functionality, including the open-source program FileZilla—right click the Perl upload file, as shown below, and set your permissions to fully executable:

Wednesday, July 08, 2009

"EntityRef: expecting ';' at line 1": XML Error SOLVED


"EntityRef: expecting ';' at line 1"

I was using the Uber Uploader script for a project, and ran into the error above in the Firebug console. In my case, I had been passing a URL with a query string that needed to have HTML entities for the ampersands.

In other words, the following URL:
http://somesite.com/file.php?c=1&d=2&e=3
was creating an error in the XML file. The ampersands had to be encoded, which solved the error:
http://somesite.com/file.php?c=1&d=2&e=3
Wikipedia has a list of all the characters that must be encoded when using XML. I list them below with the symbol followed by the HTML entity you will need to use to replace it with. If you are receiving the same error (or the error "Reserved XML Name"), you most likely need to make sure that these symbols are encoded (and with the latter error, watch out for deadly white space and newline characters).
Symbol:HTML Entity:Description:
""(double) quotation mark
&&ampersand
'' or "apostrophe (= apostrophe-quote)
<&lt;less-than sign
> &gt;greater-than sign

Note that if you are using PHP, htmlspecialchars and htmlspecialchars_decode should give you everything you need for XML; for JavaScript, you'll want a custom solution such as this one.

Thursday, April 09, 2009

Trouble getting audio and video to sync properly in Cam Studio?

The article below was written by my friend Greg Rubottom.

Cam Studio is a great free desktop recording program for Windows. However, unless you use the proper settings for video and audio (see images below) you will end up with your video and audio out of sync in the final AVI file.

Audio:
Video:

I have found little information online about this problem, and what little I did find has proven to be less than accurate.

So, I created the following YouTube video to show you what the proper settings should be to maintain audio and video sync and to ensure that your Cam Studio files will be viewed in HD on YouTube.


Related post: My Favorite Free Video Converter, also by Greg.

My Favorite Free Video Converter

The recommendation below was written by my friend Greg Rubottom.

I searched and searched the web for a decent free video file format converter. I have no idea how many I must have tried, but it was a significant number. In the end I think it is no contest.

Any Video Converter is hands down my favorite. There are two versions: a free version, which is only available on Windows, and then a professional version, which has a few more nice options on Windows and is also available for Mac.

Give it a try, I do not think you will be disappointed: free version, $29.95 version.

Related post: Trouble getting audio and video to sync properly in Cam Studio?, also by Greg.

Thursday, March 05, 2009

Speed up Torrent and Peer to Peer (P2P) Downloads on Vista and other Windows Platforms


As a security precaution, Windows XP and Vista and the new Windows 7 limit the number of so-called "half-open" outbound TCP connection attempts to 10. Normally, that poses no problems, but if you do any downloading with torrent files or other peer-to-peer (P2P) networking, that can significantly slow your downloads.

Many XP users have relied on the lvllord.de patch for their solution, but that only works for XP and it modifies the tcpip.sys file, which has been known to have disastrous results for Vista users. Fortunately, there is a simple and elegant solution that works not only on XP (and better than the patch), but on Vista and 7 as well.

TCP-Z is a small GUI program for Windows that you can use to change the number of allowed half-open connections on the fly (up to 251) without restarting the computer (click image for full-size view):

To find out more about this program—and most importantly to download a copy for yourself—just head over to the TCP-Z Network Monitor site.

Note: To open the downloaded file, you may need a copy of the excellent and free 7-Zip (which in my opinion works better than the native Windows zip program), as the native program does not recognize the .7z file extension. (7-Zip recognizes its own extension and many others, including all the standards.)

Thursday, February 26, 2009

How to Easily Combine, Minify, and Cache JavaScript and CSS with 1&1 (And Other Web Hosts)


An Opening Word

In How to Compress Perl/CGI-Scripts... and How to Compress PHP and Other Text-Based Files..., I write about achieving better standards according to Yahoo’s Best Practices for Speeding up Websites guide.

If your server supports PHP5, now there is an incredibly easy way to minify both JavaScript and CSS files on the fly, PLUS a way to combine a number of different files into a single HTTP request. (See Step 4 of my blog entry on compressing PHP scripts to convert your site to PHP5, particularly if you are using a 1&1 shared hosting package.)

Thanks to Ryan Grove and Steve Clay, Minify! is now here (download link). As the source page on GoogleCode suggests, it combines, minifies, and caches JavaScript and CSS files on demand to speed up page loads.

Setup and Basic Use

Just download the zip file and drop the min folder in your root directory.

Then, to include single resources on your page, just set them up like the following with ?f= (f short for "file"), followed by the URL of the resource:
// css
<link rel="stylesheet" href="/min/?f=/path_to_file/file.css" type="text/css" />
// js
<script type="text/javascript" src="/min/?f=/path_to_file/file.js"></script>
You can also use URL-rewriting if your host supports it (see Note below):
// js -- notice the "?" before the "f" has been dropped
<script type="text/javascript" src="/min/f=/path_to_file/file.js"></script>
Minify comes with its own URL-builder for easily creating and checking links which you can access at http://name_of_your_site_here.com/min/index.php once you have it installed on your server.

The builder interface itself will teach you how to serve groups of files as a single, minified resource by adding a few lines to /min/groupsConfig.php, but we'll go ahead and look at it here anyway:
// In /min/groupsConfig.php, I set up the unique identifier "js"
// to specify a group of 5 hypothetical JS files
return array(
    'js' => array(
        '//js/file1.js',
        '//js/file2.js',
        '//js/file3.js',
        '//js/file4.js',
        '//js/file5.js'
    )
);
You can then include this group of files in your HTML tag by specifying g (for "group"), followed by your unique js identifier.
<script type="text/javascript" src="/min/g=js"></script>
Or you can take full advantage of comma-delimited arrays and set up as many sets of files paired with unique identifiers as you want (of course including any CSS files using the link tag, as shown in the first example on this page):
// "js" and "css" file groups set in /min/groupsConfig.php
return array(
    'js' => array(
        '//js/file1.js',
        '//js/file2.js',
        '//js/file3.js',
        '//js/file4.js',
        '//js/file5.js'
    ),
    'css' => array(
        '//css/file1.css',
        '//css/file2.css',
        '//css/file2.css'
    )
);

Setting a Long Cache Life for Your Pages

Minify's default cache life is 30 minutes for CSS and JS files. This works well for development servers, but for production servers with stable code, a longer cache time is recommended. Your can easily implement a year-long cache life in the HTML simply by appending a number (yes, any number) as a query string variable.
// css
<link rel="stylesheet" href="/min/g=css&1" type="text/css" />
// js
<script type="text/javascript" src="/min/g=js&1"></script>
However, you must update this number if you make changes to these files and it is important that users have the latest versions. For this reason, I recommend starting off with the number 1 and incrementing it as your changes dictate, though as long as the numbers are always different, it makes no difference how you do it.

Since this approach requires manual editing, this may not be optimal for you. For the full spectrum of caching possibilities, see the developer doc HttpCaching; for advanced options of specifying a different cache system (as well as other advanced uses) see the CookBook doc.

For more general use ideas, including how to specify a b for "base directory" when a group of single files all share the same root directory path, see the developer README file beginning with the heading "Minifying a Single File"; for advanced usage beyond the scope of the examples here, see the developer comments at CustomSource.

Note: If you want to use rewritten urls and you are using a Linux shared hosting package with 1&1 (and maybe even if you're not), you will probably need to make a small adjustment to the included .htaccess file in the min directory. The line in the native code you're after reads as follows:
RewriteRule ^([a-z]=.*)  index.php?$1  [L,NE]
but you will probably need to change it:
RewriteRule ([a-z]=.*) /min/index.php?$1 [L,NE]
Also to point out the obvious, make sure if you put the min directory a level deeper (see below), you specify the sub-directory: for example, /myscripts/min/index.php.

Note on Use Inside A Sub-Directory

By default, Minify is intended to have the min directory parked at the root level. It works just fine a directory level deeper, however, though this sometimes causes problems with the URL-building interface. The developer docs recommend a solution that uses PHP; see AlternateFileLayouts.

Update: I also ran into a small issue when minifying HTML: see the gray box at the end of How to Minify HTML using PHP and Minify (including final HTML output of PHP files) for the work-around.

If you prefer to hatch a solution to the builder interface in JavaScript, you'll need to modify the code slightly. Use your IDE or text editor to open the _index.js file found in the builder directory.

The first block of code currently reads as follows; our interest here all deal with _minRoot:
var MUB = {
    _uid : 0
    ,_minRoot : '/min/?'
    ,checkRewrite : function () {
        var testUri = location.pathname.replace(/\/[^\/]*$/, '/rewriteTest.js').substr(1);
        function fail() {
            $('#minRewriteFailed')[0].className = 'topNote';
        };
        $.ajax({
            url : '../f=' + testUri + '&' + (new Date()).getTime()
            ,success : function (data) {
                if (data === '1') {
                    MUB._minRoot = '/min/';
                    $('span.minRoot').html('/min/');
                } else
                    fail();                
            }
            ,error : fail
        });
    }
Replace this with the following code, which is not particularly pretty, but is "smart" and will read the location of the directory when the script is called, no matter how many levels deep. The three changes are marked below by the comment // here.
var MUB = {
    _uid : 0
    ,_minRoot : location.pathname.replace('/builder/', '') + '?' // here
    ,checkRewrite : function () {
        var testUri = location.pathname.replace(/\/[^\/]*$/, '/rewriteTest.js').substr(1);
        function fail() {
            $('#minRewriteFailed')[0].className = 'topNote';
        };
        $.ajax({
            url : '../f=' + testUri + '&' + (new Date()).getTime()
            ,success : function (data) {
                if (data === '1') {
                    MUB._minRoot = MUB._minRoot.replace('?', ''); // here
                    $('span.minRoot').html(MUB._minRoot); // here
                } else
                    fail();                
            }
            ,error : fail
        });
    }

Copy to Clipboard Functionality with JavaScript and Flash 10: Creating a Loop to Instantitate Multiple Copy Buttons


As a security precaution, JavaScript is not allowed to read from or write to the system clipboard. Up until Flash 10 came out with a new security feature that required copying to the clipboard to be instantiated directly from a user clicking on a Flash control, it was a simple thing to download a tiny Flash file, include it invisibly on a web page, and send it information intended for the clipboard via JavaScript. When Flash 10 came out, however, that functionality was broken.

Not any more. There is a project on GoogleCode developed by Joseph Huckaby known as zeroclipboard that is also backwards compatible with Flash 9:
The Zero Clipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie, and a JavaScript interface. The "Zero" signifies that the library is invisible and the user interface is left entirely up to you.

This library is fully compatible with Flash Player 10, which requires that the clipboard copy operation be initiated by a user click event inside the Flash movie. This is achieved by automatically floating the invisible movie on top of a DOM element of your choice. Standard mouse events are even propagated out to your DOM element, so you can still have rollover and mouse down effects.
To date, adding the functionality to multiple controls requires you to call multiple instances, unless the size of the control remains constant. Joe put together a demo version of the latter using JQuery on this page.

I also put together a non-JQuery technique using straight JavaScript below. The code is heavily commented to explain what I am doing in each step and where everything goes.

Step 1: Find the "glue" function and space it down one space to open a blank line. Paste the following "addon" function (including ending comma!) directly above the "glue" function in the space you opened--it recreates only that portion of the "glue" function that we still need to get hover effects to work properly.
addon: function (elem) {
    this.domElement = ZeroClipboard.$(elem);
},
Step 2: Replace the original ZeroClipboard.Client() function with the following, which remembers the unique ID of each copy button (that's what the "divId" is about):
// modified ZeroClipboard.Client() function
Client: function (elem, divId) {
    // constructor for new simple upload client
    this.handlers = {};

    // unique ID
    this.id = ZeroClipboard.nextId++;
    this.movieId = 'ZeroClipboardMovie_' + this.id;

    // register client with singleton to receive flash events
    ZeroClipboard.register(this.id, this);

    // create movie
    if (elem) this.glue(elem);

    // if passed, register the unique div id
    if (divId) this.divId = divId;
}
Step 3: The following init() function is meant to replace the existing one. It loops through all objects of a given type looking for a particular class. Your HTML page will need to assign both a common class and a unique ID to these elements in order for it to work.

Example:
<div id="unique1" class="common">Copy!</div>
<div id="unique2" class="common">Copy!</div>
and so on.

Make sure you change the SOME_CLASS_NAME variable to whatever common class name you are using. As with the original script, you will also need to determine what text to copy and work out how that will be performed.
function init() {
    // set the SOME_CLASS_NAME variable to whatever common class you are
    // using. Our example above uses the class "common"
    var SOME_CLASS_NAME = 'common';
    // Gather together all the elements we're interested in 'divs' variable
    // (could be any element including '*' to grab all elements), and
    // initialize 'node' variable.
    var divs = document.getElementsByTagName('div'),
        node = '';

    // This kind of unorthodox looping has been shown to be faster with
    // benchmarking tests when looping over DOM elements
    // (http://blogs.sun.com/greimer/entry/best_way_to_code_a). Note that
    // 'node' is the unique instance of the container in each loop.
    for (; node = divs[i++];) {

        // If the elements we've collected together have the class
        // 'SOME_CLASS_NAME' set, we want them. We need to
        // ensure that they have a unique id specified as well.
        if (node.className && /SOME_CLASS_NAME/.test(node.className)) {

            // We read the id of the element automatically (the reason a unique ID
            // needs to be set in the first place).
            var buttonId = node.id;
            // We send the newly read ID for registration in the modified
            // ZeroClipboard.Client function shown above.
            clip = new ZeroClipboard.Client('', buttonId);
            // We take advantage of new 'addon' function above.
            clip.addon(buttonId);
            clip.setHandCursor(true);
            clip.addEventListener('mouseOver', function (client) {

                // 'client' is the unique instance of our movie container, so we need it
                // to prefix the setText function. (Our example below uses the instantiated
                // id of the copy button div to grab its value--$(client.divId).value
                // --though it is not likely that this will be the copy element we want.
                // A little ingenuity is probably in order here for the setText text.) 
                client.setText($(client.divId).value);

                // Here we pass the copy button id on mouseover.
                ZeroClipboard.$(buttonId).addClass('hover');
            });
            clip.addEventListener('mouseOut', function (client) {
                // We again pass the copy button id on mouseout.
                ZeroClipboard.$(buttonId).removeClass('hover');
            });

            // We read/set button_width and button_height.
            var button_width = $(buttonId).offsetWidth,
                button_height = $(buttonId).offsetHeight;

            // We create a floating DIV...
            var div = document.createElement('div'),
                zIndex = 99;
            // Check the z-index...
            if ($(buttonId).style && $(buttonId).style.zIndex) {
                zIndex = parseInt($(buttonId).style.zIndex) + 1;
            }
            div.style.position = 'absolute';
            div.style.left = 0;
            div.style.top = 0;
            div.style.width = button_width + 'px';
            div.style.height = button_height + 'px';
            div.style.zIndex = zIndex;
            // We need the button id so that we can append its movie instance.
            $(buttonId).appendChild(div);
            // All finished! :)
            div.innerHTML = clip.getHTML(button_width, button_height);
        }
    }
}

Opening/Unblocking .CHM Help Files on Windows XP SP2 or Vista


In one of my coding projects, I created a .chm help file, and loaded it to the server. However, it didn't survive the round trip: when I attempted to open a downloaded copy in Windows Vista, I saw the following message (click to view full size):


I did not for certain what was wrong, though I suspected it was some kind of security issue on Vista. I was correct.

.chm files that have been downloaded from the Internet are considered a security risk on both Windows XP and Vista. Bill Evjen blogs about the solution here; the solution, as he reports, is simple. Just right-click the file, select Properties, and then click Unblock (click to view full size):

Saturday, February 14, 2009

Getting the HTML of a Range Created in JavaScript


Working on a mainly JavaScript-driven rich-text editor for the discussion forum on Mr. Renaissance, I wanted to include a feature that automatically inserted block quotations. Two days after the editor's debut, I suddenly noticed that this feature was losing its formatting: all line breaks and markup were gone, in its place one large, crunched-together paragraph that even ran sentences together in places.

A bit of Googling revealed that using range to retrieve the user's selection returns plain text (at least in non-IE browsers), not its formatted HTML:
The contents of userSelection

The userSelection variable is now either a Mozilla Selection or a Microsoft Text Range object. As such it grants access to all methods and properties defined on such objects.

However, the Mozilla Selection object that userSelection refers to in W3C-compliant browsers also contains the text the user has selected (as text, not as HTML).

From Introduction to Range, emphasis mine.
Frustrating, to say the least. I won't go into a list of all the things I tried, but I finally found the following (slightly modified) snippet of code at Snipplr, courtesy of David King of the entirely open-source OOPStudios in Newcastle, UK:
var getSelectionHTML = function() {
    var s;
    if (window.getSelection) {
        // W3C Ranges
        s = window.getSelection();
        // Get the range:
        if (s.getRangeAt) var r = s.getRangeAt(0);
        else {
            var r = document.createRange();
            r.setStart(s.anchorNode, s.anchorOffset);
            r.setEnd(s.focusNode, s.focusOffset);
        }
        // And the HTML:
        var clonedSelection = r.cloneContents(),
            div = document.createElement('div');
        div.appendChild(clonedSelection);
        return div.innerHTML;
    } else if (document.selection) {
        // Explorer selection, return the HTML
        s = document.selection.createRange();
        return s.htmlText;
    } else {
        return '';
    }
};
I am pleased to report that David has created a wonderful little function that returns the HTML of the user's selection rather than just plain, unformatted text.

Note: If you want to simulate the action of Cut (rather than Paste), change r.cloneContents() to r.extractContents(). Normally, there would be no reason to do so, but it is a possibility.

Friday, February 13, 2009

"invalid range in character class": JavaScript Error Solved


"invalid range in character class"

It was maddening. Firebug showed this error, but gave no additional information: not even a script or function name, much less a line number. Googling didn't help very much: it suggested that it was an error involving regular expressions. Thousands and thousands of lines of code and at least half a dozen external JavaScript files later, I'm pleased to say that I discovered what the message means and now want to pass along the knowledge in hopes of saving someone else endless hours.

The short answer is that the word range refers to a "range of values," say the range from 1 to 100 typically written as 1-100. Notice that my shorthand for range involves a dash or minus sign between the 1 and 100. Therein lies the problem. If you want a regular expression to test for the presence of a dash/minus sign as a dash/minus sign, it needs to be escaped with a backslash, or else some browsers (Firefox 2 in this case) think that you are attempting to express a range instead and freak out accordingly.

In particular, I had one tiny function that was used on only one form dealing with phone numbers. Valid data included a blank space, any number, a dash, or a right or left parenthesis to accommodate a number such as (555) 555-5555. It wasn't crucial that users type in their phone number in any certain way, I just try to get in the practice of filtering my data both client and server side.

In a test copy of the production environment, I first eliminated my scripts one by one until the offending error disappeared. Then I brought that script back, eliminating each function body until the error disappeared. It turned out to be this tiny function, short for "is integer" though obviously adding the space, dash, and parentheses:
var isInt = function(s) {
    s.value = s.value.replace(/[^\d-()\s]+/g, '');
};
Notice that the - character is not escaped. Now here's the fix:
var isInt = function(s) {
    s.value = s.value.replace(/[^\d\-()\s]+/g, '');
};
I have now added the escape \-, and thousands of lines of code are now working again. Hopefully this post will save someone else hours of frustration.

Update: As Earl suggests in his comment below, you can also move the dash to the end of your regular expression square brackets without using an escape: /[^\d()\s-]+/g. 5n1p3r also brings up the point below that if you're using the RegExp object and an escape, you need two backslashes, the first to escape your escape.

Tuesday, January 27, 2009

How to Compress PHP and Other Text-Based Files with 1&1 (and Other Shared Hosts)


If you have a shared Linux hosting package through 1&1, you have no doubt discovered that the usual method of changing your .htaccess file does not allow you to compress your files using Apache because 1&1 has disabled the mod_deflate and mod_gzip modules; you also cannot specify php values in .htaccess files in 1&1 shared packages, so a solution like this one also will not work. However, there is a solution that does work, and it works even on the most basic 1&1 Beginner’s Package.

Note: If you plan to implement the Minify! option I blog about in How to Easily Combine, Minify, and Cache JavaScript and CSS..., you can safely skip steps 1 and 2 and include only the first line shown in step 3.

Step 1:

Determine what your root path is; if you are using a 1&1 package, it will look something like this:
/kunden/homepages/12/d123456789/htdocs/
You can do that by creating a text file in Notepad or other editor,* entering the following lines, then saving it with as phpinfo (or whatever you want) followed by the .php extension.
<?php
phpinfo();
?>
(* If you’re using Notepad, you’ll probably want to put quotation marks around the name "phpinfo.php" when saving, to keep it from saving the file as phpinfo.php.txt.

Personally, after passing from Geany on Windows (used Notepad++ some), and Komodo Edit on Mac OSX, my editor of choice is now a customized version of vim. Incidentally, the three previously mentioned IDEs mutually rely on the Scintilla editor as their base.)

Now browse to the newly uploaded file with your web browser and search for DOCUMENT_ROOT. This variable will tell you what the exact path is to your server.

Step 2:

Now create another php file in the same way, this time adding the following information so that when you compress your CSS and JS files, they are served with the right headers, or else they will not load properly in Firefox and other browsers:
<?php
if (isset($_SERVER['SCRIPT_FILENAME'])) {
    $timestamp = filemtime(__FILE__);
    header('Last-Modified: ' . $timestamp);
    $expires = 60*60*24*14;
    header("Pragma: public");
    header("Cache-Control: maxage=".$expires);
    header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
    header('Vary: Accept-Encoding');
    $pathinfo = pathinfo($_SERVER['SCRIPT_FILENAME']);
    $extension = $pathinfo['extension'];
    if ($extension == 'css') {
        header('Content-type: text/css');
    }    
    if ($extension == 'js') {
        header('Content-type: text/javascript');
    }
}?>
Make sure that you don’t space down between the lines or add an extra space at the end, and save this file as headers.php, uploading it to wherever you keep your scripts. For the sake of this example, I will assume that you store your scripts in a directory named webscripts.

[Update: As Brett reminds me in the comment below, you have now given PHP control of rendering your pages, so you will also need to include in this file any other header information you know you’ll need by declaring each header on its own line. For example, notice this extra line:

header('Vary: Accept Encoding');

Based on the readership of these articles, this will be particularly useful when optimizing for speed using a free tool like Google's Page Speed and the Yahoo! based YSlow. Add as many extra headers as you need, one per line.]

Step 3:

Create a third file, this time a PHP configuration file, entering the following information (though of course changed with the information you looked up in step 1 and whatever directory you use for step 2—unfortunately, you can’t use environmental variables in .ini files, as they are straight ASCII):
zlib.output_compression=1
auto_prepend_file=/kunden/homepages/12/d123456789/htdocs/webscripts/headers.php
Name your configuration file the conventional php.ini and save it. (If for some reason zlib.output_compression=1 doesn't work, try output_handler=ob_gzhandler instead.)

The first line tells the server to use PHP’s built-in g-zip handler for compression; the second line points to the exact location of the file you created in step 2: /kunden/homepages/12/d123456789/htdocs/ is the document root you located in step 1, and webscripts/ is the directory where you uploaded the headers.php script.

auto_prepend_file tells the server to automatically “prepend,” or “attach to the beginning of” all pages it serves (versus auto_append_file, which would “append,” or “attach to the end” of all files). The if/then conditional statements in step 2 ensures that the headers.php file will only attach headers if the file in question has a .css or .js extension.

Note: Check with your web host. With 1&1, at least, you will need to upload a copy of your php.ini file to EVERY directory containing files you want compressed.

Step 4:

Create an .htaccess file or modify your existing one by adding the following line (if you’re not familiar with an .htaccess file, see here):

CAUTION: If you plan to implement the Minify! solution I blog about in How to Easily Combine, Minify, and Cache JavaScript and CSS..., I do not recommend including the .css and .js file extensions on this line. Doing so processes these files twice, as Minify! already has its own mechanism for compressing files. And, as a result of double compression, certain versions of Internet Explorer choke and die silently.

AddType x-mapp-php5 .php .shtml .html .htm .txt .js .css
This line tells the server to process all the file extensions specified through PHP5; change it to x-mapp-php4 if you want to use PHP4 instead. You can add other extensions or take away from the ones here: whatever extensions are listed on the line after the x-mapp-php5 will be processed via PHP.

Note: On other Linux hosts, you may need to use php5-script instead of x-mapp-php5.

[June 4, 2011 Update: I was setting up my sister's site recently, also on a 1&1 Beginner’s Package, and the style sheets were not working correctly. The fix was to add this line to the .htaccess file immediately above the line shown in the example above:
RemoveHandler .css
I first tried removing all file extensions I was using, including .htm, but this caused problems. Just know that you have the option of removing file handlers and play around with it as needed.

Step 5:

Now test to see whether or not your pages are being compressed by using the free tool from GIDNetwork.

Tuesday, January 20, 2009

How to Compress Perl/CGI-Scripts with 1&1 (and Other Shared Hosts)

According to Yahoo’s Best Practices for Speeding up Websites, it is a good idea to compress (gzip) all text-based files, including php, css, javascript, and the topic of today: Perl CGI scripts. However, if you are hosting your website on 1&1 (or other shared hosting package*), you probably know of no easy way to g-zip such files.

May 6, 2012 Update: If you have the ability to ssh into your server as root, skip to step 4 below. By far the fastest and cleanest method is to install cpanm (if it's not already), then turn around and use it to install CGI::WebGzip:
cpan App::cpanminus
cpanm CGI::WebGzip
Any Perl module can be installed using cpanm Module::Name: see Cpan for more.

I first tried the method outlined in the article How To Install Perl Modules On Shared Hosting, but I couldn’t get it to work quite like the author suggested. With a little experimentation, however, I found a way to gzip Perl files on a 1&1 Beginner’s Package.

Step 1:
Download a copy of CGI-WebGzip from CSPAN. Unzip it, and extract WebGzip.pm, the file you need. (If you don’t have the software to unzip tar.gz files, download a copy of the free 7-zip for your operating system, and tell your computer to use the 7-Zip File Manager (7zFM.exe on Windows) to open files with the tar.gz extension. Such files are "zipped twice," so you'll have to unzip the .gz part, then the .tar part before you finally get to your files.)

Step 2:
Open WebGzip.pm and take out take out the CGI:: on the very first line, changing it from:
package CGI::WebGzip;
to:
package WebGzip;
Step 3:
Upload the WebGzip.pm file to the same directory where the Perl CGI-scripts you want to compress live.

Now open your Perl file. On the second line under the Perl shebang (#!/usr/local/bin/perl), add the following:
use lib "$ENV{DOCUMENT_ROOT}/path_to_Perl_scripts"
where you change path_to_Perl_scripts to the directory from root in which they live.

For example, if you have your Perl files stored in a directory called Perl inside another directory called scripts, the line would read use lib "$ENV{DOCUMENT_ROOT}/scripts/Perl". This tells the script to use the library located in such-and-such directory from the root.

Step 4:
Somewhere in your Perl script should be the following line or one very similar:
print "Content-type: text/html\n\n";
which tells the script to print the output to the screen as HTML. Directly above that line, add the following line:

(Update continued: cpanm installers need to use
use CGI::WebGzip
instead, the normative way of importing Perl modules. Otherwise...)
use WebGzip;
print "Content-type: text/html\n\n";
This line tells the script to include the module you just uploaded when it outputs its contents.

Step 5:
First check and make sure that your Perl script loads as expected: it is possible that you left a semi-colon off somewhere or got something a bit out of place. Once that is done, test to see that your file is being compressed using either HTTP Compression Test or GIDZipTest.

If compression is still not working, go back to step four and see if there are any other such lines in the script. If you are not using a 1&1 hosting package, you might also look at the asterisk below.*

* Your server will need to have the Compress::Zlib module installed. It is possible, even if it is not installed, you could use a method similar to the one outlined in this document to include it: you can download it here.

If you’re not sure if you have it installed and you’re not sure how to check, try the directions here and see if it works. If it works, you have Compress::Zlib installed.