XMLHttpRequest CORS preflight failing for stupid reasons

(and what eventually made it work)

I really fucking hate CORS.

I also really fucking hate web pages about technical matters which are fucking wrong.

I have just spent a day and a night trying every fucking thing I could think of to try and get a cross-origin XMLHttpRequest to work. With complete control over both client and server sides, so you would think that the usual reason people swear their guts out over this - ie. the server not sending the headers you want - wouldn't be a problem. You would think.

The request was an unexceptional GET request, which needed to (a) go to a different domain than it came from and (b) have custom headers in it. Either (a) or (b) on their own didn't give any problem: both a request to a different domain without the custom headers, and a request to the same domain with the custom headers, went straight out immediately as plain GET requests without any fucking about. (Even though all the documentation agrees that using custom headers ought to be enough all on its own to mean that it does fuck about, no matter how simple the rest of it is, that didn't actually happen. Which is fine: I don't mind infuriating awkward shit when it doesn't actually happen.)

The difficulty arose when trying to do both (a) and (b) together. This caused the browser to do this stupid fucking "preflight" bollocks with an OPTIONS request before the real GET. Which is after all the expected behaviour, and it ought not to be hard to make the server send the appropriate response. But in practice it turned into a festering purulent dog's cunt because the overwhelming majority of the documentation about what an appropriate response is supposed to look like is FUCKING WRONG.

The request coming out of the browser looked like this:

OPTIONS /some/url/or/other HTTP/1.1 User-Agent: Pigeon's Amazing Web Browser Host: www.example.org Accept: */* Accept-Language: en-GB,en;q=0.9 Accept-Encoding: gzip, deflate Referer: http://penis.example.org/a/different/url Connection: Keep-Alive Content-Length: 0 Origin: http://penis.example.org Access-Control-Request-Method: GET Access-Control-Request-Headers: Content-Type, X-Custom-Header

To which the server replied with:

HTTP/1.1 204 No Content Date: Fri, 29 Oct 2021 15:06:32 GMT Server: Pigeon's Amazing Web Server Vary: Accept-Encoding, Origin Connection: Keep-Alive Access-Control-Allow-Origin: http://penis.example.org Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: Content-Type, X-Custom-Header Access-Control-Max-Age: 300 Content-Length: 0

Which ought to be fine, right? After all if you look at some supposedly reliable reference like the MDN page about CORS it gives you this as an example:

HTTP/1.1 204 No Content Date: Mon, 01 Dec 2008 01:15:39 GMT Server: Apache/2 Access-Control-Allow-Origin: https://foo.example Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Keep-Alive: timeout=2, max=100 Connection: Keep-Alive

So what my server was responding with looks perfectly fine, doesn't it. It should do, because the MDN site was where I got it from. The few minor differences that are present don't cause any difficulty; I know this, because I went on and on making them all not different or trying other possible content etc etc etc and still it never fucking worked.

(And whatever cunt-faced bastard decided to make browsers deliberately not report any kind of error message when the CORS shit fails, and instead have them just do nothing for no apparent reason, needs their fucking arse kicked from here to next Sunday. "Security reasons" my arsehole. If you want "security" then stop making browsers so fucking opaque. Remember, by far the biggest security threat around web browsing is not the kind of people who get formally characterised as "malicious actors", but the fucking evil shite put on every fucking website these days on purpose by the fucking cunts who fucking run them; the way browsers insist on being designed you can hardly even make them show you this shit, let alone do anything to prevent it, and the various add-ons you can get to try and mitigate it all have the same problem of being magic boxes that you have to just install and then hope you can trust them when you can't see what they're doing and more importantly can't get any idea of what they're failing to do. And since the whole problem in the first place arises from hidden shit going on behind your back that you can't poke into and see what's really happening, that makes them pretty fucking shit really.)

Anyway, here I am with the browser rejecting what according to every bastarding web page I can find is a perfectly acceptable and valid response. And because of some moronic fucking design feature it refuses to give me the remotest clue as to what it thinks the problem is. So what the fuck can I do? Keep searching the fucking internet for better references and keep fucking changing increasingly random and irrelevant things since nothing I have found gives me any idea what things might not be irrelevant. And this fucking goes on and fucking on for a day and a night and still the bastard thing won't bleeding work.

So what did eventually make it work?

HTTP/1.1 200 OK Date: Fri, 29 Oct 2021 15:06:32 GMT Server: Pigeon's Amazing Web Server Vary: Accept-Encoding, Origin Connection: Keep-Alive Access-Control-Allow-Origin: http://penis.example.org Access-Control-Allow-Methods: GET Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: Content-Type, X-Custom-Header Access-Control-Max-Age: 300 Content-Length: 0

For fuck's sake. Page after page on MDN and Stack Overflow and W3C and fucking all kinds of fucking sites gives examples which use 204 No Content and it DOES NOT FUCKING WORK. But if I change it to 200 OK it works straight away: the browser accepts the response and continues with the proper GET request just like it should have been fucking doing from the start if the documentation had corresponded with reality.

You fucking absolute bastarding cunt of a wanker.

What was it that got me to try that particular experiment? Partly just random desperation and flapping about, but partly also because after far too much fruitless searching I eventually came across this Mozilla Hacks page, which gives the following example snippet of headers:

HTTP/1.1 200 OK Access-Control-Allow-Origin: http://arunranga.com Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER Access-Control-Max-Age: 1728000

Which is right. And it made it work.

And you know what the cuntiest and most arsulous aspect of the whole bleeding doings is? That MDN page referred to above - the one which is FUCKING WRONG but still gets quoted all over the fucking place and was the main cause of me being led up the fucking garden path for a day and a night - looks very much indeed as if once upon a time it used to actually be the Mozilla Hacks page which is right. It's been chopped around a bit and a lot more stuff put on it and so on but it's still recognisably of the same origin (yes I did have to do that) and the current author on MDN still seems to be the same bloke. Only somewhere in between it being on Mozilla Hacks and it ending up on MDN this very important piece of information which was ORIGINALLY RIGHT has been altered to something which is FUCKING WRONG.


...Oh, and you know what and all? That Access-Control-Allow-Methods header is a load of fucking arse too. It can be missed out altogether or set to the wrong value (such as Access-Control-Allow-Methods: OPTIONS) and it doesn't make any difference: the browser goes ahead with the GET request anyway. So I've no idea what the fuck that's all about.

And you don't need that Date header either, even though all the examples have one, so I can get rid of that too and the code to generate it.

Back to Pigeon's Nest

Be kind to pigeons

Valid HTML 4.01!