Docker 容器中的 Python 服务器脚本不接收来自主机操作系统的请求

标签 python docker port

我写了一个用于上传/下载文件的脚本。它适用于系统(此处为 Win10),但不适用于我放入的 docker 容器。

我使用了来自另一个 Python 项目(即 Django REST 框架之一)的 Dockerfile 和 docker-compose.yml(的删减版),它们工作得很好。
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

docker -compose.yml:
version: "3"

services:
  server:
    build:
      context: .
    ports:
      - "5050:5050"
    volumes:
      - .:/server
    command: >
      sh -c "python server_threaded.py"

我使用 requests 库来发出……嗯……请求,代码很简单:
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

基本上它没有任何 react 。
客户端错误信息:
requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

如果我在系统上运行脚本,客户端消息:
<Response [200]>

我尝试摆弄端口,到处更改它们,使用 Postman 和 telnet,从 Dockerfile 中删除了“ENV PYTHONUNBUFFERED 1”。没有任何效果。我显然不是 Docker 船长,但配置在我看来是非常基本的。
我究竟做错了什么?谢谢。

最佳答案

使用 ADDRESS = '0.0.0.0' 解决了问题。感谢大卫迷宫。

关于Docker 容器中的 Python 服务器脚本不接收来自主机操作系统的请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55698302/

相关文章:

docker - 在 docker-compose 中的同一个端口上运行多个服务

python - SQLAlchemy:可以以声明方式将列声明为主键吗?

python - NLTK WordNetLemmatizer 中的多线程?

powershell - 如何通过 PowerShell 脚本调整 Windows 10 上的 Docker 桌面虚拟机的大小?

unit-testing - 如何使用 GitLab CI 设置 Selenium E2E 测试?

node.js - 如何使用另一个端口在 Visual Studio Code 中调试 Serverless Offline?

c++ - 多次调用 CreateFileA 时为 INVALID_HANDLE_VALUE

python - 填写 numpy ndarray 的最佳方法?

python - 如果与黑白图像一起使用,OpenCV findContours() 会报错

docker - 在 swarm 集群中的特定节点上运行 docker 容器