【问题标题】:Django-compressor: how to write to S3, read from CloudFront?Django-compressor:如何写入 S3,从 CloudFront 读取?
【发布时间】:2011-12-31 15:04:32
【问题描述】:

我想从 CloudFront 提供我压缩的 CSS/JS(它们位于 S3 上),但无法通过 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 上看到了您的票...您介意发布您的解决方案吗?
  • 我很抱歉没有早点看到这个,我明天会在下面发布我的解决方案(希望如此)
  • 另一个技巧是转到您的 CloudFront 分配 > “编辑”行为 > 在底部显示“自动压缩对象”单击是并保存。

标签: python django amazon-s3 amazon-cloudfront django-compressor


【解决方案1】:

我围绕 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解决方案,但我会对照我的来检查它,看看它是如何工作的。我一确认就会回来检查你!不管怎样,谢谢。
【解决方案2】:

我对 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'

Compressor Docs

上述解决方案将文件保存在本地,并将它们上传到 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 行)。

但是当我运行 compress 时,我希望我压缩的 .js 和 .css 文件被推送到 s3,所以我创建了另一个 sublcas 供压缩器使用:

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'

这就是我要说的。

【讨论】:

    【解决方案3】:

    似乎问题实际上已在 Django 上游修复,https://github.com/django/django/commit/5c954136eaef3d98d532368deec4c19cf892f664

    有问题的 _get_size 方法可能会在本地修补以解决旧版本 Django 的问题。

    编辑:查看https://github.com/jezdez/django_compressor/issues/100 以获得实际解决方法。

    【讨论】:

    • 这项工作给我带来了递归深度错误。我刚刚按原样复制粘贴了您的补丁,将默认存储类更改为新的并打开了 AWS_IS_GZIPPED 标志。
    • 我也有同样的问题,请问有什么消息可以说明是什么原因造成的,或者自从它起作用后发生了什么变化?非常感谢您的任何提示!
    【解决方案4】:

    实际上这似乎也是 django-storages 中的一个问题。当压缩器比较 S3 上文件的哈希值时,django-storages 不会解压缩 Gzip 文件的内容,而是尝试比较不同的哈希值。我已经打开 https://bitbucket.org/david/django-storages/pull-request/33/fix-gzip-support 来解决这个问题。

    FWIW,还有https://bitbucket.org/david/django-storages/pull-request/32/s3boto-gzip-fix-and-associated-unit-tests 修复了在将 AWS_IS_GZIPPED 设置为 True 时实际将文件保存到 S3 的另一个问题。真是一头牦牛。

    【讨论】:

      【解决方案5】:

      此外,对于流式分发,覆盖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)
      

      【讨论】:

        【解决方案6】:

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

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

        附:对此的要求:

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

        更多信息here。

        【讨论】:

          猜你喜欢
          • 2013-02-09
          • 2015-04-05
          • 2014-09-27
          • 1970-01-01
          • 2013-03-29
          • 1970-01-01
          • 2014-01-23
          • 2015-05-23
          • 2017-07-29
          相关资源
          最近更新 更多