#python #sockets #http #post
Вопрос:
Я пытаюсь создать веб-сервер Python, который может принимать файлы. Чтобы кто-то мог зайти на веб-сайт, нажмите кнопку «Загрузить» в форме, затем файл будет отправлен на сервер и сохранен локально на сервере.
Вот содержание index.html
<form enctype="multipart/form-data" action="" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="8000000" />
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
Содержание Server.py
import socket
class server():
def __init__(self):
self.host_ip = socket.gethostbyname(socket.gethostname())
self.host_port = 81
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.data_recv_size = 1024
def get_data(self, conn):
""" gets the data from client """
data = b""
while b"rnrn" not in data:
data = conn.recv(self.data_recv_size)
return data
def server(self):
""" main method starts the server """
print(f"[ ] Server started listening on port {self.host_port}!")
print(f"[ ] Server Ip: {self.host_ip}")
self.s.bind((self.host_ip, self.host_port))
self.s.listen()
while True:
conn, addr = self.s.accept()
with conn:
data = self.get_data(conn)
# GET request
if data[0:5] == b"GET /":
index = open("index.html", "rb").read()
conn.sendall(b"HTTP/1.0 200 OKnContent-Type: text/htmlnn" index)
print("[ ] Responded to GET request")
# POST request
elif data[0:4] == b"POST":
with open("output.txt", "ab") as file:
file.write(data)
print(f"{len(data)} bytes received from post!")
conn.sendall(b"HTTP/1.0 200 OKrnContent-Type: text/html")
s = server()
s.server()
Часть GET сервера работает правильно, когда я посещаю веб-сайт, index.html файл отображается в моем веб-браузере, и я вижу форму загрузки файла.
РЕДАКТИРОВАТЬ: Я обновил форму до максимального размера файла 8 миллионов name="MAX_FILE_SIZE" value="8000000"
, ответ на сообщение, который получает сервер, намного больше (я обновил его ниже), но по-прежнему не похоже, что он содержит содержимое файла.
POST / HTTP/1.1
Host: 169.254.126.211:81
Connection: keep-alive
Content-Length: 2857323
Cache-Control: max-age=0
Origin: http://169.254.126.211:81
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryjbf7KaGShYBQ75wT
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Accept: text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://169.254.126.211:81/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,ru;q=0.7
------WebKitFormBoundaryjbf7KaGShYBQ75wT
Content-Disposition: form-data; name="MAX_FILE_SIZE"
8000000
------WebKitFormBoundaryjbf7KaGShYBQ75wT
Content-Disposition: form-data; name="uploadedfile"; filename="IMG_20210131_165637.jpg"
Content-Type: image/jpeg
ÿØÿá„ÙExif MM * @
° ö ¶ ¾POST / HTTP/1.1
Host: 169.254.126.211:81
Connection: keep-alive
Content-Length: 2857323
Cache-Control: max-age=0
Origin: http://169.254.126.211:81
Upgrade-Insecure-Requests: 1
DNT: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryjbf7KaGShYBQ75wT
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36
Accept: text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://169.254.126.211:81/
Accept-Encoding: gzip, deflate
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8,ru;q=0.7
Снимок экрана, показывающий вывод в режиме ожидания Python, когда я запускаю скрипт.
Правка: В нем указано только 1024 байта, полученных от post!, поэтому похоже, что полный файл не отправляется.
Как отправить файл из веб-браузера по ПОЧТЕ и получить файл на сервере?
Комментарии:
1. Я думаю, вам нужно увеличить максимальный размер записи в вашей форме и размер data_recv_size в вашем сценарии. Длина содержимого отображается как 2804304 байта, хотя, вероятно, оно не будет сохранено из-за ограничений по размеру.
2. Где ты видишь
2804304 bytes
? Когда я запускаю сценарий, он печатает674 bytes received from post!
3. Это в вашем ответе заголовка (
Content-Length: 2804304
). Является ли файл, который вы пытаетесь загрузить, приблизительно 2,8 Мбайт?4. Да, я пытаюсь загрузить фотографию размером 2,8 МБ, чтобы проверить, если server.py работает.
5. Попробуйте увеличить ограничения, установленные в вашем скрипте и форме загрузки.
Ответ №1:
С помощью ответа furas, пробной ошибки и множества исследований в Интернете. Мне удалось создать веб-сервер, который работает. Я собираюсь опубликовать завершенный сценарий здесь, так как в будущем он будет полезен другим людям, которые столкнутся с этим вопросом, а также попытаются создать сервер загрузки файлов.
import socket, re
class Server():
def __init__(self):
self.host_ip = "localhost"
self.host_port = 81
self.s = socket.socket()
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.data_recv_size = 1024
self.form = b"""<form enctype="multipart/form-data" action="" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="8000000" />
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>"""
def get_data(self, conn):
data = b""
while True:
chunk = conn.recv(self.data_recv_size)
if len(chunk) < self.data_recv_size:
return data chunk
else:
data = chunk
def save_file(self, packet):
name = re.compile(b'name="uploadedfile"; filename="(. )"').search(packet).group(1)
data = re.compile(b"WebKitFormBoundary((n|.)*)Content-Type. n. ?n((n|.)*)([-] WebKitFormBoundary)?")
with open(name, "wb") as file:
file.write(data.search(packet).group(3))
def run(self):
print(f"[ ] Server: http://{self.host_ip}:{self.host_port}")
self.s.bind((self.host_ip, self.host_port))
self.s.listen()
while True:
conn,addr = self.s.accept()
request = self.get_data(conn)
# GET request
if request[0:5] == b"GET /":
conn.sendall(b"HTTP/1.0 200 OKnContent-Type: text/htmlnn" self.form)
print("[ ] Responded to GET request!")
# POST request
elif request[0:4] == b"POST":
packet = self.get_data(conn)
self.save_file(packet)
ok_reponse = b"Successfully upload %d bytes to the server!" % len(packet)
conn.sendall(b"HTTP/1.0 200 OKrnContent-Type: text/htmlrnrn" ok_reponse)
print(f"[ ] {len(packet)} bytes received from POST!")
s = Server()
s.run()
Вот скриншот, показывающий запуск сценария
Вот скриншот каталога с сохраненным файлом изображения, после того как он был загружен через сервер.
Так что теперь все, кажется, работает нормально.
Ответ №2:
В get_data()
вы проверяете while b"rnrn" not in data:
, чтобы вы читали только head
с headers
body
опубликованным файлом, но не с ним.
Вы должны получить значение из заголовка Content-Length
и использовать его для чтения остальных данных — body
.
Но вы recv(1024)
, возможно, уже прочитали какую-то часть body
, и это может создать проблемы. Вы должны читать байт за байтом ( recv(1)
), пока не получите b"rnrn"
, а затем использовать Content-Length
для чтения остальных данных.
Минимальный рабочий код с HTML в коде, чтобы каждый мог просто скопировать и запустить его.
import socket
class Server(): # PEP8: `CamelCaseName` for classes
def __init__(self):
#self.host_ip = socket.gethostbyname(socket.gethostname())
self.host_ip = '0.0.0.0' # universal IP for server - to connect from other computers
self.host_port = 8000 # 81 was restricted on my computer
#self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s = socket.socket() # default values are `socket.AF_INET, socket.SOCK_STREAM`
self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # solution for '[Error 89] Address already in use'. Use before bind()
self.data_recv_size = 1024
def get_head(self, conn):
""" gets headers from client """
data = b""
while not data.endswith(b"rnrn"):
data = conn.recv(1)
return data
def get_body(self, conn, size):
""" gets the data from client """
data = b""
while b"rnrn" not in data:
data = conn.recv(self.data_recv_size)
return data
def run(self):
""" main method starts the server """
print(f"[ ] Server: http://{self.host_ip}:{self.host_port}")
self.s.bind((self.host_ip, self.host_port))
self.s.listen()
try:
while True:
conn, addr = self.s.accept()
with conn:
head = self.get_head(conn)
# todo: parse headers
lines = head.strip().splitlines()
request = lines[0]
headers = lines[1:]
headers = list(line.split(b': ') for line in headers)
#print(headers)
headers = dict(headers)
for key, value in headers.items():
print(f'{key.decode()}: {value.decode()}')
# GET request
if request[0:5] == b"GET /":
#html = open("index.html", "rb").read()
html = '''<form enctype="multipart/form-data" action="" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="8000000" />
<input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>'''
conn.sendall(b"HTTP/1.0 200 OKnContent-Type: text/htmlnn" html.encode())
print("[ ] Responded to GET request")
# POST request
elif request[0:4] == b"POST":
size = int(headers[b'Content-Length'].decode())
body = self.get_body(conn, size)
with open("output.txt", "ab") as file:
file.write(head)
file.write(body)
total_size = len(head) len(body)
print(f"{total_size} bytes received from POST")
html = f"OK: {total_size} bytes"
conn.sendall(b"HTTP/1.0 200 OKrnContent-Type: text/htmlrnrn" html.encode())
print("[ ] Responded to POST request")
except KeyboardInterrupt:
print("[ ] Stopped by Ctrl C")
finally:
self.s.close()
# --- main ---
s = Server()
s.run()
кстати:
Я отображаюсь http://0.0.0.0:8000
в консоли, потому что в некоторых консолях вы можете нажать на URL-адрес, чтобы открыть его в браузере.
Я использую универсальный адрес 0.0.0.0
, чтобы он мог получать соединения от всех NIC
( Network Internet Controller/Card
?), что означает LAN
кабель WiFi
и другие соединения одновременно.
PEP 8 — Руководство по стилю для кода на Python
Редактировать:
Это может быть намного проще с Flask
Я использую форму для загрузки сразу 3 файлов.
import os
from flask import Flask, request
# create folder for uploaded data
FOLDER = 'uploaded'
os.makedirs(FOLDER, exist_ok=True)
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return '''<form enctype="multipart/form-data" action="" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="8000000" />
<input name="uploadedfile1" type="file" /><br />
<input name="uploadedfile2" type="file" /><br />
<input name="uploadedfile3" type="file" /><br />
<input type="submit" value="Upload File" />
</form>'''
if request.method == 'POST':
for field, data in request.files.items():
print('field:', field)
print('filename:', data.filename)
if data.filename:
data.save(os.path.join(FOLDER, data.filename))
return "OK"
if __name__ == '__main__':
app.run(port=80)
Комментарии:
1. Спасибо за вашу помощь, у меня возникли проблемы с вашим кодом, так как он все еще не полностью получал весь файл. Но мне удалось исправить это, вычесав методы get_head и get_body, а затем используя оператор IF для проверки длины фрагмента. Я поместил заполненный код в отдельный ответ.
2. Мой код получает все запросы, но для отдельного сохранения файла ему все равно нужно проанализировать данные
get_head
. Это только показывает, чтоsocket
нужно много кода, и может быть проще написать его другими модулями — т. е.http
илиFlask
3. Я добавил пример,
Flask
чтобы показать , что это может быть намного проще с веб-фреймворками, такими какFlask
,Bottle
и т. Д.