├── requirements.txt └── app.py /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==0.9 2 | redis==2.6.2 3 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import string 4 | 5 | import redis 6 | from flask import request, Flask, jsonify 7 | 8 | app = Flask(__name__) 9 | r = redis.Redis() 10 | 11 | hex_digits = set(string.hexdigits.lower()) 12 | validate_hash = lambda hash: len(hash) % 16 == 0 and len(hash) <= 128 and all(chr in hex_digits for chr in hash) 13 | 14 | byte_seconds = 86400 * 365 * 2 #16 Byte Years 15 | max_value_size = 16 * 1024 #16 KB 16 | max_get_hashes = 64 17 | max_post_size = 1024 * 1024 * 1024 #1 MB 18 | 19 | @app.route('/', methods=['GET', 'POST']) 20 | def index(): 21 | minute = str(int(time.time()) / 60) 22 | ip_address = '' 23 | with r.pipeline() as pipe: 24 | pipe.incr('ratelimit.'+ip_address+'.'+minute) 25 | pipe.expire('ratelimit.'+ip_address+'.'+minute, 60) 26 | hits, _ = pipe.execute() 27 | if hits > 180: 28 | return 'Rate limit for this minute reached, slow down cowboy' 29 | if request.method == 'POST': 30 | insert_time = int(time.time()) 31 | current_post_size = 0 32 | with r.pipeline() as pipe: 33 | for hash, values in request.form.lists(): 34 | if validate_hash(hash): 35 | longest_expire_time = 0 36 | for i, v in enumerate(values): 37 | if len(v) < max_value_size and current_post_size < max_post_size: 38 | hash_key = '{hash}-{insert_time}-{i}'.format(hash=hash, insert_time=insert_time, i=i) 39 | expire_time = byte_seconds / len(v) 40 | if expire_time > longest_expire_time: 41 | longest_expire_time = expire_time 42 | pipe.set(hash_key, v) 43 | pipe.expire(hash_key, expire_time) 44 | pipe.sadd('list.'+hash, hash_key) 45 | current_post_size += len(v) 46 | pipe.expire('list.'+hash, longest_expire_time) 47 | pipe.execute() 48 | return 'ok' 49 | else: 50 | hashes = [hash for hash in request.args.getlist('hash') if validate_hash(hash)][:max_get_hashes] 51 | hash_list_keys = ['list.'+hash for hash in hashes] 52 | with r.pipeline() as pipe: 53 | for hash_list_key in hash_list_keys: 54 | pipe.smembers(hash_list_key) 55 | hash_lists = pipe.execute() 56 | with r.pipeline() as pipe: 57 | for hash_list in hash_lists: 58 | pipe.mget(hash_list) 59 | hash_list_values = pipe.execute() 60 | 61 | expired_hash_keys = [] 62 | data = {} 63 | with r.pipeline() as pipe: 64 | for keys, values in zip(hash_lists, hash_list_values): 65 | for k, v in zip(keys, values): 66 | hash = k.split('-')[0] 67 | if v != None: 68 | insert_time = int(k.split('-')[1]) 69 | if hash not in data: 70 | data[hash] = {} 71 | if insert_time not in data[hash]: 72 | data[hash][insert_time] = [] 73 | data[hash][insert_time].append(v) 74 | else: 75 | pipe.srem('list.'+hash, k) 76 | pipe.execute() 77 | return jsonify(**data) 78 | 79 | if __name__ == '__main__': 80 | port = int(os.environ.get('PORT', 5000)) 81 | app.run(host='0.0.0.0', port=port) --------------------------------------------------------------------------------