Fastly , CloudFlare and a few other CDNs offer a mode where they accelerate dynamic content.

In a nutshell you point your domain IP address at the CDN and the CDN will intelligently decide how to deal with the request.

Static content can be easily served from cache

Dynamic content can be routed to the site.

This provides some advantages over only shipping static assets which is covered in the CDN howto.

You can elect for “shielding” that protects your site from traffic spikes.

Dynamic content can be accelerated using techniques like railgun. (note: in general our paylod fits in 1 RTT so this has less of an impact)

SSL negotiation can happen at the edge cutting on expensive round trips for negotiation.

If you enable full site acceleration with a CDN it is critical you follow 3 rules

The “message bus” must be served from the origin.

You need to set up X-Forwarded-For trust. For Cloudflare, add cloudflare.template.yml to your app.yml file.

Be extra careful with techniques that apply optimisation to the site, stuff like Rocket Loader can stop Discourse from working. Discourse is already heavily optimised, this is not needed.

To serve “long polling” requests from a different domain, set the Site Setting long polling base url to the origin server.

For example, if your site is on “http://forum.example.com” you should set up http://forum-direct.example.com/ to plug into the site setting. If you don’t your site will be broken.

If you are fronting Discourse using Varnish you probably want to follow the same trick here and bypass Varnish for the message bus requests.

Boring technical notes:

Achieving a working message bus on a completely different domain is quite challenging. Our message bus is aware of which user is polling, the other domain may have no cookie set up so untouched there are two issues. Firstly, you can’t even make standard ajax requests cross domain without a huge CORS dance.

Secondly, we needed a mechanism to inform the other domain who the user is so we can poll for the correct information.

When long polling base url is changed, Discourse ships an extra meta tag that shares a “cross domain” auth token. This token is passed using a custom header back to the message bus. The token expires after 7 days or as soon as the user logs off. In future we are probably going to amend it so the token has N uses and is automatically reissued after they pass.

Now there’s this thing called “long polling” which is basically an OPTION HTTP request with a long time before returning anything. If we use the Fastly or Varnish address, as Discourse would by default, Varnish will time out and “long polling” won’t work.

More background: Varnish has this option to bypass in known contexts through vcl_pipe which is roughly a raw TCP socket. But Fastly doesn’t offer it because of the size of their setup.

Proposed setup

Let’s enable long polling and expose our site under Fastly. We’ll need two names, one pointing to Fastly’s and the other to the IP addresses we give within the service dashboard.

In my case, I generally have many web apps running that are only accessible from my internal network. I refer to them as “upstream”; the same term NGINX uses in their config. Since this number of web apps you would host on a site can fluctuate, you might still want the number public IP address to remain stable. That’s why I setup a NGINX server in front that proxies to internal web app server. I refer to them as “frontends”.

[quote=“sam, post:1, topic:21467”]
To server “long polling” requests from a different domain, set the Site Setting long polling base url to the origin server.[/quote]

Really means here is that we would have to put one of those IP addresses in Discourse settings.

What I’d recommend is to create a list of A entries for all your frontends.

In the end we need three things:

What’s the public name that Fastly will serve

Which IPs are the frontends

Which hostname we want to use for long polling and we’ll add it to our VirtualHost

The zone file would look like this;

# The public facing URL
discourse.some-origin.com. IN CNAME global.prod.fastly.net.
# The list of IP addresses you’d give to Fastly as origins/backends
frontends.some-origin.com. IN A 8.8.8.113
frontends.some-origin.com. IN A 8.8.8.115
# The long polling URL entry
discoursepolling.some-origin.com. IN CNAME frontends.some-origin.com.

That way you can setup the “long polling base url” correctly without setting a single point of failure.

Then, we can go in Discourse admin zone and adjust the “long polling base url” to our other domain name.

XMLHttpRequest cannot load https://origin.example.com/message-bus/634dd18187094c6c950c0bf14f74c239/poll. Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://example.com' that is not equal to the supplied origin. Origin 'https://mysite.com' is therefore not allowed access.

If mysite uses it’s own long polling origin as the domain I get this:

XMLHttpRequest cannot load https://origin.mysite.com/message-bus/b35c9c8e958f44f78d0d4773dc6d75f3/poll. Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'https://example.com' that is not equal to the supplied origin. Origin 'https://mysite.com' is therefore not allowed access.

Is this because of "Access-Control-Allow-Origin" => Discourse.base_url_no_prefix ?

Some additional notes for anyone who decides to use HTTPS together with Full site CDN acceleration

Discourse internally uses the value of SiteSetting.force_https to decide if your access-control-allow-origin: is the HTTP or HTTPS version of your site. If while polling you see an error in the browser console along the lines of preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value http doesn't match https, double check your force_https setting. Also note the protocol in your DISCOURSE_CORS_ORIGIN in your container definition (http|https) will be overridden by force_https.
Don’t forget to add DISCORSE_ENABLE_CORS: true in your container definition.

If you were planning to only do HTTPS from your end users to your CDN, and then HTTP from your CDN to your actual Discourse web_only containers, lots of custom configuration will be required.

If your CDN is serving your site on HTTPS, then whatever Long Polling URL you setup must also be on HTTPS, so even if the CDN is handling your HTTPS, you must still setup HTTPS on your Discourse servers. If you run into an error about Same-origin policy, double check that you’re not trying to connect to HTTP instead of HTTPS

If you use letsencrypt to generate your certificates, note that fullchain.pem => /shared/ssl/ssl.crt (ssl_certificate)

You’ll need to need to comment these lines out, otherwise you’re long polling attempts always serve up 301 redirects back to your origin, instead of respecting whatever you set in SiteSetting.long_polling_base_url

The easiest way I’ve found to do this is to copy templates/web.ssl.template.yml to local.web.ssl.template.yml and just remove those extra lines, and update your container reference to use your local template. If you go that route, you should periodically diff your local version with the origional version, because there are some security improvements that are regularly incorporated into this template.

Some of the error messages you’ll run into until things are configured correctly.

The most direct way I thought of inside the container definition is an exec line running perl, awk or sed to do the multiline replace… but then you’ve got shell escaping along with your target language to disentangle before it will work…

I’m curious to know one thing:
Eg. My discourse is hosted on forum.example.com
Can I set long polling base to poll.example.org which points to the same server IP?
Will it have any impact considering CSP?