Prototype AJAX request being sent as OPTIONS rather than GET; results in 501 error

28,405

Solution 1

Too many hours looking for a correct fix on prototypejs... finally, we have a non-intrusive solution on great kourge (Wilson Lee) article!. Here is an excerpt:

Most major Ajax frameworks like to set custom HTTP headers on the Ajax requests you instantiate; the most popular header is X-Requested-With: XMLHttpRequest. Consequently your request is promoted to a preflighted one and fails. The fix is to prevent your JavaScript framework from setting these custom headers if your request is a cross-domain one. jQuery already cleverly avoids unintentionally preflighting requests by not setting custom headers if your URL is considered to be remote. You'd have to manually prevent this if you're using other frameworks.

It can be so simple as:

new Ajax.Request('http://www.external-domain.net/my_api.php?getParameterKey=getParameterValue', {
            method:'post',
            contentType:"application/x-www-form-urlencoded",
            postBody:'key=' + value,
            onSuccess: function(response) {
                // process response
            },
            onCreate: function(response) { // here comes the fix
                var t = response.transport; 
                t.setRequestHeader = t.setRequestHeader.wrap(function(original, k, v) { 
                    if (/^(accept|accept-language|content-language)$/i.test(k)) 
                        return original(k, v); 
                    if (/^content-type$/i.test(k) && 
                        /^(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)(;.+)?$/i.test(v)) 
                        return original(k, v); 
                    return; 
                }); 
            } 
        });

If you see any disadvantage/improvement to this solution, we welcome you to share :)

Solution 2

In fact it is preflight request, because Prototype adds custom headers X-Requested-With, X-Prototype-Version to the request. Because of these headers browser sends first OPTIONS request. XHR spec says:

For non same origin requests using the HTTP GET method a preflight request is made when headers other than Accept and Accept-Language are set.

How to solve this problem? I can see only one possibility to solve this ASAP: completely overwrite method Ajax.Request#setRequestHeaders(), e.g. insert this script right after Prototype.js:

Ajax.Request.prototype.setRequestHeaders = function() {
  var headers = {
    // These two custom headers cause preflight request:
    //'X-Requested-With': 'XMLHttpRequest',
    //'X-Prototype-Version': Prototype.Version,
    'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
  };

  if (this.method == 'post') {
    headers['Content-Type'] = this.options.contentType +
      (this.options.encoding ? '; charset=' + this.options.encoding : '');

    /* Force "Connection: close" for older Mozilla browsers to work
     * around a bug where XMLHttpRequest sends an incorrect
     * Content-length header. See Mozilla Bugzilla #246651.
     */
    if (this.transport.overrideMimeType &&
        (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
          headers['Connection'] = 'close';
  }

  if (typeof this.options.requestHeaders == 'object') {
    var extras = this.options.requestHeaders;

    if (Object.isFunction(extras.push))
      for (var i = 0, length = extras.length; i < length; i += 2)
        headers[extras[i]] = extras[i+1];
    else
      $H(extras).each(function(pair) { headers[pair.key] = pair.value; });
  }

  for (var name in headers)
    this.transport.setRequestHeader(name, headers[name]);
}

This patch removes custom headers from any AJAX request. In case when you still need these headers for non-CORS requests, more logic may be added which will give possibility to disable these headers in options for new Ajax.Request() (I'll skip this variant here to make answer shorter).

Solution 3

Actually, it's much easier with Prototype.js V1.7:

Ajax.Responders.register({
    onCreate:function(r){
        r.options.requestHeaders={
        'X-Prototype-Version':null,
        'X-Requested-With':null
        };
    }
});

Prototype.js drops any pre-defined header if its value is null.

Share:
28,405
Philip David
Author by

Philip David

Updated on July 09, 2022

Comments

  • Philip David
    Philip David almost 2 years

    I'm attempting to access a web service with Prototype/AJAX and am running into an error I can't figure out: it seems that when I make a request to a server my request is interpreted as an OPTIONS rather than a GET request (and in turn throws a 501 - not implemented error since the server only allows GET requests, based on what I understand from Access-Control-Request-Method:). Am I missing something in my AJAX/request formulation that may be causing this error? I've read a bit into CORS/preflighted requests here but I'm unsure how it could apply when my code looks compliant...

    Here's the relevant AJAX request:

    function fetchMetar() {
    var station_id = $("station_input").value;
    
        new Ajax.Request(REQUEST_ADDRESS, {
            method: "get",
            parameters: {stationString: station_id},
            onSuccess: displayMetar,
            onFailure: function() {
                $("errors").update("an error occurred");
            }
        });
    }
    

    and here's the error and relevant request info I get from Chrome:

    Request URL:http://weather.aero/dataserver_current/httpparam?
     dataSource=metars&requestType=retrieve&format=xml&hoursBeforeNow=3
     &mostRecent=true&stationString=&stationString=KSBA
    Request Method:OPTIONS
    Status Code:501 Not Implemented
    Request Headers
    Accept:*/*
    Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.3
    Accept-Encoding:gzip,deflate,sdch
    Accept-Language:en-US,en;q=0.8
    Access-Control-Request-Headers:origin, x-prototype-version, x-requested-with, accept
    Access-Control-Request-Method:GET
    Connection:keep-alive
    Host:weather.aero
    Origin:http://domain.com
    Referer:http://domain.com/.../...html
    

    What could I be overlooking here? Why does Chrome say the request is being sent as OPTIONS rather than GET? When Chrome spits out the Access-Control-Request-Headers: information, are these exclusively the only headers allowed in the request?

    Thanks!