如何从现有 Python 应用程序公开带有 REST API 和 HTML/JavaScript 应用程序的 Web 服务器?
How to expose a web server with REST API and HTML/JavaScript applications from an existing Python application?
我有一个现有的 Python 应用程序可以连续抓取 Internet。它使用 requests
包向各种 Internet 网站(例如 GitHub、Twitter 等)发出 HTTP 请求,并将可用数据下载到文件系统。它还向 GitHub 存储库和 Twitter 的 REST API 发出 HTTP 请求,并下载大量元数据。它一直在无限循环中执行此操作。每次迭代后,它调用 time.sleep(3600)
在下一次迭代之前休眠 1 小时。
现在我想从此应用程序的端口 80 上公开一个 HTTP 服务器,以便任何客户端都可以连接到此应用程序的端口 80 以查询其内部状态。例如,如果某人 运行s curl http://myapp/status
它应该用 {"status": "crawling"}
或 {"status": "sleeping"}
来响应。如果有人使用他们的网络浏览器访问 http://myapp/status
,它应该显示一个显示状态的 HTML 页面。根据检测到的用户代理,它将提供 REST API 响应或 HTML 页面。如果出于任何原因,我的应用程序出现故障或崩溃,对端口 80 的 HTTP 请求当然应该失败。
如何从应用程序公开这样的 HTTP 服务器?我想到使用 Django,因为随着项目的进行,它必须做很多繁重的工作,例如身份验证、防止 CSRF 攻击、接受用户输入和查询它拥有的数据等等。 Django 似乎很适合这个目的。但是 Django 的问题是我不能在我当前的应用程序中嵌入 Django。我必须 运行 一个单独的 uwsgi
服务器来为 Django 应用程序提供服务。 Flask 也存在同样的问题。
在 Python 中解决此类问题的正确方法是什么?
在我看来,您有两种解决此问题的高级方法:
- 有单独的应用程序(一个 "server" 和一个 "crawler"),它们有一些共享的数据存储(数据库、Redis 等)。每个应用程序都将独立运行,爬虫只需更新其在共享数据存储中的状态。这种方法可能可以更好地扩展:如果你在 Docker Swarm 之类的东西中旋转它,你可以在你负担得起的范围内尽可能多地扩展爬虫实例。
- 有一个应用程序为爬虫和服务器生成单独的线程。因为它们在同一个进程中,所以您可以更快地在它们之间共享信息(尽管如果只是爬虫状态应该无关紧要)。此选项的优势似乎只是难以启动它——您不需要共享数据存储,也不需要管理多个服务。
我个人更倾向于(1),因为每一部分都比较简单。接下来是 (1) 的解决方案,以及 (2) 的快速而肮脏的解决方案。
1。使用共享数据存储分离进程
我会使用 Docker Compose 来处理启动所有服务。它增加了一层额外的复杂性(因为您需要安装 Docker),但它极大地简化了服务管理。
整个Docker组合栈
基于示例配置 here 我会制作一个 ./docker-compose.yaml
文件,看起来像
version: '3'
services:
server:
build: ./server
ports:
- "80:80"
links:
- redis
environment:
- REDIS_URL=redis://cache
crawler:
build: ./crawler
links:
- redis
environment:
- REDIS_URL=redis://cache
redis:
image: "redis/alpine"
container_name: cache
expose:
- 6379
我会将应用程序组织到单独的目录中,例如 ./server
和 ./crawler
,但这不是唯一的方法。无论您如何组织它们,上面配置中的 build
参数都应该匹配。
服务器
我会在 ./server/app.py
中编写一个简单的服务器来做类似
的事情
import os
from flask import Flask
import redis
app = Flask(__name__)
r_conn = redis.Redis(
host=os.environ.get('REDIS_HOST'),
port=6379
)
@app.route('/status')
def index():
stat = r_conn.get('crawler_status')
try:
return stat.decode('utf-8')
except:
return 'error getting status', 500
app.run(host='0.0.0.0', port=8000)
连同一个 ./server/requirements.txt
具有依赖关系的文件
Flask
redis
最后 ./server/Dockerfile
告诉 Docker 如何构建您的服务器
FROM alpine:latest
# install Python
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
rm -r /root/.cache
# copy the app and make it your current directory
RUN mkdir -p /opt/server
COPY ./ /opt/server
WORKDIR /opt/server
# install deps and run server
RUN pip3 install -qr requirements.txt
EXPOSE 8000
CMD ["python3", "app.py"]
停下来检查一切是否正常
此时,如果您在 ./docker-compose.yaml
目录中打开 CMD 提示符或终端,您应该能够 运行 docker-compose build && docker-compose up
检查所有内容是否已构建,并且 运行 很高兴。您将需要禁用 YAML 文件的 crawler
部分(因为它尚未写入),但您应该能够启动与 Redis 通信的服务器。如果您对此感到满意,请取消注释 YAML 的 crawler
部分并继续。
爬虫进程
由于 Docker 处理重新启动爬虫进程,您实际上可以将其编写为一个非常简单的 Python 脚本。像 ./crawler/app.py
这样的东西可能看起来像
from time import sleep
import os
import sys
import redis
TIMEOUT = 3600 # seconds between runs
r_conn = redis.Redis(
host=os.environ.get('REDIS_HOST'),
port=6379
)
# ... update status and then do the work ...
r_conn.set('crawler_status', 'crawling')
sleep(60)
# ... okay, it's done, update status ...
r_conn.set('crawler_status', 'sleeping')
# sleep for a while, then exit so Docker can restart
sleep(TIMEOUT)
sys.exit(0)
然后像之前一样需要 ./crawler/requirements.txt
文件
redis
还有一个(非常类似于服务器的)./crawler/Dockerfile
FROM alpine:latest
# install Python
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
rm -r /root/.cache
# copy the app and make it your current directory
RUN mkdir -p /opt/crawler
COPY ./ /opt/crawler
WORKDIR /opt/crawler
# install deps and run server
RUN pip3 install -qr requirements.txt
# NOTE that no port is exposed
CMD ["python3", "app.py"]
总结
在 7 个文件中,您有两个由 Docker 管理的独立应用程序以及一个 Redis 实例。如果你想缩放它,你可以查看 --scale
option for docker-compose up
. This is not necessarily the simplest solution, but it manages some of the unpleasant bits about process management. For reference, I also made a Git repo for it here.
为了 运行 它作为一个无头服务,只是 运行 docker-compose up -d
.
从这里开始,您可以并且应该向搜寻器添加更好的日志记录。您当然可以使用 Django 而不是 Flask 作为服务器(尽管我更熟悉 Flask,Django 可能会引入新的依赖项)。当然,你总是可以让它变得更复杂。
2。带线程的单进程
此解决方案不需要任何 Docker,并且应该只需要一个 Python 文件来管理。除非 OP 需要,否则我不会编写完整的解决方案,但基本草图类似于
import threading
import time
from flask import Flask
STATUS = ''
# run the server on another thread
def run_server():
app = Flask(__name__)
@app.route('/status')
def index():
return STATUS
server_thread = threading.Thread(target=run_server)
server_thread.start()
# run the crawler on another thread
def crawler_loop():
while True:
STATUS = 'crawling'
# crawl and wait
STATUS = 'sleeping'
time.sleep(3600)
crawler_thread = threading.Thread(target=crawler_loop)
crawler_thread.start()
# main thread waits until the app is killed
try:
while True:
sleep(1)
except:
server_thread.kill()
crawler_thread.kill()
此解决方案不处理与保持服务活动有关的任何事情,实际上与错误处理有关,最后的块不会很好地处理来自 OS 的信号。也就是说,这是一个快速而肮脏的解决方案,可以让您脱离实际。
我有一个现有的 Python 应用程序可以连续抓取 Internet。它使用 requests
包向各种 Internet 网站(例如 GitHub、Twitter 等)发出 HTTP 请求,并将可用数据下载到文件系统。它还向 GitHub 存储库和 Twitter 的 REST API 发出 HTTP 请求,并下载大量元数据。它一直在无限循环中执行此操作。每次迭代后,它调用 time.sleep(3600)
在下一次迭代之前休眠 1 小时。
现在我想从此应用程序的端口 80 上公开一个 HTTP 服务器,以便任何客户端都可以连接到此应用程序的端口 80 以查询其内部状态。例如,如果某人 运行s curl http://myapp/status
它应该用 {"status": "crawling"}
或 {"status": "sleeping"}
来响应。如果有人使用他们的网络浏览器访问 http://myapp/status
,它应该显示一个显示状态的 HTML 页面。根据检测到的用户代理,它将提供 REST API 响应或 HTML 页面。如果出于任何原因,我的应用程序出现故障或崩溃,对端口 80 的 HTTP 请求当然应该失败。
如何从应用程序公开这样的 HTTP 服务器?我想到使用 Django,因为随着项目的进行,它必须做很多繁重的工作,例如身份验证、防止 CSRF 攻击、接受用户输入和查询它拥有的数据等等。 Django 似乎很适合这个目的。但是 Django 的问题是我不能在我当前的应用程序中嵌入 Django。我必须 运行 一个单独的 uwsgi
服务器来为 Django 应用程序提供服务。 Flask 也存在同样的问题。
在 Python 中解决此类问题的正确方法是什么?
在我看来,您有两种解决此问题的高级方法:
- 有单独的应用程序(一个 "server" 和一个 "crawler"),它们有一些共享的数据存储(数据库、Redis 等)。每个应用程序都将独立运行,爬虫只需更新其在共享数据存储中的状态。这种方法可能可以更好地扩展:如果你在 Docker Swarm 之类的东西中旋转它,你可以在你负担得起的范围内尽可能多地扩展爬虫实例。
- 有一个应用程序为爬虫和服务器生成单独的线程。因为它们在同一个进程中,所以您可以更快地在它们之间共享信息(尽管如果只是爬虫状态应该无关紧要)。此选项的优势似乎只是难以启动它——您不需要共享数据存储,也不需要管理多个服务。
我个人更倾向于(1),因为每一部分都比较简单。接下来是 (1) 的解决方案,以及 (2) 的快速而肮脏的解决方案。
1。使用共享数据存储分离进程
我会使用 Docker Compose 来处理启动所有服务。它增加了一层额外的复杂性(因为您需要安装 Docker),但它极大地简化了服务管理。
整个Docker组合栈
基于示例配置 here 我会制作一个 ./docker-compose.yaml
文件,看起来像
version: '3'
services:
server:
build: ./server
ports:
- "80:80"
links:
- redis
environment:
- REDIS_URL=redis://cache
crawler:
build: ./crawler
links:
- redis
environment:
- REDIS_URL=redis://cache
redis:
image: "redis/alpine"
container_name: cache
expose:
- 6379
我会将应用程序组织到单独的目录中,例如 ./server
和 ./crawler
,但这不是唯一的方法。无论您如何组织它们,上面配置中的 build
参数都应该匹配。
服务器
我会在 ./server/app.py
中编写一个简单的服务器来做类似
import os
from flask import Flask
import redis
app = Flask(__name__)
r_conn = redis.Redis(
host=os.environ.get('REDIS_HOST'),
port=6379
)
@app.route('/status')
def index():
stat = r_conn.get('crawler_status')
try:
return stat.decode('utf-8')
except:
return 'error getting status', 500
app.run(host='0.0.0.0', port=8000)
连同一个 ./server/requirements.txt
具有依赖关系的文件
Flask
redis
最后 ./server/Dockerfile
告诉 Docker 如何构建您的服务器
FROM alpine:latest
# install Python
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
rm -r /root/.cache
# copy the app and make it your current directory
RUN mkdir -p /opt/server
COPY ./ /opt/server
WORKDIR /opt/server
# install deps and run server
RUN pip3 install -qr requirements.txt
EXPOSE 8000
CMD ["python3", "app.py"]
停下来检查一切是否正常
此时,如果您在 ./docker-compose.yaml
目录中打开 CMD 提示符或终端,您应该能够 运行 docker-compose build && docker-compose up
检查所有内容是否已构建,并且 运行 很高兴。您将需要禁用 YAML 文件的 crawler
部分(因为它尚未写入),但您应该能够启动与 Redis 通信的服务器。如果您对此感到满意,请取消注释 YAML 的 crawler
部分并继续。
爬虫进程
由于 Docker 处理重新启动爬虫进程,您实际上可以将其编写为一个非常简单的 Python 脚本。像 ./crawler/app.py
这样的东西可能看起来像
from time import sleep
import os
import sys
import redis
TIMEOUT = 3600 # seconds between runs
r_conn = redis.Redis(
host=os.environ.get('REDIS_HOST'),
port=6379
)
# ... update status and then do the work ...
r_conn.set('crawler_status', 'crawling')
sleep(60)
# ... okay, it's done, update status ...
r_conn.set('crawler_status', 'sleeping')
# sleep for a while, then exit so Docker can restart
sleep(TIMEOUT)
sys.exit(0)
然后像之前一样需要 ./crawler/requirements.txt
文件
redis
还有一个(非常类似于服务器的)./crawler/Dockerfile
FROM alpine:latest
# install Python
RUN apk add --no-cache python3 && \
python3 -m ensurepip && \
rm -r /usr/lib/python*/ensurepip && \
pip3 install --upgrade pip setuptools && \
rm -r /root/.cache
# copy the app and make it your current directory
RUN mkdir -p /opt/crawler
COPY ./ /opt/crawler
WORKDIR /opt/crawler
# install deps and run server
RUN pip3 install -qr requirements.txt
# NOTE that no port is exposed
CMD ["python3", "app.py"]
总结
在 7 个文件中,您有两个由 Docker 管理的独立应用程序以及一个 Redis 实例。如果你想缩放它,你可以查看 --scale
option for docker-compose up
. This is not necessarily the simplest solution, but it manages some of the unpleasant bits about process management. For reference, I also made a Git repo for it here.
为了 运行 它作为一个无头服务,只是 运行 docker-compose up -d
.
从这里开始,您可以并且应该向搜寻器添加更好的日志记录。您当然可以使用 Django 而不是 Flask 作为服务器(尽管我更熟悉 Flask,Django 可能会引入新的依赖项)。当然,你总是可以让它变得更复杂。
2。带线程的单进程
此解决方案不需要任何 Docker,并且应该只需要一个 Python 文件来管理。除非 OP 需要,否则我不会编写完整的解决方案,但基本草图类似于
import threading
import time
from flask import Flask
STATUS = ''
# run the server on another thread
def run_server():
app = Flask(__name__)
@app.route('/status')
def index():
return STATUS
server_thread = threading.Thread(target=run_server)
server_thread.start()
# run the crawler on another thread
def crawler_loop():
while True:
STATUS = 'crawling'
# crawl and wait
STATUS = 'sleeping'
time.sleep(3600)
crawler_thread = threading.Thread(target=crawler_loop)
crawler_thread.start()
# main thread waits until the app is killed
try:
while True:
sleep(1)
except:
server_thread.kill()
crawler_thread.kill()
此解决方案不处理与保持服务活动有关的任何事情,实际上与错误处理有关,最后的块不会很好地处理来自 OS 的信号。也就是说,这是一个快速而肮脏的解决方案,可以让您脱离实际。