Yeoyou....

Personal blogs

Django를 이용한 웹 파일공유

django 에서는 기본적으로 디버깅 환경을 위해 static file serve 기능이 있다. 이부분을 조금만 응용해서 서비스 환경에서 웹 파일 공유 서버로 만들어 보면 어떨까 생각해 보았다.

하지만 이 기능은 문서에는 디버깅용이라 씌여있고, 실제 장고 개발용 서버 (manage.py runserver)를 통해 개발할 때는 이것이 없으면 css나 js 파일들이 없어서 관리자 사이트 등이 정상으로 보이지 않는다. 더구나 실제 코드 내부를 살펴 보면, f.read() 와 같이 python 상에서 파일을 메모리로 올려 출력하게 된다.

실제 서비스 환경에서는 이런 파일 출력 기능은 웹 서버가 전담하고 있고, 웹서버-cgi-웹서버 를 거치는 것 보다 더 효율적으로 서비스 된다.

하지만 장점도 무시할 수 없다고 생각한다

보통 인증된 회원에게만 파일을 서비스 하고 싶을 때, 웹서버가 직접 파일을 서비스 하게 하기 위해서는

  • 링크를 복잡하게 만들어 숨기거나
  • 제한된 시간동안 만 접근 가능한 임시 링크를 열거나

복잡하고도 완벽하기 힘든 방법으로 웹서버를 이용하게 해야 한다. 하지만 장고가 파일을 서비스하면 확실하게 회원 대상으로 서비스를 할 수 있게 된다.

회원여부를 확인 하는 동작이 간단할 수 많은 없기 때문에 일반적인 파일은 웹서버가 바로 서비스 하도록 하는것이 맞지만, 포인트 소비와 관련되거나 결제와 연계된 파일을 서비스 하는 경우에는 장고를 거쳐서 서비스 하는게 확실할 수 도 있다.

또한, 장고는 파일 전송과 직접 관계가 없는 디렉토리 인덱싱 페이지도 제공한다.

효율의 문제만 아니라면, 파일 공유 서버로 장고를 이용하는 것도 좋은 방법이 될 수 있다.

이럴 때에 적어도 f.read 만이라도 피해보면 어떨까?

웹 표준은 아니지만 X-SENDFILE 이라는 메타헤더를 이용하면, cgi는 웹서버에게 파일위치를 알려주고, 웹서버는 해당하는 파일을 서비스를 하게 할 수 있다. 서비스 할지 말지의 여부를 먼저 cgi가 판단하고 그 결과를 받아서 웹서버는 파일을 직접 쏴주게 된다.

결론적으로, 장고를 이용하면 간단하게 회원 한정의 웹 파일 공유 서버를 구축 할 수 있다.

필요한 것

  • Django
  • 웹서버 (아래중 하나)
    • apache와 mod_xsendfile
    • (lighttpd)
    • nginx

작업 순서

  1. 웹서버 xsendfile 설정
  2. view 작성
  3. urls.py 세팅

작업 상세

apache case

다음과 같은 명령으로 mod_xsendfile 설치

1
2
$ curl -O https://tn123.org/mod_xsendfile/mod_xsendfile.c
$ sudo apxs -cia mod_xsendfile.c

아파치 설정은 다음과 같이

1
2
3
4
5
6
7
<VirtualHost *>
  ServerName anotherserver
    XSendFilePath /var/www/somesite/
    <Directory /var/www/somesite/fastcgis>
      XSendFilePath /var/www/shared
    </Directory>
</VirtualHost>

nginx case

nginx.conf
1
2
3
4
location /nginx-internal/ {
    internal;
    alias /storage/django-static/;
}

실제 서버 절대경로는 /storage/django-static/, cgi 가 nginx 로 넘겨주는 경로는 /nginx-internal/ 이 된다.

django

그럼 장고에서 views.py 에 다음을 추가 (django.views.static.serve를 약간 수정한 것)

views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import os
import posixpath
import urllib

from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified
from django.utils.translation import ugettext as _, ugettext_noop
from django.contrib.auth.decorators import login_required
from django.views.static import DEFAULT_DIRECTORY_INDEX_TEMPLATE, directory_index, was_modified_since

@login_required
def static_serve(request, path, document_root=None, show_indexes=False):
    path = posixpath.normpath(urllib.unquote(path))
    path = path.lstrip('/')
    newpath = ''
    for part in path.split('/'):
        if not part:
            # Strip empty path components.
            continue
        drive, part = os.path.splitdrive(part)
        head, part = os.path.split(part)
        if part in (os.curdir, os.pardir):
            # Strip '.' and '..' in path.
            continue
        newpath = os.path.join(newpath, part).replace('\\', '/')
    if newpath and path != newpath:
        return HttpResponseRedirect(newpath)
    fullpath = os.path.join(document_root, newpath)
    if os.path.isdir(fullpath):
        if show_indexes:
            return directory_index(newpath, fullpath)
        raise Http404(_(u"Directory indexes are not allowed here."))
    if not os.path.exists(fullpath):
      #request.META['wsgi.errors'].write('>> static_serve: ' + fullpath + '\n')
        raise Http404(_(u'"%(path)s" does not exist') % {'path': fullpath})
    response = HttpResponse() # 200 OK
    del response['content-type'] # We'll let the web server guess this.
    response['X-Sendfile'] = fullpath # for apache or lighttpd
    response['X-Accel-Redirect'] = '/nginx-internal/'+path # for nginx
    return response

그럼 urls.py 에서

urls.py
1
2
(r'^shared/(?P<path>.*)$', 'shared.views.static_serve',
    {'document_root' : settings.SHARED_ROOT, 'show_indexes' : True})

아파치를 재시작 하고 /shared/ 로 접속하면 디렉토리 목록 화면을 보게 된다.

인덱싱 화면을 더 멋지게 표시하고 싶으면

static/directory_index.html

를 추가하고 입맛에 맞게 수정한다.

마치며

작업하면서 로그를 남겨두지 않고 나중에 작성한 글이라 빠진 내용이 있을지 모르겠다.

실제 웹서버에서 x-sendfile 을 얼마나 사용할 지 모르겠지만, 이런 훌륭한 기능은 옵션으로 라도 기본기능으로 지원되어야 하는거 아닌가 싶으다..

참고

Comments