Скачать целые каталоги в Python SimpleHTTPServer - PullRequest
5 голосов
/ 04 апреля 2010

Мне очень нравится, как я могу легко обмениваться файлами в сети, используя SimpleHTTPServer, но мне хотелось бы, чтобы была опция, например, «загрузить весь каталог». Есть ли простой (один вкладыш) способ реализовать это?

Спасибо

Ответы [ 4 ]

5 голосов
/ 04 апреля 2010

Посмотрите на источники, например онлайн здесь . Прямо сейчас, если вы вызываете сервер с URL-адресом, который является каталогом, его файл index.html обслуживается, или, если его пропустить, вызывается метод list_directory. Предположительно, вместо этого вы хотите создать файл zip с содержимым каталога (рекурсивно, я полагаю) и обслуживать это? Очевидно, что нет способа сделать это с изменением в одну строку, так как вы хотите заменить то, что сейчас является строками 68-80 (в методе send_head) плюс весь метод list_directory, строками 98-137 - это уже по крайней мере, более 50 строк; -).

Если вы согласны с изменением нескольких дюжин строк, а не одной, и семантика, которую я описал, - это то, что вам нужно, вы, конечно, можете создать требуемый zip-файл как cStringIO.StringIO объект с классом ZipFile и заполнить его os.walk в рассматриваемом каталоге (при условии, что вы хотите, рекурсивно, также получить все подкаталоги). Но это определенно не будет одной строкой; -).

5 голосов
/ 06 февраля 2013

Я сделал эту модификацию для вас, я не знаю, есть ли лучшие способы сделать это, но:

Просто сохраните файл (например: ThreadedHTTPServer.py) и получите доступ как:

$ python -m /path/to/ThreadedHTTPServer PORT

Версия BPaste Raw

Модификация также работает в многопоточном режиме, поэтому у вас не будет проблем с загрузкой и навигацией одновременно, код не организован, но:

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
import threading
import SimpleHTTPServer
import sys, os, zipfile

PORT = int(sys.argv[1])

def send_head(self):
    """Common code for GET and HEAD commands.

    This sends the response code and MIME headers.

    Return value is either a file object (which has to be copied
    to the outputfile by the caller unless the command was HEAD,
    and must be closed by the caller under all circumstances), or
    None, in which case the caller has nothing further to do.

    """
    path = self.translate_path(self.path)
    f = None

    if self.path.endswith('?download'):

        tmp_file = "tmp.zip"
        self.path = self.path.replace("?download","")

        zip = zipfile.ZipFile(tmp_file, 'w')
        for root, dirs, files in os.walk(path):
            for file in files:
                if os.path.join(root, file) != os.path.join(root, tmp_file):
                    zip.write(os.path.join(root, file))
        zip.close()
        path = self.translate_path(tmp_file)

    elif os.path.isdir(path):

        if not self.path.endswith('/'):
            # redirect browser - doing basically what apache does
            self.send_response(301)
            self.send_header("Location", self.path + "/")
            self.end_headers()
            return None
        else:

            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
    ctype = self.guess_type(path)
    try:
        # Always read in binary mode. Opening files in text mode may cause
        # newline translations, making the actual size of the content
        # transmitted *less* than the content-length!
        f = open(path, 'rb')
    except IOError:
        self.send_error(404, "File not found")
        return None
    self.send_response(200)
    self.send_header("Content-type", ctype)
    fs = os.fstat(f.fileno())
    self.send_header("Content-Length", str(fs[6]))
    self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
    self.end_headers()
    return f

def list_directory(self, path):

    try:
        from cStringIO import StringIO
    except ImportError:
        from StringIO import StringIO
    import cgi, urllib

    """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 os.error:
        self.send_error(404, "No permission to list directory")
        return None
    list.sort(key=lambda a: a.lower())
    f = StringIO()
    displaypath = cgi.escape(urllib.unquote(self.path))
    f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
    f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
    f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
    f.write("<a href='%s'>%s</a>\n" % (self.path+"?download",'Download Directory Tree as Zip'))
    f.write("<hr>\n<ul>\n")
    for name in list:
        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 /
        f.write('<li><a href="%s">%s</a>\n'
                % (urllib.quote(linkname), cgi.escape(displayname)))
    f.write("</ul>\n<hr>\n</body>\n</html>\n")
    length = f.tell()
    f.seek(0)
    self.send_response(200)
    encoding = sys.getfilesystemencoding()
    self.send_header("Content-type", "text/html; charset=%s" % encoding)
    self.send_header("Content-Length", str(length))
    self.end_headers()
    return f

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
Handler.send_head = send_head
Handler.list_directory = list_directory

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

if __name__ == '__main__':
    server = ThreadedHTTPServer(('0.0.0.0', PORT), Handler)
    print 'Starting server, use <Ctrl-C> to stop'
    server.serve_forever()
4 голосов
/ 04 апреля 2010

Не существует ни одного лайнера, который бы это делал, а также что вы подразумеваете под «загрузить весь каталог» в виде tar или zip?

В любом случае вы можете выполнить следующие действия

  1. Получите класс из SimpleHTTPRequestHandler или просто скопируйте его код
  2. Измените метод list_directory, чтобы он возвращал ссылку на «скачать всю папку»
  3. Измените метод copyfile, чтобы для ваших ссылок вы архивировали весь каталог и возвращали его
  4. Вы можете кэшировать zip, чтобы каждый раз не заархивировать папку, вместо этого посмотрите, изменен ли какой-либо файл

Было бы забавным занятием:)

0 голосов
/ 16 марта 2019

Простого пути не существует.

Альтернативой является использование приведенного ниже скрипта python для рекурсивной загрузки всей папки. Это хорошо работает для Python 3. При необходимости измените URL.

import os
from pathlib import Path
from urllib.parse import urlparse, urljoin
import requests
from bs4 import BeautifulSoup

def get_links(content):
    soup = BeautifulSoup(content)
    for a in soup.findAll('a'):
        yield a.get('href')

def download(url):
    path = urlparse(url).path.lstrip('/')
    print(path)
    r = requests.get(url)
    if r.status_code != 200:
        raise Exception('status code is {} for {}'.format(r.status_code, url))
    content = r.text
    if path.endswith('/'):
        Path(path.rstrip('/')).mkdir(parents=True, exist_ok=True)
        for link in get_links(content):
            if not link.startswith('.'): # skip hidden files such as .DS_Store
                download(urljoin(url, link))
    else:
        with open(path, 'w') as f:
            f.write(content)


if __name__ == '__main__':
    # the trailing / indicates a folder
    url = 'http://ed470d37.ngrok.io/a/bc/'
    download(url)
...