zoukankan      html  css  js  c++  java
  • python3 http.server作为服务端,h5作为网页实现手机传输文件到PC

    一、背景

    把手机上的文件传到PC;
    原因:手机存储太小
    矛盾:
    1.不想安装乱七八糟的软件;
    2.不想安装手机助手,不想插USB;
    3.不想用社交软件QQ、微信、钉钉等传文件;
    那么能不能自己做一个简单工具呢
    答案:可以,正好PC有python3环境作为服务端,手机端使用浏览器访问html上传文件。
    结果:最终手机端用QQ浏览器尝试成功,一次上传200张图片都没有问题。

    二、方法

    文件

    目录如下

    [root@lh uploader]# tree 
    └────css
        └────bootstrap.min.css
        └────demo.css
        └────font-awesome.css
        └────style.css
        └────webuploader.css
    └────index.html    #主要的网页文件
    └────script
        └────jquery-1.8.2.min.js
        └────Uploader.swf
        └────webuploader.custom.js
        └────webuploader.custom.min.js
        └────webuploader.fis.js
        └────webuploader.flashonly.js
        └────webuploader.flashonly.min.js
        └────webuploader.html5only.js
        └────webuploader.html5only.min.js
        └────webuploader.js
        └────webuploader.min.js
        └────webuploader.noimage.js
        └────webuploader.noimage.min.js
        └────webuploader.nolog.js
        └────webuploader.nolog.min.js
        └────webuploader.withoutimage.js
        └────webuploader.withoutimage.min.js
    └────simple.py     #作为服务端的python程序
    

    其中css和script中的文件从以下两个地址中下载抽取:

    https://codeload.github.com/fex-team/webuploader/zip/refs/tags/0.1.5
    https://fontawesome.dashgame.com/assets/font-awesome-4.7.0.zip
    

    服务端

    # -*- coding: utf-8 -*-
    #!/usr/bin/env python3
    """Simple HTTP Server With Upload.
    
    This module builds on BaseHTTPServer by implementing the standard GET
    and HEAD requests in a fairly straightforward manner.
    
    see: https://gist.github.com/UniIsland/3346170
    """
     
     
    __version__ = "0.1"
    __all__ = ["SimpleHTTPRequestHandler"]
     
    import os
    import posixpath
    import http.server
    import urllib.request, urllib.parse, urllib.error
    import cgi
    import shutil
    import mimetypes
    import re
    from io import BytesIO
     
     
    class SimpleHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
     
        """Simple HTTP request handler with GET/HEAD/POST commands.
    
        This serves files from the current directory and any of its
        subdirectories.  The MIME type for files is determined by
        calling the .guess_type() method. And can reveive file uploaded
        by client.
    
        The GET/HEAD/POST requests are identical except that the HEAD
        request omits the actual contents of the file.
    
        """
     
        def _send_cors_headers(self):
            """ Sets headers required for CORS """
            self.send_header('Content-type', 'application/json')
            self.send_header("Access-Control-Allow-Origin", "*")
            self.send_header("Access-Control-Allow-Methods", "*")
            self.send_header("Access-Control-Allow-Headers", "Authorization, Content-Type")
    
        def do_GET(self):
            """Serve a GET request."""
            f = self.send_head()
            if f:
                self.copyfile(f, self.wfile)
                f.close()
    
        def do_OPTIONS(self):
            self.send_response(200)
            self._send_cors_headers()
            self.end_headers()
    
        def do_HEAD(self):
            """Serve a HEAD request."""
            f = self.send_head()
            if f:
                f.close()
     
        def do_POST(self):
            """Serve a POST request."""
            r, info = self.deal_post_data()
            print((r, info, "by: ", self.client_address))
            f = BytesIO()
            f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
            f.write(b"<html>
    <title>Upload Result Page</title>
    ")
            f.write(b"<body>
    <h2>Upload Result Page</h2>
    ")
            f.write(b"<hr>
    ")
            if r:
                f.write(b"<strong>Success:</strong>")
            else:
                f.write(b"<strong>Failed:</strong>")
            f.write(info.encode())
            f.write(("<br><a href="%s">back</a>" % self.headers['referer']).encode())
            f.write(b"<hr><small>Powerd By: bones7456, check new version at ")
            f.write(b"<a href="http://li2z.cn/?s=SimpleHTTPServerWithUpload">")
            f.write(b"here</a>.</small></body>
    </html>
    ")
            length = f.tell()
            f.seek(0)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.send_header("Content-Length", str(length))
            self.end_headers()
            if f:
                self.copyfile(f, self.wfile)
                f.close()
            
        def deal_post_data(self):
            content_type = self.headers['content-type']
            if not content_type:
                return (False, "Content-Type header doesn't contain boundary")
            boundary = content_type.split("=")[1].encode()
            remainbytes = int(self.headers['content-length'])
            line = self.rfile.readline()
            remainbytes -= len(line)
            if not boundary in line:
                return (False, "Content NOT begin with boundary")
            line = self.rfile.readline()
            remainbytes -= len(line)
            fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode())
            while not fn:
                line = self.rfile.readline()
                remainbytes -= len(line)
                fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode())
                if (remainbytes <= 0):
                    return (False, "Can't find out file name...")
            path = self.translate_path(self.path)
            fn = os.path.join(path, fn[0])
            line = self.rfile.readline()
            remainbytes -= len(line)
            line = self.rfile.readline()
            remainbytes -= len(line)
            try:
                out = open(fn, 'wb')
            except IOError:
                return (False, "Can't create file to write, do you have permission to write?")
                    
            preline = self.rfile.readline()
            remainbytes -= len(preline)
            while remainbytes > 0:
                line = self.rfile.readline()
                remainbytes -= len(line)
                if boundary in line:
                    preline = preline[0:-1]
                    if preline.endswith(b'
    '):
                        preline = preline[0:-1]
                    out.write(preline)
                    out.close()
                    return (True, "File '%s' upload success!" % fn)
                else:
                    out.write(preline)
                    preline = line
            return (False, "Unexpect Ends of data.")
     
        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 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
                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.send_header("Access-Control-Allow-Origin", "*")
            self.send_header("Access-Control-Allow-Methods", "*")
            self.send_header("Access-Control-Allow-Headers", "Authorization, Content-Type")
            self.end_headers()
            return f
     
        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 os.error:
                self.send_error(404, "No permission to list directory")
                return None
            list.sort(key=lambda a: a.lower())
            f = BytesIO()
            displaypath = cgi.escape(urllib.parse.unquote(self.path))
            f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
            f.write(("<html>
    <title>Directory listing for %s</title>
    " % displaypath).encode())
            f.write(("<body>
    <h2>Directory listing for %s</h2>
    " % displaypath).encode())
            f.write(b"<hr>
    ")
            f.write(b"<form ENCTYPE="multipart/form-data" method="post">")
            f.write(b"<input name="file" type="file"/>")
            f.write(b"<input type="submit" value="upload"/></form>
    ")
            f.write(b"<hr>
    <ul>
    ")
            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>
    '
                        % (urllib.parse.quote(linkname), cgi.escape(displayname))).encode())
            f.write(b"</ul>
    <hr>
    </body>
    </html>
    ")
            length = f.tell()
            f.seek(0)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.send_header("Content-Length", str(length))
            self.end_headers()
            return f
     
        def translate_path(self, path):
            """Translate a /-separated PATH to the local filename syntax.
    
            Components that mean special things to the local file system
            (e.g. drive or directory names) are ignored.  (XXX They should
            probably be diagnosed.)
    
            """
            # abandon query parameters
            path = path.split('?',1)[0]
            path = path.split('#',1)[0]
            path = posixpath.normpath(urllib.parse.unquote(path))
            words = path.split('/')
            words = [_f for _f in words if _f]
            path = os.getcwd()
            for word in words:
                drive, word = os.path.splitdrive(word)
                head, word = os.path.split(word)
                if word in (os.curdir, os.pardir): continue
                path = os.path.join(path, word)
            return path
     
        def copyfile(self, source, outputfile):
            """Copy all data between two file objects.
    
            The SOURCE argument is a file object open for reading
            (or anything with a read() method) and the DESTINATION
            argument is a file object open for writing (or
            anything with a write() method).
    
            The only reason for overriding this would be to change
            the block size or perhaps to replace newlines by CRLF
            -- note however that this the default server uses this
            to copy binary data as well.
    
            """
            shutil.copyfileobj(source, outputfile)
     
        def guess_type(self, path):
            """Guess the type of a file.
    
            Argument is a PATH (a filename).
    
            Return value is a string of the form type/subtype,
            usable for a MIME Content-type header.
    
            The default implementation looks the file's extension
            up in the table self.extensions_map, using application/octet-stream
            as a default; however it would be permissible (if
            slow) to look inside the data to make a better guess.
    
            """
     
            base, ext = posixpath.splitext(path)
            if ext in self.extensions_map:
                return self.extensions_map[ext]
            ext = ext.lower()
            if ext in self.extensions_map:
                return self.extensions_map[ext]
            else:
                return self.extensions_map['']
     
        if not mimetypes.inited:
            mimetypes.init() # try to read system mime.types
        extensions_map = mimetypes.types_map.copy()
        extensions_map.update({
            '': 'application/octet-stream', # Default
            '.py': 'text/plain',
            '.c': 'text/plain',
            '.h': 'text/plain',
            })
     
     
    def test(HandlerClass = SimpleHTTPRequestHandler,
             ServerClass = http.server.HTTPServer):
        http.server.test(HandlerClass, ServerClass, "HTTP/1.0", 8085)
     
    if __name__ == '__main__':
        test()   
    

    执行方法:

    python3 simple.py
    

    网页代码

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
      <title>WebUploader文件上传示例</title>
      
      <script type="text/javascript" src="script/jquery-1.8.2.min.js"></script>   
      <script type="text/javascript" src="script/webuploader.min.js"></script>
      <link href="css/webuploader.css" rel="stylesheet" />
      <link href="css/bootstrap.min.css" rel="stylesheet"/>   
      <link href="css/style.css" rel="stylesheet" />
      <link href="css/demo.css" rel="stylesheet" />
      <link href="css/font-awesome.css" rel="stylesheet" /> 
      
      <script type="text/javascript">
        var applicationPath = window.applicationPath === "" ? "" : window.applicationPath || "../../";
        // 文件上传
        jQuery(function () {
            var $ = jQuery,
                $list = $('#fileList'),
                $btn = $('#ctlBtn'),
                state = 'pending',
                uploader;
            uploader = WebUploader.create({
                // 不压缩image
                resize: false, 
    
                // swf文件路径
                swf: applicationPath + 'Script/webuploader/Uploader.swf',
    
                // 文件接收服务端。
                server: 'http://192.168.1.123:8085/',
    
                // 选择文件的按钮。可选。
                // 内部根据当前运行是创建,可能是input元素,也可能是flash.
                pick: {
                   id:'#picker',
                   multiple:true
                },
                multiple: true
    
            }); 
    
            // 当有文件添加进来的时候
            uploader.on('fileQueued', function (file) {
    
                $list.append('<div id="' + file.id + '" class="item">' +
                    '<h4 class="info">' + file.name + '</h4>' +
                    '<p class="state">等待上传...</p>' +
                '</div>');
    
            });
    
            // 文件上传过程中创建进度条实时显示。
            uploader.on('uploadProgress', function (file, percentage) {
    
                var $li = $('#' + file.id),
                    $percent = $li.find('.progress .progress-bar'); 
                // 避免重复创建
                if (!$percent.length) {
                    $percent = $('<div class="progress progress-striped active">' +
                      '<div class="progress-bar" role="progressbar" style=" 0%">' +
                      '</div>' +
                    '</div>').appendTo($li).find('.progress-bar');
                }
                $li.find('p.state').text('上传中');
                $percent.css('width', percentage * 100 + '%');
    
            });
    
            uploader.on('uploadSuccess', function (file) {
                $('#' + file.id).find('p.state').text('已上传');
            });
    
            uploader.on('uploadError', function (file) {
                $('#' + file.id).find('p.state').text('上传出错');
            });
    
            uploader.on('uploadComplete', function (file) {
                $('#' + file.id).find('.progress').fadeOut();
            });
            uploader.on('all', function (type) {
                if (type === 'startUpload') {
                    state = 'uploading';
                } else if (type === 'stopUpload') {
                    state = 'paused';
                } else if (type === 'uploadFinished') {
                    state = 'done';
                }
                if (state === 'uploading') {
                    $btn.text('暂停上传');
                } else {
                    $btn.text('开始上传');
                }
    
                 });
    
                 $btn.on('click', function () {
                     if (state === 'uploading') {
                         uploader.stop();
                     } else {
                         uploader.upload();
                     }
                 });
        });
      </script>
    </head>
    
    <body>
      <div  class="container-fluid">
         <div class="col-md-10">
           <div class="row">文件上传示例:</div>
           <div class="row">
             <div id="uploader" class="wu-example">
             <!--用来存放文件信息-->
               <div id="fileList" class="uploader-list"></div>
               <div class="btns">
                 <div id="picker" class="btn btn-primary">选择文件</div>
               </div>
             </div>
           </div>
           <div class="row"></div>
           <div class="row"><button id="ctlBtn" class="btn btn-default">开始上传</button></div>
        </div>
        <div>
        </div>
      </div>
    </body>
    </html>
    

    使用方法
    直接访问"http://192.168.1.123:8085/"即可,其中192.168.1.123是PC机的IP。

    手机端

    很遗憾,大多数手机浏览器不支持多选,只有QQ和Chrome两款手机浏览器支持。
    QQ浏览器84MB,Chrome94MB,其中Chrome在应用商店还找不到。
    强忍着QQ浏览器恶心的各种垃圾推送完成了测试。

    三、问题和解决方法

    1.如何修改监听端口?

    http.server.test(HandlerClass, ServerClass, "HTTP/1.0", 8085) #修改simple.py这句中的8085
    

    2.如何修改上传目录

    #simple.py中把path = self.translate_path(self.path)改成自己的目录即可
    path = /home/upload/
    

    3.上传没有反应或报错"Provisional headers are shown"
    跨域问题导致的,在index.html中,将如下部分改成自己PC的地址

    // 文件接收服务端。
    server: 'http://192.168.1.123:8085/',
    

    4.手机上不能多选图片
    答:目前支持多选的浏览器只有QQ浏览器和chrome浏览器。

    四、参考网址

    https://blog.csdn.net/weixin_33595571/article/details/86558658
    https://blog.csdn.net/qq_37254866/article/details/84826219
    https://www.cnblogs.com/sheqiuluo/p/7061278.html
    https://www.zhihu.com/question/24212111
    https://blog.csdn.net/yyt593891927/article/details/112025503
    https://blog.csdn.net/syc000666/article/details/107846080

    转载请注明来源:https://www.cnblogs.com/bugutian/
  • 相关阅读:
    前端通过ajax请求,调用net core webapi接口
    WeUI——手机验证码
    Docker学习笔记之--安装mssql(Sql Server)并使用Navicat连接测试(环境:centos7)
    Docker学习笔记之-推送镜像到Registrys仓库
    PuppeteerSharp 在asp.net中使用 PuppeteerSharp生命周期管理
    vscode 插件记录下
    angular 项目8升级9 踩坑
    Skoruba.IdentityServer4.Admin 踩坑
    VUE-MATOMO实现埋点
    netcore rpm
  • 原文地址:https://www.cnblogs.com/bugutian/p/14884847.html
Copyright © 2011-2022 走看看