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 又回来了。 来自 大脸猫 写于 2017-10-14 22:03 -- 更新于2020-10-19 13:06 -- 0 条评论