Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
C
corepost
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
nexlab
corepost
Commits
29c6715b
Commit
29c6715b
authored
Sep 20, 2011
by
Jacek Furmankiewicz
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
routing by incoming content type works
parent
89c4bf7c
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
139 additions
and
73 deletions
+139
-73
arguments.feature
corepost/test/feature/arguments.feature
+2
-2
content_types.feature
corepost/test/feature/content_types.feature
+23
-7
home_resource.py
corepost/test/home_resource.py
+20
-1
steps.py
corepost/test/steps.py
+6
-2
utils.py
corepost/utils.py
+4
-1
web.py
corepost/web.py
+84
-60
No files found.
corepost/test/feature/arguments.feature
View file @
29c6715b
...
...
@@ -5,7 +5,7 @@ Feature: Arguments
CorePost should be able to correctly extract arguments
from paths, query arguments and form arguments
@
path_arguments
@
arguments_ok
Scenario Outline
:
Path argument extraction
Given 'arguments' is running
When as user 'None
:
None' I GET 'http
:
//127.0.0.1
:
8082<url>'
...
...
@@ -18,7 +18,7 @@ Feature: Arguments
|
/int/1/float/1/string/TEST
|
200
|
[(<type
'int'>,
1),
(<type
'float'>,
1.0),
(<type
'str'>,
'TEST')]
|
|
/int/1/float/1/string/23
|
200
|
[(<type
'int'>,
1),
(<type
'float'>,
1.0),
(<type
'str'>,
'23')]
|
@
path_arguments
@
arguments_error
Scenario Outline
:
Path argument extraction - error handling
Given 'arguments' is running
When as user 'None
:
None' I GET 'http
:
//127.0.0.1
:
8082<url>'
...
...
corepost/test/feature/content_types.feature
View file @
29c6715b
...
...
@@ -6,9 +6,11 @@ Feature: Content types
correctly parse/generate
JSON/XML/YAML based on content types
Background
:
Given 'home_resource' is running
@json
Scenario Outline
:
Parse incoming JSON data
Given 'home_resource' is running
When as user 'None
:
None' I <method> 'http
:
//127.0.0.1
:
8080/post/json'
with
JSON
"""
{"test":"test2"}
...
...
@@ -26,7 +28,6 @@ Feature: Content types
@json
Scenario Outline
:
Handle invalid incoming JSON data
Given 'home_resource' is running
When as user 'None
:
None' I <method> 'http
:
//127.0.0.1
:
8080/post/json'
with
JSON
"""
wrong_json
...
...
@@ -41,7 +42,6 @@ Feature: Content types
@xml
Scenario Outline
:
Parse incoming XML data
Given 'home_resource' is running
When as user 'None
:
None' I <method> 'http
:
//127.0.0.1
:
8080/post/xml'
with
XML
"""
<root><test>TEST</test><test2>Yo</test2></root>
...
...
@@ -57,7 +57,6 @@ Feature: Content types
@xml
Scenario Outline
:
Handle invalid XML data
Given 'home_resource' is running
When as user 'None
:
None' I <method> 'http
:
//127.0.0.1
:
8080/post/xml'
with
XML
"""
wrong xml
...
...
@@ -73,7 +72,6 @@ Feature: Content types
@yaml
Scenario Outline
:
Parse incoming YAML data
Given 'home_resource' is running
When as user 'None
:
None' I <method> 'http
:
//127.0.0.1
:
8080/post/yaml'
with
YAML
"""
invoice: 34843
...
...
@@ -144,7 +142,6 @@ total: 4443.52
@yaml
Scenario Outline
:
Handle invalid YAML data
Given 'home_resource' is running
When as user 'None
:
None' I <method> 'http
:
//127.0.0.1
:
8080/post/yaml'
with
YAML
"""
- test
...
...
@@ -156,4 +153,23 @@ total: 4443.52
Examples
:
|
method
|
|
POST
|
|
PUT
|
\ No newline at end of file
|
PUT
|
@json
@yaml
@xml
@route_content_type
Scenario Outline
:
Route by incoming content type
When
I prepare HTTP header 'content-type' = '<content>'
When as user 'None
:
None' I <method> 'http
:
//127.0.0.1
:
8080/post/by/content'
with
<type>
body
'<body>'
Then
I expect HTTP code
<code>
And
I expect content contains '<content>'
Examples
:
|
method
|
type
|
body
|
content
|
code
|
|
POST
|
JSON
|
{"test":2}
|
application/json
|
201
|
|
POST
|
XML
|
<test>1</test>
|
application/xml
|
201
|
|
POST
|
XML
|
<test>1</test>
|
text/xml
|
201
|
|
POST
|
YAML
|
test:
2
|
text/yaml
|
201
|
|
PUT
|
JSON
|
{"test":2}
|
application/json
|
200
|
|
PUT
|
XML
|
<test>1</test>
|
text/xml
|
200
|
|
PUT
|
XML
|
<test>1</test>
|
application/xml
|
200
|
|
PUT
|
YAML
|
test:
2
|
text/yaml
|
200
|
\ No newline at end of file
corepost/test/home_resource.py
View file @
29c6715b
...
...
@@ -4,7 +4,7 @@ Server tests
'''
from
corepost.web
import
CorePost
,
route
from
corepost.enums
import
Http
from
corepost.enums
import
Http
,
MediaType
,
HttpHeader
from
twisted.internet
import
defer
from
xml.etree
import
ElementTree
import
json
,
yaml
...
...
@@ -54,6 +54,25 @@ class HomeApp(CorePost):
def
test_yaml
(
self
,
request
,
**
kwargs
):
return
"
%
s"
%
yaml
.
dump
(
request
.
yaml
,
indent
=
4
,
width
=
130
,
default_flow_style
=
False
)
##################################################################
# same URLs, routed by incoming content type
###################################################################
@
route
(
"/post/by/content"
,(
Http
.
POST
,
Http
.
PUT
),
MediaType
.
APPLICATION_JSON
)
def
test_content_app_json
(
self
,
request
,
**
kwargs
):
return
request
.
received_headers
[
HttpHeader
.
CONTENT_TYPE
]
@
route
(
"/post/by/content"
,(
Http
.
POST
,
Http
.
PUT
),(
MediaType
.
TEXT_XML
,
MediaType
.
APPLICATION_XML
))
def
test_content_xml
(
self
,
request
,
**
kwargs
):
return
request
.
received_headers
[
HttpHeader
.
CONTENT_TYPE
]
@
route
(
"/post/by/content"
,(
Http
.
POST
,
Http
.
PUT
),
MediaType
.
TEXT_YAML
)
def
test_content_yaml
(
self
,
request
,
**
kwargs
):
return
request
.
received_headers
[
HttpHeader
.
CONTENT_TYPE
]
@
route
(
"/post/by/content"
,(
Http
.
POST
,
Http
.
PUT
))
def
test_content_catch_all
(
self
,
request
,
**
kwargs
):
return
MediaType
.
WILDCARD
def
run_app_home
():
app
=
HomeApp
()
...
...
corepost/test/steps.py
View file @
29c6715b
...
...
@@ -68,8 +68,12 @@ def when_as_user_i_send_post_put_to_url(user,password,method,url,params):
scc
.
http_headers
[
'Content-type'
]
=
'application/x-www-form-urlencoded'
scc
.
response
,
scc
.
content
=
h
.
request
(
url
,
method
,
urlencode
(
as_dict
(
params
)),
headers
=
scc
.
http_headers
)
@
When
(
r"^as user '(.+):(.+)' I (POST|PUT) '(.+)' with (XML|JSON|YAML) body '(.+)'\s*$"
)
def
when_as_user_i_send_post_put_xml_json_to_url
(
user
,
password
,
method
,
url
,
request_type
,
body
):
when_as_user_i_send_post_put_xml_json_to_url_multiline
(
body
,
user
,
password
,
method
,
url
,
request_type
)
@
When
(
r"^as user '(.+):(.+)' I (POST|PUT) '(.+)' with (XML|JSON|YAML)\s*$"
)
def
when_as_user_i_send_post_put_xml_json_to_url
(
payload
,
user
,
password
,
method
,
url
,
request_type
):
def
when_as_user_i_send_post_put_xml_json_to_url
_multiline
(
body
,
user
,
password
,
method
,
url
,
request_type
):
h
=
httplib2
.
Http
()
h
.
follow_redirects
=
False
h
.
add_credentials
(
user
,
password
)
...
...
@@ -79,7 +83,7 @@ def when_as_user_i_send_post_put_xml_json_to_url(payload,user,password,method,ur
scc
.
http_headers
[
'Content-type'
]
=
'text/xml'
elif
request_type
==
"YAML"
:
scc
.
http_headers
[
'Content-type'
]
=
'text/yaml'
scc
.
response
,
scc
.
content
=
h
.
request
(
url
,
method
,
payload
,
headers
=
scc
.
http_headers
)
scc
.
response
,
scc
.
content
=
h
.
request
(
url
,
method
,
body
,
headers
=
scc
.
http_headers
)
@
When
(
"I prepare HTTP header '(.*)' = '(.*)'"
)
def
when_i_define_http_header_with_value
(
header
,
value
):
...
...
corepost/utils.py
View file @
29c6715b
...
...
@@ -10,4 +10,7 @@ def getMandatoryArgumentNames(f):
return
args
else
:
return
args
[
0
:
len
(
args
)
-
len
(
defaults
)]
\ No newline at end of file
def
getRouterKey
(
method
,
url
):
'''Returns the common key used to represent a function that a request can be routed to'''
return
"
%
s
%
s"
%
(
method
,
url
)
\ No newline at end of file
corepost/web.py
View file @
29c6715b
...
...
@@ -27,7 +27,7 @@ class RequestRouter:
def
__init__
(
self
,
f
,
url
,
methods
,
accepts
,
produces
,
cache
):
self
.
__url
=
url
self
.
__methods
=
methods
if
isinstance
(
methods
,
tuple
)
else
(
methods
,)
self
.
__accepts
=
accepts
self
.
__accepts
=
accepts
if
isinstance
(
accepts
,
tuple
)
else
(
accepts
,)
self
.
__produces
=
produces
self
.
__cache
=
cache
self
.
__f
=
f
...
...
@@ -66,6 +66,10 @@ class RequestRouter:
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
...
...
@@ -123,8 +127,8 @@ class CorePost(Resource):
Constructor
'''
Resource
.
__init__
(
self
)
self
.
__urls
=
defaultdict
(
dict
)
self
.
__cachedUrls
=
defaultdict
(
dict
)
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
()
...
...
@@ -139,8 +143,9 @@ class CorePost(Resource):
if
type
(
func
)
==
FunctionType
and
hasattr
(
func
,
'corepostRequestRouter'
):
rq
=
func
.
corepostRequestRouter
for
method
in
rq
.
methods
:
self
.
__urls
[
method
][
rq
.
url
]
=
rq
self
.
__routers
[
func
]
=
rq
# needed so that we can lookup the router for a specific function
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'''
...
...
@@ -165,64 +170,83 @@ class CorePost(Resource):
def
__renderUrl
(
self
,
request
):
"""Finds the appropriate router and dispatches the request to the registered function"""
# see if already cached
path
=
'/'
+
'/'
.
join
(
request
.
postpath
)
urlrouter
,
pathargs
=
None
,
None
if
path
in
self
.
__cachedUrls
[
request
.
method
]:
cachedUrl
=
self
.
__cachedUrls
[
request
.
method
][
path
]
urlrouter
,
pathargs
=
cachedUrl
.
router
,
cachedUrl
.
args
else
:
# first time this URL is called
for
router
in
self
.
__urls
[
request
.
method
]
.
values
():
args
=
router
.
getArguments
(
path
)
if
args
!=
None
:
if
router
.
cache
:
self
.
__cachedUrls
[
request
.
method
][
path
]
=
CachedUrl
(
router
,
args
)
urlrouter
,
pathargs
=
router
,
args
try
:
path
=
'/'
+
'/'
.
join
(
request
.
postpath
)
contentType
=
MediaType
.
WILDCARD
if
HttpHeader
.
CONTENT_TYPE
not
in
request
.
received_headers
else
request
.
received_headers
[
HttpHeader
.
CONTENT_TYPE
]
#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
)
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
#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
):
# we assume the method will call request.finish()
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
:
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
):
# we assume the method will call request.finish()
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
)
else
:
request
.
setResponseCode
(
201
)
return
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
)
return
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
:
return
self
.
__renderError
(
request
,
500
,
"Internal server error:
%
s"
%
ex
)
def
__renderError
(
self
,
request
,
code
,
message
):
"""Common method for rendering errors"""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment