{ "info": { "author": "Travis Cunningham", "author_email": "tcunningham@smartfile.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": ".. figure:: https://travis-ci.org/smartfile/django-transfer.png\n :alt: Travis CI Status\n :target: https://travis-ci.org/smartfile/django-transfer\n\nA `SmartFile`_ Open Source project. `Read more`_ about how SmartFile\nuses and contributes to Open Source software.\n\n.. figure:: http://www.smartfile.com/images/logo.jpg\n :alt: SmartFile\n\nIntroduction\n------------\n\nThe Django project recommends serving static files from a different web\nserver than the one executing the web application. This is easy to implement\nwhen the static files are web assets. These resources can be served to any\nanonymous user and can easily be cached. However, in some cases, an\napplication must control access to files, or even allow users to upload\nfiles. In these cases, there is a need to tightly control the process,\nwhich runs contrary to the Django project's recommendations.\n\nLuckily, there are a few tools available that allow removing downloads\nand even uploads from the application server, while still allowing it\nto control the process. This Django application is meant to help\nintegrate with such tools, so that your web application can hand off\nfile transfers to a downstream proxy server, which is better equipped\nto handle this task, freeing up the application server for the heavy\nlifting.\n\ndjango-transfer integrates with:\n\n- `mod_xsendfile`_ for Apache\n- `X-Accel-Redirect`_ for Nginx\n- `X-SendFile`_ header in Lighttpd\n- `mod_upload`_ for Nginx\n\nThe first three of the above allow the web application to emit a header\ninstructing the content server to transfer a file to the HTTP client.\nThis way, the web app still receives the download request, performs any\nchecks required, and sends a header instead of the actual file contents.\n\nThe last, `mod_upload`_ does something similar, but for file UPLOADS.\nmod_upload will receive files POSTed to the server and save them off\nto temporary files. It will then forward the request to the web\napplication, replacing the file bodies with paths to the temporary files\ncontaining them.\n\n`mod_upload`_ is better than simply buffering the upload because the file\nbodies are NEVER handled by the application server. In fact, if you can\nwrite the temporary files to a holding area that exists on the same volume\nas their final location, a simple move is all that is required to finish\nthe upload. In fact, the ``ProxyUploadedFile`` class (contained in\n``request.FILES`` has a convenience ``move()`` method.\n\nDownloading\n-----------\n\ndjango-transfer provides an HttpResponse subclass that handles downloads\ntriggered via response header. The actual header and format are handled by\nthis class. TransferHttpResponse accepts a path, and handles the transfer.\nWhen ``settings.DEBUG == True`` the path is sent directly to the client,\nthis allows the Django development server to function as normal without\nchanging your application code.\n\nThe timeline of events for a download looks like the following.\n\n1. A client initiates a download (GET request).\n2. The downstream server forwards the request to Django.\n3. Django application authenticates the user and does other necessary\n processing.\n4. Django application returns a ``TransferHttpResponse``.\n5. The ``TransferHttpResponse`` emits a header instructing the downstream\n server to transfer a file to the client.\n\nFirst you must configure django-transfer and let it know the details\nabout your downstream server.\n\n*Server Types*\n\n::\n\n TRANSFER_SERVER = 'apache' # or 'nginx' or 'lighttpd'\n\nYou can change the server type and TransferHttpResponse will use the\ncorrect header(s) for the configured server.\n\n*Nginx Mappings*\n\nNginx has support for the X-Accel-Redirect header built in. However, it\ndoes not accept arbitrary paths for transfer. Nginx requires that you\nconfigure internal locations, and return a path relative to one of those.\n\nFor example, if you configure:\n\n::\n\n location /downloads {\n internal;\n alias /mnt/shared/downloads;\n }\n\nWhen nginx receives the header ``X-Accel-Redirect: /downloads/foo/bar.png``\nit will transfer ``'/mnt/shared/downloads/foo/bar.png'`` to the client.\n\ndjango-transfer needs to know about such locations. You can inform it of\nthem by configuring the mappings.\n\n::\n\n TRANSFER_MAPPINGS = {\n '/mnt/shared/downloads': '/downloads',\n }\n\nOnce the mapping is configured, you can use absolute paths, which will\nbe converted to the locations required by nginx. If you later switch to\na different server (apache or lighttpd), these absolute paths will continue\nto function without changing your code. Similarly, when ``settings.DEBUG ==\nTrue``, absolute paths will be required so that the development server can\nsend the file directly.\n\nIf you do not configure any mappings, and you are using server type\n``'nginx'``, an ImproperlyConfigured exception will be raised. Mappings\nare ignored when the server type is not ``'nginx'``.\n\n*Apache Configuration*\n\nApache requires a module to be installed in order to use the X-Sendfile\nheader. Once installed, this module must be enabled, and you must define\nthe locations that allow downloads. Much like Nginx, Apache will not\nserve arbitrary paths, only those specifically configured.\n\n::\n\n XSendFile On\n XSendFilePath /mnt/shared/downloads\n\nWhen apache receives the header ``X-SendFile: /mnt/shared/downloads/foo/bar.png``\nIt will transfer ``'/mnt/shared/downloads/foo/bar.png'`` to the client.\ndjango-transfer will pass along absolute paths when the server type is\n``'apache'``.\n\n*Lighttpd Configuration*\n\nTODO: I have never used lighttpd, but I know it supports this.\n\nUploading\n---------\n\nUploads are handled using a similar (but reversed) process. Nginx\nsupports uploading with `mod_upload`_. This is not part of the default\nserver, so you must build nginx with support for uploading. If available,\nthe upload module will strip file contents from POST requests, save\nthem to temporary files and then forward those file names to your\napplication.\n\n1. A client initiates an upload (POST reqest).\n2. The downstream server saves any file(s) to a holding area.\n3. The downstream server forwards the request (minus the file content) to\n Django.\n4. Django does any processing that is necessary and returns a response.\n5. The downstream server relays the response to the client.\n\nTo handle downstream uploads in the same way you handle regular file\nuploads, you must install the ``TransferMiddleware``. This middleware\nprocesses the ``request.POST`` data, identifying uploaded files and\ncreating new entries in ``request.FILES`` to represent them.\n\n::\n\n MIDDLEWARE_CLASSES = (\n ...\n 'django_transfer.TransferMiddleware',\n ...\n )\n\nNginx requires a bit of configuration to make this possible. Below is a\nsample configuration.\n\n::\n\n location /upload {\n upload_pass @application;\n\n # The path below must exist, so must subdirectories named 0-9\n # $ mkdir -p /mnt/shared/uploads/{0-9}\n upload_store /mnt/shared/uploads 1;\n upload_store_access user:r;\n\n # You can limit file size here...\n upload_max_file_size 0;\n\n # These are the MINIMUM fields required by django-transfer.\n # mod_upload will replace $upload_field_name with the name of the file\n # field. If there are multiple files, your web application will receive\n # a set of filename/paths for each.\n upload_set_form_field $upload_field_name[filename] \"$upload_file_name\";\n upload_set_form_field $upload_field_name[path] \"$upload_tmp_path\";\n\n # You can also pass along the following fields, otherwise\n # django-transfer will attempt to \"figure out\" these values on it's\n # own.\n upload_set_form_field $upload_field_name[content_type] \"$upload_content_type\";\n upload_aggregate_form_field $upload_field_name[size] \"$upload_file_size\";\n\n # If you want to receive non-file fields provide the following, note\n # that if nginx supports it, this can be a regular expression. If not\n # you can define allowed fields separately, by providing this argument\n # multiple times.\n upload_pass_form_field \".*\";\n\n # If you want to receive querystring arguments...\n upload_pass_args on;\n }\n\n location / {\n # ... proxy-pass or FCGI directives here ...\n # This is where requests to URLs other than /upload go.\n }\n\n location @application {\n # ... proxy-pass or FCGI directives here ...\n # This is where to pass upload requests, most frequently, it will be\n # the same as the previous location.\n }\n\nFor more information on how to install and configure mod_upload, see the\nfollowing pages, I found them useful while implementing this.\n\nhttp://www.grid.net.ru/nginx/upload.en.html\nhttp://blog.joshsoftware.com/2010/10/20/uploading-multiple-files-with-nginx-upload-module-and-upload-progress-bar/\nhttp://bclennox.com/extremely-large-file-uploads-with-nginx-passenger-rails-and-jquery\n\nYour views can now handle regular or downstream uploads in the same fashion.\n\nDevelopment / Debugging\n-----------------------\n\nWhen ``settings.DEBUG == True``, ``TransferHttpResponse`` will transfer the\nfile directly which suitable for use with the Django development server.\nThe ``TransferMiddleware`` always supports regular file uploads, so it\nwill also function properly when ``settings.DEBUG == True``.\n\nNon-ASCII File Names\n--------------------\n\nThis library does nothing to help with non-ASCII filenames, however, a\nquick note on this topic might save you some headache.\n\nA common practice is to include a Content-Disposition header that\nincludes the file name. This breaks when the filename contains non-ASCII\ncharacters (UTF-8 etc). Specifically, Django will raise an exception when\nyou try to set the header. The HTTP specification states that headers must\ncontain only ASCII.\n\nThe best workaround I have found for this is to include the file name in\nthe URL. It must be the last element of the URL. All browsers I know of\nwill use this file name in the \"Save As\" dialog. Since a URL can contain\nany character, this works around the issue. To implement this, I\ngenerally add a regular expression to urls.py that ignores the file name.\nThe file name is there only for the benefit of the browser, and is not\nused by the Django view. Thus::\n\n url('^/download/.*', 'myapp.views.download'),\n\nwill allow an optional trailing file name for our purposes. You then must\nensure that any links to your download view include the file name, like so::\n\n http://myapp.com/download/desired_filename.png\n\nWhen the user clicks that link and the application sends file contents, the\nbrowser will obtain the file name from the URL. The browser may decide to\nrender or save the file. You can force the issue (saving vs. rendering) by\nincluding a Content-Disposition header with the value \"attachment;\"\nexcluding the (unsafe) filename.\n\n.. _SmartFile: http://www.smartfile.com/\n.. _Read more: http://www.smartfile.com/open-source.html\n.. _mod_xsendfile: https://tn123.org/mod_xsendfile/\n.. _X-Accel-Redirect: http://wiki.nginx.org/XSendfile\n.. _X-SendFile: http://redmine.lighttpd.net/projects/1/wiki/Docs_ModFastCGI#X-Sendfile\n.. _mod_upload: http://wiki.nginx.org/HttpUploadModule", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://github.com/smartfile/django-transfer/", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "django-transfer", "package_url": "https://pypi.org/project/django-transfer/", "platform": "", "project_url": "https://pypi.org/project/django-transfer/", "project_urls": { "Homepage": "http://github.com/smartfile/django-transfer/" }, "release_url": "https://pypi.org/project/django-transfer/0.4/", "requires_dist": null, "requires_python": "", "summary": "A django application that offloads file transfers to a downstream proxy.", "version": "0.4" }, "last_serial": 2956480, "releases": { "0.1-1": [ { "comment_text": "", "digests": { "md5": "aef4bcaf3be3a1dcf5f3367e5ca12445", "sha256": "92ce4ea74a0271ea035c0904997e1573d20794ca7f6e48675ed167aae9b79ae4" }, "downloads": -1, "filename": "django-transfer-0.1-1.tar.gz", "has_sig": false, "md5_digest": "aef4bcaf3be3a1dcf5f3367e5ca12445", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14607, "upload_time": "2013-02-12T18:28:19", "url": "https://files.pythonhosted.org/packages/77/54/97422c3c30790a18f69f6bfacb859dd7a496f47606fb72e540115b2a8401/django-transfer-0.1-1.tar.gz" } ], "0.2-1": [ { "comment_text": "", "digests": { "md5": "bfb3e05f439418ee0d59212ef2b9660e", "sha256": "939020278cb596630e7e6aef3ae9cd9369c10316c45b70ef33f757f791d40845" }, "downloads": -1, "filename": "django-transfer-0.2-1.tar.gz", "has_sig": false, "md5_digest": "bfb3e05f439418ee0d59212ef2b9660e", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15034, "upload_time": "2013-02-13T19:54:01", "url": "https://files.pythonhosted.org/packages/69/a9/661c867da019f9ad046e0e32e86ab5ea511b855cbcda8eff499b73ad6c05/django-transfer-0.2-1.tar.gz" } ], "0.2-2": [ { "comment_text": "", "digests": { "md5": "a289f23890445f9a8966a078d2b4b843", "sha256": "85962728916478128365ac6dc77128dabbafbe477be82cf98b01eeb543eda834" }, "downloads": -1, "filename": "django-transfer-0.2-2.tar.gz", "has_sig": false, "md5_digest": "a289f23890445f9a8966a078d2b4b843", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 15063, "upload_time": "2013-02-13T21:12:48", "url": "https://files.pythonhosted.org/packages/f2/18/b861feb0171509c665f326ed664c505ee7dcc5b522adbbfae2f1773bd55c/django-transfer-0.2-2.tar.gz" } ], "0.3": [ { "comment_text": "", "digests": { "md5": "302931417a3a3d126c957b5e4ee20349", "sha256": "526a8aeba6ad10e982cbd6cf117fc32b32a1a196759bb110a413b927a61c54eb" }, "downloads": -1, "filename": "django-transfer-0.3.tar.gz", "has_sig": false, "md5_digest": "302931417a3a3d126c957b5e4ee20349", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16169, "upload_time": "2017-06-16T13:56:23", "url": "https://files.pythonhosted.org/packages/cf/df/95a24d068cecb03e4726ece4508d743134a132bb49f6cea74dff9b6fd377/django-transfer-0.3.tar.gz" } ], "0.4": [ { "comment_text": "", "digests": { "md5": "fc908ccfc1ee4aaf8130b7ca0337fdf9", "sha256": "75ecd9ef287493914e51d9828acacd8737e5e3cae32b24ef46cb7495ebda9d5d" }, "downloads": -1, "filename": "django-transfer-0.4.tar.gz", "has_sig": false, "md5_digest": "fc908ccfc1ee4aaf8130b7ca0337fdf9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16211, "upload_time": "2017-06-17T16:06:47", "url": "https://files.pythonhosted.org/packages/4f/85/086ea0e40853e475da468751940abd6f1fa9e95eb026095043b2b05ae7ca/django-transfer-0.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "fc908ccfc1ee4aaf8130b7ca0337fdf9", "sha256": "75ecd9ef287493914e51d9828acacd8737e5e3cae32b24ef46cb7495ebda9d5d" }, "downloads": -1, "filename": "django-transfer-0.4.tar.gz", "has_sig": false, "md5_digest": "fc908ccfc1ee4aaf8130b7ca0337fdf9", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16211, "upload_time": "2017-06-17T16:06:47", "url": "https://files.pythonhosted.org/packages/4f/85/086ea0e40853e475da468751940abd6f1fa9e95eb026095043b2b05ae7ca/django-transfer-0.4.tar.gz" } ] }