Commit 65689030 authored by Jacek Furmankiewicz's avatar Jacek Furmankiewicz

Code is broken now: working on separating router from actual Http Resource, in...

Code is broken now: working on separating router from actual Http Resource, in preparation for ZeroMQ multicore support...
parent d2897764
'''
Common classes
'''
class Response:
"""
Custom response object, can be returned instead of raw string response
"""
def __init__(self,code=200,entity=None,headers={}):
self.code = code
self.entity=entity
self.headers=headers
\ No newline at end of file
'''
Created on 2011-10-03
@author: jacekf
Common routing classes, regardless of whether used in HTTP or ZeroMQ context
'''
from collections import defaultdict
from corepost.enums import Http, HttpHeader
from corepost.utils import getMandatoryArgumentNames, convertToJson
from corepost import Response
from enums import MediaType
from formencode import FancyValidator, Invalid
from twisted.internet import reactor, defer
from twisted.web.http import parse_qs
from twisted.web.resource import Resource
from twisted.web.server import Site, NOT_DONE_YET
import re, copy, exceptions, json, yaml
from xml.etree import ElementTree
from xml.etree.ElementTree import Element
class UrlRouter:
''' Common class for containing info related to routing a request to a function '''
__urlMatcher = re.compile(r"<(int|float|):?([a-zA-Z0-9]+)>")
__urlRegexReplace = {"":r"(?P<arg>.+)","int":r"(?P<arg>\d+)","float":r"(?P<arg>\d+.?\d*)"}
__typeConverters = {"int":int,"float":float}
def __init__(self,f,url,methods,accepts,produces,cache):
self.__url = url
self.__methods = methods if isinstance(methods,tuple) else (methods,)
self.__accepts = accepts if isinstance(accepts,tuple) else (accepts,)
self.__produces = produces
self.__cache = cache
self.__f = f
self.__argConverters = {} # dict of arg names -> group index
self.__validators = {}
self.__mandatory = getMandatoryArgumentNames(f)[2:]
#parse URL into regex used for matching
m = UrlRouter.__urlMatcher.findall(url)
self.__matchUrl = "^%s$" % url
for match in m:
if len(match[0]) == 0:
# string
self.__argConverters[match[1]] = None
self.__matchUrl = self.__matchUrl.replace("<%s>" % match[1],
UrlRouter.__urlRegexReplace[match[0]].replace("arg",match[1]))
else:
# non string
self.__argConverters[match[1]] = UrlRouter.__typeConverters[match[0]]
self.__matchUrl = self.__matchUrl.replace("<%s:%s>" % match,
UrlRouter.__urlRegexReplace[match[0]].replace("arg",match[1]))
self.__matcher = re.compile(self.__matchUrl)
@property
def cache(self):
'''Indicates if this URL should be cached or not'''
return self.__cache
@property
def methods(self):
return self.__methods
@property
def url(self):
return self.__url
@property
def accepts(self):
return self.__accepts
def addValidator(self,fieldName,validator):
'''Adds additional field-specific formencode validators'''
self.__validators[fieldName] = validator
def getArguments(self,url):
'''
Returns None if nothing matched (i.e. URL does not match), empty dict if no args found (i,e, static URL)
or dict with arg/values for dynamic URLs
'''
g = self.__matcher.search(url)
if g != None:
args = g.groupdict()
# convert to expected datatypes
if len(args) > 0:
for name in args.keys():
converter = self.__argConverters[name]
if converter != None:
args[name] = converter(args[name])
return args
else:
return None
def call(self,instance,request,**kwargs):
'''Forwards call to underlying method'''
for arg in self.__mandatory:
if arg not in kwargs:
raise TypeError("Missing mandatory argument '%s'" % arg)
return self.__f(instance,request,**kwargs)
class CachedUrl:
'''
Used for caching URLs that have been already routed once before. Avoids the overhead
of regex processing on every incoming call for commonly accessed REST URLs
'''
def __init__(self,router,args):
self.__router = router
self.__args = args
@property
def router(self):
return self.__router
@property
def args(self):
return self.__args
class RequestRouter:
'''
Class that handles request->method routing functionality to any type of resource
'''
def __init__(self,urlContainer,schema=None):
'''
Constructor
'''
self.__urls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)}
self.__cachedUrls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)}
self.__routers = {}
self.__schema = schema
self.__registerRouters(urlContainer)
@property
def path(self):
return self.__path
def __registerRouters(self,urlContainer):
"""Main method responsible for registering routers"""
from types import FunctionType
for _,func in urlContainer.__class__.__dict__.iteritems():
if type(func) == FunctionType and hasattr(func,'corepostRequestRouter'):
rq = func.corepostRequestRouter
for method in rq.methods:
for accepts in rq.accepts:
self.__urls[method][rq.url][accepts] = rq
self.__routers[func] = rq # needed so that we cdan lookup the router for a specific function
def route(self,url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True):
'''Obsolete'''
raise RuntimeError("Do not @app.route() any more, as of 0.0.6 API has been re-designed around class methods, see docs and examples")
@defer.inlineCallbacks
def getResponse(self,request):
"""Finds the appropriate router and dispatches the request to the registered function. Returns the appropriate Response object"""
# see if already cached
try:
path = '/' + '/'.join(request.postpath)
contentType = MediaType.WILDCARD if HttpHeader.CONTENT_TYPE not in request.received_headers else request.received_headers[HttpHeader.CONTENT_TYPE]
urlrouter, pathargs = None, None
# fetch URL arguments <-> function from cache if hit at least once before
if contentType in self.__cachedUrls[request.method][path]:
cachedUrl = self.__cachedUrls[request.method][path][contentType]
urlrouter,pathargs = cachedUrl.router, cachedUrl.args
else:
# first time this URL is called
router = None
for contentTypeFunctions in self.__urls[request.method].values():
if contentType in contentTypeFunctions:
# there is an exact function for this incoming content type
router = contentTypeFunctions[contentType]
elif MediaType.WILDCARD in contentTypeFunctions:
# fall back to any wildcard method
router = contentTypeFunctions[MediaType.WILDCARD]
if router != None:
# see if the path arguments match up against any function @route definition
args = router.getArguments(path)
if args != None:
if router.cache:
self.__cachedUrls[request.method][path][contentType] = CachedUrl(router, args)
urlrouter,pathargs = router,args
break
#actual call
if urlrouter != None and pathargs != None:
allargs = copy.deepcopy(pathargs)
# handler for weird Twisted logic where PUT does not get form params
# see: http://twistedmatrix.com/pipermail/twisted-web/2007-March/003338.html
requestargs = request.args
if request.method == Http.PUT and HttpHeader.CONTENT_TYPE in request.received_headers.keys() \
and request.received_headers[HttpHeader.CONTENT_TYPE] == MediaType.APPLICATION_FORM_URLENCODED:
requestargs = parse_qs(request.content.read(), 1)
#merge form args
for arg in requestargs.keys():
# maintain first instance of an argument always
if arg not in allargs:
allargs[arg] = requestargs[arg][0]
try:
# if POST/PUT, check if we need to automatically parse JSON
self.__parseRequestData(request)
val = urlrouter.call(self,request,**allargs)
#handle Deferreds natively
if isinstance(val,defer.Deferred):
# add callback to finish the request
val.addCallback(self.__finishDeferred,request)
yield val.callback(request)
else:
#special logic for POST to return 201 (created)
if request.method == Http.POST:
if hasattr(request, 'code'):
if request.code == 200:
request.setResponseCode(201)
else:
request.setResponseCode(201)
defer.returnValue(self.__renderResponse(request, val))
except exceptions.TypeError as ex:
defer.returnValue(self.__createErrorResponse(request,400,"%s" % ex))
except Exception as ex:
defer.returnValue(self.__createErrorResponse(request,500,"Unexpected server error: %s\n%s" % (type(ex),ex)))
else:
defer.returnValue(self.__createErrorResponse(request,404,"URL '%s' not found\n" % request.path))
except Exception as ex:
defer.returnValue(self.__createErrorResponse(request,500,"Internal server error: %s" % ex))
def __renderResponse(self,request,response,code=200):
"""
Takes care of automatically rendering the response and converting it to appropriate format (text,XML,JSON,YAML)
depending on what the caller can accept. Returns Response
"""
if isinstance(response, str):
return Response(code,response)
elif isinstance(response, Response):
return response
else:
(content,contentType) = self.__convertObjectToContentType(request, response)
return Response(code,content,{HttpHeader.CONTENT_TYPE:contentType})
def __convertObjectToContentType(self,request,obj):
"""
Takes care of converting an object (non-String) response to the appropriate format, based on the what the caller can accept.
Returns a tuple of (content,contentType)
"""
if HttpHeader.ACCEPT in request.received_headers:
accept = request.received_headers[HttpHeader.ACCEPT]
if MediaType.APPLICATION_JSON in accept:
return (convertToJson(obj),MediaType.APPLICATION_JSON)
elif MediaType.TEXT_YAML in accept:
return (yaml.dump(obj),MediaType.TEXT_YAML)
elif MediaType.APPLICATION_XML in accept or MediaType.TEXT_XML in accept:
if isinstance(obj,Element):
return (ElementTree.tostring(obj, encoding='utf-8'),MediaType.APPLICATION_XML)
else:
raise RuntimeError("Unable to convert String response to XML automatically")
else:
# no idea, let's do JSON
return (convertToJson(obj),MediaType.APPLICATION_JSON)
else:
# called has no accept header, let's default to JSON
return (convertToJson(obj),MediaType.APPLICATION_JSON)
def __finishDeferred(self,val,request):
"""Finishes any Defered/inlineCallback methods. Returns Response"""
if val != None:
try:
defer.returnValue(self.__renderResponse(request,val))
except Exception as ex:
msg = "Unexpected server error: %s\n%s" % (type(ex),ex)
defer.returnValue(self.__createErrorResponse(request, 500, msg))
else:
defer.returnValue(Response(209,None))
def __createErrorResponse(self,request,code,message):
"""Common method for rendering errors"""
return Response(code=code, entity=message, headers={"content-type": MediaType.TEXT_PLAIN})
def __parseRequestData(self,request):
'''Automatically parses JSON,XML,YAML if present'''
if request.method in (Http.POST,Http.PUT) and HttpHeader.CONTENT_TYPE in request.received_headers.keys():
type = request.received_headers["content-type"]
if type == MediaType.APPLICATION_JSON:
try:
request.json = json.loads(request.content.read())
except Exception as ex:
raise TypeError("Unable to parse JSON body: %s" % ex)
elif type in (MediaType.APPLICATION_XML,MediaType.TEXT_XML):
try:
request.xml = ElementTree.XML(request.content.read())
except Exception as ex:
raise TypeError("Unable to parse XML body: %s" % ex)
elif type == MediaType.TEXT_YAML:
try:
request.yaml = yaml.safe_load(request.content.read())
except Exception as ex:
raise TypeError("Unable to parse YAML body: %s" % ex)
...@@ -186,7 +186,7 @@ total: 4443.52 ...@@ -186,7 +186,7 @@ total: 4443.52
| Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet | Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet
| - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 | | - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 |
@json @yaml @xml @return_accept_deferred @json @yaml @xml @return_accept_deferred @tmp
Scenario Outline: Return content type based on caller's Accept from Deferred methods Scenario Outline: Return content type based on caller's Accept from Deferred methods
When I prepare HTTP header 'Accept' = '<accept>' When I prepare HTTP header 'Accept' = '<accept>'
When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/deferred' When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/deferred'
...@@ -196,10 +196,10 @@ total: 4443.52 ...@@ -196,10 +196,10 @@ total: 4443.52
Examples: Examples:
| content | accept | code | | content | accept | code |
| [{"test1": "Test1"}, {"test2": "Test2"}] | application/json | 200 | | [{"test1": "Test1"}, {"test2": "Test2"}] | application/json | 200 |
| Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet #| Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet
| - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 | #| - {test1: Test1}\n- {test2: Test2} | text/yaml | 200 |
@json @yaml @xml @return_accept @json @yaml @xml @return_accept
Scenario Outline: Return class content type based on caller's Accept Scenario Outline: Return class content type based on caller's Accept
When I prepare HTTP header 'Accept' = '<accept>' When I prepare HTTP header 'Accept' = '<accept>'
When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/class' When as user 'None:None' I GET 'http://127.0.0.1:8080/return/by/accept/class'
...@@ -208,6 +208,6 @@ total: 4443.52 ...@@ -208,6 +208,6 @@ total: 4443.52
Examples: Examples:
| content | accept | code | | content | accept | code |
| is not JSON serializable | application/json | 500 | # not supported yet | [{}, {}] | application/json | 200 | # not supported yet
| Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet | Unable to convert String response to XML automatically | application/xml | 500 | # not supported yet
\ No newline at end of file
...@@ -7,6 +7,7 @@ from corepost.web import CorePost, route ...@@ -7,6 +7,7 @@ from corepost.web import CorePost, route
from corepost.enums import Http, MediaType, HttpHeader from corepost.enums import Http, MediaType, HttpHeader
from twisted.internet import defer from twisted.internet import defer
from xml.etree import ElementTree from xml.etree import ElementTree
from UserDict import UserDict
import json, yaml import json, yaml
class HomeApp(CorePost): class HomeApp(CorePost):
...@@ -86,18 +87,20 @@ class HomeApp(CorePost): ...@@ -86,18 +87,20 @@ class HomeApp(CorePost):
def test_return_content_by_accept_deferred(self,request,**kwargs): def test_return_content_by_accept_deferred(self,request,**kwargs):
"""Ensure support for inline callbacks and deferred""" """Ensure support for inline callbacks and deferred"""
val = yield [{"test1":"Test1"},{"test2":"Test2"}] val = yield [{"test1":"Test1"},{"test2":"Test2"}]
defer.returnValue(val) defer.returnValue(val)
@route("/return/by/accept/class") @route("/return/by/accept/class")
def test_return_class_content_by_accepts(self,request,**kwargs): def test_return_class_content_by_accepts(self,request,**kwargs):
"""Uses Python class instead of dict/list""" """Uses Python class instead of dict/list"""
class Test: pass class Test(UserDict,dict):
pass
t1 = Test() t1 = Test()
t1.test1 = "Test1" t1.test1="Test1"
t2 = Test() t2 = Test()
t2.test2 = "Test2" t2.test2="Test2"
val = [t1,t2] val = [t1,t2]
return val return (t1,t2)
......
...@@ -3,6 +3,7 @@ Various CorePost utilities ...@@ -3,6 +3,7 @@ Various CorePost utilities
''' '''
from inspect import getargspec from inspect import getargspec
import json import json
from corepost.enums import MediaType
def getMandatoryArgumentNames(f): def getMandatoryArgumentNames(f):
'''Returns a tuple of the mandatory arguments required in a function''' '''Returns a tuple of the mandatory arguments required in a function'''
...@@ -22,3 +23,10 @@ def convertToJson(obj): ...@@ -22,3 +23,10 @@ def convertToJson(obj):
return json.dumps(obj) return json.dumps(obj)
except Exception as ex: except Exception as ex:
raise RuntimeError(str(ex)) raise RuntimeError(str(ex))
def applyResponse(self,request,code,headers={"content-type":MediaType.TEXT_PLAIN}):
"""Applies response to current request"""
request.setResponseCode(code)
if headers != None:
for header,value in headers.iteritems():
request.setHeader(header, value)
...@@ -4,8 +4,10 @@ Main server classes ...@@ -4,8 +4,10 @@ Main server classes
@author: jacekf @author: jacekf
''' '''
from collections import defaultdict from collections import defaultdict
from corepost import Response
from corepost.enums import Http, HttpHeader from corepost.enums import Http, HttpHeader
from corepost.utils import getMandatoryArgumentNames, convertToJson from corepost.utils import getMandatoryArgumentNames, convertToJson
from corepost.routing import UrlRouter, CachedUrl, RequestRouter
from enums import MediaType from enums import MediaType
from formencode import FancyValidator, Invalid from formencode import FancyValidator, Invalid
from twisted.internet import reactor, defer from twisted.internet import reactor, defer
...@@ -15,105 +17,7 @@ from twisted.web.server import Site, NOT_DONE_YET ...@@ -15,105 +17,7 @@ from twisted.web.server import Site, NOT_DONE_YET
import re, copy, exceptions, json, yaml import re, copy, exceptions, json, yaml
from xml.etree import ElementTree from xml.etree import ElementTree
from xml.etree.ElementTree import Element from xml.etree.ElementTree import Element
from twisted.internet.defer import Deferred
class RequestRouter:
''' Common class for containing info related to routing a request to a function '''
__urlMatcher = re.compile(r"<(int|float|):?([a-zA-Z0-9]+)>")
__urlRegexReplace = {"":r"(?P<arg>.+)","int":r"(?P<arg>\d+)","float":r"(?P<arg>\d+.?\d*)"}
__typeConverters = {"int":int,"float":float}
def __init__(self,f,url,methods,accepts,produces,cache):
self.__url = url
self.__methods = methods if isinstance(methods,tuple) else (methods,)
self.__accepts = accepts if isinstance(accepts,tuple) else (accepts,)
self.__produces = produces
self.__cache = cache
self.__f = f
self.__argConverters = {} # dict of arg names -> group index
self.__validators = {}
self.__mandatory = getMandatoryArgumentNames(f)[2:]
#parse URL into regex used for matching
m = RequestRouter.__urlMatcher.findall(url)
self.__matchUrl = "^%s$" % url
for match in m:
if len(match[0]) == 0:
# string
self.__argConverters[match[1]] = None
self.__matchUrl = self.__matchUrl.replace("<%s>" % match[1],
RequestRouter.__urlRegexReplace[match[0]].replace("arg",match[1]))
else:
# non string
self.__argConverters[match[1]] = RequestRouter.__typeConverters[match[0]]
self.__matchUrl = self.__matchUrl.replace("<%s:%s>" % match,
RequestRouter.__urlRegexReplace[match[0]].replace("arg",match[1]))
self.__matcher = re.compile(self.__matchUrl)
@property
def cache(self):
'''Indicates if this URL should be cached or not'''
return self.__cache
@property
def methods(self):
return self.__methods
@property
def url(self):
return self.__url
@property
def accepts(self):
return self.__accepts
def addValidator(self,fieldName,validator):
'''Adds additional field-specific formencode validators'''
self.__validators[fieldName] = validator
def getArguments(self,url):
'''
Returns None if nothing matched (i.e. URL does not match), empty dict if no args found (i,e, static URL)
or dict with arg/values for dynamic URLs
'''
g = self.__matcher.search(url)
if g != None:
args = g.groupdict()
# convert to expected datatypes
if len(args) > 0:
for name in args.keys():
converter = self.__argConverters[name]
if converter != None:
args[name] = converter(args[name])
return args
else:
return None
def call(self,instance,request,**kwargs):
'''Forwards call to underlying method'''
for arg in self.__mandatory:
if arg not in kwargs:
raise TypeError("Missing mandatory argument '%s'" % arg)
return self.__f(instance,request,**kwargs)
class CachedUrl():
'''
Used for caching URLs that have been already routed once before. Avoids the overhead
of regex processing on every incoming call for commonly accessed REST URLs
'''
def __init__(self,router,args):
self.__router = router
self.__args = args
@property
def router(self):
return self.__router
@property
def args(self):
return self.__args
class CorePost(Resource): class CorePost(Resource):
''' '''
...@@ -125,30 +29,8 @@ class CorePost(Resource): ...@@ -125,30 +29,8 @@ class CorePost(Resource):
''' '''
Constructor Constructor
''' '''
self.__router = RequestRouter(self,schema)
Resource.__init__(self) Resource.__init__(self)
self.__urls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)}
self.__cachedUrls = {Http.GET: defaultdict(dict),Http.POST: defaultdict(dict),Http.PUT: defaultdict(dict),Http.DELETE: defaultdict(dict)}
self.__routers = {}
self.__schema = schema
self.__registerRouters()
@property
def path(self):
return self.__path
def __registerRouters(self):
from types import FunctionType
for _,func in self.__class__.__dict__.iteritems():
if type(func) == FunctionType and hasattr(func,'corepostRequestRouter'):
rq = func.corepostRequestRouter
for method in rq.methods:
for accepts in rq.accepts:
self.__urls[method][rq.url][accepts] = rq
self.__routers[func] = rq # needed so that we can lookup the router for a specific function
def route(self,url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True):
'''Obsolete'''
raise RuntimeError("Do not @app.route() any more, as of 0.0.6 API has been re-designed around class methods, see docs and examples")
def render_GET(self,request): def render_GET(self,request):
""" Handles all GET requests """ """ Handles all GET requests """
...@@ -167,177 +49,31 @@ class CorePost(Resource): ...@@ -167,177 +49,31 @@ class CorePost(Resource):
return self.__renderUrl(request) return self.__renderUrl(request)
def __renderUrl(self,request): def __renderUrl(self,request):
"""Finds the appropriate router and dispatches the request to the registered function"""
# see if already cached
try: try:
path = '/' + '/'.join(request.postpath) val = self.__router.getResponse(request)
contentType = MediaType.WILDCARD if HttpHeader.CONTENT_TYPE not in request.received_headers else request.received_headers[HttpHeader.CONTENT_TYPE] val.addCallback(self.__finishRequest,request)
return NOT_DONE_YET
urlrouter, pathargs = None, None
# fetch URL arguments <-> function from cache if hit at least once before
if contentType in self.__cachedUrls[request.method][path]:
cachedUrl = self.__cachedUrls[request.method][path][contentType]
urlrouter,pathargs = cachedUrl.router, cachedUrl.args
else:
# first time this URL is called
router = None
for contentTypeFunctions in self.__urls[request.method].values():
if contentType in contentTypeFunctions:
# there is an exact function for this incoming content type
router = contentTypeFunctions[contentType]
elif MediaType.WILDCARD in contentTypeFunctions:
# fall back to any wildcard method
router = contentTypeFunctions[MediaType.WILDCARD]
if router != None:
# see if the path arguments match up against any function @route definition
args = router.getArguments(path)
if args != None:
if router.cache:
self.__cachedUrls[request.method][path][contentType] = CachedUrl(router, args)
urlrouter,pathargs = router,args
break
#actual call
if urlrouter != None and pathargs != None:
allargs = copy.deepcopy(pathargs)
# handler for weird Twisted logic where PUT does not get form params
# see: http://twistedmatrix.com/pipermail/twisted-web/2007-March/003338.html
requestargs = request.args
if request.method == Http.PUT and HttpHeader.CONTENT_TYPE in request.received_headers.keys() \
and request.received_headers[HttpHeader.CONTENT_TYPE] == MediaType.APPLICATION_FORM_URLENCODED:
requestargs = parse_qs(request.content.read(), 1)
#merge form args
for arg in requestargs.keys():
# maintain first instance of an argument always
if arg not in allargs:
allargs[arg] = requestargs[arg][0]
try:
# if POST/PUT, check if we need to automatically parse JSON
self.__parseRequestData(request)
val = urlrouter.call(self,request,**allargs)
#handle Deferreds natively
if isinstance(val,defer.Deferred):
# add callback to finish the request
val.addCallback(self.__finishDeferred,request)
return NOT_DONE_YET
else:
#special logic for POST to return 201 (created)
if request.method == Http.POST:
if hasattr(request, 'code'):
if request.code == 200:
request.setResponseCode(201)
else:
request.setResponseCode(201)
return self.__renderResponse(request, val)
except exceptions.TypeError as ex:
return self.__renderError(request,400,"%s" % ex)
except Exception as ex:
return self.__renderError(request,500,"Unexpected server error: %s\n%s" % (type(ex),ex))
else:
return self.__renderError(request,404,"URL '%s' not found\n" % request.path)
except Exception as ex: except Exception as ex:
return self.__renderError(request,500,"Internal server error: %s" % ex) self.__applyResponse(request, 500, None)
return str(ex)
def __renderResponse(self,request,response):
""" def __finishRequest(self,response,request):
Takes care of automatically rendering the response and converting it to appropriate format (text,XML,JSON,YAML) self.__applyResponse(request, response.code,response.headers)
depending on what the caller can accept request.write(response.entity)
""" request.finish()
if isinstance(response, str):
return response def __applyResponse(self,request,code,headers={"content-type":MediaType.TEXT_PLAIN}):
elif isinstance(response, Response):
# TODO
return "TODO: Response"
else:
return self.__convertObjectToContentType(request, response)
def __convertObjectToContentType(self,request,obj):
"""Takes care of converting an object (non-String) response to the appropriate format, based on the what the caller can accept"""
if HttpHeader.ACCEPT in request.received_headers:
accept = request.received_headers[HttpHeader.ACCEPT]
if MediaType.APPLICATION_JSON in accept:
request.headers[HttpHeader.CONTENT_TYPE] = MediaType.APPLICATION_JSON
return convertToJson(obj)
elif MediaType.TEXT_YAML in accept:
request.headers[HttpHeader.CONTENT_TYPE] = MediaType.TEXT_YAML
return yaml.dump(obj)
elif MediaType.APPLICATION_XML in accept or MediaType.TEXT_XML in accept:
if isinstance(obj,Element):
request.headers[HttpHeader.CONTENT_TYPE] = MediaType.APPLICATION_XML
return ElementTree.tostring(obj, encoding='utf-8')
else:
raise RuntimeError("Unable to convert String response to XML automatically")
else:
# no idea, let's do JSON
request.headers[HttpHeader.CONTENT_TYPE] = MediaType.APPLICATION_JSON
return convertToJson(obj)
else:
# called has no accept header, let's default to JSON
request.headers[HttpHeader.CONTENT_TYPE] = MediaType.APPLICATION_JSON
return convertToJson(obj)
def __finishDeferred(self,val,request):
"""Finishes any Defered/inlineCallback methods"""
if not request.finished:
if val != None:
try:
request.write(self.__renderResponse(request,val))
except Exception as ex:
msg = "Unexpected server error: %s\n%s" % (type(ex),ex)
self.__renderError(request, 500, msg)
request.write(msg)
request.finish()
def __renderError(self,request,code,message):
"""Common method for rendering errors"""
request.setResponseCode(code) request.setResponseCode(code)
request.setHeader("content-type", MediaType.TEXT_PLAIN) if headers != None:
return message for header,value in headers.iteritems():
request.setHeader(header, value)
def __parseRequestData(self,request):
'''Automatically parses JSON,XML,YAML if present'''
if request.method in (Http.POST,Http.PUT) and HttpHeader.CONTENT_TYPE in request.received_headers.keys():
type = request.received_headers["content-type"]
if type == MediaType.APPLICATION_JSON:
try:
request.json = json.loads(request.content.read())
except Exception as ex:
raise TypeError("Unable to parse JSON body: %s" % ex)
elif type in (MediaType.APPLICATION_XML,MediaType.TEXT_XML):
try:
request.xml = ElementTree.XML(request.content.read())
except Exception as ex:
raise TypeError("Unable to parse XML body: %s" % ex)
elif type == MediaType.TEXT_YAML:
try:
request.yaml = yaml.safe_load(request.content.read())
except Exception as ex:
raise TypeError("Unable to parse YAML body: %s" % ex)
def run(self,port=8080): def run(self,port=8080):
"""Shortcut for running app within Twisted reactor""" """Shortcut for running app within Twisted reactor"""
factory = Site(self) factory = Site(self)
reactor.listenTCP(port, factory) #@UndefinedVariable reactor.listenTCP(port, factory) #@UndefinedVariable
reactor.run() #@UndefinedVariable reactor.run() #@UndefinedVariable
class Response:
"""
Custom response object, can be returned instead of raw string response
"""
def __init__(self,code=200,entity=None,headers={}):
self.code = 200
self.entity=entity
self.headers=headers
################################################################################################## ##################################################################################################
# #
...@@ -352,12 +88,11 @@ def route(url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache ...@@ -352,12 +88,11 @@ def route(url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache
def decorator(f): def decorator(f):
def wrap(*args,**kwargs): def wrap(*args,**kwargs):
return f return f
router = RequestRouter(f, url, methods, accepts, produces, cache) router = UrlRouter(f, url, methods, accepts, produces, cache)
setattr(wrap,'corepostRequestRouter',router) setattr(wrap,'corepostRequestRouter',router)
return wrap return wrap
return decorator return decorator
def validate(schema=None,**vKwargs): def validate(schema=None,**vKwargs):
''' '''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment