Compare commits
4 Commits
nilmdb-1.2
...
nilmdb-1.2
Author | SHA1 | Date | |
---|---|---|---|
2d45466f66 | |||
c6a0e6e96f | |||
79755dc624 | |||
c512631184 |
@@ -53,6 +53,7 @@ def main():
|
|||||||
|
|
||||||
# Print info
|
# Print info
|
||||||
if not args.quiet:
|
if not args.quiet:
|
||||||
|
print "Version: %s" % nilmdb.__version__
|
||||||
print "Database: %s" % (os.path.realpath(args.database))
|
print "Database: %s" % (os.path.realpath(args.database))
|
||||||
if args.address == '0.0.0.0' or args.address == '::':
|
if args.address == '0.0.0.0' or args.address == '::':
|
||||||
host = socket.getfqdn()
|
host = socket.getfqdn()
|
||||||
|
@@ -221,9 +221,11 @@ class File(object):
|
|||||||
# An optimized verison of append, to avoid flushing the file
|
# An optimized verison of append, to avoid flushing the file
|
||||||
# and resizing the mmap after each data point.
|
# and resizing the mmap after each data point.
|
||||||
try:
|
try:
|
||||||
|
rows = []
|
||||||
for i in xrange(count):
|
for i in xrange(count):
|
||||||
row = dataiter.next()
|
row = dataiter.next()
|
||||||
self._f.write(packer(*row))
|
rows.append(packer(*row))
|
||||||
|
self._f.write("".join(rows))
|
||||||
finally:
|
finally:
|
||||||
self._f.flush()
|
self._f.flush()
|
||||||
self.size = self._f.tell()
|
self.size = self._f.tell()
|
||||||
|
@@ -71,16 +71,51 @@ def exception_to_httperror(*expected):
|
|||||||
# care of that.
|
# care of that.
|
||||||
return decorator.decorator(wrapper)
|
return decorator.decorator(wrapper)
|
||||||
|
|
||||||
# Custom Cherrypy tools
|
# Custom CherryPy tools
|
||||||
def allow_methods(methods):
|
|
||||||
method = cherrypy.request.method.upper()
|
def CORS_allow(methods):
|
||||||
if method not in methods:
|
"""This does several things:
|
||||||
if method in cherrypy.request.methods_with_bodies:
|
|
||||||
cherrypy.request.body.read()
|
Handles CORS preflight requests.
|
||||||
allowed = ', '.join(methods)
|
Adds Allow: header to all requests.
|
||||||
cherrypy.response.headers['Allow'] = allowed
|
Raise 405 if request.method not in method.
|
||||||
raise cherrypy.HTTPError(405, method + " not allowed; use " + allowed)
|
|
||||||
cherrypy.tools.allow_methods = cherrypy.Tool('before_handler', allow_methods)
|
It is similar to cherrypy.tools.allow, with the CORS stuff added.
|
||||||
|
"""
|
||||||
|
request = cherrypy.request.headers
|
||||||
|
response = cherrypy.response.headers
|
||||||
|
|
||||||
|
if not isinstance(methods, (tuple, list)): # pragma: no cover
|
||||||
|
methods = [ methods ]
|
||||||
|
methods = [ m.upper() for m in methods if m ]
|
||||||
|
if not methods: # pragma: no cover
|
||||||
|
methods = [ 'GET', 'HEAD' ]
|
||||||
|
elif 'GET' in methods and 'HEAD' not in methods: # pragma: no cover
|
||||||
|
methods.append('HEAD')
|
||||||
|
response['Allow'] = ', '.join(methods)
|
||||||
|
|
||||||
|
# Allow all origins
|
||||||
|
if 'Origin' in request:
|
||||||
|
response['Access-Control-Allow-Origin'] = request['Origin']
|
||||||
|
|
||||||
|
# If it's a CORS request, send response.
|
||||||
|
request_method = request.get("Access-Control-Request-Method", None)
|
||||||
|
request_headers = request.get("Access-Control-Request-Headers", None)
|
||||||
|
if (cherrypy.request.method == "OPTIONS" and
|
||||||
|
request_method and request_headers):
|
||||||
|
response['Access-Control-Allow-Headers'] = request_headers
|
||||||
|
response['Access-Control-Allow-Methods'] = ', '.join(methods)
|
||||||
|
# Try to stop further processing and return a 200 OK
|
||||||
|
cherrypy.response.status = "200 OK"
|
||||||
|
cherrypy.response.body = ""
|
||||||
|
cherrypy.request.handler = lambda: ""
|
||||||
|
return
|
||||||
|
|
||||||
|
# Reject methods that were not explicitly allowed
|
||||||
|
if cherrypy.request.method not in methods:
|
||||||
|
raise cherrypy.HTTPError(405)
|
||||||
|
|
||||||
|
cherrypy.tools.CORS_allow = cherrypy.Tool('on_start_resource', CORS_allow)
|
||||||
|
|
||||||
# CherryPy apps
|
# CherryPy apps
|
||||||
class Root(NilmApp):
|
class Root(NilmApp):
|
||||||
@@ -142,7 +177,7 @@ class Stream(NilmApp):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@exception_to_httperror(NilmDBError, ValueError)
|
@exception_to_httperror(NilmDBError, ValueError)
|
||||||
@cherrypy.tools.allow_methods(methods = ["POST"])
|
@cherrypy.tools.CORS_allow(methods = ["POST"])
|
||||||
def create(self, path, layout):
|
def create(self, path, layout):
|
||||||
"""Create a new stream in the database. Provide path
|
"""Create a new stream in the database. Provide path
|
||||||
and one of the nilmdb.layout.layouts keys.
|
and one of the nilmdb.layout.layouts keys.
|
||||||
@@ -153,7 +188,7 @@ class Stream(NilmApp):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@exception_to_httperror(NilmDBError)
|
@exception_to_httperror(NilmDBError)
|
||||||
@cherrypy.tools.allow_methods(methods = ["POST"])
|
@cherrypy.tools.CORS_allow(methods = ["POST"])
|
||||||
def destroy(self, path):
|
def destroy(self, path):
|
||||||
"""Delete a stream and its associated data."""
|
"""Delete a stream and its associated data."""
|
||||||
return self.db.stream_destroy(path)
|
return self.db.stream_destroy(path)
|
||||||
@@ -186,7 +221,7 @@ class Stream(NilmApp):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@exception_to_httperror(NilmDBError, LookupError, TypeError)
|
@exception_to_httperror(NilmDBError, LookupError, TypeError)
|
||||||
@cherrypy.tools.allow_methods(methods = ["POST"])
|
@cherrypy.tools.CORS_allow(methods = ["POST"])
|
||||||
def set_metadata(self, path, data):
|
def set_metadata(self, path, data):
|
||||||
"""Set metadata for the named stream, replacing any
|
"""Set metadata for the named stream, replacing any
|
||||||
existing metadata. Data should be a json-encoded
|
existing metadata. Data should be a json-encoded
|
||||||
@@ -198,7 +233,7 @@ class Stream(NilmApp):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@exception_to_httperror(NilmDBError, LookupError, TypeError)
|
@exception_to_httperror(NilmDBError, LookupError, TypeError)
|
||||||
@cherrypy.tools.allow_methods(methods = ["POST"])
|
@cherrypy.tools.CORS_allow(methods = ["POST"])
|
||||||
def update_metadata(self, path, data):
|
def update_metadata(self, path, data):
|
||||||
"""Update metadata for the named stream. Data
|
"""Update metadata for the named stream. Data
|
||||||
should be a json-encoded dictionary"""
|
should be a json-encoded dictionary"""
|
||||||
@@ -208,7 +243,7 @@ class Stream(NilmApp):
|
|||||||
# /stream/insert?path=/newton/prep
|
# /stream/insert?path=/newton/prep
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@cherrypy.tools.allow_methods(methods = ["PUT"])
|
@cherrypy.tools.CORS_allow(methods = ["PUT"])
|
||||||
def insert(self, path, start, end):
|
def insert(self, path, start, end):
|
||||||
"""
|
"""
|
||||||
Insert new data into the database. Provide textual data
|
Insert new data into the database. Provide textual data
|
||||||
@@ -264,7 +299,7 @@ class Stream(NilmApp):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@exception_to_httperror(NilmDBError)
|
@exception_to_httperror(NilmDBError)
|
||||||
@cherrypy.tools.allow_methods(methods = ["POST"])
|
@cherrypy.tools.CORS_allow(methods = ["POST"])
|
||||||
def remove(self, path, start = None, end = None):
|
def remove(self, path, start = None, end = None):
|
||||||
"""
|
"""
|
||||||
Remove data from the backend database. Removes all data in
|
Remove data from the backend database. Removes all data in
|
||||||
@@ -419,16 +454,14 @@ class Server(object):
|
|||||||
'error_page.default': self.json_error_page,
|
'error_page.default': self.json_error_page,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Send a permissive Access-Control-Allow-Origin (CORS) header
|
# Some default headers to just help identify that things are working
|
||||||
# with all responses so that browsers can send cross-domain
|
app_config.update({ 'response.headers.X-Jim-Is-Awesome': 'yeah' })
|
||||||
# requests to this server.
|
|
||||||
app_config.update({ 'response.headers.Access-Control-Allow-Origin':
|
|
||||||
'*' })
|
|
||||||
|
|
||||||
# Only allow GET and HEAD by default. Individual handlers
|
# Set up Cross-Origin Resource Sharing (CORS) handler so we
|
||||||
# can override.
|
# can correctly respond to browsers' CORS preflight requests.
|
||||||
app_config.update({ 'tools.allow_methods.on': True,
|
# This also limits verbs to GET and HEAD by default.
|
||||||
'tools.allow_methods.methods': ['GET', 'HEAD'] })
|
app_config.update({ 'tools.CORS_allow.on': True,
|
||||||
|
'tools.CORS_allow.methods': ['GET', 'HEAD'] })
|
||||||
|
|
||||||
# Send tracebacks in error responses. They're hidden by the
|
# Send tracebacks in error responses. They're hidden by the
|
||||||
# error_page function for client errors (code 400-499).
|
# error_page function for client errors (code 400-499).
|
||||||
|
@@ -55,12 +55,6 @@ class TestClient(object):
|
|||||||
client.version()
|
client.version()
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
# Trigger same error with a PUT request
|
|
||||||
client = nilmdb.Client(url = "http://localhost:1/")
|
|
||||||
with assert_raises(nilmdb.client.ServerError):
|
|
||||||
client.version()
|
|
||||||
client.close()
|
|
||||||
|
|
||||||
# Then a fake URL on a real host
|
# Then a fake URL on a real host
|
||||||
client = nilmdb.Client(url = "http://localhost:32180/fake/")
|
client = nilmdb.Client(url = "http://localhost:32180/fake/")
|
||||||
with assert_raises(nilmdb.client.ClientError):
|
with assert_raises(nilmdb.client.ClientError):
|
||||||
@@ -360,12 +354,6 @@ class TestClient(object):
|
|||||||
raise AssertionError("/stream/extract is not text/plain:\n" +
|
raise AssertionError("/stream/extract is not text/plain:\n" +
|
||||||
headers())
|
headers())
|
||||||
|
|
||||||
# Make sure Access-Control-Allow-Origin gets set
|
|
||||||
if "access-control-allow-origin: " not in headers():
|
|
||||||
raise AssertionError("No Access-Control-Allow-Origin (CORS) "
|
|
||||||
"header in /stream/extract response:\n" +
|
|
||||||
headers())
|
|
||||||
|
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
def test_client_08_unicode(self):
|
def test_client_08_unicode(self):
|
||||||
|
@@ -11,12 +11,7 @@ from nose.tools import assert_raises
|
|||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import threading
|
|
||||||
import urllib2
|
|
||||||
from urllib2 import urlopen, HTTPError
|
|
||||||
import Queue
|
|
||||||
import StringIO
|
import StringIO
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
|
@@ -9,14 +9,7 @@ from nose.tools import assert_raises
|
|||||||
import distutils.version
|
import distutils.version
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import cherrypy
|
|
||||||
import threading
|
|
||||||
import urllib2
|
|
||||||
from urllib2 import urlopen, HTTPError
|
|
||||||
import Queue
|
|
||||||
import cStringIO
|
|
||||||
import random
|
import random
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
@@ -6,15 +6,13 @@ import distutils.version
|
|||||||
import simplejson as json
|
import simplejson as json
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import cherrypy
|
|
||||||
import threading
|
import threading
|
||||||
import urllib2
|
import urllib2
|
||||||
from urllib2 import urlopen, HTTPError
|
from urllib2 import urlopen, HTTPError
|
||||||
import Queue
|
|
||||||
import cStringIO
|
import cStringIO
|
||||||
import time
|
import time
|
||||||
|
import requests
|
||||||
|
|
||||||
from nilmdb.utils import serializer_proxy
|
from nilmdb.utils import serializer_proxy
|
||||||
|
|
||||||
@@ -208,3 +206,34 @@ class TestServer(object):
|
|||||||
data = getjson("/stream/get_metadata?path=/newton/prep"
|
data = getjson("/stream/get_metadata?path=/newton/prep"
|
||||||
"&key=foo")
|
"&key=foo")
|
||||||
eq_(data, {'foo': None})
|
eq_(data, {'foo': None})
|
||||||
|
|
||||||
|
def test_cors_headers(self):
|
||||||
|
# Test that CORS headers are being set correctly
|
||||||
|
|
||||||
|
# Normal GET should send simple response
|
||||||
|
url = "http://127.0.0.1:32180/stream/list"
|
||||||
|
r = requests.get(url, headers = { "Origin": "http://google.com/" })
|
||||||
|
eq_(r.status_code, 200)
|
||||||
|
if "access-control-allow-origin" not in r.headers:
|
||||||
|
raise AssertionError("No Access-Control-Allow-Origin (CORS) "
|
||||||
|
"header in response:\n", r.headers)
|
||||||
|
eq_(r.headers["access-control-allow-origin"], "http://google.com/")
|
||||||
|
|
||||||
|
# OPTIONS without CORS preflight headers should result in 405
|
||||||
|
r = requests.options(url, headers = {
|
||||||
|
"Origin": "http://google.com/",
|
||||||
|
})
|
||||||
|
eq_(r.status_code, 405)
|
||||||
|
|
||||||
|
# OPTIONS with preflight headers should give preflight response
|
||||||
|
r = requests.options(url, headers = {
|
||||||
|
"Origin": "http://google.com/",
|
||||||
|
"Access-Control-Request-Method": "POST",
|
||||||
|
"Access-Control-Request-Headers": "X-Custom",
|
||||||
|
})
|
||||||
|
eq_(r.status_code, 200)
|
||||||
|
if "access-control-allow-origin" not in r.headers:
|
||||||
|
raise AssertionError("No Access-Control-Allow-Origin (CORS) "
|
||||||
|
"header in response:\n", r.headers)
|
||||||
|
eq_(r.headers["access-control-allow-methods"], "GET, HEAD")
|
||||||
|
eq_(r.headers["access-control-allow-headers"], "X-Custom")
|
||||||
|
Reference in New Issue
Block a user