Django-compressor:如何将内容写入S3并从CloudFront读取?

18

我希望能够通过CloudFront(它们存储在S3上)提供已压缩的CSS/JS,但是我无法通过settings.py中的压缩器设置来解决问题,以下是我的设置:

    COMPRESS_OFFLINE = True 
    COMPRESS_URL = 'http://static.example.com/' #same as STATIC_URL, so unnecessary, just here for simplicity
    COMPRESS_STORAGE = 'my_example_dir.storage.CachedS3BotoStorage' #subclass suggested in [docs][1]
    COMPRESS_OUTPUT_DIR = 'compressed_static'
    COMPRESS_ROOT = '/home/dotcloud/current/static/' #location of static files on server
尽管使用了 COMPRESS_URL,我的文件仍然从 S3 存储桶中读取:
<link rel="stylesheet" href="https://example.s3.amazonaws.com/compressed_static/css/e0684a1d5c25.css?Signature=blahblahblah;Expires=farfuture;AWSAccessKeyId=blahblahblah" type="text/css" /> 我猜问题在于我想把文件写入 S3,但是要从 CloudFront 读取。这种做法是否可行?

在 GitHub 上看到了你的问题单... 你介意发布一下你的解决方案吗? - Jiaaro
非常抱歉没有尽早看到这个问题,明天(希望如期)我会发布我的解决方案。 - Matt Parrilla
另一个技巧是进入您的CloudFront分发>“编辑”行为>在底部,找到“自动压缩对象”,点击“是”并保存。 - Josh
6个回答

33

我编写了一个基于boto提供的存储后端的包装器

myapp/storage_backends.py:

import urlparse
from django.conf import settings
from storages.backends.s3boto import S3BotoStorage

def domain(url):
    return urlparse.urlparse(url).hostname    

class MediaFilesStorage(S3BotoStorage):
    def __init__(self, *args, **kwargs):
        kwargs['bucket'] = settings.MEDIA_FILES_BUCKET
        kwargs['custom_domain'] = domain(settings.MEDIA_URL)
        super(MediaFilesStorage, self).__init__(*args, **kwargs)

class StaticFilesStorage(S3BotoStorage):
    def __init__(self, *args, **kwargs):
        kwargs['bucket'] = settings.STATIC_FILES_BUCKET
        kwargs['custom_domain'] = domain(settings.STATIC_URL)
        super(StaticFilesStorage, self).__init__(*args, **kwargs)

我的settings.py文件在哪里?

STATIC_FILES_BUCKET = "myappstatic"
MEDIA_FILES_BUCKET = "myappmedia"
STATIC_URL = "http://XXXXXXXX.cloudfront.net/"
MEDIA_URL = "http://XXXXXXXX.cloudfront.net/"

DEFAULT_FILE_STORAGE = 'myapp.storage_backends.MediaFilesStorage'
COMPRESS_STORAGE = STATICFILES_STORAGE = 'myapp.storage_backends.StaticFilesStorage'

会尽快查看@Jiaao。我已经实现了自己的hackish解决方案,但我会将其与我的解决方案进行比较,并查看它的工作原理。一旦确认,我会回来并将你标记为已确认!无论如何都谢谢。 - Matt Parrilla

11

我对settings.py做了几个不同的更改。

AWS_S3_CUSTOM_DOMAIN = 'XXXXXXX.cloudfront.net' #important: no "http://"
AWS_S3_SECURE_URLS = True #default, but must set to false if using an alias on cloudfront

COMPRESS_STORAGE = 'example_app.storage.CachedS3BotoStorage' #from the docs (linked below)
STATICFILES_STORAGE = 'example_app.storage.CachedS3BotoStorage'

压缩器文档

上述解决方案不仅可以将文件保存在本地,还可以将其上传到s3。这使我可以离线压缩文件。如果您没有使用gzip,则上述方法应该适用于从CloudFront提供压缩文件。

添加gzip会增加一些复杂性:

settings.py

AWS_IS_GZIPPED = True

尽管这样做会导致在collectstatic期间将可压缩文件(根据存储,包括css和js)推送到s3时出现错误:
AttributeError: 'cStringIO.StringO'对象没有属性'name'
这是由于某些与压缩css/js文件有关的奇怪错误造成的,我不理解。我需要这些文件在本地解压缩,而不是在s3上,因此,如果我调整上面提到的存储子类(并提供压缩器docs中),就可以完全避免这个问题。
新的storage.py
from os.path import splitext 
from django.core.files.storage import get_storage_class  
from storages.backends.s3boto import S3BotoStorage  


class StaticToS3Storage(S3BotoStorage): 

    def __init__(self, *args, **kwargs): 
        super(StaticToS3Storage, self).__init__(*args, **kwargs) 
        self.local_storage = get_storage_class('compressor.storage.CompressorFileStorage')() 

    def save(self, name, content): 
        ext = splitext(name)[1] 
        parent_dir = name.split('/')[0] 
        if ext in ['.css', '.js'] and not parent_dir == 'admin': 
            self.local_storage._save(name, content) 
        else:     
            filename = super(StaticToS3Storage, self).save(name, content) 
            return filename 

这个操作将保存所有的.css和.js文件(不包括管理文件,我从CloudFront未压缩地提供),同时将其余文件推送到s3(不必在本地保存,但可以轻松添加self.local_storage._save行)。
但是当我运行压缩时,我希望我的压缩后的.js和.css文件被推送到s3,因此我创建了另一个子类供压缩器使用:
class CachedS3BotoStorage(S3BotoStorage): 
        """ 
        django-compressor uses this class to gzip the compressed files and send them to s3 
        these files are then saved locally, which ensures that they only create fresh copies 
        when they need to 
        """ 
        def __init__(self, *args, **kwargs): 
            super(CachedS3BotoStorage, self).__init__(*args, **kwargs) 
            self.local_storage = get_storage_class('compressor.storage.CompressorFileStorage')() 


        def save(self, filename, content): 
            filename = super(CachedS3BotoStorage, self).save(filename, content) 
            self.local_storage._save(filename, content) 
            return filename 

最后,鉴于这些新的子类,我需要更新一些设置:
COMPRESS_STORAGE = 'example_app.storage.CachedS3BotoStorage' #from the docs (linked below)
STATICFILES_STORAGE = 'example_app.storage.StaticToS3Storage'

“关于这个,我所要说的就是这些了。”

4

这个解决方法对我来说会导致递归深度错误。我只是复制粘贴了您的补丁,将默认存储类更改为新的,并打开了AWS_IS_GZIPPED标志。 - sbidwai
我有同样的问题,是否有任何消息关于它的原因或自从它工作以来发生了什么变化?非常感谢任何提示! - tiwei

3
实际上,在Django存储中也存在这个问题。当压缩程序比较S3上文件的哈希值时,Django存储不会展开Gzip压缩的文件内容,而是试图比较不同的哈希值。我已经打开了https://bitbucket.org/david/django-storages/pull-request/33/fix-gzip-support 来修复这个问题。
顺便说一下,还有一个https://bitbucket.org/david/django-storages/pull-request/32/s3boto-gzip-fix-and-associated-unit-tests,用于修复另一个问题:当AWS_IS_GZIPPED设置为True时,实际上将文件保存到S3。这真是一件麻烦的事情。

1
此外,对于流式分发,覆盖url函数以允许使用rtmp:// URL 是非常有用的,例如:
import urlparse
class VideoStorageForCloudFrontStreaming(S3BotoStorage):
    """
    Use when needing rtmp:// urls for a CloudFront Streaming distribution. Will return
    a proper CloudFront URL.

    Subclasses must be sure to set custom_domain.
    """
    def url(self, name):
        name = urlparse.quote(self._normalize_name(self._clean_name(name)))
        return "rtmp://%s/cfx/st/%s" % (self.custom_domain, name)

    # handy for JW Player:
    @Property
    def streamer(self):
        return "rtmp://%s/cfx/st" % (self.custom_domain)

0

看起来CloudFront现在提供了内置的压缩功能。如果启用了该功能,则会向CloudFront发出请求。如果CF没有存储压缩缓存版本,则会向源服务器(S3)发出请求,该服务器返回未压缩的文件。然后,CloudFront将自动压缩文件,将其存储在缓存中并提供给查看者。

您可以通过编辑分发中的“行为”来启用CF中的自动压缩。在底部询问“自动压缩文件”的位置,您可以将其保存为“是”。

P.S.对此的要求:

在权限中更改CORS以显示Content-Length,即<AllowedHeader>Content-Length</AllowedHeader>

更多信息请点击这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接