[英]Python server script in a Docker container doesn't receive requests from host OS
我寫了一個上傳/下載文件的腳本。 它適用於系統(這里是Win10),但不適用於我放入的docker容器。
我使用了另一個Python項目的Dockerfile和docker-compose.yml(一個剪切版本),它是Django REST Framework的一個,它們工作得很好。 Python版本3.7.0。 僅使用標准庫模塊。 Docker圖像是官方的python-3.7-alpine。
Python腳本(跳過導入):
ADDRESS, PORT = 'localhost', 5050
DATABASE = 'db.sqlite'
FILEDIR = 'Uploads'
class HttpHandler(BaseHTTPRequestHandler):
'''A tiny request handler for uploading and downloading files.'''
def __init__(self: '__main__.HttpHandler', *args, **kwargs) -> None:
'''
The handler class constructor. Before initialization checks if
the DATABASE file and the FILEDIR directory/folder both exist,
otherwise creates them.
'''
makedirs(FILEDIR, exist_ok=True)
if not path.isfile(DATABASE):
conn = sqlite3.connect(DATABASE)
with conn:
conn.execute('''CREATE TABLE filepaths (
uuid CHARACTER(36) PRIMARY KEY,
filepath TEXT NOT NULL,
filename TEXT NOT NULL,
extension TEXT,
upload_date TEXT
);''')
conn.close()
print(f'Database {DATABASE} created')
super().__init__(*args, **kwargs)
def read_from_db(self: '__main__.HttpHandler',
file_id: str) -> Union[tuple, None]:
'''Fetch the file record from the database.'''
try:
conn = sqlite3.connect(DATABASE)
with closing(conn):
cursor = conn.cursor()
query = f'''SELECT filepath, filename, extension, upload_date
FROM filepaths
WHERE uuid=:id;
'''
cursor.execute(query, {'id': file_id})
return cursor.fetchone()
except sqlite3.DatabaseError as error:
self.send_response(code=500, message='Database error')
self.end_headers()
print('Database error :', error)
def send_file(self: '__main__.HttpHandler',
file_id: str,
filepath: str,
filename: str,
extension: str) -> None:
'''Send the requested file to user.'''
try:
with open(filepath, 'rb') as file:
self.send_response(code=200)
self.send_header(
'Content-Disposition',
f'attachment; filename="{filename}.{extension}"'
)
self.end_headers()
data = file.read()
self.wfile.write(data)
except FileNotFoundError:
self.send_response(
code=410,
message=f'File with id {file_id} was deleted.'
)
self.end_headers()
def do_GET(self: '__main__.HttpHandler') -> None: # pylint: disable=C0103
'''
Check if a record for the given id exists in the DATABASE and
send the respective response to user; if 'download' parameter
provided, download the existing file to user from FILEPATH.
Usage is as follows:
CHECK
http://<ADDRESS>:<PORT>/?id=<file_id>
DOWNLOAD
http://<ADDRESS>:<PORT>/?id=<file_id>&download=1
'''
get_query = urlsplit(self.path).query
params = dict(parse_qsl(get_query))
if 'id' not in params:
self.send_response_only(code=200)
self.end_headers()
return
file_id = params['id']
db_response = self.read_from_db(file_id)
if not db_response:
self.send_response(code=204,
message=f'No files found with id {file_id}')
self.end_headers()
return
filepath, filename, extension, upload_date = db_response
if 'download' not in params:
self.send_response(
code=200,
message=f'{filename}.{extension} was uploaded at {upload_date}'
)
self.end_headers()
else:
self.send_file(file_id, filepath, filename, extension)
def do_POST(self: '__main__.HttpHandler') -> None: # pylint: disable=C0103
'''
Upload a file to FILEPATH and create the record for that
in the DATABASE, then send it's id in the response message.
Usage is as follows:
UPLOAD
POST request containing the file body to http://<ADDRESS>:<PORT>/
Content-Length must be provided in the headers;
If Content-Disposition is absent, the file will be saved as
"filename.not_provided"
'''
content_length = int(self.headers.get('Content-Length', 0))
if content_length == 0:
self.send_response(code=411, message='Length required')
self.end_headers()
return
content_disposition = self.headers.get('Content-Disposition',
'name="filename.not_provided"')
filename, extension = re.findall(r'name="(.+)\.(\S+)"',
content_disposition)[0]
file_content = self.rfile.read(content_length)
uuid = uuid4()
filepath = path.join(getcwd(), FILEDIR, f'{uuid}.{extension}')
with open(filepath, 'wb') as file:
file.write(file_content)
try:
with sqlite3.connect(DATABASE) as conn:
query = '''INSERT INTO filepaths VALUES (
:uuid,
:filepath,
:filename,
:extension,
:upload_date
);'''
conn.execute(query, {'uuid': str(uuid),
'filepath': filepath,
'filename': filename,
'extension': extension,
'upload_date': datetime.now()})
conn.close()
self.send_response(code=201, message=uuid)
self.end_headers()
except sqlite3.DatabaseError as error:
self.send_response(code=500, message='Database error')
self.end_headers()
print('Database error :', error)
if __name__ == "__main__":
with ThreadingTCPServer((ADDRESS, PORT), HttpHandler) as httpd:
print('Serving on port', PORT)
SERVER_THREAD = Thread(httpd.serve_forever(), daemon=True)
SERVER_THREAD.start()
Dockerfile:
FROM python:3.7-alpine
ENV PYTHONUNBUFFERED 1
RUN mkdir /server
WORKDIR /server
COPY . /server
RUN adduser -D user
RUN chown -R user:user /server
RUN chmod -R 755 /server
USER user
泊塢窗,compose.yml:
version: "3"
services:
server:
build:
context: .
ports:
- "5050:5050"
volumes:
- .:/server
command: >
sh -c "python server_threaded.py"
我使用請求庫來制作......呃......請求,代碼很簡單:
import requests
print(requests.get('http://localhost:5050/'))
服務器端的輸出不會改變:
$ docker-compose up
Recreating servers_server_1 ... done
Attaching to servers_server_1
server_1 | Serving on port 5050
基本上它無論如何都不會響應。 客戶端錯誤消息:
requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
如果我在系統上運行腳本,則為客戶端消息:
<Response [200]>
我嘗試擺弄端口,在這里和那里更改它們,使用Postman和telnet,從Dockerfile中刪除'ENV PYTHONUNBUFFERED 1'。 什么都行不通。 我顯然不是Docker隊長,但配置看起來非常基本。 我究竟做錯了什么? 謝謝。
如果Windows是主機,Docker正在使用Linux虛擬機來托管其環境,因此來自Windows的“localhost”或反之亦然,它們是不同的主機。
您可以獲取有關VM docker正在使用docker-machine
。
使用ADDRESS ='0.0.0.0'解決了問題。 感謝David Maze。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.