miniweb框架

WSGI接口

而通常web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口,它让你不对代码作修改就能使服务器和特点的web框架协同工作。

WSGI由web服务器支持,而web框架允许你选择适合自己的配对,但它同样对于服务器和框架开发者提供便利使他们可以专注于自己偏爱的领域和专长而不至于相互牵制。其他语言也有类似接口:java有Servlet API,Ruby 有 Rack。

我们需在web框架内定义一个WSGI接口函数,而web服务器与框架进行协同工作时,只需通过该接口函数即可,其他的实现均封装在该框架的底部;该接口函数如下:

# WSGI接口函数
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return 'Hello World!'

参数:

environ :一个包含所有HTTP请求信息的dict对象;即告诉框架需要调取哪些动态资源页面;

start_response :一个发送HTTP响应的函数。该函数相当于HTTP服务器给与框架的一个容器,而框架将头部信息通过参数的方式存放在该函数内,最后服务器再从中取出;

返回值:

返回的是请求的动态资源的响应消息的body部分;通常web框架将动态资源替换HTML模板的消息返回给服务器;


代码实例

web_server.py

解析:

1、在web服务器中,我们首先根据资源的请求不同,给出的处理方式不同即静态资源直接从本地磁盘读取,而动态资源交给web框架处理。

2、在与web框架进行交互,调用了wsgi接口函数,传入了字典env,用来告诉服务器需求页面;以及函数set_response_header用来获取web框架传递的头部信息;并且将返回值作为body;整个与web框架的交互均是围绕着wsgi接口函数来实现的;

3、随后便是让我们的服务器运行可以指定端口和web框架来运行,则定义了main函数下面的一些代码,通过sys.argv来获取到指定的端口和web_frame:application等,再将这些作为参数传入类 WSGIServer中来执行;

import socket
import re
import multiprocessing
import time
# import dynamic.mini_frame
import sys
 
 
class WSGIServer(object):
    def __init__(self, port, app, static_path):
        # 1. 创建套接字
        self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
        # 2. 绑定
        self.tcp_server_socket.bind(("", port))
 
        # 3. 变为监听套接字
        self.tcp_server_socket.listen(128)
 
        self.application = app
        self.static_path = static_path
 
    def service_client(self, new_socket):
        """为这个客户端返回数据"""
 
        # 1. 接收浏览器发送过来的请求 ,即http请求 
        # GET / HTTP/1.1
        # .....
        request = new_socket.recv(1024).decode("utf-8")
        # print(">>>"*50)
        # print(request)
 
        request_lines = request.splitlines()
        print("")
        print(">"*20)
        print(request_lines)
 
        # GET /index.html HTTP/1.1
        # get post put del
        file_name = ""
        ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
        if ret:
            file_name = ret.group(1)
            # print("*"*50, file_name)
            if file_name == "/":
                file_name = "/index.html"
 
        # 2. 返回http格式的数据,给浏览器
        # 2.1 如果请求的资源不是以.html结尾,那么就认为是静态资源(css/js/png,jpg等)
        if not file_name.endswith(".html"):
            try:
                f = open(self.static_path + file_name, "rb")
            except:
                response = "HTTP/1.1 404 NOT FOUND\r\n"
                response += "\r\n"
                response += "------file not found-----"
                new_socket.send(response.encode("utf-8"))
            else:
                html_content = f.read()
                f.close()
                # 2.1 准备发送给浏览器的数据---header
                response = "HTTP/1.1 200 OK\r\n"
                response += "\r\n"
                # 2.2 准备发送给浏览器的数据---boy
                # response += "hahahhah"
 
                # 将response header发送给浏览器
                new_socket.send(response.encode("utf-8"))
                # 将response ic.mini_frame.applicationbody发送给浏览器
                new_socket.send(html_content)
        else:
            # 2.2 如果是以.py结尾,那么就认为是动态资源的请求
 
            env = dict()  # 这个字典中存放的是web服务器要传递给 web框架的数据信息
            env['PATH_INFO'] = file_name
            # {"PATH_INFO": "/index.py"}
            # body = dynamic.mini_frame.application(env, self.set_response_header)
            body = self.application(env, self.set_response_header)
 
            header = "HTTP/1.1 %s\r\n" % self.status
 
            for temp in self.headers:
                header += "%s:%s\r\n" % (temp[0], temp[1])
 
            header += "\r\n"
 
            response = header+body
            # 发送response给浏览器
            new_socket.send(response.encode("utf-8"))
 
 
        # 关闭套接
        new_socket.close()
 
    def set_response_header(self, status, headers):
        self.status = status
        self.headers = [("server", "mini_web v8.8")]
        self.headers += headers
       
 
    def run_forever(self):
        """用来完成整体的控制"""
 
        while True:
            # 4. 等待新客户端的链接
            new_socket, client_addr = self.tcp_server_socket.accept()
 
            # 5. 为这个客户端服务
            p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
            p.start()
 
            new_socket.close()
 
 
        # 关闭监听套接字
        self.tcp_server_socket.close()
 
 
def main():
    """控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
    if len(sys.argv) == 3:
        try:
            port = int(sys.argv[1])  # 7890
            frame_app_name = sys.argv[2]  # mini_frame:application
        except Exception as ret:
            print("端口输入错误。。。。。")
            return
    else:
        print("请按照以下方式运行:")
        print("python3 xxxx.py 7890 mini_frame:application")
        return
   
    # mini_frame:application
    ret = re.match(r"([^:]+):(.*)", frame_app_name)
    if ret:
        frame_name = ret.group(1)  # mini_frame
        app_name = ret.group(2)  # application
    else:
        print("请按照以下方式运行:")
        print("python3 xxxx.py 7890 mini_frame:application")
        return
 
    with open("./web_server.conf") as f:
        conf_info = eval(f.read())
 
    # 此时 conf_info是一个字典里面的数据为:
    # {
    #     "static_path":"./static",
    #     "dynamic_path":"./dynamic"
    # }
 
 
    sys.path.append(conf_info['dynamic_path'])
 
    # import frame_name --->找frame_name.py
    frame = __import__(frame_name)  # 返回值标记这 导入的这个模板
    app = getattr(frame, app_name)  # 此时app就指向了 dynamic/mini_frame模块中的application这个函数
 
    # print(app)
 
    wsgi_server = WSGIServer(port, app, conf_info['static_path'])
    wsgi_server.run_forever()
 
 
if __name__ == "__main__":
    main()

dynamic/mini_frame.py
 在该框架内:定义了wsgi接口函数,以及获取指定页面的函数,在下篇中我们将会介绍如何将数据库的内容导入;

import re
import urllib.parse
import logging
from pymysql import connect

"""
URL_FUNC_DICT = {
   "/index.html": index,
   "/center.html": center
}
"""

URL_FUNC_DICT = dict()


def route(url):
   def set_func(func):
       # URL_FUNC_DICT["/index.py"] = index
       URL_FUNC_DICT[url] = func
       def call_func(*args, **kwargs):
           return func(*args, **kwargs)
       return call_func
   return set_func


@route(r"/index.html")
def index(ret):
   with open("./templates/index.html") as f:
       content = f.read()

   # my_stock_info = "哈哈哈哈 这是你的本月名称....."
   # content = re.sub(r"\{%content%\}", my_stock_info, content)
   # 创建Connection连接
   conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
   # 获得Cursor对象
   cs = conn.cursor()
   cs.execute("select * from info;")
   stock_infos = cs.fetchall()
   cs.close()
   conn.close()

   tr_template = """<tr>
       <td>%s</td><td>%s</td> <td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>
           <input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="%s">
       </td>
       </tr>
   """

   html = ""
   for line_info in stock_infos:
       html += tr_template % (line_info[0],line_info[1],line_info[2],line_info[3],line_info[4],line_info[5],line_info[6],line_info[7], line_info[1])

   # content = re.sub(r"\{%content%\}", str(stock_infos), content)
   content = re.sub(r"\{%content%\}", html, content)

   return content
   

@route(r"/center.html")
def center(ret):
   with open("./templates/center.html") as f:
       content = f.read()

   # my_stock_info = "这里是从mysql查询出来的数据。。。"
   # content = re.sub(r"\{%content%\}", my_stock_info, content)
   # 创建Connection连接
   conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
   # 获得Cursor对象
   cs = conn.cursor()
   cs.execute("select i.code,i.short,i.chg,i.turnover,i.price,i.highs,f.note_info from info as i inner join focus as f on i.id=f.info_id;")
   stock_infos = cs.fetchall()
   cs.close()
   conn.close()

   tr_template = """
       <tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td>
           <td>
               <a type="button" class="btn btn-default btn-xs" href="/update/%s.html"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> 修改 </a>
           </td>
           <td>
               <input type="button" value="删除" id="toDel" name="toDel" systemidvaule="%s">
           </td>
       </tr>
   """

   html = ""
   for line_info in stock_infos:
       html += tr_template % (line_info[0],line_info[1],line_info[2],line_info[3],line_info[4],line_info[5],line_info[6], line_info[0], line_info[0])

   # content = re.sub(r"\{%content%\}", str(stock_infos), content)
   content = re.sub(r"\{%content%\}", html, content)

   return content

# 给路由添加正则表达式的原因:在实际开发时,url中往往会带有很多参数,例如/add/000007.html中000007就是参数,
# 如果没有正则的话,那么就需要编写N次@route来进行添加 url对应的函数 到字典中,此时字典中的键值对有N个,浪费空间
# 而采用了正则的话,那么只要编写1次@route就可以完成多个 url例如/add/00007.html /add/000036.html等对应同一个函数,此时字典中的键值对个数会少很多
@route(r"/add/(\d+)\.html")
def add_focus(ret):

   # 1. 获取股票代码
   stock_code = ret.group(1)

   # 2. 判断试下是否有这个股票代码
   conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
   cs = conn.cursor()
   sql = """select * from info where code=%s;"""
   cs.execute(sql, (stock_code,))
   # 如果要是没有这个股票代码,那么就认为是非法的请求
   if not cs.fetchone():
       cs.close()
       conn.close()
       return "没有这支股票,大哥 ,我们是创业公司,请手下留情..."

   # 3. 判断以下是否已经关注过
   sql = """ select * from info as i inner join focus as f on i.id=f.info_id where i.code=%s;"""
   cs.execute(sql, (stock_code,))
   # 如果查出来了,那么表示已经关注过
   if cs.fetchone():
       cs.close()
       conn.close()
       return "已经关注过了,请勿重复关注..."

   # 4. 添加关注
   sql = """insert into focus (info_id) select id from info where code=%s;"""
   cs.execute(sql, (stock_code,))
   conn.commit()
   cs.close()
   conn.close()

   return "关注成功...."


@route(r"/del/(\d+)\.html")
def del_focus(ret):

   # 1. 获取股票代码
   stock_code = ret.group(1)

   # 2. 判断试下是否有这个股票代码
   conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
   cs = conn.cursor()
   sql = """select * from info where code=%s;"""
   cs.execute(sql, (stock_code,))
   # 如果要是没有这个股票代码,那么就认为是非法的请求
   if not cs.fetchone():
       cs.close()
       conn.close()
       return "没有这支股票,大哥 ,我们是创业公司,请手下留情..."

   # 3. 判断以下是否已经关注过
   sql = """ select * from info as i inner join focus as f on i.id=f.info_id where i.code=%s;"""
   cs.execute(sql, (stock_code,))
   # 如果没有关注过,那么表示非法的请求
   if not cs.fetchone():
       cs.close()
       conn.close()
       return "%s 之前未关注,请勿取消关注..." % stock_code

   # 4. 取消关注
   # sql = """insert into focus (info_id) select id from info where code=%s;"""
   sql = """delete from focus where info_id = (select id from info where code=%s);"""
   cs.execute(sql, (stock_code,))
   conn.commit()
   cs.close()
   conn.close()

   return "取消关注成功...."


@route(r"/update/(\d+)\.html")
def show_update_page(ret):
   """显示修改的那个页面"""
   # 1. 获取股票代码
   stock_code = ret.group(1)

   # 2. 打开模板
   with open("./templates/update.html") as f:
       content = f.read()

   # 3. 根据股票代码查询相关的备注信息
   conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
   cs = conn.cursor()
   sql = """select f.note_info from focus as f inner join info as i on i.id=f.info_id where i.code=%s;"""
   cs.execute(sql, (stock_code,))
   stock_infos = cs.fetchone()
   note_info = stock_infos[0]  # 获取这个股票对应的备注信息
   cs.close()
   conn.close()

   content = re.sub(r"\{%note_info%\}", note_info, content)
   content = re.sub(r"\{%code%\}", stock_code, content)

   return content


@route(r"/update/(\d+)/(.*)\.html")
def save_update_page(ret):
   """"保存修改的信息"""
   stock_code = ret.group(1)
   comment = ret.group(2)
   comment = urllib.parse.unquote(comment)
  
   conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
   cs = conn.cursor()
   sql = """update focus set note_info=%s where info_id = (select id from info where code=%s);"""
   cs.execute(sql, (comment, stock_code))
   conn.commit()
   cs.close()
   conn.close()

   return "修改成功..."


def application(env, start_response):
   start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
  
   file_name = env['PATH_INFO']
   # file_name = "/index.py"

   """
   if file_name == "/index.py":
       return index()
   elif file_name == "/center.py":
       return center()
   else:
       return 'Hello World! 我爱你中国....'
   """

   logging.basicConfig(level=logging.INFO, 
                       filename='./log.txt', 
                       filemode='a', 
                       format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s') 

   logging.info("访问的是,%s" % file_name)

   try:
       # func = URL_FUNC_DICT[file_name]
       # return func()
       # return URL_FUNC_DICT[file_name]()
       for url, func in URL_FUNC_DICT.items():
           # {
           #   r"/index.html":index,
           #   r"/center.html":center,
           #   r"/add/\d+\.html":add_focus
           # }
           ret = re.match(url, file_name)
           if ret:
               return func(ret)
       else:
           logging.warning("没有对应的函数....")
           return "请求的url(%s)没有对应的函数...." % file_name


   except Exception as ret:
       return "产生了异常:%s" % str(ret)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容

  • 谈论WEB编程的时候常说天天在写CGI,那么CGI是什么呢?可能很多时候并不会去深究这些基础概念,再比如除了CGI...
    __七把刀__阅读 2,197评论 2 11
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,101评论 1 32
  • 转载自标点符的《网关协议学习:CGI、FastCGI、WSGI》 CGI CGI即通用网关接口(Common Ga...
    李绍俊阅读 1,660评论 0 1
  • property shorthand & short methods 示例: object destructuri...
    zshanjun阅读 195评论 0 0
  • 今年夏天你像一条逆流而上的一尾游鱼,从挣扎了三年高中毕业,六月的高考,七月的报考,七月中旬的录取,八月的你,已经是...
    姜几牙阅读 413评论 5 5