Speed-up your website with one click in eclipse
The most important influence on the loading time of a website, apart from the amount and size of images, is the loading of JS and CSS files.
This tutorial shows you three approaches to speed up the loading of JS and CSS and how to combine them in a batch script. Finally it shows how to integrate the script in eclipse and to execute it with one click.
First of all, I will shortly describe the different approaches, before going into detail.
Consolidation
During development you need structure and overview, so it is necessary to split the JS and CSS code into different files. But the more files you include in your website, the more network connections have to be set up on loading, which costs unnecessary time and traffic.
To avoid this problem, the idea is to consolidate all files (of one type) to one file, so that only one connection has to be set up.
Shrinking
Shrinking is the removal of all unneeded characters like whitespace and comments in your code. Further the replacement of long variable-names by short ones, which simultaneously obfuscates the code.
Compression
To reduce the data being transferred, the webserver can compress it before sending. A receiving webbrowser should be able to recognize the compression and decompress the data.
The most popular compression for websites is the GZIP compression, which is supported by pretty much all browsers.
The following steps are for Windows users, but I am sure you can transform them to Linux without much effort!
1) Consolidation
The first step is to consolidate your JS and CSS files, so you only have to shrink and compress one file of each type.
Because JS and CSS includes are globally loaded into the browser, we are able to just glue all files together and include them as one file. A very simple method to achieve that, is the use of the copy command as follows:
>copy *.css styles_consolidated.css /B >copy *.js scripts_consolidated.js /B
The /B does a binary copy and avoids text encoding problems!
2) Shrinking
To get rid of unnecessary characters in your code, there are several tools available. As we want to automate the procedure later, we choose the YUI-Compressor which is a great java command line tool that supports JS as well as CSS.
The only thing we have to do, is to set it on a JS or CSS file to create a shrinked version:
>java -jar yuicompressor-2.4.1.jar scripts_consolidated.js -o scripts_shrinked.js >java -jar yuicompressor-2.4.1.jar styles_consolidated.css -o styles_shrinked.css
If there are any errors in your code, you will get a notice here!
3) Compression
There are two ways compression can be applied. You can compress a file once and deposit it, or you can compress a file on-the-fly when transmitting it.
I use the on-the-fly compression with PHP, because I use PHP anyway and the file is still readable. This is useful during development, where you may want to skip the shrinking for debugging and get useful line numbers in error messages.
To activate the compression for a file, you just have to make it into a PHP file and add one line of code:
scripts_shrinked.js.php
<?php ob_start('ob_gzhandler');header("Content-type: text/javascript; charset: UTF-8"); ?>
[content of scripts_shrinked.js]
styles_shrinked.css.php
<?php ob_start('ob_gzhandler');header("Content-type: text/css; charset: UTF-8"); ?>
[content of styles_shrinked.css]
You include these PHP files like normal JS and CSS files:
<script type="text/javascript" src="scripts_shrinked.js.php"></script> <link rel="stylesheet" type="text/css" href="styles_shrinked.css.php" />
4) Combination
To combine all three approaches, we will create a little batch script.
But first, we need to create two PHP files that will be used to apply the GZIP compression to our consolidated and shrinked JS and CSS file.
gzip_js.php
<?php ob_start('ob_gzhandler');header("Content-type: text/javascript; charset: UTF-8"); ?>
gzip_css.php
<?php ob_start('ob_gzhandler');header("Content-type: text/css; charset: UTF-8"); ?>
The following batch script is just an example and is set up for CakePHP and the use in different environments. You have to adapt the paths for individual usage!
combine.bat
@echo on copy %1\app\vendors\js\protoaculous1.6.packed.js+%1\app\webroot\js\*.js %1\app\webroot\js\scripts_consolidated.js /B java -jar %1\app\vendors\yuicompressor-2.4.1.jar %1\app\webroot\js\scripts_consolidated.js -o %1\app\webroot\js\scripts_shrinked.js copy %1\app\gzip_js.php+%1\app\webroot\js\scripts_shrinked.js %1\app\webroot\js\scripts.js.php /B /Y @echo off del %1\app\webroot\js\scripts_consolidated.js del %1\app\webroot\js\scripts_shrinked.js @echo on copy %1\app\webroot\css\*.css %1\app\webroot\css\styles_consolidated.css /B java -jar %1\app\vendors\yuicompressor-2.4.1.jar %1\app\webroot\css\styles_consolidated.css -o %1\app\webroot\css\styles_shrinked.css copy %1\app\gzip_css.php+%1\app\webroot\css\styles_shrinked.css %1\app\webroot\css\styles.css.php /B /Y @echo off del %1\app\webroot\css\styles_consolidated.css del %1\app\webroot\css\styles_shrinked.css
Note that additional files are added with a plus sign!
Finally, we have to execute the script with the approot as first parameter and let the magic happen:
>combine.bat [cakePHP approot]
5) Eclipse integration
Eclipse offers an easy way to integrate external programs and scripts. Here is what you have to do to be able to execute the script with one click:
- Open the External Tools Dialog (in submenu of the run-button with red toolbox)
- Create a new entry
- Enter some name
- Enter the location: ${workspace_loc:cake\app\combine.bat}
- Enter Arguments: ${workspace_loc:cake}
- Choose the “Common” tab
- Check “External Tools” under “Display in favorites menu”
- Check “Allocate console (necessary for input)” to see the output in the console view
Now you should be able to select the created external tool and execute it with one click.
AES 128Bit encryption between Java and PHP
Here is how AES encryption works between Java and PHP using the mcrypt module in PHP.
This is mainly a quick summary of the 4-part tutorial at: http://propaso.com/blog/?cat=6
String iv = "fedcba9876543210";
IvParameterSpec ivspec;
KeyGenerator keygen;
Key key;
ivspec = new IvParameterSpec(iv.getBytes());
keygen = KeyGenerator.getInstance("AES");
keygen.init(128);
key = keygen.generateKey();
keyspec = new SecretKeySpec(key.getEncoded(), "AES");
Cipher cipher;
byte[] encrypted;
cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
encrypted = cipher.doFinal(padString(text).getBytes());
Cipher cipher;
byte[] decrypted;
cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
decrypted = cipher.doFinal(hexToBytes(code));
function encrypt($str, $key) {
$key = $this->hex2bin($key);
$td = mcrypt_module_open("rijndael-128", "", "cbc", "fedcba9876543210");
mcrypt_generic_init($td, $key, CIPHER_IV);
$encrypted = mcrypt_generic($td, $str);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return bin2hex($encrypted);
}
function decrypt($code, $key) {
$key = $this->hex2bin($key);
$code = $this->hex2bin($code);
$td = mcrypt_module_open("rijndael-128", "", "cbc", "fedcba9876543210");
mcrypt_generic_init($td, $key, CIPHER_IV);
$decrypted = mdecrypt_generic($td, $code);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return utf8_encode(trim($decrypted));
}
private byte[] hexToBytes(String hex) {
String HEXINDEX = "0123456789abcdef";
int l = hex.length() / 2;
byte data[] = new byte[l];
int j = 0;
for (int i = 0; i < l; i++) {
char c = hex.charAt(j++);
int n, b;
n = HEXINDEX.indexOf(c);
b = (n & 0xf) << 4;
c = hex.charAt(j++);
n = HEXINDEX.indexOf(c);
b += (n & 0xf);
data[i] = (byte) b;
}
return data;
}
private String padString(String source) {
char paddingChar = ' ';
int size = 16;
int padLength = size - source.length() % size;
for (int i = 0; i < padLength; i++) {
source += paddingChar;
}
return source;
}
function hex2bin($hexdata) {
$bindata = "";
for ($i = 0; $i < strlen($hexdata); $i += 2) {
$bindata .= chr(hexdec(substr($hexdata, $i, 2)));
}
return $bindata;
}
RSA encryption between Java and PHP
This article shows you one way to get RSA encryption working between Java and PHP without any extra libraries or classes, you only need the openssl module activated on PHP side.
The goal is to encrypt a text with a public key in Java and send the code to PHP where it is decoded with the private key.
It took me two days and a lot of googling to figure this out, and I hope this will help others not to spend so much time on this topic.
1) Install and Configure PHP OpenSSL (Windows)
- php.ini: extension=php_openssl.dll
- Set environment variable
OPENSSL_CONF to C:\Programme\Apache2.2\php\extras\openssl\openssl.cnf - Set environment variable
PATH to C:\Programme\Apache2.2\php
2) Generate a private keyfile with PHP
$keys = openssl_pkey_new(); $priv = openssl_pkey_get_private($keys); openssl_pkey_export_to_file($priv, 'private.pem');
3) Generate a public .der-file from the private keyfile with OpenSSL
- openssl rsa -in private.pem -pubout -outform DER -out public.der
4) Import the public key in Java
File pubKeyFile = new File("public.der");
DataInputStream dis = new DataInputStream(new FileInputStream(pubKeyFile));
byte[] keyBytes = new byte[(int) pubKeyFile.length()];
dis.readFully(keyBytes);
dis.close();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic(keySpec);
5) Encode the data in Java with the public key
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encrypted = cipher.doFinal(text.getBytes());
6) Decode the data with the private key in PHP
$fp = fopen("private.pem", "r");
$privateKey = fread($fp, 8192);
fclose($fp);
$res = openssl_get_privatekey($privateKey);
openssl_private_decrypt($this->hex2bin($params['cipher']), $decrypted, $res);
// $decrypted is the result
function hex2bin($hexdata) {
$bindata = "";
for ($i = 0; $i < strlen($hexdata); $i += 2) {
$bindata .= chr(hexdec(substr($hexdata, $i, 2)));
}
return $bindata;
}
Pausing remoteTimer
This is a simple example on how to create a remoteTimer that can be paused by the user, in this case this is done over a checkbox.
Because the ajax-Helper offers little help in this case, we need the help of javascript. We also have to simulate pausing with start and stop, because the prototype remoteTimer doesn’t offer it.
Javascript
First of all we need a variable to store the timer, in order to be able to stop it later.
var mytimer = null;
Next we need a function to start the timer with the important parameters.
function startTimer(url, update, frequency) {
mytimer = new PeriodicalExecuter(function() {
new Ajax.Updater(update, url , {asynchronous:true, evalScripts:true,
requestHeaders:['X-Update', update]})}, frequency);
}
And for last we need a function to stop the timer.
function stopTimer() {
mytimer.stop();
}
View
Now we can use the javascript-functions in the view.
echo $javascript->codeBlock("startTimer('/yourapp/posts/view', 'mydiv', 5)");
echo $form->checkbox('pause', array('checked' => true,
'onclick' => "if (this.checked){
startTimer('/yourapp/posts/view', 'mydiv', 5);
} else {
stopTimer();
}"));
Multiple ajax requests problems and AjaxQueue as solution
For my app I had to load many import processes at once by Ajax requests, so I ran into some serious problems.
1. Session data was not available each second request
I used the database option for Sessions, and that seemed to be the problem in this case. Because I don’t worry much about how sessions are saved, I changed it to cake in core.php and it solved this problem, not a really good solution, but I’m fine with it.
2. Timed out Socket connections
During the import process I had to make some Socket Connections and however there suddenly was no connection possible anymore after 10-15 requests, so the following ran into timeout.
3. The solution: AjaxQueue
After some search and search and search, I finally found a script called AjaxQueue posted on a mailing list. There you can set the maximum amout of simultaneous Ajax requests, exactly what I was looking for. After some testing it turned out to do a really wonderful job and all my problems were solved without loosing much of performance.
The following code is for the Prototype framework, but it should be no problem to adapt it to other frameworks in replacing the “Ajax.”-statements to similar ones of another framework.
var AjaxQueue = {
batchSize: 1, //No.of simultaneous AJAX requests allowed, Default : 1
urlQueue: [], //Request URLs will be pushed into this array
elementsQueue: [], //Element IDs of elements to be updated on completion of a request ( as in Ajax.Updater )
optionsQueue: [], //Request options will be pushed into this array
setBatchSize: function(bSize){ //Method to set a different batch size. Recommended: Set batchSize before making requests
this.batchSize = bSize;
},
push: function(url, options, elementID){ //Push the request in the queue. elementID is optional and required only for Ajax.Updater calls
this.urlQueue.push(url);
this.optionsQueue.push(options);
if(elementID!=null){
this.elementsQueue.push(elementID);
} else {
this.elementsQueue.push("NOTSPECIFIED");
}
this._processNext();
},
_processNext: function() { // Method for processing the requests in the queue. Private method. Don't call it explicitly
if(Ajax.activeRequestCount < AjaxQueue.batchSize) // Check if the currently processing request count is less than batch size
{
if(AjaxQueue.elementsQueue.first()=="NOTSPECIFIED") { //Check if an elementID was specified
// Call Ajax.Request if no ElementID specified
//Call Ajax.Request on the first item in the queue and remove it from the queue
new Ajax.Request(AjaxQueue.urlQueue.shift(), AjaxQueue.optionsQueue.shift());
var junk = AjaxQueue.elementsQueue.shift();
} else {
// Call Ajax.Updater if an ElementID was specified.
//Call Ajax.Updater on the first item in the queue and remove it from the queue
new Ajax.Updater(AjaxQueue.elementsQueue.shift(), AjaxQueue.urlQueue.shift(), AjaxQueue.optionsQueue.shift());
}
}
}
};
Ajax.Responders.register({
//Call AjaxQueue._processNext on completion ( success / failure) of any AJAX call.
onComplete: AjaxQueue._processNext
});
/************* SYNTAX ***************
AjaxQueue.setBatchSize(size);
AjaxQueue.push(URL , OPTIONS, [ElementID]);
************** USAGE ***************
AjaxQueue.setBatchSize(4);
AjaxQueue.push("http://www.testingqueue.com/process/",{onSucess: funcSuccess, onfailure: funcFailure});
AjaxQueue.push("http://www.testingqueue.com/process1/",{onSucess: funcSuccess1, onfailure: funcFailure1}, "myDiv");
AjaxQueue.push("http://www.testingqueue.com/process2/",{onSucess: funcSuccess2, onfailure: funcFailure2});
AjaxQueue.push("http://www.testingqueue.com/process3/",{onSucess: funcSuccess3, onfailure: funcFailure3});
AjaxQueue.push("http://www.testingqueue.com/process4/",{onSucess: funcSuccess4, onfailure: funcFailure4});
AjaxQueue.push("http://www.testingqueue.com/process5/",{onSucess: funcSuccess5, onfailure: funcFailure5});
**********************************/
Update multiple fields with one ajax request response
I searched for a possibility to update multiple fields with each ajax request made by a remoteTimer, but didn’t find any satisfying explanation on the web. To get that right, each field should get updated with the same response of a request.
So I had a look closer into the ajax helper and the prototype framework and came up with a little hack that suffice my needs and might be also helpful for others.
In the ajax helper (cake/libs/helpers/ajax.php) we first have to look into what happens when the options[update] parameter is set to an array with the different fields we want to update. That leads us to the ‘remoteFunction’ where following happens in that case:
$func = "new Ajax.Updater(document.createElement('div'),";
I have no clue what the creation of the div is really good for, but anyway we have to replace it with a JavaScript array holding the id’s of our fields:
$update = '[';
foreach($options['update'] as $option) {
$update .= "'" . $option . "',";
}
$update .= ']';
$func = "new Ajax.Updater({$update},";
Now we must have a look into the prototype framework (app/webroot/js/prototype.js) and make sure the function Ajax.Updater can handle that array. The important function there is called updateContent:
Ajax.Updater = Class.create();
Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
[...]
updateContent: function() {
[...]
if (receiver = $(receiver)) {
if (this.options.insertion)
new this.options.insertion(receiver, response);
else
receiver.update(response);
}
[...]
}
});
Because the receiver is now our array, we must step through it and send a response to each of it:
Ajax.Updater = Class.create();
Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
[...]
updateContent: function() {
[...]
if (receiver.constructor.toString().indexOf("Array") != -1) {
for(var i = 0; i < receiver.length; i++) {
if(r = $(receiver[i])) {
if (this.options.insertion)
new this.options.insertion(r, response);
else
r.update(response);
}
}
} else {
if (receiver = $(receiver)) {
if (this.options.insertion)
new this.options.insertion(receiver, response);
else
receiver.update(response);
}
}
[...]
}
});
After checking if the receiver is actually an array, we step through it and send the response to every our fields.
Finished!
You can now use it like:
<?php echo $ajax->remoteTimer(array('url' => 'controller/action', 'update' => array('field1', 'field2', 'field3'), 'frequency' => '5')); ?>
Sortable table rows with ajax helper
Most examples for drag&drop sorting with the ajax helper use lists (<ul>, <ol>) to demonstrate, but if you have several columns each row, you really would like to use a table and drag&drop it’s rows.
There is a solution mentioned at script.aculo.us, where you just have to use the HTML 4.0 specified table looking like:
<table> <thead><tr><td></td></tr></thead> <tfoot><tr><td></td></tr></tfoot> <tbody><tr><td></td></tr></tbody> </table>
Now you can use the tbody as parent in the ajax sortable function and set the ‘tag’-option to ‘tr’.Here is a small example in cakePHP:
<table>
<thead>
<tr><th>Sortable Table</th></tr>
</thead>
<tbody id="sortable_table">
<tr><td>row 1</td></tr>
<tr><td>row 2</td></tr>
<tr><td>row 3</td></tr>
</tbody>
</table>
<?php echo $ajax->sortable('sortable_table', array('tag' => 'tr')); ?>
Because the table rows are not set to float, you don’t see any moving effect while dragging a row. I found no way to make it float without the table beeing messed up.
Eclipse Code Completion for cakePHP
Code Completion in Controllers
Add this to your app_controller.php for every Component and Model you want to have completed by eclipse:
/** * @var CookieComponent */ var $Cookie; . . . /** * @var Yourmodel */ var $Yourmodel; . . .
Eclipse will use the PHPdoc for code completion.
Code Completion in Views
Add this to an extra file e.g. eclipse_cc.php:
$html = new HtmlHelper(); $javascript = new JavascriptHelper(); . . .
There is no need to include this file, just put it in your app folder and it will fool eclipse.