From 13162ac35710b3dabf76c8a85da0f3165ebc9298 Mon Sep 17 00:00:00 2001 From: Nuwan Goonasekera Date: Thu, 23 Aug 2012 14:22:13 +1000 Subject: [PATCH 1/2] Modified REST API to support multipart/form-data requests, to enable large file upload. --- lib/galaxy/tools/actions/upload_common.py | 2 +- lib/galaxy/web/framework/__init__.py | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/tools/actions/upload_common.py b/lib/galaxy/tools/actions/upload_common.py index 3af3b64eaee..ab0f76b38ca 100644 --- a/lib/galaxy/tools/actions/upload_common.py +++ b/lib/galaxy/tools/actions/upload_common.py @@ -28,7 +28,7 @@ def persist_uploads( params ): local_filename = local_filename ) elif type( f ) == dict and 'filename' and 'local_filename' not in f: raise Exception( 'Uploaded file was encoded in a way not understood by Galaxy.' ) - if upload_dataset['url_paste'].strip() != '': + if upload_dataset['url_paste'] and upload_dataset['url_paste'].strip() != '': upload_dataset['url_paste'], is_multi_byte = datatypes.sniff.stream_to_file( StringIO.StringIO( upload_dataset['url_paste'] ), prefix="strio_url_paste_" ) else: upload_dataset['url_paste'] = None diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 3cb0a147343..0537deb7327 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -5,6 +5,7 @@ Galaxy web application framework import pkg_resources import os, sys, time, socket, random, string +import inspect pkg_resources.require( "Cheetah" ) from Cheetah.Template import Template import base @@ -125,9 +126,25 @@ def expose_api( func ): return error trans.set_user( provided_key.user ) if trans.request.body: - try: - payload = util.recursively_stringify_dictionary_keys( simplejson.loads( trans.request.body ) ) - kwargs['payload'] = payload + def extract_payload_from_request(trans, func, kwargs): + content_type = trans.request.headers['content-type'] + if content_type.startswith('multipart/form-data'): + # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body + # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which + # are not a part of the request body. This is a problem because it's not possible to differentiate between values + # which are a part of the request body, and therefore should be a part of the payload, and values which should not be + # in the payload. Therefore, the decorated method's formal arguments are discovered through reflection and removed from + # the payload dictionary. This helps to prevent duplicate argument conflicts in downstream methods. + payload = kwargs.copy() + named_args, _, _, _ = inspect.getargspec(func) + for arg in named_args: + payload.pop(arg, None) + else: + # When it's a non standard request body (e.g. json), the wsgi framework does not parse the request body. So do so manually. + payload = util.recursively_stringify_dictionary_keys( simplejson.loads( trans.request.body ) ) + return payload + try: + kwargs['payload'] = extract_payload_from_request(trans, func, kwargs) except ValueError: error_status = '400 Bad Request' error_message = 'Your request did not appear to be valid JSON, please consult the API documentation' From e794921a3415222e656522f28f602a6b1c3b7edd Mon Sep 17 00:00:00 2001 From: Nuwan Goonasekera Date: Thu, 23 Aug 2012 15:09:20 +1000 Subject: [PATCH 2/2] Also added support for x-www-form-urlencoded REST API requests, for consistencies sake. This way, API clients can post as either JSON, multi-part form data, or standard url form encoding. --- lib/galaxy/web/framework/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/web/framework/__init__.py b/lib/galaxy/web/framework/__init__.py index 0537deb7327..1cdbb85e0d3 100644 --- a/lib/galaxy/web/framework/__init__.py +++ b/lib/galaxy/web/framework/__init__.py @@ -128,7 +128,7 @@ def expose_api( func ): if trans.request.body: def extract_payload_from_request(trans, func, kwargs): content_type = trans.request.headers['content-type'] - if content_type.startswith('multipart/form-data'): + if content_type.startswith('application/x-www-form-urlencoded') or content_type.startswith('multipart/form-data'): # If the content type is a standard type such as multipart/form-data, the wsgi framework parses the request body # and loads all field values into kwargs. However, kwargs also contains formal method parameters etc. which # are not a part of the request body. This is a problem because it's not possible to differentiate between values @@ -140,7 +140,9 @@ def expose_api( func ): for arg in named_args: payload.pop(arg, None) else: - # When it's a non standard request body (e.g. json), the wsgi framework does not parse the request body. So do so manually. + # Assume application/json content type and parse request body manually, since wsgi won't do it. However, the order of this check + # should ideally be in reverse, with the if clause being a check for application/json and the else clause assuming a standard encoding + # such as multipart/form-data. Leaving it as is for backward compatibility, just in case. payload = util.recursively_stringify_dictionary_keys( simplejson.loads( trans.request.body ) ) return payload try: