LoginSignup
45
71

More than 1 year has passed since last update.

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

Posted at

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

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

環境

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

簡易的に行う.

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

$ python -m http.server 8000

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

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

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

カスタマイズする.

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

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

http.serverの公式ドキュメントソースとにらめっこすると
SimpleHTTPRequestHandlerのlist_directoryがページ生成してることがわかるので
継承して目的の関数だけオーバーロードする.

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


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

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

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,
    )

あとは

python myserver.py

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

45
71
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
71