Skip to content

Instantly share code, notes, and snippets.

@alex9311
Last active August 7, 2018 00:25
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alex9311/08bf9b452fa17765a936 to your computer and use it in GitHub Desktop.
Save alex9311/08bf9b452fa17765a936 to your computer and use it in GitHub Desktop.
Enabling CORS on PredictionIO engine server

###Enabling CORS for a PredictionIO Engine This guide is meant for people who have no experience with nginx and want to enable Cross-Origin Resource Sharing (CORS) with their predictionIO instance. Enabling CORS is needed if you'd like to query your engine from anther domain's front end.

In my case, I had the recommender engine running on an aws instance. My goal was to query with AJAX from another domain. For example:

data = {'user': 'Bob', 'num': 4};
$.ajax({
    url: 'http://<pio ip>:8000/queries.json',
    type: 'POST',
    data: JSON.stringify(data),
    header: {
      'Content-Type': 'application/json'
    },
    complete: function (response) {
      console.log(response.responseText);
    }
  });

Without some extra work however, this request returns an error

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://<pio ip>:8000/queries.json. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

The workaround to this is to use an nginx proxy. As stated on the pio github cors issue, essentially putting a man in the middle that allows CORS. Nginx will take the cross-domain queries and modify them before sending them on to the engine. This can all be done on a single server.

#####Prerequisites

  1. build, train, and deploy your predictionIO engine on your AWS instance. After deploying, navigate to http://<pio ip>:8000 in your browser and you should an engine status page. If you want to be sure that queries will work, you can do a curl request from your local machine curl -H "Content-Type: application/json" -d '{ "user": "Bob", "num": 4 }' http://<pio ip>:8000/queries.json

  2. Authorize traffic on a new port, I used 8082

  3. Install nginx on your predictionIO engine server (sudo apt-get install nginx)

#####Configure nginx Now we need to configure nginx to listen on port 8082, grab the requests, add the CORS-enabling headers, and forward it to your engine at port 8000.

Open up /etc/nginx/nginx.conf with your favorite editor and replace what is in there with the following:

#System configuration ##################
worker_processes  3;
events {
    worker_connections  1024;
}

# Web configuration #####################
http {
    server {
        listen 8082 default;
        location / {
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header   Host             $host;
            if ($request_method = 'POST') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            }
            if ($request_method = 'GET') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            }
            proxy_pass http://[::1]:8000;
        }
    }
}

The particularly tricky part here was that the predictionIO engine is listening on IPv6, so you need to pass to http://[::1]:8000 rather than http://127.0.0.1:8000;.

#####Start it all up Make sure your engine is still running at http://<pio ip>:8000. Then restart nginx with sudo service nginx restart. Whenever you make changes to nginx.conf you need to restart to apply them. Once nginx is running, go to http://<pio ip>:8082 and you should see the same engine status page.

If that's all working, you can now do cross-origin requests. The query from the introduction just needs to be modified with the new port number as such:

data = {'user': 'Bob', 'num': 4};
$.ajax({
    url: 'http://<pio ip>:8082/queries.json',
    type: 'POST',
    data: JSON.stringify(data),
    header: {
      'Content-Type': 'application/json'
    },
    complete: function (response) {
      console.log(response.responseText);
    }
  });

That should do the trick! Now you'll get back a nice json response

{"itemScores":[{"item":"1","score":1.29},{"item":"2","score":1.27},{"item":"3","score":1.21},{"item":"4","score":1.14}]}
@matthew-valenti
Copy link

matthew-valenti commented Jul 13, 2016

Excellent guide! On CentOS I also had to run the commands below.

semanage port -a -t http_port_t -p tcp 8082
setsebool -P httpd_can_network_connect true

And update /etc/nginx/nginx.conf to accept $request_method = 'OPTIONS' and proxy the PIO event server on port 7070.

#System configuration ##################
worker_processes  3;
events {
    worker_connections  1024;
}

# Web configuration #####################
http {
    server {

        # Proxy all calls thru port 8082.
        listen 8082 default;

        # Proxy calls to queries.json to standard PIO port 8000.
        location /queries.json {
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header   Host             $host;
            if ($request_method = 'POST') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            }
            if ($request_method = 'GET') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            }
            #CORS does an OPTIONS REQUEST PRIOR TO POST/GET so enable it: http://enable-cors.org/server_nginx.html
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
                # Tell client that this pre-flight info is valid for 20 days
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Type' 'text/plain charset=UTF-8';
                add_header 'Content-Length' 0;
                return 204;
            }
            proxy_pass http://[::1]:8000;
        }

        # Proxy calls to events.json to standard PIO port 7070.
        location /events.json {
            proxy_set_header   X-Real-IP        $remote_addr;
            proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header   Host             $host;
            if ($request_method = 'POST') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            }
            if ($request_method = 'GET') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Credentials' 'true';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
            }
            #CORS does an OPTIONS REQUEST PRIOR TO POST/GET so enable it: http://enable-cors.org/server_nginx.html
            if ($request_method = 'OPTIONS') {
                add_header 'Access-Control-Allow-Origin' '*';
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
                # Tell client that this pre-flight info is valid for 20 days
                add_header 'Access-Control-Max-Age' 1728000;
                add_header 'Content-Type' 'text/plain charset=UTF-8';
                add_header 'Content-Length' 0;
                return 204;
            }
            proxy_pass http://[::1]:7070;
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment