Question

I am running a webserver based on Flask, which serves a resource being versioned (e.g. installation file of some versioned program). I want to serve my HTTP client with new resource only in case, it already does not have the current version available. If there is new version, I want the client to download the resource and install it.

my Flask server looks like this

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
    )

my client is very basic:

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)

I will likely have to recode the entire client, this is not a problem but I really have no idea where to start....

Was it helpful?

Solution

Requirements Review

  • have web app, serving a versioned resource. It can be e.g. file with an applications.
  • have client, which allows fetching the resource only in case, the version of resource on the server and what client has locally already available differ
  • the client is aware of version string of the resource
  • allow client to learn new version string if new version is available

HTTP like design of your solution

If you want to allow downloading an application only in case, the client does not have it already, following design could be used:

  • use etag header. This usually contains some string describing unique status of resource you want to get from that url. In your case it could be current version number of your application.
  • in your request, use header "if-none-match", providing version number of your application present at client. This will result in HTTP Status code 306 - Not Modified in case, your client and server share the same version of resource. In case it differs, you would simply provide the content of the resource and use it. Your resource shall also denote in etag current version of the resource and your client shall take note of it, or find new version name from other sources (like from the downloaded file).

This design follows HTTP principles.

Flask serving resource with declaring version in etag

This is focusing on showing the principle, you shall elaborate on providing real content of the resource.

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)

Client getting resource only, if it is new

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"

Alternative solution of web part using web server (e.g. NGINX)

Assuming the resource is static file, which updates only sometime, you shall be able configuring your web server, e.g. NGINX, to serve that resource and declaring in your configuration explicit value for etag header to the version string.

Note, that as it was not requested, this alternative solution is not elaborated here (and was not tested).

Client implementation would not be modified by that (here it pays back the design is following HTTP concepts).

OTHER TIPS

There are multiple ways of achieving this but as this is a Flask app, here's one using HTTP.

If the version is OK, just return a relevant status code, like a 200 OK. You can add a JSON response in the body if that's necessary. If you return a string with flask, the status code will be 200 OK and you can inspect that in your client.

If the version differs, return the URL where the file is located. The client will have to download the file. That's pretty simple using requests. Here's a typical example for downloading file by streaming 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

This is very simplified. If your file is not static and cannot live on the server (like software update patches probably shouldn't) then you'll have to figure out a way to get the file from a database or generate it on the fly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top