Improving network performance with server side HTTP Compression

I spend a lot of my time tweaking the performance of web applications, in addition to optimizing code it’s also necessary to verify that your server settings are also optimized for network performance to reduce bandwidth usage and thus client response times.

NOTE: This is a tradeoff between CPU and network performance, it works by compressing the content on the server just before it is sent over the wire…. when the client receives it, it then also spends some of it’s resources to decompress the content.

The Apache HTTP server provided mod_deflate (for 2.x) or mod_gzip (for 1.3).

Here’s a quick start as well as a few references:

In httpd.conf:

1. Uncomment the module:

LoadModule deflate_module modules/mod_deflate.so

2. Add the following (modify if required):

<IfModule deflate_module>
#AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE text/*
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
#AddOutputFilterByType DEFLATE application/x-javascript

<Location />
# Insert filter
SetOutputFilter DEFLATE

# Netscape 4.x has some problems…
BrowserMatch ^Mozilla/4 gzip-only-text/html

# Netscape 4.06-4.08 have some more problems
BrowserMatch ^Mozilla/4\.0[678] no-gzip

# MSIE masquerades as Netscape, but it is fine
# BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

# NOTE: Due to a bug in mod_setenvif up to Apache 2.0.48
# the above regex won’t work. You can use the following
# workaround to get the desired effect:
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

# Don’t compress images or ZIP/GZ/7Z
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png|zip|7z|gz)$ no-gzip dont-vary

# Make sure proxies don’t deliver the wrong content
Header append Vary User-Agent env=!dont-vary
</Location>
</IfModule>

REFERENCES:

Cheers!

Writing Popup HTML content with Javascript

Occasionally, there come a time when old tricks become handy on solving new problems. Recently, an application that I support had some serious network latency when attempting to open a popup consisting exclusively of static content. While this small page came from a webserver with appropriate caching headers being sent, it still took upwards of 2 seconds for the page to be displayed by the client browser because of network latency issues. 

True, this was for MSIE, and there are some notorious performance issues with the javascript used for popups, but those are different topics to be discussed later.

Old implementation, loads content of ‘somefile.html’ as a url popup:

<script type=”text/javascript”><!–
function testwindow(){
  var linkWin = window.open(‘/somefile.html’,”,’width=300,height=200,scrollbars=no,toolbar=no’);
}
// –></script>
<a href=”javascript:testwindow();”>TEST</a>

New implementation, creates content of ‘somefile.html’ in javascript instead:

<html>
<head>
<title>Popup Demo</title>
<script type=”text/javascript”>
function testwindow(x) {
  var content;
  var linkWin = window.open(”,”,’width=300,height=200,scrollbars=no,toolbar=no’);
  if (!linkWin.opener) { linkWin.opener = self; }
    content = “<!DOCTYPE html><html><head>”;
    content += “<title>Example</title>”;
    content += ‘</head><body bgcolor=”#ccc”>’;
    content += “<p>Any HTML will work, just make sure to escape \”quotes\”,”;
    content += ‘or use single-quotes instead.</p>’;
    content += “<p>You can even pass parameters… (” + x + “)</p>”;
    content += “</body></html>”;
    linkWin.document.open();
    linkWin.document.write(content);
    linkWin.document.close();
    //return false;
  }
</script>
</head>
<body>
<a href=”javascript:testwindow(‘this is x’);”>TEST</a>
</body>
</html>

NOTE: there is one small performance tradeoff here,  in the ‘new’ case you will always be downloading the content of the popup, even if you never use it.  This can be remediated by adding it to an external static .JS file.
Cheers!

Add HTTP Headers in Apache Response

This can be used to for several reasons:

  1. To add headers to modify the behavior of a specific ‘misbehaving’ browser or client.
  2. To replace headers that you don’t want leaked to the Internet.
  3. To add monitoring information to your server responses.

Changes can be accomplished in the Apache2 ‘httpd.conf’ file.

  1. Verify that the module is not disabled or commented out:

    LoadModule headers_module modules/mod_headers.so

  2. To add some common metrics:

    <IfModule headers_module>
    Header append MyHeader “%D %t”
    </IfModule>

  3. To Hide the HTTP Server header that you send in your responses (often done for security through obscurity):
    <IfModule headers_module>
    Header unset Server
    </IfModule>
  4. You could also replace the Server Header like this:

    <IfModule headers_module>
    Header set Server “ScottServer 1.0”
    </IfModule>

Cheers!
REFERENCES:

Configuring Apache webserver for browser caching of web content…

This is a HUGE topic, I’ve outlined some simple steps below as well as my initial configuration for you to start with…

NOTE: this is for simple ‘static’ content such as images, additional work is required for dynamic (program generated) content, such as that generated in PHP.

1. In ‘httpd.conf’ make sure the following line is uncommented.

LoadModule expires_module modules/mod_expires.so

2.  In ‘httpd.conf’ add the following:

ExpiresActive On
### Expire images 1 day from when they’re accessed
ExpiresByType application/java-archive “access plus 1 day”
ExpiresByType image/gif “access plus 1 day”
ExpiresByType image/png “access plus 1 day”
ExpiresByType image/jpg “access plus 1 day”
ExpiresByType image/jpeg “access plus 1 day”
ExpiresByType image/x-icon “access plus 1 day”
ExpiresByType text/css “access plus 1 day”
ExpiresByType text/javascript “access plus 1 day”
ExpiresByType text/xml “access plus 1 day”
ExpiresByType application/xml “access plus 1 day”
ExpiresByType text/plain “access plus 1 month”
 

3. (Optional) Set default expiry of content in ‘httpd.conf’:

### Expire everything else 1 day from when it’s last modified
ExpiresDefault “modified plus 1 day”

NOTE: These we’re my original settings, you may want to add attitional MIME type and expiry configurations particular to your web content.

REFERENCES:

Downloadable WebFonts

To maintain accessibility and SEO (Search Engine Optimization), there’s often a need to be creative with fonts. This is sometimes due to aesthetics, but often to meet technical needs like foreign non-Latin languages that have unique characters/glyphs not normally installed on workstations. Producing images for each character would be very time consuming, bandwidth intensive and destroy search engine rankings.

Create embedded fonts using one of 2 available formats:

1. Portable Font Resources (.pfr): TrueDoc technology was developed by Bitstream and licensed by Netscape. It can be viewed by Navigator 4.0+ and Explorer 4.0+ on Windows, Mac, and Unix platforms.

<link rel = “fontdef” src=”myfont.pfr” />

2. Embeddable Open Type (.eot): Compatible only with Explorer 4.0+ on the Windows platform. Create .eot files using Microsoft’s free Web Embedding Font Tool (WEFT).

<style type=”text/css”>
<–!
@font-face {
src:url(/fonts/myfont.eot);
}
–>
</style>

References:

Tooling:

Tutorials:

Cheers!

Preventing portions of a webpage from printing

A colleague asked me about my solution for this just the other day, here’s the quick solution.

  1. Add a CSS class attribute to the items.  Assuming they are <div>’s for header and footer, they would look like my example below, but you can add the ‘no-print’ class to anything you don’t want printed.
  2. Add a stylesheet with media=”print” to change the visibility and/or display attributes of that class.
  3. With a little more work, you could add a ‘no-screen’ solution too… this would be advantageous in cases where you may need to mask an account number or SSN.

<html>
<head>
<title>Example</title>
<link media=”print” href=”print.css” type=”text/css” rel=”stylesheet” />
</head>
<body>
<div class=”no-print”>This is your header</div>
<div>this is the body</div>
<div class=”no-print”>this is your footer</div>
</body>
</html>

print.css could then contain:

.no-print { display:none; }

Cheers!

HTTP Forward vs. Redirect

A Controller servlet may perform either a forward or a redirect operation at the end of processing a request. It is important to understand the difference between these two cases, in particular with respect to browser reloads of web pages.

Forward

  • a forward is performed internally by the application (servlet).
  • the browser is completely unaware that it has taken place, so its original URL remains intact
  • any browser reload of the resulting page will simple repeat the original request, with the original URL

Redirect

  • a redirect is a two step process, where the web application instructs the browser to fetch a second URL, which differs from the original
  • a browser reload of the second URL will not repeat the original request, but will rather fetch the second URL
  • redirect is marginally slower than a forward, since it requires two browser requests, not one
  • objects placed in the original request scope are not available to the second request.

There are several ways to perform a Redirect, here are a few common ones:

  • URL Redirection (HTTP 301):
    HTTP/1.1 301 moved permanently
    
    Location: http://www.example.org/
  • HTTP Refresh Header (Not Recommended)
    HTTP/1.1 200 ok
    
    Refresh: 0; url=http://www.example.com/
  • HTML <meta /> tag
    <meta http-equiv="refresh" content="0; URL=http://www.example.org/" />
  • JavaScript (many possible solutions, generally not accessible or searchable)
    <script type="text/javascript">location.href='http://www.example.org/';</script>

In general, a forward should be used if the operation can be safely repeated upon a browser reload of the resulting web page; otherwise, redirect must be used. Typically, if the operation performs an edit on the datastore, then a redirect, not a forward, is required. This is simply to avoid the possibility of inadvertently duplicating an edit to the database.

More explicitly :

  • for SELECT operations, use a forward
  • for INSERT, UPDATE, or DELETE operations, use a redirect

In HTML, a <FORM> tag can either GET or POST its data. In this context, a GET corresponds to a SELECT-then-forward, and a POST corresponds to an edit-then-redirect.

It is strongly recommended that forms for the input of search criteria should use GET, while forms for editing database records should use POST.

SECURITY NOTE: When using GET, be sure to not expose sensitive data in the URL’s.

Dynamic Site Navigation with AJAX

Recently a navigation challenge that I encountered was “better” resolved by implementing an AJAX “like” solution. While not a complete AJAX solution, this works by requesting and inserting HTML into the DOM dynamically. The HTML could even be the same content that is used when the page was initially generated allowing for the initial page render to be static HTML, and only the options to be dynamic.

This is an extension on my previous ‘Flexible AJAX Framework’ entry and adds the following features and methods:

  • Two column example page layout.
  • Page level caching of AJAX Responses.
  • JavaScript methods:
    • var pageLoadTime; – variable for page level caching.
    • function ajaxObjPageCache(obj,url,async,callback) – method for page level caching.
    • function menuAjaxHook(obj,customObj); – response handler that updates UI.
    • function xinnerHTML(id,txt); – this is a common component used by the example.
    • function testAjaxMenu(obj,menu); – test method for example.

The full example (in PHP) follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>AJAX Menu Example</title>
<script type="text/javascript">
var xbusy = true;
var xhr = null;
var otherHost = "http://example.giantgeek.com"; // do not use training slash!
var pageLoadTime = xmillis();// set this once per page for some caching solutions!
/**
* Check for existance on DOM browsers (Mozilla, etc.)
* @return xhr
*/
function ajaxCheckDOM(){
var myxhr = null;
if(window.XMLHttpRequest) {
try {
myxhr = new XMLHttpRequest();
}
catch(e) {}
}
return myxhr;
}
/**
* Check for existance on Windows/MSIE (prior to MSIE7 which is now DOM)
* Evaluate using - new ActiveXObject("Microsoft.XMLDOM");
* @return xhr
*/
function ajaxCheckActiveX(){
var myxhr = null;
//if(window.ActiveXObject){
try {
myxhr = new ActiveXObject("Msxml2.XMLHTTP");
}
catch(e) {
try {
myxhr = new ActiveXObject("Microsoft.XMLHTTP");
}
catch(e) {}
}
//}
return myxhr;
}
/**
* This is a default response hook for AJAX, you SHOULD create your own as it's only for DEMO
* @param obj - the clicked item
* @param customObj - user defined
*/
function ajaxResponseHook(obj,customObj){
var txt = xhr.responseText;// NOTE: xhr.responseXML is also valid
popStatus('AJAX Response value [' + txt + '|' + customObj +']','');
}
/**
* This is a common Response handler AJAX, you SHOULD NOT need to modify, use a 'custom' ajaxhook function to process the responses.
* @param obj - the clicked item
* @param ajaxhook Function (default will be used if undefined)
* @param customObj (optional - left for developer implementation, passed to the ajaxhook)
*/
function ajaxResponse(obj,ajaxhook,customObj){
var status='';
if(xhr!=undefined){
var state=xhr.readyState;
if(state!=undefined){
if(state==4){
var code = xhr.status;
status = xhr.statusText;
if(code==200){
popStatus('AJAX Response Received.','');
if(ajaxhook==undefined){
ajaxhook = function(){ ajaxResponseHook(obj,customObj); }
}
ajaxhook(obj,customObj);
ajaxBusy(obj,false);
} else {
popStatus('AJAX Error ' + code + ' ' + status + '.','');
}
ajaxBusy(obj,false);
xhr=null; /* MSIE6 leak fix */
}
}
}
}
/**
* NOTE: MSIE6-7 supports XSS url's (be careful!)
* @param obj - the clicked item
* @param url - the GET url params (FQDN)
* @param async (true or false) - false will LOCK browser until response - use cautiously!
* @param callback Function - allows for customized response handling
*/
function ajaxObj(obj,url,async,callback){
if(xbusy == true){
popStatus('AJAX BUSY, Please Retry.','');
} else {
if(callback==undefined){
callback = function(){ ajaxResponse(obj,hook,''); }
}
ajaxInit(obj,url,async,callback);
}
}
/**
* This is a Non-Caching implementation of ajaxObj() to show how you can avoid the MSIE caching issue.
* NOTE: You can use similar approaches to cache per page or session.
* @param obj - the clicked item
* @param url - the GET url params (FQDN)
* @param async (true or false) - false will LOCK browser until response - use cautiously!
* @param callback Function - allows for customized response handling
*/
function ajaxObjNoCache(obj,url,async,callback){
var cacheBuster=uniqueUrl(url);// damn MSIE!
ajaxObj(obj,cacheBuster,async,callback)
}
/**
* This is a Page-Caching implementation of ajaxObj() to allow for requests to be cached per pageload.
* @param obj - the clicked item
* @param url - the GET url params (FQDN)
* @param async (true or false) - false will LOCK browser until response - use cautiously!
* @param callback Function - allows for customized response handling
*/
function ajaxObjPageCache(obj,url,async,callback){
var cacheBuster=urlAppender(url,'.cache',pageLoadTime); // pageLoadTime should remain unchanged for page life.
ajaxObj(obj,cacheBuster,async,callback)
}
/**
* Initializes the AJAX operation
* @param obj - item clicked
* @param url - FQDN
* @param async (true/false) - for locking
* @param callback Function - provided for customization.
*/
function ajaxInit(obj,url,async,callback){
xhr=ajaxCheckDOM();
if(!xhr){
xhr=ajaxCheckActiveX();
}
if(xhr){
ajaxBusy(obj,true);
popStatus('AJAX Start.','');
xhr.onreadystatechange = callback; // method call
try {
xhr.open('GET',url,async); /* POST */
//xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
xhr.send(''); /* null */
}
catch(e)
{
if(xhr){
var code = xhr.status;
var status = xhr.statusText;
popStatus('AJAX Client SECURITY ' + navigator.appName +' '+ navigator.appVersion + ' ' + code + ' ' + status + '.','');
}
}
ajaxBusy(obj,false);
} else {
popStatus('AJAX Client ERROR.','');
}
}
function ajaxBusy(obj,yn){
if(yn){
swapStyleObj(obj,'idle','busy');
} else {
swapStyleObj(obj,'busy','idle');
}
xbusyInd(yn,'');
}
function xmillis(){
return new Date().getTime();
}
function swapStyleObj(obj,oldCSS,newCSS){
if(obj!=undefined) {
var current=obj.className;
if(current!=undefined){
var txtOld = current.replace(newCSS,' ');//no doubles
var txtMid = txtOld.replace(oldCSS, ' ');
var txtNew = (txtMid + ' ' + newCSS);
obj.className = txtNew;
} else {
obj.className = newCSS;
}
}
}
function xbusyInd(valnew, msg){
xbusy = valnew;
if(xbusy==true){
document.body.style.cursor='wait';
if(msg != ''){
top.window.defaultStatus=msg;
popStatus(msg,'');
}
showDiv('throbber');
//blockScreen();
} else {
document.body.style.cursor='default';
hideDiv('throbber');
//unBlockScreen();
}
}
function showDiv(id) { //show a div
var obj=xgetHelper(id);
if(obj!=null){ obj.style.display="block";}
//xrestart();
}
function hideDiv(id) { //hide a div
var obj=xgetHelper(id);
if(obj!=null){ obj.style.display="none";}
//xrestart();
}
function popStatus(txt,title){
popit('statusdyn','statusarrow','statusul','statusdiv',txt,false,title,'');
}
function popit(dynId,arrowId,ulId,divId,txt,expand,title,cssCls){
popText(ulId,txt,title,cssCls);
showDiv(divId);
if(expand == true){
var arrowObj=xgetHelper(arrowId); if(arrowObj!=null){ arrowObj.style.backgroundPosition='2px -106px'; }
showDiv(dynId);
}
}
function popText(id,txt,title,cssCls){
var obj = xgetHelper(id);
if(obj != null){
var oldHTML = obj.innerHTML;
var cls=''; if(cssCls!=''){ cls=' class="' + cls +'"'; }
var htm = '<'+'li'+ cls +'>' + txt + '<'+'/'+'li'+'>' + oldHTML;
obj.innerHTML = htm;
}
}
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
function arrowTog(objectID,arrow) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display =='block') {
arrow.style.backgroundPosition = '2px -21px';}
else {arrow.style.backgroundPosition = '2px -106px';}
objTog(objectID);
}
return false;
}
function objTog(objectID) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display =='block') obj.style.display='none';
else {obj.style.display='block';}
}
return false;
}
function headTog(objectID,arrow) {
var obj = xgetHelper(objectID);
if(obj!=null){
if (obj.style.display =='block') {
arrow.style.borderBottomWidth = '1px';}
else {arrow.style.borderBottomWidth = '0px';}
arrowTog(objectID,arrow);
}
return false;
}
/*
* adds timestamp to URLs to make them unique
* @param URL String
*/
function uniqueUrl(x){
return urlAppender(x,'.cache',xmillis());
}
/*
* helps to add parms to the url
* @param URL String
* @param aname String
* @param avalue String
*/
function urlAppender(x,aname,avalue){
var delim = "?";
if(x.indexOf("?") >=0) { delim = "&"; }
return x + delim + aname + '=' + avalue;
}
function xload(){
hideDiv('throbber');
xbusy=false;
}
function testAjax(obj){
var callback=function(){ ajaxResponse(obj,null,'testAjax'); }
var x = ajaxObjNoCache(obj,'/ajax.php',true,callback);
}
function testAjaxParms(obj){
var callback=function(){ ajaxResponse(obj,null,'testAjaxParms'); }
var x = ajaxObjNoCache(obj,'/ajax.php?testing=Y',true,callback);
}
function testAjaxXSS(obj){
var callback=function(){ ajaxResponse(obj,null,'testAjaxXSS'); }
var x = ajaxObjNoCache(obj,otherHost+'/ajax.php',true,callback,otherHost);
}
function testAjaxHook(obj){
var hook=function(){ customAjaxHook(obj,'ajaxhookTestMessageObect'); }
var callback=function(){ ajaxResponse(obj,hook,'testAjaxHook'); }
var x = ajaxObjNoCache(obj,'/ajax.php',true,callback);
}
function testAjaxHookXSS(obj){
var hook=function(){ customAjaxHook(obj,'ajaxhookXSSMessageObect'); }
var callback=function(){ ajaxResponse(obj,hook,'testAjaxHookXSS'); }
var x = ajaxObjNoCache(obj,otherHost+'/ajax.php',true,callback);
}
function testAjaxMenu(obj,menu){
var mms_start=xmillis();
var hook=function(){ menuAjaxHook(obj,mms_start); }
var callback=function(){ ajaxResponse(obj,hook,'testAjaxHookXSS'); }
var x = ajaxObjPageCache(obj,'/ajaxmenu.php?menu='+menu,true,callback);
}
/**
* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)
* @param obj - the clicked item
* @param customObj - user defined
*/
function menuAjaxHook(obj,customObj){
var diff = xmillis() - customObj; // difference from click to now!
popStatus('AJAX Menu Response time [' + diff +'mms]','');
var htm = xhr.responseText;// NOTE: xhr.responseXMLis also valid
xinnerHTML('menu',htm);
}
/**
* This is a custom implemenation, the customObj COULD be used for anything (perhaps delay measurement!)
* @param obj - the clicked item
* @param customObj - user defined
*/
function customAjaxHook(obj,customObj){
var xml = xhr.responseXML;// NOTE: xhr.responseText is also valid
var tmp = 'DEMO customAjaxHook [' + customObj + ']\n' + xml;
alert(tmp);
}
function xinnerHTML(id,txt){
var obj = xgetHelper(id);
if(obj!=null){
if((txt != null) && (txt != "")){
obj.innerHTML = txt;
}
}
}
</script>
<style type="text/css">
div#container {position:absolute;top:0;left:0;padding-right:10px;}
div#nav_col {position:absolute;left:0;
float:left;
width:140px;padding-left:7px;
}
div#container > div#nav_col {position:relative;}

div#main {margin-left:160px;border:1px dotted #fff;/*vertical-align:top; causes table gaps bug*/
width:99.9%;
voice-family: “\”}\””;
voice-family:inherit;
width:auto;}
.busy { color:red; }
.idle { color:black; }
</style>
</head>
<body onload=”xload();”><!– onbeforeunload=”alert(‘before’);” onunload=”alert(‘after’);” –>
<div id=”throbber”>WORKING!</div>
<div id=”container”>
<div id=”nav_col”>
<div id=”menu”>EMPTY Menu</div>
</div>
<div id=”main”>
<div id=”statusdiv” class=”dyn” style=”display:none;”>
<h3><a id=”statusarrow” onclick=”headTog(‘statusdyn’,this);” href=”javascript:void(0);”>Status</a></h3>
<fieldset id=”statusdyn” style=”display:block;background-color:#ffffcc;”>
<div id=”statusBox” class=”box”>
<ul id=”statusul” class=”error”>
<li id=”ajaxStatus” style=”list-style:none;display:none;”>FILLER</li>
</ul>
</div>
</fieldset>
</div>
<h3><a href=”javascript:void(0);” onclick=”testAjax(this);”>TEST</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxParms(this);”>TESTPARMS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxXSS(this);”>TESTXSS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxHook(this);”>TESTHOOK</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxHookXSS(this);”>TESTHOOKXSS</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxMenu(this,’ACCT’);”>TESTMenuACCT</a></h3>
<h3><a href=”javascript:void(0);” onclick=”testAjaxMenu(this,’USER’);”>TESTMenuUSER</a></h3>
<p><a href=”index.php”>RELOAD</a></p>
</div><!– main –>
</div><!– container –>
</body>
</html>

ajax.php – for the original test code:

<?php
//header("Cache-Control: no-store");
header("Charset: UTF-8");
header("Content-Type: text/xml");
echo("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
?>
<test><?php echo( gmdate("D, d M Y H:i:s") ) ?> </test>

ajaxmenu.php – example menu behavior:

<?php
//header("Cache-Control: no-store");
header("Charset: UTF-8");
header("Content-Type: text/html");
?>
<ul>
<li>MENU:</li>
<li><?php echo($_GET['menu']) ?> </li>
<li><?php echo( gmdate("D, d M Y H:i:s") ) ?> </li>
</ul>

Cheers!

Href Links as HTTP POST (complex DOM solution)

The earlier post, while easy to implement, has some well known security issues. Now lets get around them. First we’ll remove the FORM from the HTML itself, and instead build it dynamically and insert it into the BODY via the DOM and then submit it with JavaScript.

Again, if you’ve already implemented my prior solutions, this is just a small code refactor.

<html>
<head>
<title>Example FORM Post - DOM</title>
<script type="text/javascript">
/*
* Uses "location.replace()" vs. "location.href()" for all valid links.
* 'replace' has side-effect of 'restricting' back-button, or 'location'
* @param obj Object clicked
* @param x URL
*/
function xlinkObj(obj,url){
//Consider replacing w/ "return xlinkFrm(obj,url); " if you want POST behavior.
window.location.replace(uniqueUrl(url));
return false;
}
/*
* uses a FORM for the requested URL, could be POSTed!
* This is a simple solution, more complex solution COULD build the FORM and then parse attributes into INPUT's
* NOTE: you probably SHOULD NOT use this for external links, unless you intend for them to receive your params!
* @param obj Object clicked (NOT USED in this Example)
* @param x URL
*/
function xlinkFrm(obj,x){
var url=uniqueUrl(x);
var frmObj=xlinkFrmHelp(url);
if(frmObj!=null){
frmObj.submit();
}else{
alert('ERROR!');
}
return false;
}
/*
* Expects URL with queryString as param href
* @param x URL
* @return obj Object of the generated FORM
*/
function xlinkFrmHelp(x){
var rc=null;
try{
var ar = x.split("?");
var act = ar[0];
var str = ar[1];
var id="frm" + xmillis();
var oFORM=document.createElement("form");
oFORM.setAttribute("id",id);
oFORM.setAttribute("method","post");
oFORM.setAttribute("action",act);
if(str!=null){
var parms=str.split('&');
for(i=0; i < parms.length; i++){
var parm=parms[i];
var pair=parm.split('=');
var oINPUT=document.createElement("input");
oINPUT.setAttribute("type","hidden");
oINPUT.setAttribute("name",pair[0]);
oINPUT.setAttribute("value",pair[1]);
oFORM.appendChild(oINPUT);
}
}
var oBODY=document.getElementsByTagName("body")[0];
oBODY.appendChild(oFORM);
rc=oFORM;
}catch(e){
alert("Error"+e);
}
return rc;
}
/*
* generates a timestamp as a number
*/
function xmillis(){
return new Date().getTime();
}
/*
* adds timestamp to URLs to make them unique
* @param URL String
*/
function uniqueUrl(x){
return urlAppender(x,'time',xmillis());
}
/*
* helps to add parms to the url
* @param URL String
* @param aname String
* @param avalue String
*/
function urlAppender(x,aname,avalue){
var delim = "?";
if(x.indexOf("?") >=0) { delim = "&"; }
return x + delim + aname + '=' + avalue;
}
/*
* Abstracts "document.getElementById()" with appropriate error handling.
* @param id String
* @returns Object (NULL when not found!)
*/
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
</script>
<a href="javascript:void(0);" onclick="return xlinkObj(this,'index.php');">REFRESH</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.php');">TEST-POST</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.php?a=b');">TEST-POST2</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'http://www.skotfred.com/hello');">TEST-XSS</a>
</body>
</html>

Cheers!

Href Links as HTTP POST (simple solution)

Another interesting challenge, The standard <a href=”…”></a> style link performs an HTTP GET, however you might want to perform a POST in some cases. HTML does not natively support this behavior, but it can be accomplished in JavaScript. If you have already implemented some of my security ‘hacks’ from previous posts this is only a small change. As usual I’ve included the minimum code required for this task in the example, but you should be able to easily merge the different features back into this!

Later on (in a different post), I’ll expand this to make it even more secure as this solution simply puts all of the URL into the FORM ‘action’ attribute and it would be better to pass them in the FORM body itself to hide them from the URL shown in the browser.

<html>
<head>
<title>Link to FORM POST simple example</title>
<script type="text/javascript">
/*
* Uses "location.replace()" vs. "location.href()" for all valid links.
* 'replace' has side-effect of 'restricting' back-button, or 'location'
* @param obj Object clicked (NOT used in this example)
* @param x URL
*/
function xlinkObj(obj,url){
//Consider replacing w/ "return xlinkFrm(obj,url); " if you want POST behavior in all cases!
window.location.replace(uniqueUrl(url));
return false;
}
/*
* uses a FORM for the requested URL, could be POST'ed!
* This is a simple solution, using an existing empty FORM on the page.
* A more complex and secure solution COULD build the FORM dynamically and then parse attributes into INPUT's
* NOTE: you probably SHOULD NOT use this for external links, unless you intend for them to receive your params!
* @param obj Object clicked (NOT used in this example)
* @param x URL
*/
function xlinkFrm(obj,x){
var frmObj=xgetHelper('frmXlink');
if(frmObj!=null){
frmObj.action=uniqueUrl(x);
frmObj.submit();
}else{
alert('ERROR!');
}
return false;
}
/*
* generates a timestamp as a number
*/
function xmillis(){
return new Date().getTime();
}
/*
* adds timestamp to URLs to make them unique
* @param URL String
*/
function uniqueUrl(x){
return urlAppender(x,'time',xmillis());
}
/*
* helps to add parms to the url
* @param URL String
* @param aname String
* @param avalue String

*/
function urlAppender(x,aname,avalue){
var delim = "?";
if(x.indexOf("?") >=0) { delim = "&"; }
return x + delim + aname + '=' + avalue;
}
/*
* Abstracts "document.getElementById()" with appropriate error handling.
* @param id String
* @returns Object (NULL when not found!)
*/
function xgetHelper(id){
var obj = null;
try {
obj = document.getElementById(id);
} catch(z) {
var dummy=alert("Error:" + z);
}
return obj;
}
</script>
</head>
<body>
<form id="frmXlink" action="#" method="post"></form>
<a href="javascript:void(0);" onclick="return xlinkObj(this,'index.html');">REFRESH</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.html');">TEST-POST</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'index.html?a=b');">TEST-POST PARMS</a>
<a href="javascript:void(0);" onclick="return xlinkFrm(this,'http://www.giantgeek.com/hello');">TEST-XSS</a>
</body>
</html>

This just uses an “empty” FORM in the page and uses the new ‘xlinkFrm()’ method to copy the URL to the FORM ‘action’.

Like i said, this is a simple solution as the params are still on the URL making them less secure. I’ll be refactoring it to parse the params to dynamically build the FORM (that will no longer be hardcoded).

Cheers!