仅在变更时通过Flask提供版本化资源的HTTP客户端
我正在运行一个基于Flask的网络服务器,这个服务器提供一个需要版本管理的资源(比如某个程序的安装文件)。我希望只有在客户端没有当前版本的情况下,才给它提供新的资源。如果有新版本,我希望客户端能够下载这个资源并进行安装。
我的Flask服务器代码是这样的:
import json
import redis
import math
import requests
from flask import Flask,render_template,request
app=Flask(__name__)
@app.route('/version', methods=['GET','POST'])
def getversion():
r_server=redis.Redis("127.0.0.1")
if request.method == 'POST':
jsonobj_recieve=request.data
data=json.loads(jsonobj)
currentversion=r_server.hget('version')
if data == currentversion:
#code to return a 'ok'
else:
#code to return 'not ok' also should send the updated file to the client
else:
return r_server.hget('version')
if __name__ == '__main__':
app.run(
debug=True,
host="127.0.0.1",
port=80
)
我的客户端非常简单:
import sys
import json
import requests
url="http://127.0.0.1/version"
jsonobj=json.dumps(str(sys.argv[1]))
print jsonobj
r=requests.post(url,data=jsonobj)
我可能需要重新编写整个客户端,这不是问题,但我真的不知道从哪里开始……
2 个回答
0
有很多方法可以做到这一点,但因为这是一个Flask应用,所以这里介绍一种使用HTTP的方法。
如果版本没问题,就返回一个相关的状态码,比如200 OK
。如果需要的话,你可以在响应的内容里加上JSON格式的数据。如果你用Flask返回一个字符串,状态码会是200 OK,你可以在客户端查看这个状态码。
如果版本不一样,就返回文件所在的URL。客户端需要去下载这个文件。使用requests
库来下载文件是非常简单的。下面是一个通过流式请求下载文件的典型例子:
def get(url, chunk_size=1024):
""" Download a file in chunks of n bytes """
fn = url.split("/")[-1] # if you're url is complicated, use urlparse.
stream = requests.get(url, stream=True)
with open(fn, "wb") as local:
for chunk in stream.iter_content(chunk_size=chunk_size):
if chunk:
f.write(chunk)
return fn
这只是一个非常简单化的例子。如果你的文件不是静态的,不能一直放在服务器上(比如软件更新补丁可能就不应该这样),那么你就需要想办法从数据库中获取文件,或者实时生成它。
1
需求审查
- 需要有一个网页应用,它能提供一个带版本号的资源。比如说,这可以是一个应用程序的文件。
- 需要有一个客户端,它只在服务器上的资源版本和客户端本地已有的版本不同时,才允许获取这个资源。
- 客户端需要知道这个资源的版本号。
- 如果有新版本可用,客户端要能够获取到新的版本号。
类似HTTP的解决方案设计
如果你想让应用程序只能在客户端没有的情况下下载,可以使用以下设计:
- 使用
etag
头部。这个通常包含一个字符串,用来描述你想从那个网址获取的资源的唯一状态。在你的情况下,这可以是你应用程序的当前版本号。 - 在你的请求中,使用“if-none-match”这个头部,提供客户端上应用程序的版本号。如果客户端和服务器的资源版本相同,服务器会返回HTTP状态码
306 - Not Modified
。如果版本不同,服务器就会提供资源的内容。你的资源也应该在etag
中标明当前的版本号,客户端要记住这个版本号,或者从其他来源(比如下载的文件)找到新版本的名称。
这个设计遵循了HTTP的原则。
Flask提供带版本声明的资源
这里主要是展示这个原则,你需要进一步提供资源的实际内容。
from flask import Flask, Response, request
import werkzeug.exceptions
app = Flask(__name__)
class NotModified(werkzeug.exceptions.HTTPException):
code = 304
def get_response(self, environment):
return Response(status=304)
@app.route('/download/app')
def downloadapp():
currver = "1.0"
if request.if_none_match and currver in request.if_none_match:
raise NotModified
def generate():
yield "app_file_part 1"
yield "app_file_part 2"
yield "app_file_part 3"
return Response(generate(), headers={"etag": currver})
if __name__ == '__main__':
app.run(debug=True)
客户端只获取新资源
import requests
ver = "1.0"
url = "http://localhost:5000/download/app"
req = requests.get(url, headers={"If-None-Match": ver})
if req.status_code == 200:
print "new content of resource", req.content
new_ver = req.headers["etag"]
else:
print "resource did not change since last time"
使用网页服务器(例如NGINX)的替代方案
假设这个资源是一个静态文件,只在某些时候更新,你应该能够配置你的网页服务器,比如NGINX,来提供这个资源,并在你的配置中明确设置 etag
头部的版本号。
注意,由于没有请求这个替代方案,这里没有详细说明(也没有进行测试)。
客户端的实现不会受到影响(这也是遵循HTTP概念的好处)。