使用FaaS改造SGK查询站

为什么要改造

原来的裤子查询站是py写的,部署是要服务器的。学生云三次续费机会用完了而且也不想再续服务器了,这些零零碎碎的网站得找个便(白)宜(嫖)的地方再跑起来。

FaaS是按调用付费的,不调用不付费。企鹅云不只免费额度,而且还便宜的一批:

image-20200101034442544

100万次对于小应用基本上就是无限了,哪怕被人打了,免费的100万次都用完了,付费的价格也同样美丽,每百万次也才1块3.

image-20200101034822321

什么是Serverless

无服务器(Serverless)不是表示没有服务器,而表示当您在使用 Serverless 时,您无需关心底层资源,也无需登录服务器和优化服务器,只需关注最核心的代码片段,即可跳过复杂的、繁琐的基本工作。核心的代码片段完全由事件或者请求触发,平台根据请求自动平行调整服务资源。Serverless 拥有近乎无限的扩容能力,空闲时,不运行任何资源。代码运行无状态,可以轻易实现快速迭代、极速部署。

FaaS (函数即服务) 提供了一种直接在云上运行无状态的、短暂的、由事件触发的代码的能力。

FaaS调用过程

配置 API 网关这里不再细说。

FaaS是事件触发的。这里我们是通过API网关触发我们的函数。用户访问我们的API网关地址,API网关再调用我们写好的函数,函数会返回一个json字符串,启用了API网关 的集成响应后能将里面的body字段根据content-type展示给用户。

下面是企鹅云给的例子,功能就是返回一个页面。

#!/usr/bin/env python
# -*- coding:utf-8 -*-


def main_handler(event, context):
    f = open("./demo.html", encoding='utf-8')
    html = f.read()
    return {
        "isBase64Encoded": False,
        "statusCode": 200,
        "headers": {'Content-Type': 'text/html; charset=utf-8'},
        "body": html
    }

执行时会调用main_handler,传入的event参数为当前请求的信息,如请求头,路径,参数,请求类型等等

context参数为当前事件的相关信息,如事件id等等。

改造过程

原来的裤子站用的是flask,为了让他能用的上FaaS并尽可能少的对原来代码进行修改。

原裤子站代码:

# -*- coding: utf-8 -*-
from flask import Flask, render_template, \
    request, jsonify
import json
from searchdata import *

TOKEN="..."
BLACKLIST=[...]
app = Flask(__name__)


def getjson():
    return json.loads(request.get_data().decode("utf-8"))

def responseTo(jsonformeddata):
    resp=jsonify(jsonformeddata)
    resp.headers['Access-Control-Allow-Origin'] = '*'
    return resp


@app.route("/")
@app.route("/index")
def index():
    return render_template("index.html")


@app.route('/api/search', methods=['POST'])
def return_json():
    jsons = getjson()
    try:
        checkwebtoken=jsons["token"]
    except:
        return responseTo({"status":0,"msg":"FUCCCCCK!WHO R U?"})
    if checkwebtoken == TOKEN:
        if jsons['id'] in BLACKLIST:
            return responseTo({"status":0,"msg":"你想干什么???"})
        result=search(jsons['id'])
        return responseTo({"status":1 if result else 0,"msg":result if result else "找不到这个人"})
    else:
        return responseTo({"status":0,"msg":"FUCCCCCK!WHO R U?"})

@app.route('/api/checktoken', methods=['POST'])
def checktoken():
    jsons = getjson()
    try:
        if jsons["token"] == TOKEN:
            return responseTo({"status": 1, "msg":"ok"})
        else:
            return responseTo({"status": 0, "msg": "FUCCCCCK!WHO R U?"})
    except:
        return responseTo({"status": 0, "msg": "FUCCCCCK!WHO R U?"})

功能很简单,其中search是具体进行搜索的功能,移植到FaaS也不影响它的功能,不需要进行修改。

原来调用的flask的这些模块需要换成自己的路由模块以适应新的环境

这里我准备尽可能小而简单的进行

调用函数main_handler:

我声明了两个全局变量,req和resp分别对应请求和响应

def main_handler(event, context):
    global req
    req=event_handler(event)
    return app.run(req,resp)

event_handler,事件处理类:

​ 这里对请求过来的事件进行处理,将请求传来的event中的一些有用的信息转换为自己的值,并使用__getitem__魔术方法使他能够允许像字典一样访问。

​ 其中body字段是只有在POST请求中才会出现。这里用与运算进行一个简单判断。

​ 如果POST过来的请求是json格式,还可以很方便的调用json方法将它转换成py的数据类型

class event_handler(object):

    def __init__(self,event):
        self.__value={"path": event['path'],
                      "method": event['httpMethod'],
                      "queryString": event['queryString'],
                      "headers":event['headers'],
                      "body": str('body' in event and event['body']),
                      "full": event
                      }

    def __getitem__(self, item):
        return item in self.__value and self.__value[item]

    def json(self,encoding='utf-8'):
        return json.loads(self.__value['body'],encoding=encoding)

响应类:根据响应类型的不同分了两种方法,分别对应返回html或json,并支持自定义状态码和header

class response(object):
    def __init__(self):
        self.resp = {
            "isBase64Encoded": False,
            "statusCode": 200,
            "headers": {},
            "body": ""
        }

    def html(self, value, headers={}, statusCode=200):
        self.resp['headers'] = dict({'Content-Type': 'text/html'}, **headers)
        self.resp['statusCode'] = statusCode
        self.resp['body'] = value
        return self.resp

    def json(self, value, headers={}, statusCode=200):
        self.resp['headers'] = dict({'Content-Type': 'text/json'}, **headers)
        self.resp['statusCode'] = statusCode
        self.resp["body"] = json.dumps(value)
        return self.resp

路由控制类:

路由控制和flask一样,可以通过装饰器进行路由添加

class hexlt_app(object):
    def __init__(self):
        self.routes = {}

    def route(self, path=None):
        def decorator(func):
            self.routes[path] = func
            return func
        return decorator

    def run(self,request,resp):
        if request['path'] in self.routes:
            return self.routes[request['path']]()
        else:
            return resp.html("REQUEST_ERROR!",statusCode=404)

路由控制、响应、事件处理三个类放在同一个文件(hexlt.py)中,作为应用基础,之后其他网站FaaS移植时都可以调用它。

from hexlt import hexlt_app,response,event_handler
from searchdata import *

TOKEN="..."
BLACKLIST=[...]

resp = response()
app = hexlt_app()


def main_handler(event, context):
    global req
    req=event_handler(event)
    return app.run(req,resp)


@app.route('/api/search')
def return_json():
    if req['method'] == 'POST':
        jsons = req.json()
        try:
            checkwebtoken=jsons["token"]
        except:
            return resp.json({"status":0,"msg":"FUCCCCCK!WHO R U?"})
        if checkwebtoken == TOKEN:
            if jsons['id'] in BLACKLIST:
                return resp.json({"status":0,"msg":"你想干什么???"})
            result=search(jsons['id'])
            return resp.json({"status":1 if result else 0,"msg":result if result else "找不到这个人"})
        else:
            return resp.json({"status":0,"msg":"FUCCCCCK!WHO R U?"})
    else:
        return resp.html("<h1>FUCK U!")
    

@app.route('/api/checktoken')
def checktoken():
    if req['method'] == 'POST':
        jsons = req.json()
        try:
            if jsons["token"] == TOKEN:
                return resp.json({"status": 1, "msg":"ok"})
            else:
                return resp.json({"status": 0, "msg": "FUCCCCCK!WHO R U?"})
        except:
            return resp.json({"status": 0, "msg": "FUCCCCCK!WHO R U?"})
    else:
        return resp.html("<h1>FUCK U!")

    
@app.route("/")
@app.route("/index")
def index():
    with open('templates/index.html',"r",encoding='utf-8') as f:
        data=f.read()
    return resp.html(data)

因为是前后端分离的,我可以对前端不做任何修改,只移植后端就行。移植后的代码基本上和之前相差不多。改造完成。

结束

相关文件已经打包到: https://github.com/iceyhexman/tencentyun_FaaS
又水了一篇(逃