python HTTP Server 文件上传与下载
实现在局域网(同一WIFI下) 文件上传与下载
该模块通过实现标准GET在BaseHTTPServer上构建
和HEAD请求。(将所有代码粘贴到同一个py文件中,即可使用)
所需包
基于python3版本实现,python2版本无涉猎
import os
import sys
import argparse
import posixpath
try:
from html import escape
except ImportError:
from cgi import escape
import shutil
import mimetypes
import re
import signal
from io import StringIO, BytesIO
if sys.version_info.major == 3:
from urllib.parse import quote
from urllib.parse import unquote
from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
基本类 简单HTTP服务类
带有GET/HEAD/POST命令的简单HTTP请求处理程序。
这将提供当前目录中的文件及其子目录。
文件的MIME类型由调用.gues_type()方法。
并且可以接收上传的文件由客户提供。
GET/HEAD/POST请求是相同的,除了HEAD请求忽略文件的实际内容。
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
server_version = "simple_http_server/" + __version__
def do_GET(self):
"""Serve a GET request."""
fd = self.send_head()
if fd:
shutil.copyfileobj(fd, self.wfile)
fd.close()
def do_HEAD(self):
"""Serve a HEAD request."""
fd = self.send_head()
if fd:
fd.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>\n<title>Upload Result Page</title>\n")
f.write(b"<body>\n<h2>Upload Result Page</h2>\n")
f.write(b"<hr>\n")
if r:
f.write(b"<strong>Success:</strong>")
else:
f.write(b"<strong>Failed:</strong>")
f.write(info.encode('utf-8'))
f.write(b"<br><a href=\".\">back</a>")
f.write(b"<hr><small>Powered By: freelamb, check new version at ")
f.write(b"<a href=\"https://github.com/freelamb/simple_http_server\">")
f.write(b"here</a>.</small></body>\n</html>\n")
length = f.tell()
f.seek(0)
self.send_response(200)
self.send_header("Content-type", "text/html;charset=utf-8")
self.send_header("Content-Length", str(length))
self.end_headers()
if f:
shutil.copyfileobj(f, self.wfile)
f.close()
def deal_post_data(self):
boundary = self.headers["Content-Type"].split("=")[1].encode('utf-8')
remain_bytes = int(self.headers['content-length'])
line = self.rfile.readline()
remain_bytes -= len(line)
if boundary not in line:
return False, "Content NOT begin with boundary"
line = self.rfile.readline()
remain_bytes -= len(line)
fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode('utf-8'))
if not fn:
return False, "Can't find out file name..."
path = translate_path(self.path)
fn = os.path.join(path, fn[0])
while os.path.exists(fn):
fn += "_"
line = self.rfile.readline()
remain_bytes -= len(line)
line = self.rfile.readline()
remain_bytes -= len(line)
try:
out = open(fn, 'wb')
except IOError:
return False, "Can't create file to write, do you have permission to write?"
pre_line = self.rfile.readline()
remain_bytes -= len(pre_line)
while remain_bytes > 0:
line = self.rfile.readline()
remain_bytes -= len(line)
if boundary in line:
pre_line = pre_line[0:-1]
if pre_line.endswith(b'\r'):
pre_line = pre_line[0:-1]
out.write(pre_line)
out.close()
return True, "File '%s' upload success!" % fn
else:
out.write(pre_line)
pre_line = line
return False, "Unexpect Ends of data."
def send_head(self):
"""
GET和HEAD命令的通用代码。
这将发送响应代码和MIME标头。
返回值要么是文件对象
(除非命令是HEAD,否则调用方必须将其复制到输出文件中,
并且在任何情况下都必须由调用方关闭),
要么是None,在这种情况下,调用方无需进一步操作。
"""
path = translate_path(self.path)
if os.path.isdir(path):
if not self.path.endswith('/'):
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)
content_type = self.guess_type(path)
try:
f = open(path, 'rb')
except IOError:
self.send_error(404, "File not found")
return None
self.send_response(200)
self.send_header("Content-type", content_type)
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):
"""
帮助程序生成目录列表(缺少index.html)。
返回值为file对象或None(表示错误)。
无论哪种情况,都会发送标头接口与send_head()相同。
"""
try:
list_dir = os.listdir(path)
except os.error:
self.send_error(404, "No permission to list directory")
return None
list_dir.sort(key=lambda a: a.lower())
f = BytesIO()
display_path = escape(unquote(self.path))
f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
f.write(b"<html>\n<title>Directory listing for %s</title>\n" % display_path.encode('utf-8'))
f.write(b"<body>\n<h2>Directory listing for %s</h2>\n" % display_path.encode('utf-8'))
f.write(b"<hr>\n")
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>\n")
f.write(b"<hr>\n<ul>\n")
for name in list_dir:
fullname = os.path.join(path, name)
display_name = linkname = name
if os.path.isdir(fullname):
display_name = name + "/"
linkname = name + "/"
if os.path.islink(fullname):
display_name = name + "@"
f.write(
b'<li><a href="%s">%s</a>\n' % (quote(linkname).encode('utf-8'), escape(display_name).encode('utf-8')))
f.write(b"</ul>\n<hr>\n</body>\n</html>\n")
length = f.tell()
f.seek(0)
self.send_response(200)
self.send_header("Content-type", "text/html;charset=utf-8")
self.send_header("Content-Length", str(length))
self.end_headers()
return f
def guess_type(self, path):
"""
参数是PATH(文件名)。
返回值是表单类型/子类型的字符串,
可用于MIME内容类型标头。
默认实现在self.extensions_map表中查找文件的扩展名,默认使用application/octet流;
"""
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()
extensions_map = mimetypes.types_map.copy()
extensions_map.update({
'': 'application/octet-stream',
'.py': 'text/plain',
'.c': 'text/plain',
'.h': 'text/plain',
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
文件路径处理
将/分隔的PATH转换为本地文件名语法。
对本地文件系统有特殊意义的组件(例如驱动器或目录名),那么可能会被阻止或诊断。
def translate_path(path):
path = path.split('?', 1)[0]
path = path.split('#', 1)[0]
path = posixpath.normpath(unquote(path))
words = path.split('/')
words = filter(None, words)
path = os.getcwd()
path = path+"/file_xxx/xxx"
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
信息提醒
如果HTTP Server被关闭
def signal_handler(signal, frame):
print("You choose to stop me.")
exit()
HTTP Server 初始化
设置HTTP Server初始数值,基于自己电脑设置。
对于IP来说,双方ip设置应一样。
__version__ = "0.3.1"
def _argparse():
parser = argparse.ArgumentParser()
ip = input("请输入IP地址:")
parser.add_argument('--bind', '-b', metavar='ADDRESS', default=ip,
help='Specify alternate bind address [default: all interfaces]')
parser.add_argument('--version', '-v', action='version', version=__version__)
parser.add_argument('port', action='store', default=8000, type=int, nargs='?',
help='Specify alternate port [default: 8000]')
return parser.parse_args()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
启用HTTP Server
py程序启动后,会输出网址,点击后,会自动进入HTTP服务,可以进行文件传输操作。
def main():
args = _argparse()
server_address = (args.bind, args.port)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
server = httpd.socket.getsockname()
print(
"server_version: " + SimpleHTTPRequestHandler.server_version + ", python_version: " + SimpleHTTPRequestHandler.sys_version)
print("sys encoding: " + sys.getdefaultencoding())
print("Serving http on: " + str(server[0]) + ", port: " + str(server[1]) + " ... (http://" + server[0] + ":" + str(
server[1]) + "/)")
httpd.serve_forever()
if __name__ == '__main__':
main()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
=======================================================================================
=======================================================================================
一、服务端接口
import flask, os,sys,time
from flask import request, send_from_directory
interface_path = os.path.dirname(__file__)
sys.path.insert(0, interface_path) #将当前文件的父目录加入临时系统变量
server = flask.Flask(__name__)
#get方法:指定目录下载文件
@server.route('/download', methods=['get'])
def download():
fpath = request.values.get('path', '') #获取文件路径
fname = request.values.get('filename', '') #获取文件名
if fname.strip() and fpath.strip():
print(fname, fpath)
if os.path.isfile(os.path.join(fpath,fname)) and os.path.isdir(fpath):
return send_from_directory(fpath, fname, as_attachment=True) #返回要下载的文件内容给客户端
else:
return '{"msg":"参数不正确"}'
else:
return '{"msg":"请输入参数"}'
# get方法:查询当前路径下的所有文件
@server.route('/getfiles', methods=['get'])
def getfiles():
fpath = request.values.get('fpath', '') #获取用户输入的目录
print(fpath)
if os.path.isdir(fpath):
filelist = os.listdir(fpath)
files = [file for file in filelist if os.path.isfile(os.path.join(fpath, file))]
return '{"files":"%s"}' % files
# post方法:上传文件的
@server.route('/upload', methods=['post'])
def upload():
fname = request.files.get('file') #获取上传的文件
if fname:
t = time.strftime('%Y%m%d%H%M%S')
new_fname = r'upload/' + t + fname.filename
fname.save(new_fname) #保存文件到指定路径
return '{"code": "ok"}'
else:
return '{"msg": "请上传文件!"}'
server.run(port=8000, debug=True)
二、客户端发送请求
import requests
import os
#上传文件到服务器
file = {'file': open('hello.txt','rb')}
r = requests.post('http://127.0.0.1:8000/upload', files=file)
print(r.text)
#查询fpath下的所有文件
r1 = requests.get('http://127.0.0.1:8000/getfiles',data={'fpath': r'download/'})
print(r1.text)
#下载服务器download目录下的指定文件
r2 = requests.get('http://127.0.0.1:8000/download',data={'filename':'hello_upload.txt', 'path': r'upload/'})
file = r2.text #获取文件内容
basepath = os.path.join(os.path.dirname(__file__), r'download/')
with open(os.path.join(basepath, 'hello_download.txt'),'w',encoding='utf-8') as f: #保存文件
f.write(file)
=======================================================================================
一、数据粘包
【1】客户端两次发送请求,但是可能被服务端的同个recv收到,不能区分,会造成数据粘包(实际上需要服务端将两次请求区分接受)
二、服务器
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8889))
sk.listen()
def get_file(sk_obj):
'''
接收文件
:param sk_obj: 文件对象
:return:
'''
file_size = int(sk_obj.recv(1024).decode('utf8'))
sk_obj.sendall(b'ok')
file_name = sk_obj.recv(1024).decode('utf8')
sk_obj.sendall(b'ok')
with open('./%s' %file_name,'wb') as f:
while file_size > 0:
f.write(sk_obj.recv(1024))
file_size -= 1024
conn ,addr = sk.accept()
get_file(conn)
conn.close()
sk.close()
三、客户端
import os
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8889))
def post_file(sk_obj,file_path):
'''
发送文件,需要和接收文件一一对应
:param sk_obj:文件对象
:param file_path:文件路径
:return:
'''
file_size = os.stat(file_path).st_size
sk_obj.sendall(str(file_size).encode('utf8'))
sk_obj.recv(1024)
file_name = os.path.split(file_path)[1]
sk_obj.sendall(file_name.encode('utf8'))
sk_obj.recv(1024)
with open(file_path,'rb') as f:
while file_size > 0:
sk_obj.sendall(f.read(1024))
file_size -= 1024
path = '/Users/panshaoying/Desktop/database/data/img1.png'
post_file(sk,path)
sk.close()
=======================================================================================