Fixed Use absolute URLs with AJAX requests

digitalpoint

Well-known member
I've successfully set up different parts of XF on different sub-domains without needing to make any file edits... except for one.

If you are on a sub-domain, AJAX requests will fail (for example when looking at the Alerts drop-down) because XF sets the <base> to be the "primary" sub-domain based on HTTP headers.

XF seems to always do AJAX requests via a relative URL (which adheres to the <base> tag. The problem is how browsers treat an AJAX request to a different sub-domain the same as a completely different primary domain... so the browser makes an HTTP request to the server with the OPTIONS method to check if it's allowed to make the *actual* request.

I went down the road of setting the server up to answer the OPTIONS/origins requests properly, but the problem is even when you use the "Access-Control-Allow-Credentials" header, not all browsers support it, so cookies don't get sent with the cross-domain AJAX request (no cookies means no logged in user for that request).

Thankfully, XF routes all AJAX requests through a single method so I was able to just prepend the *actual* hostname to the AJAX URL to make it work with a single file edit.

But... considering AJAX will always fail unless it's sent to the current sub-domain the request originated from, wouldn't it make sense to just prepend window.location protocol, and host to the AJAX URL before it's processed? The relative AJAX URLs being routed to the sub-domain specified in <base> won't fail.

http://en.wikipedia.org/wiki/Same_origin_policy
https://developer.mozilla.org/En/HTTP_access_control
 
First I should note, that by default, the base tag should be pointing to your sub-domain, unless your web server isn't exposing the correct stuff. Though I'm assuming that you want to override that and have it point to the primary domain.

window.location won't take into account the "real" XF root (for rewritten URLs), so that won't really work. The only thing we could really do is provide a JS variable that overrides the base tag for URL canonicalization.
 
Yes, by default XF always points to the sub-domain you are on because it uses the $_SERVER['HTTP_HOST'] global. In order to get sub-domains working without editing any core XF php files, I'm exploiting a couple things here, so I'll explain exactly what I'm doing...

The base URL is calculated by XenForo_Application::getRequestPaths(). If HTTP_HOST isn't set, it will fall back to SERVER_NAME.

Normally routes are calculated based on REQUEST_URI, but Zend_Controller_Request_Http::setRequestUri() first checks a couple other things... in my case, I'm exploiting the fact it looks for HTTP_X_REWRITE_URL first.

Putting it all together...

In my config.php file, I'm first checking the sub-domain the request is on... if the sub-domain is not the normal base installation of my XF, I'm doing this:
PHP:
$_SERVER['HTTP_X_REWRITE_URL'] = '/subdomain-' . $subdomain . $_SERVER['REQUEST_URI'];

So then I can have a route that is named subdomain-xxxxx where xxxxx is the sub-domain, and the route action is the URI. For example http://tools.digitalpoint.com/geovisitors internally is hitting the route of "subdomain-tools/geovisitors".

In the config.php I also do this:
PHP:
unset ($_SERVER['HTTP_HOST']);

It allows the route calculation to be based on HTTP_X_REWRITE_URL but at the same time allows the base (and links with full URLs) to still be set properly because it will fallback to SERVER_NAME.

So in the end I have the page generated on the sub-domain with the base set to the primary URL of the XF install (exactly what I want so that links in things like the navigation still work properly).

The only issue I've run into is the AJAX request stuff being based on BASE instead of window.location (since the nature of AJAX requests will make that fail for security reasons).

What I did to fix it was in xenforo.js, is exactly that (overriding canonicalization) I changed this:

Code:
canonicalizeUrl: function(url)
{
	if (url.indexOf('/') == 0)
	{
		return url;
	}
		else if (url.match(/^https?:/i))
	{
		return url;
	}
		else
	{
		return $('base').attr('href') + url;
	}
},

to this:

Code:
canonicalizeUrl: function(url, useWindowLocation)
	{
		if (url.indexOf('/') == 0)
	{
		return url;
	}
	else if (url.match(/^https?:/i))
	{
		return url;
	}
		else if (useWindowLocation)
	{
		return window.location.protocol + '//' + window.location.host + window.location.pathname + url;
	}
	else
	{
		return $('base').attr('href') + url;
	}
},

Then I'm using XenForo.canonicalizeUrl in the beginning of XenForo.ajax (it appears all URLs passed to XenForo.ajax() are non-canonicalized, so it works.

Bottom line is that if an AJAX request is *ever* made to something other than window.location, it will fail... it just so happens that *normally* BASE = window.location, so it's okay.

As it is now, I can get XF stuff 100% working on sub-domains (base being set properly, routes working, etc.) without editing the PHP files. The only edit is the one shown in the JS.
 
The problem is that window.location.pathname is more specific than the base URL and therefore won't be correct. (View a thread, echo it out, and you'll see what I mean.)

However, I have created a JS variable XenForo.ajaxBaseHref that you can set and it will override canonicalizing Ajax requests with the base tag URL. It just needs to be set after xenforo.js is included, so you could set it in the JS at the end of the body.
 
So noticed the quick navigation menu wasn't working on my dev setup (when on sub-domain)... The primary AJAX request was working, but then it was failing with a subsequent AJAX request for the CSS file.

Will XenForo.ajaxBaseHref be applied to the scriptLoader() and loadCss() methods?
 
Top Bottom