Browse files

Respond to Flash uploads with header Content-Type: application/json

Uploading files via Flash for Windows uses an Accept header of text/*
and there's no way to override it. @expose('json') insists on returning
a Content-Type of application/json.

TurboGears2 can't cope with returning a Content-Type that doesn't match
the Accept header. It doesn't throw a 403 Not Acceptable response, it
dies with a 500 error. This logic was buried in controller code so
extending wasn't a viable option. We decided to just return text/plain.

Pylons 0.10 happily responds with application/json despite it not
matching the Accept text/* header from Flash. To simplify things, we're
taking advantage of this flexibility and returning application/json even
though Flash didn't explicitly request it.

This works fine but if we want to ensure we're returning acceptable
headers at all times, we should improve the expose decorator rather than
handling it in every controller that uses a Flash uploader.

This also removes the need for the helper best_json_content_type, which
relied on a mimeparse function that was removed in Paste 1.7.3.
  • Loading branch information...
1 parent ccad0a5 commit 9bad4b9504cfb2f072afd025f954e7334793d4c1 @natedub natedub committed May 5, 2010
View
31 mediacore/controllers/admin/media.py
@@ -19,7 +19,6 @@
import os.path
import re
import shutil
-import simplejson as json
from PIL import Image
from datetime import datetime
from urlparse import urlparse, urlunparse
@@ -247,7 +246,7 @@ def save(self, id, slug, title, author_name, author_email,
redirect(action='edit', id=media.id)
- @expose()
+ @expose('json')
@validate(add_file_form)
def add_file(self, id, file=None, url=None, **kwargs):
"""Save action for the :class:`~mediacore.forms.admin.media.AddFileForm`.
@@ -279,24 +278,6 @@ def add_file(self, id, file=None, url=None, **kwargs):
for this file.
status_form
The rendered XHTML :class:`~mediacore.forms.admin.media.UpdateStatusForm`
- :raises webob.exc.HTTPNotAcceptable: If the Accept header won't
- work with application/json or text/plain.
-
- .. note::
-
- This method returns incorrect Content-Type headers under
- some circumstances. It should be ``application/json``, but
- sometimes ``text/plain`` is used instead.
-
- This is because this method is used from the flash based
- uploader; Swiff.Uploader (which we use) uses Flash's
- FileReference.upload() method, which doesn't allow
- overriding the default HTTP headers.
-
- On windows, the default Accept header is "text/\*". This
- means that it won't accept "application/json". Rather than
- throw a 406 Not Acceptable response, or worse, a 500 error,
- we've chosen to return an incorrect ``text/plain`` type.
"""
if id == 'new':
@@ -368,8 +349,7 @@ def add_file(self, id, file=None, url=None, **kwargs):
status_form = status_form_xhtml,
))
- response.headers['Content-Type'] = helpers.best_json_content_type()
- return json.dumps(data)
+ return data
@expose('json')
@@ -429,7 +409,7 @@ def edit_file(self, id, file_id, file_type=None, delete=None, **kwargs):
return data
- @expose()
+ @expose('json')
@validate(thumb_form, error_handler=edit)
def save_thumb(self, id, thumb, **kwargs):
"""Save a thumbnail uploaded with :class:`~mediacore.forms.admin.ThumbForm`.
@@ -487,12 +467,11 @@ def save_thumb(self, id, thumb, **kwargs):
success = False
message = e.message
- response.headers['Content-Type'] = helpers.best_json_content_type()
- return json.dumps(dict(
+ return dict(
success = success,
message = message,
id = media.id,
- ))
+ )
@expose('json')
View
24 mediacore/controllers/admin/podcasts.py
@@ -15,7 +15,6 @@
import os.path
import shutil
-import simplejson as json
import transaction
from pylons import config, request, response, session, tmpl_context
@@ -169,7 +168,7 @@ def save(self, id, slug, title, subtitle, author_name, author_email,
redirect(action='edit', id=podcast.id)
- @expose()
+ @expose('json')
@validate(thumb_form, error_handler=edit)
def save_thumb(self, id, thumb, **values):
"""Save a thumbnail uploaded with :class:`~mediacore.forms.admin.ThumbForm`.
@@ -189,22 +188,6 @@ def save_thumb(self, id, thumb, **values):
The :attr:`~mediacore.model.podcasts.Podcast.id` which is
important if a new podcast has just been created.
- .. note::
-
- This method returns incorrect Content-Type headers under
- some circumstances. It should be ``application/json``, but
- sometimes ``text/plain`` is used instead.
-
- This is because this method is used from the flash based
- uploader; Swiff.Uploader (which we use) uses Flash's
- FileReference.upload() method, which doesn't allow
- overriding the default HTTP headers.
-
- On windows, the default Accept header is "text/\*". This
- means that it won't accept "application/json". Rather than
- throw a 406 Not Acceptable response, or worse, a 500 error,
- we've chosen to return an incorrect ``text/plain`` type.
-
"""
if id == 'new':
podcast = create_podcast_stub()
@@ -243,9 +226,8 @@ def save_thumb(self, id, thumb, **values):
success = False
message = e.message
- response.headers['Content-Type'] = helpers.best_json_content_type()
- return json.dumps(dict(
+ return dict(
success = success,
message = message,
id = podcast.id,
- ))
+ )
View
24 mediacore/controllers/upload.py
@@ -37,8 +37,7 @@
from mediacore.lib.base import BaseController
from mediacore.lib.decorators import expose, expose_xhr, paginate, validate
from mediacore.lib.filetypes import external_embedded_containers, guess_container_format, guess_media_type
-from mediacore.lib.helpers import (redirect, url_for, best_json_content_type,
- create_default_thumbs_for, fetch_setting)
+from mediacore.lib.helpers import redirect, url_for, create_default_thumbs_for, fetch_setting
from mediacore.model import (fetch_row, get_available_slug,
Media, MediaFile, Comment, Tag, Category, Author, AuthorWithIP, Podcast)
from mediacore.model.meta import DBSession
@@ -84,7 +83,7 @@ def index(self, **kwargs):
form_values = kwargs,
)
- @expose()
+ @expose('json')
@validate(upload_form)
def submit_async(self, **kwargs):
"""Ajax form validation and/or submission.
@@ -113,22 +112,6 @@ def submit_async(self, **kwargs):
redirect
If valid, the redirect url for the upload successful page.
- .. note::
-
- This method returns an incorrect Content-Type header under
- some circumstances. It should be ``application/json``, but
- sometimes ``text/plain`` is used instead.
-
- This is because this method is used from the flash based
- uploader; Swiff.Uploader (which we use) uses Flash's
- FileReference.upload() method, which doesn't allow
- overriding the default HTTP headers.
-
- On windows, the default Accept header is "text/\*". This
- means that it won't accept "application/json". Rather than
- throw a 406 Not Acceptable response, or worse, a 500 error,
- we've chosen to return an incorrect ``text/plain`` type.
-
"""
if 'validate' in kwargs:
# we're just validating the fields. no need to worry.
@@ -164,8 +147,7 @@ def submit_async(self, **kwargs):
redirect = url_for(action='success')
)
- response.headers['Content-Type'] = best_json_content_type()
- return json.dumps(data)
+ return data
@expose()
@validate(upload_form, error_handler=index)
View
27 mediacore/lib/helpers.py
@@ -518,33 +518,6 @@ def create_default_thumbs_for(item):
dst_file = thumb_path(item, key)
shutil.copyfile(src_file, dst_file)
-def best_json_content_type(accept=None, raise_exc=True):
- """Return the best possible JSON header we can return for a client.
-
- Sometimes we want to return JSON as ``text/plain``: on windows,
- Flash uploads always request ``text/\*`` so the proper
- ``application/json`` header won't work.
-
- :param accept: An HTTP Accept header, defaults to that of the
- current request.
- :type accept: string
- :param raise_exc: By default an webob.exc.HTTPNotAcceptable header
- will be raised if the given Accept header does not match any of
- the possible JSON content types.
- :type raise_exc: bool
- :raises webob.exc.HTTPNotAcceptable: If `raise_exc` is True
- and the given Accept header doesn't work for our json types.
- :returns: mimetype
- :rtype: string
-
- """
- desired_matches = mimeparse.desired_matches(
- ['application/json', 'text/plain'],
- accept or request.environ.get('HTTP_ACCEPT', '*/*'))
- if raise_exc and not desired_matches:
- raise webob.exc.HTTPNotAcceptable # 406
- return desired_matches[0]
-
def append_class_attr(attrs, class_name):
"""Append to the class for any input that Genshi's py:attrs understands.

0 comments on commit 9bad4b9

Please sign in to comment.