夜明け前の最も暗いとき

技術的なやったことをメモするブログ

Python + Djangoで動画共有サイトを作る Part4

前回の続きです。視聴ページとデプロイをします。

視聴ページ作成

動画を再生する視聴ページを作成します。HTML5ではvideoタグを使うことでブラウザがUIを提供してくれます。

  • video/templates/video/watch.html
{% extends "video/base.html" %}

{% block title %}{{ content.title }} - Video{% endblock %}

{% block header %}
{% load static %}
  <script src="{% static 'video/jquery-3.4.1.min.js' %}"></script>
  <script src="{% static 'video/watch.js' %}"></script>
{% endblock %}

{% block main %}
<br>
<br>

<h2>{{ content.title }}</h2><br>
{{ content.description }}
<hr>
tags:
{% for tag in tags %}
  <a href="../tag/{{ tag.tag.name }}">{{ tag.tag.name }}</a>
{% endfor %}
<br>
<hr>
<video id="video_content" src="/media/video/{{ content.id }}/{{ content.filename}}" poster="/media/video/{{ content.id }}/thumb.jpg" controls>
  動画の再生にはHTML5が必要です。
</video>
<hr>
<br>
{% endblock %}

再生画面で音量を変更した場合、その値を記憶するようにします。

  • video/static/video/watch.js
$(function() {

  function getCookie() {
    var cookie = [];
    if (document.cookie != '') {
      cookie_list = document.cookie.split(';');
      for (var item of cookie_list) {
        [key, value] = item.split('=');
        cookie[key.replace(' ', '')] = decodeURIComponent(value);
      }
    }
    return cookie;
  }

  function saveCookie(_key, _value) {
    document.cookie = _key + '=' + encodeURIComponent(_value) + '; max-age=31536000; path=/';
  }

  $(document).ready(function(){
    cookie = getCookie();
    if (cookie['volume']) {
      $('#video_content').get(0).volume = cookie['volume'];
    }
  });

  $('#video_content').on('volumechange', function() {
    saveCookie('volume', $('#video_content').get(0).volume);
  });

});

音量の値の記憶にはcookieを用います。

  • video/views.py
def watch(request, content_id):
    content = get_object_or_404(VideoContent, pk=content_id)
    tags = VideoTagList.objects.filter(content_id=content_id).select_related('content')
    return render(request, 'video/watch.html', {'content':content, 'tags':tags})

コメントアウトを外した後にrunserverを実行してWebサイトにアクセスします。動画のリンクをクリックすると再生画面に飛びます。

デプロイ

いままで開発環境だったためrunserverを実行して動かしました。今後はHTTPサーバから呼び出す形で実行します。HTTPサーバとしてはapacheを使用します。

#yum install httpd python36u-mod_wsgi policycoreutils-python

インストールが完了したらデーモンとして起動します。

# sudo systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:httpd(8)
           man:apachectl(8)
#
# sudo systemctl start httpd
# sudo systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2019-08-27 18:36:40 JST; 2s ago
     Docs: man:httpd(8)
           man:apachectl(8)
 Main PID: 26104 (httpd)
   Status: "Processing requests..."
   CGroup: /system.slice/httpd.service
           tq26104 /usr/sbin/httpd -DFOREGROUND
           tq26105 /usr/sbin/httpd -DFOREGROUND
           tq26106 /usr/sbin/httpd -DFOREGROUND
           tq26107 /usr/sbin/httpd -DFOREGROUND
           tq26108 /usr/sbin/httpd -DFOREGROUND
           mq26109 /usr/sbin/httpd -DFOREGROUND

Aug 27 18:36:40 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
Aug 27 18:36:40 localhost.localdomain httpd[26104]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using l... message
Aug 27 18:36:40 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
Hint: Some lines were ellipsized, use -l to show in full.
#
# sudo systemctl enable httpd
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.
#

続いて、HTTPサーバでWSGIが動作するように設定を変更します。まずはロケールの設定を変更します。

LANG=ja_JP.utf8

今回は日本語を使用するためロケール文字コードをUTF8に変更します。この設定を入れないと文字をASCIIとして扱うためUnicodeEncodeError: 'ascii' codec can't encode characters in position 40-43: ordinal not in range(128)というエラーが発生します。

続いてHTTPサーバの設定をします。今回は/var/www/django/以下にファイルを設置します。

WSGIScriptAlias / /var/www/django/mysite/mysite/wsgi.py
WSGIPythonPath /var/www/django/mysite/
<Directory "/var/www/django/mysite/video">
    <Files wsgi.py>
        Require all granted
    </Files>
</Directory>

Alias /static/ /var/www/django/mysite/video/static/
<Directory "/var/www/django/mysite/video/static">
  Require all granted
</Directory>

Alias /media/ /storage/
<Directory "/storage/">
  Require all granted
</Directory>

設定が完了したらHTTPサーバを再起動します。

# systemctl restart httpd
# sudo systemctl status httpd
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2019-08-27 18:53:18 JST; 4s ago
     Docs: man:httpd(8)
           man:apachectl(8)
  Process: 26163 ExecStop=/bin/kill -WINCH ${MAINPID} (code=exited, status=0/SUCCESS)
 Main PID: 26167 (httpd)
   Status: "Processing requests..."
   CGroup: /system.slice/httpd.service
           tq26167 /usr/sbin/httpd -DFOREGROUND
           tq26168 /usr/sbin/httpd -DFOREGROUND
           tq26169 /usr/sbin/httpd -DFOREGROUND
           tq26170 /usr/sbin/httpd -DFOREGROUND
           tq26171 /usr/sbin/httpd -DFOREGROUND
           mq26172 /usr/sbin/httpd -DFOREGROUND

Aug 27 18:53:17 localhost.localdomain systemd[1]: Stopped The Apache HTTP Server.
Aug 27 18:53:17 localhost.localdomain systemd[1]: Starting The Apache HTTP Server...
Aug 27 18:53:17 localhost.localdomain httpd[26167]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using l... message
Aug 27 18:53:18 localhost.localdomain systemd[1]: Started The Apache HTTP Server.
Hint: Some lines were ellipsized, use -l to show in full.

HTTPサーバの設定ファイルで指定したフォルダに作成したdjangoプロジェクトのファイルを設置します。

# mkdir /var/www/django
# mkdir /var/www/django/mysite
# cp * /var/www/django/mysite/ -R
# chown apache:apache /var/www/django/mysite -R
# ls -l /var/www/django/mysite
/var/www/django/mysite:
total 4
-rwxr-xr-x. 1 apache apache 626 Aug 27 19:01 manage.py
drwxr-xr-x. 3 apache apache  93 Aug 27 19:01 mysite
drwxr-xr-x. 6 apache apache 188 Aug 27 19:01 video
#

また、ストレージについてもHTTPサーバからアクセスできるようにします。

# chown apache:apache -R /storage/video/
# semanage fcontext -a -t httpd_sys_rw_content_t '/storage/video(/.*)?'
# restorecon -v -R /storage/video/

djangoの設定も公開用に書き換えます。

  • mysite/settings.py
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ['192.168.12.9']

djangoではデプロイ時に設定をチェックする機能があります。

$ python3 manage.py check --deploy

必要に応じて警告の内容を修正します。修正後は設定内容を反映するためHTTPサーバを再起動する必要があります。

# systemctl restart httpd

サーバの設定が完了したのでファイアウォールの設定を変更します。

$ sudo firewall-cmd --info-zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh dhcpv6-client
  ports: 8000/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

$ sudo firewall-cmd --add-service http --zone=public --permanent
success
$ sudo firewall-cmd --reload
success
$ sudo firewall-cmd --info-zone=public
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: ssh dhcpv6-client http
  ports: 8000/tcp
  protocols:
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

今までのrunserverではhttp://192.168.12.9:8000/でアクセスしていましたが、HTTPサーバに配置したのでhttp://192.168.12.9/にアクセスします。 正常に表示・動作できていればOKです。

トラブルシューティング

ファイルが存在するのに500 Internal Errorになる

サーバのエラーに関する情報はerror_logに記録されています。

# tailf /var/log/httpd/error_log
[Tue Aug 27 19:13:30.382093 2019] [core:error] [pid 26296] (13)Permission denied: [client 192.168.12.4:57900] AH00035: access to /media/video/3/thumb.jpg denied (filesystem path '/storage/video/3/thumb.jpg') because search permissions are missing on a component of the path, referer: http://192.168.12.9/video/
[Tue Aug 27 19:13:31.346448 2019] [wsgi:error] [pid 26296] [client 192.168.12.4:57900] Not Found: /favicon.ico

Permission deniedの場合は権限に由来するものです。拒否されたファイルを調査します。

# ls -lZ storage/video/3/
-rw-------. root root unconfined_u:object_r:user_tmp_t:s0 video.mp4
-rw-------. root root unconfined_u:object_r:default_t:s0 thumb.jpg

apache(HTTPサーバ)が所有者になっていない場合はchmodコマンドで所有者を変更します。

# chmod apache:apache /storage/video/3/video.mp4
# chmod apache:apache /storage/video/3/thumb.jpg
# ls -lZ /storage/video/3/
-rw-------. apache apache unconfined_u:object_r:user_tmp_t:s0 video.mp4
-rw-------. apache apache unconfined_u:object_r:default_t:s0 thumb.jpg

また、SELinuxの権限が異なる場合もアクセスが拒否されます。SELinuxが動作しているかは次のコマンドで確認できます。

# getenforce
Enforcing

EnforcingであればSELinuxが動作しています。SELinuxは一時的に停止することができます。

# setenforce 0
# getenforce
Permissive

PermissiveであればSELinuxによるアクセス拒否は発生しません。アクセスに関するログはaudit.logに記録されています。

# tailf /var/log/httpd/error_log
type=AVC msg=audit(1566901506.108:729): avc:  denied  { getattr } for  pid=26343 comm="httpd" path="/storage/video/3/thumb.jpg" dev="dm-0" ino=17209734 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=1

HTTPサーバのコンテキストsystem_u:system_r:httpd_tではファイルのコンテキストunconfined_u:object_r:default_t:s0を読みことができないため拒否(denied)されています。一時的にファイルのコンテキストを変更してみます。

# chcon -t httpd_sys_content_t /storage/video/3/thumb.jpg
# ls -lZ /storage/video/3/
-rw-------. apache apache unconfined_u:object_r:user_tmp_t:s0 video.mp4
-rw-------. apache apache unconfined_u:object_r:httpd_sys_content_t thumb.jpg

これでコンテキストが変更されたので再度ブラウザからアクセスし、audit.logに拒否の記録が残るかどうか確かめます。問題なく表示された場合はSELinuxの設定を戻します。

# setenforce 1
# getenforce
Enforcing

chconは一時的なものなので新しくSELinuxのルールを作成します。

# semanage fcontext -a -t httpd_sys_rw_content_t '/storage/video(/.*)?'
# semanage fcontext -l | grep /storage/
/storage/video(/.*)?                               all files          system_u:object_r:httpd_sys_rw_content_t:s0

続いて、設定したルールを適用します。

# restorecon -v -R /storage/video/
restorecon reset /storage/video/3/video.mp4 context unconfined_u:object_r:user_tmp_t:s0->unconfined_u:object_r:httpd_sys_rw_content_t:s0

他のファイルのコンテキストもルールに従って再設定されました。