pythonでローカルwebサーバーをたてていい感じにファイルを管理する．

# pythonでローカルwebサーバーをたてていい感じにファイルを管理する．

webサーバーを立ち上げてブラウザからファイルを見れるようにすることで
wifi経由でスマホやiPad等からPC内のファイルを見れるようにできる．
[pythonでローカルwebサーバを立ち上げる](https://qiita.com/okhrn/items/4d3c74563154f191ba16)より込み入った内容になっている．

## 環境

```sh
$ uname -a
Linux ubuntu20 5.4.0-73-generic
$ python --version
Python 3.8.5
```

## 簡易的に行う．

rootにしたいディレクトリで以下のコマンドを打てばいい．

```sh
$ python -m http.server 8000
```

http://localhost:8000/
にアクセスすると成功．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70126/e9752007-d897-9216-975e-95052b3921f8.png" width="150">

実行してるパソコン以外，スマホやiPadからならPCのIPアドレスしらべて
そのIPアドレスにアクセスすればいい．
たとえばPCのIPアドレスが192.168.11.11なら
http://192.168.11.11:8000/
にアクセスすればいい．

IPアドレスはipconfigで調べればいい．

## カスタマイズする．

なんか物足りないので自分でカスタマイズする．

クライアント（ブラウザ）側でjavascript書くなりする方法と
サーバー側でpythonいじる方法があるが
複数の端末で使用することを想定するとサーバーを書き換えたほうが良さそう．

http.serverの[公式ドキュメント](https://docs.python.org/3/library/http.server.html)や[ソース](https://github.com/python/cpython/blob/3.9/Lib/http/server.py)とにらめっこすると
SimpleHTTPRequestHandlerのlist_directoryがページ生成してることがわかるので
継承して目的の関数だけオーバーロードする．

- 隠しフォルダを表示させない
- 画像やpdfファイルのサムネイルを表示させる．

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70126/c8197c1a-e746-aaa2-b3f6-9e218d569522.png" width="300">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70126/bd496bbe-c672-5e6b-c52d-062e363cc924.png" width="300">

なんかいい感じになった．
ただしpdfのサムネは別途つくる必要あり．

もとのソースの変更を最小限にして書き換えてみた．推奨されていない表記もあるが
もとのソースに準じてそのままにしている．

```python:myserver.py
# import http.server
from http.server import test, SimpleHTTPRequestHandler, CGIHTTPRequestHandler, ThreadingHTTPServer
import html
import io
import os
import socket  # For gethostbyaddr()
import sys
import urllib.parse
import contextlib
from functools import partial

from http import HTTPStatus


class MyHTTPRequestHandler(SimpleHTTPRequestHandler):
    def list_directory(self, path):
        """Helper to produce a directory listing (absent index.html).
        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().
        """
        try:
            list = os.listdir(path)
        except OSError:
            self.send_error(
                HTTPStatus.NOT_FOUND,
                "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        r = []
        try:
            displaypath = urllib.parse.unquote(self.path,
                                               errors='surrogatepass')
        except UnicodeDecodeError:
            displaypath = urllib.parse.unquote(path)
        displaypath = html.escape(displaypath, quote=False)
        enc = sys.getfilesystemencoding()
        title = 'Directory listing for %s' % displaypath
        r.append('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" '
                 '"http://www.w3.org/TR/html4/strict.dtd">')
        r.append('<html>\n<head>')
        r.append('<meta http-equiv="Content-Type" '
                 'content="text/html; charset=%s">' % enc)
        r.append('<title>%s</title>\n</head>' % title)
        # cssの追加
        r.append('<style>img{height:240px}</style>\n')
        r.append('<body>\n<h1>%s</h1>' % title)
        r.append('<hr>\n<ul>')
        r.append('<div style="float:left">')
        r_sub = []
        for name in list:
            # 隠しフォルダを表示しない．
            # linux以外では知らん．
            if name[0] == '.':
                continue
            #
            fullname = os.path.join(path, name)
            displayname = linkname = name
            # Append / for directories or @ for symbolic links
            if os.path.isdir(fullname):
                displayname = name + "/"
                linkname = name + "/"
            if os.path.islink(fullname):
                displayname = name + "@"
                # Note: a link to a directory displays with @ and links with /
            r.append('<li><a href="%s">%s</a></li>'
                     % (urllib.parse.quote(linkname,
                                           errors='surrogatepass'),
                        html.escape(displayname, quote=False)))
        # ---- My Settings ----
        # 画像とPDFの場合，サムネも表示させるようにする．
        # PDFのサムネは事前に作成して，規定の場所に保存しておく．
            base, extension = os.path.splitext(name)
            if extension in [".png", ".jpg", ".jpeg"]:
                quote_path = urllib.parse.quote(
                    linkname, errors='surrogatepass')
                r_sub.append('<a href="%s"><img src="%s"></img></a>'
                             % (quote_path, quote_path))
            elif extension in [".pdf"]:
                quote_path = urllib.parse.quote(
                    linkname, errors='surrogatepass')
                head, tail = os.path.split(quote_path)
                thumnail_path = os.path.join(head, ".thumnail", base + ".jpeg")
                r_sub.append('<a href="%s"><img src="%s"></img></a>' %
                             (quote_path, thumnail_path))
        r.append('</div>')
        r += r_sub
        r.append('<div style="clear:left"></div>')
        # ---- My Setting ----
        r.append('</ul>\n<hr>\n</body>\n</html>\n')
        encoded = '\n'.join(r).encode(enc, 'surrogateescape')
        f = io.BytesIO()
        f.write(encoded)
        f.seek(0)
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        return f


if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('--cgi', action='store_true',
                        help='Run as CGI Server')
    parser.add_argument('--bind', '-b', metavar='ADDRESS',
                        help='Specify alternate bind address '
                             '[default: all interfaces]')
    parser.add_argument('--directory', '-d', default=os.getcwd(),
                        help='Specify alternative directory '
                        '[default:current directory]')
    parser.add_argument('port', action='store',
                        default=8000, type=int,
                        nargs='?',
                        help='Specify alternate port [default: 8000]')
    args = parser.parse_args()
    if args.cgi:
        handler_class = CGIHTTPRequestHandler
    else:
        # ここだけSimpleHTTPRequestHandlerからMyHTTPRequestHandlerに書き換える
        handler_class = partial(MyHTTPRequestHandler,
                                directory=args.directory)

    # ensure dual-stack is not disabled; ref #38907
    class DualStackServer(ThreadingHTTPServer):
        def server_bind(self):
            # suppress exception when protocol is IPv4
            with contextlib.suppress(Exception):
                self.socket.setsockopt(
                    socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
            return super().server_bind()

    test(
        HandlerClass=handler_class,
        ServerClass=DualStackServer,
        port=args.port,
        bind=args.bind,
    )
```

あとは

```sh
python myserver.py
```

で起動する．
これでiPadからPCに保存した自炊本たちが見れる〜〜．
pythonつかってpdfからサムネ作成するコードは需要があれば別途あげる．