livenson / opennode-demo-api

Demo API for Opennode
2 stars 1 forks source link

Cross-Origin access patch #2

Closed AndresTraks closed 13 years ago

AndresTraks commented 13 years ago

Firefox and Chrome require the HTTP OPTIONS request to work so that it could connect to the OMS on a different server. IE does not follow the W3C standard, so it just sends a GET request without a preceding OPTIONS request. Read about it here: http://www.w3.org/TR/cors/

I couldn't figure out how to do create a patch file with git, so here's the updated demo.py. It still needs some tweaking, because FF still refused to do the DELETE request. We also haven't tested the POST request, but GET requests do work with this.

!/usr/bin/env python

import web import json import string import random

urls = ( '/computes/', 'ComputeList', '/computes/(\d+)/', 'Compute', '/networks/', 'NetworkList', '/networks/(\d+)/', 'Network', '/storages/', 'StorageList', '/storages/(\d+)/', 'Storage', '/templates/', 'TemplateList', '/templates/(\d+)/', 'Template', '/news/', 'NewsList', '/news/(\d+)/', 'News' ) app = web.application(urls, globals())

def gen_compute_data(id): return {'id': id, 'name': 'hostname %s' %id, 'arch': ['x86', 'x64', 'win32', 'win64', 'macosx'][id % 5], 'memory': 2id, 'cpu': 0.2 \ id, 'cores': range(1,16)[id % 15], 'template': ['centos5', 'centos6', 'rhel6-jbos', 'winserver2008', 'jetty-cluster'][id % 5], 'state': ['running', 'stopped', 'suspended'][id % 3]}

def gen_network_data(id): return {'id': id, 'name': 'network %s' %id, 'ip': '%s.%s.%s.%s' %(id, id, id, id), 'mask': '%s.%s.%s.0' %(id * 2, id * 2, id * 2), 'address_allocation': ['dhcp', 'static'][id % 2], 'gateway': '%s.%s.%s.1' %(id * 2, id * 2, id * 2) }

def gen_storage_data(id): return {'id': id, 'name': 'storage_pool %s' %id, 'size': id * 3000, 'type': ['local', 'iscsi', 'lvm', 'nfs'][id % 4] }

def gen_template_data(id): return {'id': id, 'name': ['centos5', 'centos6', 'rhel6-jbos', 'winserver2008', 'jetty-cluster'][id % 5], 'min_disk_size': id * 3000, 'min_memory_size': id * 300 }

def gen_news_data(id): def get_string(length): return ''.join(random.choice(string.letters) for i in xrange(length)) return {'id': id, 'type': ['info', 'warning', 'error', 'system_message'][id % 4], 'name': get_string(20), 'content': get_string(400) }

limit = 20

computes = [gen_compute_data(i) for i in range(limit)] storages = [gen_storage_data(i) for i in range(limit)] networks = [gen_network_data(i) for i in range(limit)] templates = [gen_template_data(i) for i in range(limit)] news = [gen_news_data(i) for i in range(limit)]

class GenericContainer(object):

resource = {'ComputeList': computes,
            'StorageList': storages,
            'NetworkList': networks,
            'TemplateList': templates,
            'NewsList': news
            }

def OPTIONS(self):
    web.header('Access-Control-Allow-Method', web.ctx.environ['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])
    web.header('Access-Control-Allow-Headers', web.ctx.environ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])
    web.header('Access-Control-Allow-Origin', web.ctx.environ['HTTP_ORIGIN'])

def GET(self):
    if 'HTTP_ORIGIN' in web.ctx.environ:
        web.header('Access-Control-Allow-Origin', web.ctx.environ['HTTP_ORIGIN'])

    # deduce resource type from the object name
    cls = self.__class__.__name__
    type = self.resource[cls]

    return json.dumps([{t['id']: t['name']} for t in type])

def POST(self):
    if 'HTTP_ORIGIN' in web.ctx.environ:
        web.header('Access-Control-Allow-Origin', web.ctx.environ['HTTP_ORIGIN'])

    # deduce resource type from the object name
    cls = self.__class__.__name__
    type = self.resource[cls]

    # create a new object of a given type
    new_id = max([t['id'] for t in type]) + 1
    submitted_data = json.loads(web.data())
    submitted_data.update({'id': new_id})
    type.append(submitted_data)
    return json.dumps(type[-1], sort_keys = 4, indent = 4)

class GenericResource(object):

resource = {'Compute': computes,
            'Storage': storages,
            'Network': networks,
            'Template': templates,
            'News': news
            }

def OPTIONS(self, id):
    web.header('Access-Control-Allow-Method', web.ctx.environ['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])
    web.header('Access-Control-Allow-Headers', web.ctx.environ['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])
    web.header('Access-Control-Allow-Origin', web.ctx.environ['HTTP_ORIGIN'])

def PUT(self, id):
    if 'HTTP_ORIGIN' in web.ctx.environ:
        web.header('Access-Control-Allow-Origin', web.ctx.environ['HTTP_ORIGIN'])

    id = int(id)
    # deduce resource type from the object name
    cls = self.__class__.__name__
    type = self.resource[cls]

    # locate the object for updating
    for o in type:
        if o['id'] == id:
            submitted_data = json.loads(web.data())
            o.update(submitted_data)
            o['id'] = id # just in case request overwrites it
            return json.dumps(o, sort_keys = 4, indent = 4)
    # nothing found
    raise web.notfound()

def DELETE(self, id):
    if 'HTTP_ORIGIN' in web.ctx.environ:
        web.header('Access-Control-Allow-Origin', web.ctx.environ['HTTP_ORIGIN'])

    id = int(id)
    # deduce resource type from the object name
    cls = self.__class__.__name__
    type = self.resource[cls]

    # locate the object for deleting
    for o in type:
        if o['id'] == id:
            type.remove(o)
            return
    # nothing found
    raise web.notfound()

def GET(self, id):
    if 'HTTP_ORIGIN' in web.ctx.environ:
        web.header('Access-Control-Allow-Origin', web.ctx.environ['HTTP_ORIGIN'])

    id = int(id)
    cls = self.__class__.__name__
    resource_type = self.resource[cls]
    # locate object
    for obj in resource_type:
        if obj['id'] == id:
            return json.dumps(resource_type[id], sort_keys = 4, indent = 4)
    raise web.notfound()

class ComputeList(GenericContainer): pass class Compute(GenericResource): pass

class NetworkList(GenericContainer): pass class Network(GenericResource): pass

class StorageList(GenericContainer): pass class Storage(GenericResource): pass

class TemplateList(GenericContainer): pass class Template(GenericResource): pass

class NewsList(GenericContainer): pass class News(GenericResource): pass

if name == "main": app.run()

livenson commented 13 years ago

Fixed by 1912170.

A better way to commit would be to clone a project, do modifications and issue a pull request.

AndresTraks commented 13 years ago

I've made a typo. In both of the OPTIONS handlers, web.header('Access-Control-Allow-Method', web.ctx.environ['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) should be web.header('Access-Control-Allow-Methods', web.ctx.environ['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])

This makes the DELETE request work.

livenson commented 13 years ago

Fixed in 3782d63483 .

AndresTraks commented 13 years ago

Sorry, but you changed the wrong thing.

Access-Control-Allow-Origin should be singular and Access-Control-Allow-Methods should be plural.

Just to be clear, the browser will send these HTTP headers: Access-Control-Request-Headers Access-Control-Request-Method

and the server should respond with: Access-Control-Allow-Headers Access-Control-Allow-Methods Access-Control-Allow-Origin

livenson commented 13 years ago

Sorry, fixed again.