django:自定义静态文件服务器

静态文件使用nginx是比较有效率的,但是有时,我们需要对文件下载做细粒度的处理,比如鉴权下载,此时就需要写代码了。
下面将一步步实现一个自定义的文件handler。

关闭自带的static handler

确保没有开启 django.contrib.staticfiles

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    # 'django.contrib.staticfiles',

定义文件下载handler

读取一个文件建议使用迭代器,否则内存吃不消。
要下载一个文件,需要返回正确的 Content-Type 和 Content-Disposition。
get_real_path 是把相对路径转成硬盘的实际路径,自已实现吧
具体看代码:

from django.http import HttpResponse, StreamingHttpResponse
....

def handler_statics(request, path):
    short_file_name, real_file_path = get_real_path(request, path)

    response = StreamingHttpResponse(readFile(real_file_path))
    response['Content-Type'] = get_right_content_type(short_file_name)
    response['Content-Disposition'] = get_right_content_disposition(short_file_name)

    logging.info(u"handler_statics type {} path {}".format(get_right_content_type(short_file_name), real_file_path))
    return response

def get_right_content_disposition(filename):
    filename = filename.lower()
    img_types = ['jpg', 'png', 'jpeg', 'gif']
    content_disposition = ""
    for img_type in img_types:
        if img_type in filename:
            content_disposition = u'inline;filename="{}"'.format(filename)
            break
    if content_disposition == "":
        content_disposition = u'attachment;filename="{}"'.format(filename)
    return content_disposition


def get_right_content_type(filename):
    filename = filename.lower()
    if ".css" in filename:
        return "text/css"
    if ".png" in filename:
        return "image/png"
    if '.jpg' in filename or '.jpeg' in filename:
        return "image/jpeg"
    else:
        return "application/octet-stream"

def readFile(file_name, chunk_size=512):
    try:
        with open(file_name, 'rb') as f:
            while True:
                c = f.read(chunk_size)
                if c:
                    yield c
                else:
                    break
    except:
        yield b""

对用户鉴权

既然自己实现了文件服务器,那鉴权还不是小儿科。
比如,可以hack真实的文件路径。如果权限不正确,就返回一个error的图片给他。

def get_real_path(request, path):
    real_file_path = u"{}/dazhu/static/{}".format(os.getcwd(), path)
    if real_file_path.endswith("/"):
        real_file_path = real_file_path[:-1]

    short_file_name = real_file_path.split("/")
    short_file_name = short_file_name[len(short_file_name)-1]

    # 对 album 要鉴权
    if "album/" in path:
        real_file_path = handler_album(request, short_file_name, real_file_path)
    return short_file_name, real_file_path

# 相册要鉴权
def handler_album(request, short_name, file_path):
    pic = Photoes.objects.get(rndName = short_name)
    if pic.phototype == "private":
        if not request.user.is_authenticated():
            file_path = u"{}/dazhu/static/{}".format(os.getcwd(), u'/images/dazhu.jpg')
    return file_path

服务器爆炸了?

如果本文就这么完了,那就太对不起观众了。
当我们把实现改成这样,很快,你会发现服务器爆炸了。
一般来说,浏览器请求静态资源会带上一个头 If-Modified-Since,文件服务器会根据这个头,判定文件是否已经修改。如果文件不变,则直接返回code 304给浏览器。浏览器将直接使用缓存。
我们的文件服务器漏了这一步。所以,每次请求,服务端都会把文件读取任劳任怨的重新来一次。这样用户体验很差。尤其是图片用户。

实现304

可爱的django给我们提供了一个装饰器 condition。
具体参考:http://wiki.jikexueyuan.com/project/django-chinese-docs-1.8/14-1-conditional-content-processing.html

代码可以这么写:

from django.views.decorators.http import last_modified

def get_file_m_time(request, path):
    try:
        _, real_file_path = get_real_path(request, path)
        mtime = time.ctime(os.path.getmtime(real_file_path))
        logging.info("last modified: {}".format(mtime))
        return datetime.datetime.strptime(mtime, "%a %b %d %H:%M:%S %Y")
    except:
        return datetime.datetime.now()

然后,我们给handler加个佐料,注意,装饰的参数要和handler的一样(?存疑)

@last_modified(get_file_m_time)
def handler_statics(request, path):
    short_file_name, real_file_path = get_real_path(request, path)
    ...

重新部署,刷新同一页面,304 code 又回来了。

猜你喜欢

转载自blog.csdn.net/yzh900927/article/details/78237604