Hello,

I have a need to integrate the services module in a mobile app I am currently buried in. First, let me say just how amazing it is that so much hard work has been done to the nginx config to support specific modules and caching. It's absolutely incredible.

Anyway, I need to make cross-domain requests to my barracuda instance, and these requests will be handled by the server. However, I cannot get my barracuda instance to allow other domains via CORS. I have tried multiple nginx configs, but I believe this is as close as I have come.

First, I see that nginx is denying OPTIONS requests. So I added OPTIONS to the list of allowed request methods by changing:

if ( $request_method !~ ^(?:GET|HEAD|POST|<strong>OPTIONS</strong>)$ ) {
  return 444;
}

Then, under "location /", I modified it to read:

###
### Catch all unspecified requests.
###
location / {
  if ( $http_user_agent ~* wget ) {
    return 444;
  }
  try_files $uri @cache;

    # For CORS
    if ($request_method = OPTIONS ) {
      add_header Access-Control-Allow-Origin "*"; ### <<< I hope it accepts wildcards
      add_header Access-Control-Allow-Methods "POST, GET, OPTIONS"; ###<<< Is this overwriting the previous directive?
      add_header Access-Control-Allow-Headers "Authorization";   ### <<< Not sure if I need this
      add_header Access-Control-Allow-Credentials "true";        ### <<< Once again, might need it later.
      add_header Content-Length 0; ### <<< ???
      add_header Content-Type text/plain; ### <<< ??? Isn't json/jsonp application/json?
      return 200;
    }
}

Has anyone been able to, well, enable this on a barracuda instance before? I think this is going to become more common in the future, as we can already see certain browsers needing their hand held for @font-face and other cross-domain requests.

Just in case, my actual error in chrome's console is:
XMLHttpRequest cannot load http://www.DOMAIN.TLD/system/connect.jsonp. Origin http://localhost is not allowed by Access-Control-Allow-Origin

Thanks.

Comments

omega8cc’s picture

The problem is not only missing header (or headers), but the fact that you can use add_header only in the location context, so it will never work inside an if{} block. Maybe just try to add only add_header Access-Control-Allow-Origin "*"; directly there, outside of if{} first and see if that helps.

flexgrip’s picture

Thanks for the quick response.

I just tried breaking the add_header directive out of the if statement, like so.

###
### Catch all unspecified requests.
###
location / {

  add_header Access-Control-Allow-Origin *; # <- OUTSIDE OF THE IF



  if ( $http_user_agent ~* wget ) {
    return 444;
  }
  try_files $uri @cache;

    # For CORS
    #if ($request_method = OPTIONS ) {
    #  add_header Access-Control-Allow-Origin "http://localhost"; # <- needs to be updated
    #  add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
    #  add_header Access-Control-Allow-Headers "Authorization";   # <- You may not need this...it's for Basic Auth
    #  add_header Access-Control-Allow-Credentials "true";        # <- Basic Auth stuff, again
    #  add_header Content-Length 0;
    #  add_header Content-Type text/plain;
    #  return 200;
    #}

}

Unfortunately I cannot get it to work. Just to be clear, I saw that /var/aegir/config/includes/nginx_modern_include.conf was being included in all of my vhosts, so that is where I am putting it. After ever save of that nginx_modern_include.conf file, I am doing an nginx reload, stop, and start.

But no matter what I try, this is the error from Chrome:
XMLHttpRequest cannot load http://domain.tld/wineries/system/connect.jsonp. Origin http://localhost is not allowed by Access-Control-Allow-Origin.

Is there somewhere else I need to be putting this for each vhost as well, since that is a global file? Is there any other place it could be being overridden?

omega8cc’s picture

Component: Miscellaneous » Code
Category: support » feature
Priority: Minor » Normal

Could you post the result of:

curl -I http://domain.tld/wineries/system/connect.jsonp

Thanks.

omega8cc’s picture

Note that there are also separate locations for system and json so depending on where the rewrites etc terminate, you may need to add it also there. The curl -I test will show you if the expected header has been sent as expected.

omega8cc’s picture

Also, you should do that only it this one include file, which is included in the affected vhosts.

omega8cc’s picture

flexgrip’s picture

HTTP/1.1 200 OK
Server: nginx
Date: Wed, 11 Apr 2012 20:31:07 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
X-Backend: A
X-Purge-Level: none
X-Accel-Expires: 10800
Expires: Thu, 01 Jan 1970 00:00:01 GMT
Last-Modified: Wed, 11 Apr 2012 20:31:07 +0000
Cache-Control: no-cache
ETag: "1334176267"
X-Engine: Barracuda 1.0 ET
X-Device: normal
X-Speed-Cache: BYPASS
X-Speed-Cache-Expire: 3h
X-NoCache: Skip
X-GeoIP-Country-Code: US
X-GeoIP-Country-Name: United States

Although the result in two different browsers is the same error... Returns 301 w/ "Origin http://localhost is not allowed by Access-Control-Allow-Origin"

I looked through that other ticket. It would seem that my OPTIONS method is being blocked by something. However, I see exactly where you guys are doing that in the nginx config. I added OPTIONS as an allowed method.

I'm completely baffled at this point. It must be being overridden somewhere. My experience with nginx is literally this server. So I'm pretty fresh. I knew Apache well enough to get this solved a long time ago.

omega8cc’s picture

Thanks. This result suggests that it is terminated in this location:

location ~* /(?:external|system|files/imagecache|files/styles)/

Please try to add that required header there and reload Nginx.

flexgrip’s picture

No dice.

I was looking at the different config files... modern, advanced, compact.

Is the add_header option smart enough to know if I add the same headers twice, to only include it once? Or is it truly split by each location *?

omega8cc’s picture

It should be only in one location and you should see it in the header response.

You could try to add it outside of any location, just at the top of the include file.

flexgrip’s picture

I think I got it!

Add OPTIONS to the allowed request methods...

###
### Deny not compatible request methods without 405 response.
###
if ( $request_method !~ ^(?:GET|HEAD|POST|OPTIONS)$ ) {
  return 444;
}

And then in the /index.php section (I still can't figure out why it needs to be added here, I tried almost every relevant section and this was the only one that worked.)

###
### Send all non-static requests to php-fpm, restricted to known php file.
###
location = /index.php {
  add_header    X-Engine "Barracuda 1.0 ET";
  add_header    X-Device "$device";
  add_header    X-Speed-Cache "$upstream_cache_status";
  add_header    X-Speed-Cache-Expire "$will_expire_in";
  add_header    X-Speed-Cache-UID "$cache_uid";
  add_header    X-NoCache "$nocache_details";
  add_header    X-GeoIP-Country-Code "$geoip_country_code";
  add_header    X-GeoIP-Country-Name "$geoip_country_name";
  add_header    X-This-Proto "$http_x_forwarded_proto";

  ### Modification for CORS
     add_header Access-Control-Allow-Origin *;
     add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
     add_header Access-Control-Allow-Headers "X-Requested-With";

  ###
  ### Use Nginx cache for all visitors.
  ###
  set $nocache "";
  if ( $nocache_details ~ (?:OctopusNCookie|Args|Skip) ) {
    set $nocache "NoCache";
  }

Now, after all of this, I still don't think this is the "right" way to do it. There is no reason it should be in the index.php section. At least with the way I am thinking of it, it should be in the ~* section and also something would have to change in the section where you control "system" paths because the services module uses /servicename/system/ for connections.

Any thoughts?

omega8cc’s picture

If this is the location where it works, then this is the final location where request arrives. It could be terminated in some of above locations only when it is not send internally to @drupal and finally to /index.php, but if it is, then any headers added above will be always ignored.

I will submit a patch shortly.

Thanks for debugging!

flexgrip’s picture

I actually made a patch against STABLE for it. Although I'm unsure if anything has changed in HEAD.

omega8cc’s picture

Please try your patch with head then and submit for review. Thanks!

flexgrip’s picture

Status: Active » Needs review
StatusFileSize
new1 KB

Here is the patch that adds OPTIONS as an allowed request method (I think that should be on by default, but its up to you) and the header for origin and the "X-Requested-With" in the right section. Those are commented out by default, because after thinking about it, its obviously not a good idea to turn on cross-origin requests by default. The X-Requested-With flag is there because, if they are turning on CORS, there is a 99% change they are going to need and use X-Requested-With. Especially since I've heard chatter of Mozilla requiring it soon.

I'm not sure about your comment styles... I tried to match them. Hope this helps.

omega8cc’s picture

Status: Needs review » Needs work

We can add OPTIONS as allowed by default, but the rest will not be really useful when included as commented out because then the (default) result will be the same as it is now, and any changes will be overwritten on upgrade or server node re-verify.

We could introduce another include, as we did already above: http://drupalcode.org/project/barracuda.git/blob/HEAD:/aegir/conf/nginx_... so any extra config there will survive during upgrade.

Or maybe we could add some extra locations trick with local termination to pass correct headers only when they are really required/expected, but then how to define such location? Is there any regex matching we could use? I have no idea if all URLs requiring those headers end with .jsonp always?

flexgrip’s picture

No they don't always end with .jsonp. In fact, even among the services module they can have multiple different ways of formatting the url.

Also, another HUGE problem. My patch above is worthless in multiple ways. Not only is it not formatted right, but it doesn't even work. Something was going on the day I tested it, and it seemed to be spitting out jsonp the way I needed it to. But I found out that it wasn't the case. In the services module, when I enable the rest server, I still get a screwed up return error saying that Origin http://localhost is not allowed by Access-Control-Allow-Origin. So I'm back to square one.

I will continue to mess with it, but for now, I'm going to change the development environment for this app so I can at least continue working on it and do not have to wait to enable CORS.

At this point, I've added the config options to just about every spot in that nginx_modern_config. And its simply not working. I am open to ideas, but I feel kind of like it is being overwritten or blocked somewhere else.

omega8cc’s picture

We will at least change the main config to allow services/REST specific request methods: #1472460: Nginx configuration denies HTTP request methods PUT DELETE and OPTIONS.

It is not overwritten anywhere, so you will be able to test this setup further, I hope. Let us know when you will find the source of the problem.

flexgrip’s picture

Just throwing this out there. It may be a bad idea so fire away if so. But I was thinking about this, and a few other configuration options that I needed to change specific to the services api in drupal (I am running into some issues with nginx caching json responses with services api... yeah I know it doesn't make sense but it seems to be happening). But most of these changes really don't need to be on by default, for security's sake.

So what if I made a stand-alone module, or forked provision/barracuda, that would allow you to turn these options on on a per-site basis while, well, provisioning them. That, or at least some functionality for the entire platform to adjust the settings accordingly. Even if it was platform specific, and we just had an installation profile for it.

Maybe I'm wrong, but I see more and more use for cross-origin requests, and usable services api with these mobile apps and even new html5 specs (we already saw it with IE and fonts). I don't think that every site you provision on aegir needs to have these options, but it would be a nice thing to have when you are using octopus to slam out a bunch of instances and platforms that will be doing advanced ajax and cross-origin stuff.

Just an idea... As always, I'm more worried about where to put it than how to implement it.

omega8cc’s picture

What we need to know is: do we need to add the ability to include extra config in the /index.php location as we did already for other locations/rewrites here: http://drupalcode.org/project/barracuda.git/blob/HEAD:/aegir/conf/nginx_... ? I think yes, but since your results were inconsistent, I'm not sure if this helps or not. Maybe the already existing ability to add extra locations above is enough, as you can do whatever you want there, but maybe it is not enough.

omega8cc’s picture

Status: Needs work » Postponed
omega8cc’s picture

Version: » 6.x-2.x-dev
Issue summary: View changes
Status: Postponed » Closed (won't fix)

Closing due to the lack of feedback for a long time. Feel free to reopen if you see fit.

  • omega8cc committed 5805cab on HEAD
    BOA: Issue #1472460 by dmiric and #1524738 by flexgrip - Nginx...