支持upload文件的SimpleHTTPServer

作为 Python2 的标准库,SimpleHTTPServer 可以很方便的提供一个简单的页面服务器,很多文章已经进行了介绍。其默认的功能很简单——当前路径下如果有 index.html 或者 index.htm 文件,则直接解析显示;如果没有,则列出当前目录下的文件和文件夹目录。这个模块已知被我当做分享文件很快捷同时逼格又很高的方式,不过一直的问题就是,无法上传!于是耗费半个下午给它增加了上传文件的功能。下文是记录自己的处理问题的过程,最后对 SimpleHTTPServer 里的函数进行了简单介绍,不想看废话的可以直接去 gist 上获得文件源码:gist 地址

注:Python3版本中,SimpleHTTPServer 更名为 http.server,官方链接声明

The SimpleHTTPServer module has been merged into http.server in Python 3. The 2to3 tool will automatically adapt imports when converting your sources to Python 3.

查文档

遇到问题首先想到的当然是查文档。模块只定义了一个类 SimpleHTTPRequestHandler ,对它的描述是“interface-compatible with BaseHTTPServer.BaseHTTPRequestHandler.”,其实就是继承的BaseHTTPRequestHandler。类定义了两个属性和两个方法,方法是 do_GET() 和 do_HEAD(),看起来是对应 GET 请求相关的内容。于是直接跳转到 BaseHTTPServer 看文档。

BaseHTTPServer定义了两个类:

  • HTTPServer:基于 TCPServer 类,保存服务器地址和端口作为实例变量,是 Handler 可访问的。
  • BaseHTTPRequestHandler:用来处理服务器收到的 HTTP 请求,需要由子类来处理不同的 HTTP 请求。

重要的一句话:The handler will parse the request and the headers, then call a method specific to the request type. The method name is constructed from the request. For example, for the request method SPAM, the do_SPAM()method will be called with no arguments. All of the relevant information is stored in instance variables of the handler.

于是,我们需要做的,就是在 SimpleHTTPServer 文件中,添加 do_POST() 函数,并且处理 POST 的内容(文件),保存到本地就可以了。文档其他内容记录解释了其他的属性和方法,包括 rfile、wfile、headers 等,这些在处理 POST 请求时都需要用到。

找源文件

知道了怎么改,可是根本的 SimpleHTTPServer.py 的文件还不知道长什么样子呢啊。。。

在 Mac 系统中,对应的文件路径是:/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/SimpleHTTPServer.py  当然我也搜到了 Github 上的一个版本,不过是 Python2.7.3中的。

当时顺手搜了一下 “SimpleHTTPServer upload”,于是发现了前人已经造过轮子了……。后来又做了一番搜索,发现多数的相关的搜索结果,都是这个版本的文件。而且在某 gist 的评论中,有很多人添加了 SSL 支持,认证支持等等。本着不重复造轮子的原则,直接拿来用了。。。

似乎有些问题?

bug1:中文文件名问题

本地实验后,成功上传文件,但是中文的文件名不显示,保存文件名为“???.file_extenion”。当然想到是因为编码的问题,没有做过前端,一直以为是 Python 收到请求后编码保存不对,直到我在 Chrome 的审查元素中发现,POST 的内容直接就是不包含中文名的。。。(膝盖中了一箭!都是中文惹的祸)

在第一次请求页面时(最终定位是 list_directory 函数),返回的 HTML 文本中添加标签

使用 UTF-8 对页面进行解码,同时 POST 请求的编码也自动为 UTF-8(在浏览器中直接选择当前页面的编码也可以解决这个问题)。

bug2:不选择文件时,会创建文件名为”_”的文件

测试中文文件名问题的时候,意外的发现,没有选择文件时,也会显示 post 成功,当前目录下出现”_”的文件。查 POST 请求发现对应 file 字段为空字符串,问题一定在代码中

在 deal_post_data 函数中,有如下代码片段:

第一行正则匹配获得文件名,然后判断文件名是否为空,之后判断文件在系统中是否存在,添加下划线进行重命名。问题一定在第一行的结果!print 大法之后发现,fn 是一个 list,并不是直接一个字符串,于是 fn 就是一个包含空字符串的 list,对应为 True 。不清楚是因为版本变化让正则的返回结果变化还是原作者没有发现这个问题。修改第二行 fn 为 fn[0]解决问题。

最终版本

赶时髦,也创建个 gist 来展示代码。https://gist.github.com/BUPTGuo/007a6e589c0d2e48aac6 ,而且厚颜无耻的在作者里加上自己的名字。

后话

来回几遍看 SimpleHTTPServer 的代码,也基本搞清楚了代码中各个函数和属性的含义,记录如下:

  • do_xxx:响应对应的 HTTP 请求操作,目前有 GET、HEAD、POST(我都忘了还有 HEAD 这个请求)
  • send_head:返回请求头部,无参版本,判断 URL 结尾、目录下是否有 index.html等。调用了基类中对应填充 header 内容的有参数版本
  • list_directory:获取目录下的文件列表,并且构造页面。
  • translate_path:清除 URL 请求中的参数,获得对应的目录
  • copyfile:文件拷贝,GET 下载文件和 POST 上传文件时使用。使用的 shutil 中的 copyfileobj 函数
  • guess_type:对文件类型进行推测,在 header 的 “Content-type” 字段中用到,使用 posixpath 对文件名和扩展名进行分割实现。可以添加 extensions_map 对更多文件扩展名进行支持
  • rfile、wfile:基类 BaseHTTPRequestHandler 中的属性。是输入流(上传)和输出流(下载),进行上传和下载时需要进行处理。
  • headers:Holds an instance of the class specified by the MessageClass class variable. This instance parses and manages the headers in the HTTP request.

最后的最后,还是因为懒没有自己实现。。。不过自己实现的话,看明白文档之后,更多的就是对请求中各个字段进行处理,这样看明白代码实现也挺好的。

支持upload文件的SimpleHTTPServer》上有4条评论

    1. FrozenMap

      Hi, bones7456 and BUPTGuo~

      根据你俩的代码和思路,我写了基于python3.4的SimpleHttpServerWithUpload,代码托管在 [这里](https://github.com/jJayyyyyyy/cs/tree/master/just%20for%20fun/file_transfer/http) ,
      然后整个修改过程写在了 [这篇文章](https://jjayyyyyyy.github.io/2016/10/07/python3%E9%87%8D%E5%86%99SimpleHTTPServerWithUpload.html) 。

      另外还有对BUPTGuo同学在 [gist](https://gist.github.com/BUPTGuo/007a6e589c0d2e48aac6) 上留下的TODO的一些想法,文章中和gist上都有提到。

      欢迎拍砖哈~

      回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注