Permalink
Browse files

finish protocol operation implementations for pylon (still no multipa…

…rt support), plus more logging. Have also duplicated any useful logging changes from pylons into the webpy impl

git-svn-id: http://sword-app.svn.sourceforge.net/svnroot/sword-app/sss/branches/sss-2@448 2bf6ea0f-123d-0410-b71a-f1a21eb24612
  • Loading branch information...
1 parent ad9c3e5 commit c41f17d7b5da89fbf5a2613bca0595224f49eace richard-jones committed Jan 14, 2012
Showing with 260 additions and 9 deletions.
  1. +257 −8 sss/pylons_sword_controller.py
  2. +3 −1 sss/webpy.py
View
265 sss/pylons_sword_controller.py
@@ -61,7 +61,7 @@ def http_basic_authenticate(self):
ssslog.error("unable to interpret authentication header: " + auth_header)
raise SwordError(error_uri=Errors.bad_request, msg="unable to interpret authentication header")
- ssslog.info("Authentication details: " + str(username) + ":" + str(password) + "; On Behalf Of: " + str(obo))
+ ssslog.info("Authentication details: " + str(username) + ":[**password**]; On Behalf Of: " + str(obo))
authenticator = Authenticator(config)
try:
@@ -76,7 +76,7 @@ def http_basic_authenticate(self):
def manage_error(self, sword_error):
response.status_int = sword_error.status
- ssslog.info("Returning error status: " + str(sword_error.status))
+ ssslog.info("Returning error (" + str(sword_error.status) + ") - " + str(sword_error.error_uri))
if not sword_error.empty:
response.content_type = "text/xml"
return sword_error.error_document
@@ -129,7 +129,10 @@ def validate_deposit_request(self, entry_section=None, binary_section=None, mult
#if len(webin) != 2: # if it is not multipart
# FIXME: this is reading everything in, and should be re-evaluated for performance/scalability
wsgi_input = request.environ['wsgi.input']
- wsgi_input.seek(0, 0)
+ if hasattr(wsgi_input, "seek"):
+ # in empty requests, the wsgi input object doesn't have a seek() method
+ # so we have to check for it
+ wsgi_input.seek(0, 0)
if wsgi_input is None or wsgi_input.read().strip() == "": # FIXME: this IS NOT safe to scale
if allow_empty:
@@ -182,6 +185,7 @@ def get_deposit(self, auth=None, atom_only=False):
empty_request = False
if d.content_length == 0:
+ ssslog.info("Received empty deposit request")
empty_request = True
if d.content_length > config.max_upload_size:
raise SwordError(error_uri=Errors.max_upload_size_exceeded,
@@ -259,6 +263,7 @@ def service_document(self, sub_path=None):
if http_method == "GET":
return self._GET_service_document(sub_path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
@@ -269,6 +274,7 @@ def collection(self, path=None):
elif http_method == "POST":
return self._POST_collection(path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
@@ -283,6 +289,7 @@ def media_resource(self, path=None):
elif http_method == "DELETE":
return self._DELETE_media_resource(path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
@@ -297,10 +304,18 @@ def container(self, path=None):
elif http_method == "DELETE":
return self._DELETE_container(path)
else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
abort(405, "Method Not Allowed")
return
- def statement(self, path=None): pass
+ def statement(self, path=None):
+ http_method = request.environ['REQUEST_METHOD']
+ if http_method == "GET":
+ return self._GET_statement(path)
+ else:
+ ssslog.info("Returning (405) Method Not Allowed; Received " + http_method + " request on " + __name__)
+ abort(405, "Method Not Allowed")
+ return
def aggregation(self, path=None): pass
def part(self, path=None): pass
@@ -326,6 +341,7 @@ def _GET_service_document(self, path=None):
ss = SwordServer(config, auth)
sd = ss.service_document(path)
response.content_type = "text/xml"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return sd
def _GET_collection(self, path=None):
@@ -347,6 +363,7 @@ def _GET_collection(self, path=None):
ss = SwordServer(config, auth)
cl = ss.list_collection(path)
response.content_type = "text/xml"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return cl
def _POST_collection(self, path=None):
@@ -381,9 +398,11 @@ def _POST_collection(self, path=None):
response.status = "201 Created"
if config.return_deposit_receipt:
ssslog.info("Returning deposit receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return result.receipt
else:
ssslog.info("Omitting deposit receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return
except SwordError as e:
@@ -407,8 +426,7 @@ def _GET_media_resource(self, path=None):
# 406 Not Acceptable without looking first to see if there is even any media to content negotiate for
# which would be weird from a client perspective
if not ss.media_resource_exists(path):
- abort(404)
- return
+ return self.manage_error(SwordError(status=404, empty=True))
# get the content negotiation headers
accept_header = request.environ.get("HTTP_ACCEPT")
@@ -438,6 +456,7 @@ def _GET_media_resource(self, path=None):
f = open(media_resource.filepath, "r")
response.status_int = 200
response.status = "200 OK"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return f.read()
def _PUT_media_resource(self, path=None):
@@ -474,11 +493,92 @@ def _PUT_media_resource(self, path=None):
ssslog.info("Content replaced")
response.status_int = 204
response.status = "204 No Content" # notice that this is different from the POST as per AtomPub
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return
except SwordError as e:
return self.manage_error(e)
+ def _POST_media_resource(self, path=None):
+ """
+ POST a simple package into the specified media resource
+ Args:
+ - id: The ID of the media resource as specified in the requested URL
+ Returns a Deposit Receipt
+ """
+ ssslog.debug("POST to Media Resource (add new file); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if update is allowed
+ if not config.allow_update:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
+ return self.manage_error(error)
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_deposit_request(None, "6.7.1", None, allow_multipart=False)
+
+ deposit = self.get_deposit(auth)
+
+ # if we get here authentication was successful and we carry on
+ ss = SwordServer(config, auth)
+ result = ss.add_content(path, deposit)
+
+ response.content_type = "application/atom+xml;type=entry"
+ response.headers["Location"] = result.location
+ response.status_int = 201
+ response.status = "201 Created"
+ if config.return_deposit_receipt:
+ ssslog.info("Returning Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return result.receipt
+ else:
+ ssslog.info("Omitting Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+
+ def _DELETE_media_resource(self, path=None):
+ """
+ DELETE the contents of an object in the store (but not the object's container), leaving behind an empty
+ container for further use
+ Args:
+ - id: the ID of the object to have its content removed as per the requested URI
+ Return a Deposit Receipt
+ """
+ ssslog.debug("DELETE on Media Resource (remove content, leave container); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if delete is allowed
+ if not config.allow_delete:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Delete operations not currently permitted")
+ return self.manage_error(error)
+
+ # authenticate
+ try:
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_delete_request("6.6")
+
+ # parse the delete request out of the HTTP request
+ delete = self.get_delete(auth)
+
+ # carry out the delete
+ ss = SwordServer(config, auth)
+ result = ss.delete_content(path, delete)
+
+ # just return, no need to give any more feedback
+ response.status_int = 204
+ response.status = "204 No Content" # No Content
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
def _GET_container(self, path=None):
"""
@@ -501,8 +601,7 @@ def _GET_container(self, path=None):
# 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
# which would be weird from a client perspective
if not ss.container_exists(path):
- abort(404)
- return
+ return self.manage_error(SwordError(status=404, empty=True))
# get the content negotiation headers
accept_header = request.environ.get("HTTP_ACCEPT")
@@ -520,8 +619,158 @@ def _GET_container(self, path=None):
# now actually get hold of the representation of the container and send it to the client
cont = ss.get_container(path, accept_parameters)
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
return cont
except SwordError as e:
return self.manage_error(e)
+
+ def _PUT_container(self, path=None):
+ """
+ PUT a new Entry over the existing entry, or a multipart request over
+ both the existing metadata and the existing content
+ """
+ ssslog.debug("PUT on Container (replace); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if update is allowed
+ if not config.allow_update:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
+ return self.manage_error(error)
+
+ try:
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_deposit_request("6.5.2", None, "6.5.3")
+
+ # get the deposit object
+ deposit = self.get_deposit(auth)
+
+ ss = SwordServer(config, auth)
+ result = ss.replace(path, deposit)
+
+ response.headers["Location"] = result.location
+ if config.return_deposit_receipt:
+ response.content_type = "application/atom+xml;type=entry"
+ response.status_int = 200
+ response.status = "200 OK"
+ ssslog.info("Returning Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return result.receipt
+ else:
+ response.status_int = 204
+ response.status = "204 No Content"
+ ssslog.info("Omitting Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+
+ def _POST_container(self, path=None):
+ """
+ POST some new content into the container identified by the supplied id,
+ or complete an existing deposit (using the In-Progress header)
+ Args:
+ - id: The ID of the container as contained in the URL
+ Returns a Deposit Receipt
+ """
+ ssslog.debug("POST to Container (add new content and metadata); Incoming HTTP headers: " + str(request.environ))
+
+ # find out if update is allowed
+ if not config.allow_update:
+ error = SwordError(error_uri=Errors.method_not_allowed, msg="Update operations not currently permitted")
+ return self.manage_error(error)
+
+ try:
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_deposit_request("6.7.2", None, "6.7.3", "9.3", allow_empty=True)
+
+ deposit = self.get_deposit(auth)
+
+ ss = SwordServer(config, auth)
+ result = ss.deposit_existing(path, deposit)
+
+ # NOTE: spec says 201 Created for multipart and 200 Ok for metadata only
+ # we have implemented 200 OK across the board, in the understanding that
+ # in this case the spec is incorrect (correction need to be implemented
+ # asap)
+
+ response.headers["Location"] = result.location
+ response.status_int = 200
+ response.status = "200 OK"
+ if config.return_deposit_receipt:
+ response.content_type = "application/atom+xml;type=entry"
+ ssslog.info("Returning Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return result.receipt
+ else:
+ ssslog.info("Omitting Deposit Receipt")
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+
+ def _DELETE_container(self, path=None):
+ """
+ DELETE the container (and everything in it) from the store, as identified by the supplied id
+ Args:
+ - id: the ID of the container
+ Returns nothing, as there is nothing to return (204 No Content)
+ """
+ ssslog.debug("DELETE on Container (remove); Incoming HTTP headers: " + str(request.environ))
+
+ try:
+ # find out if update is allowed
+ if not config.allow_delete:
+ raise SwordError(error_uri=Errors.method_not_allowed, msg="Delete operations not currently permitted")
+
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ # check the validity of the request
+ self.validate_delete_request("6.8")
+
+ # get the delete request
+ delete = self.get_delete(auth)
+
+ # do the delete
+ ss = SwordServer(config, auth)
+ result = ss.delete_container(path, delete)
+
+ # no need to return any content
+ response.status_int = 204
+ response.status = "204 No Content"
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return
+
+ except SwordError as e:
+ return self.manage_error(e)
+ def _GET_statement(self, path=None):
+ ssslog.debug("GET on Statement (retrieve); Incoming HTTP headers: " + str(request.environ))
+
+ try:
+ # authenticate
+ auth = self.http_basic_authenticate()
+
+ ss = SwordServer(config, auth)
+
+ # first thing we need to do is check that there is an object to return, because otherwise we may throw a
+ # 415 Unsupported Media Type without looking first to see if there is even any media to content negotiate for
+ # which would be weird from a client perspective
+ if not ss.container_exists(path):
+ raise SwordError(status=404, empty=True)
+
+ # now actually get hold of the representation of the statement and send it to the client
+ cont = ss.get_statement(path)
+ ssslog.info("Returning " + response.status + " from request on " + __name__)
+ return cont
+
+ except SwordError as e:
+ return self.manage_error(e)
View
4 sss/webpy.py
@@ -94,7 +94,7 @@ def http_basic_authenticate(self, web):
ssslog.error("unable to interpret authentication header: " + auth_header)
raise SwordError(error_uri=Errors.bad_request, msg="unable to interpret authentication header")
- ssslog.info("Authentication details: " + str(username) + ":" + str(password) + "; On Behalf Of: " + str(obo))
+ ssslog.info("Authentication details: " + str(username) + ":[**password**]; On Behalf Of: " + str(obo))
authenticator = Authenticator(config)
try:
@@ -109,6 +109,7 @@ def http_basic_authenticate(self, web):
def manage_error(self, sword_error):
status = STATUS_MAP.get(sword_error.status, "400 Bad Request")
+ ssslog.info("Returning error (" + str(sword_error.status) + ") - " + str(sword_error.error_uri))
web.ctx.status = status
if not sword_error.empty:
web.header("Content-Type", "text/xml")
@@ -204,6 +205,7 @@ def get_deposit(self, web, auth=None, atom_only=False):
empty_request = False
if d.content_length == 0:
+ ssslog.info("Received empty deposit request")
empty_request = True
if d.content_length > config.max_upload_size:
raise SwordError(error_uri=Errors.max_upload_size_exceeded,

0 comments on commit c41f17d

Please sign in to comment.