您的位置:首页 > 编程语言 > Go语言

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

2017-10-14 22:33 871 查看
静态文件使用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 又回来了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息