使用Python Tornado搭建web服务器

11 篇文章 0 订阅
10 篇文章 0 订阅

一. Tornado简介

Tornado是使用Python编写的Web服务器兼Web应用框架,与主流Web服务器框架不同的是,Tornado是异步非阻塞式服务器,得益于非阻塞式和对epoll模型的运用,Tornado是实时Web服务的一个理想框架,它非常适合开发长轮询、WebSocket和需要与每个用户建立持久连接的应用。
官网:https://www.tornadoweb.org/en/stable/index.html#
安装:https://pypi.org/project/tornado/

二. windows / python3.8 / Tornado / NotImplementedError 问题

windows下,python3.8运行tornado报错,如下

Traceback (most recent call last):
  File "E:\PythonProject\tornado2\tornado\server_main.py", line 79, in <module>
    app.listen(PORT)
  File "C:\Users\linxinfa\AppData\Local\Programs\Python\Python38\lib\site-packages\tornado-6.0.3-py3.8-win-amd64.egg\tornado\web.py", line 2112, in listen
    server.listen(port, address)
  File "C:\Users\linxinfa\AppData\Local\Programs\Python\Python38\lib\site-packages\tornado-6.0.3-py3.8-win-amd64.egg\tornado\tcpserver.py", line 152, in listen
    self.add_sockets(sockets)
  File "C:\Users\linxinfa\AppData\Local\Programs\Python\Python38\lib\site-packages\tornado-6.0.3-py3.8-win-amd64.egg\tornado\tcpserver.py", line 165, in add_sockets
    self._handlers[sock.fileno()] = add_accept_handler(
  File "C:\Users\linxinfa\AppData\Local\Programs\Python\Python38\lib\site-packages\tornado-6.0.3-py3.8-win-amd64.egg\tornado\netutil.py", line 279, in add_accept_handler
    io_loop.add_handler(sock, accept_handler, IOLoop.READ)
  File "C:\Users\linxinfa\AppData\Local\Programs\Python\Python38\lib\site-packages\tornado-6.0.3-py3.8-win-amd64.egg\tornado\platform\asyncio.py", line 99, in add_handler
    self.asyncio_loop.add_reader(fd, self._handle_events, fd, IOLoop.READ)
  File "C:\Users\linxinfa\AppData\Local\Programs\Python\Python38\lib\asyncio\events.py", line 501, in add_reader
    raise NotImplementedError
NotImplementedError

是由于 python3.8 asyncio 在 windows 上默认使用 ProactorEventLoop 造成的,而不是之前的 SelectorEventLoop。jupyter 依赖 tornado,而 tornado 在 window 上需要使用 SelectorEventLoop,所以产生这个报错.
请看官方文档: https://www.tornadoweb.org/en/stable/index.html#installation

On Windows, Tornado requires the WindowsSelectorEventLoop. This is the default in Python 3.7 and older, but Python 3.8 defaults to an event loop that is not compatible with Tornado. Applications that use Tornado on Windows with Python 3.8 must call asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
at the beginning of their main file/function.

解决方法,在 tornado开始执行前添加以下代码,在windows下单独处理:

# windows 系统下 tornado 使用 使用 SelectorEventLoop
import platform

if platform.system() == "Windows":
    import asyncio
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

三. 跨域访问问题

使用javascript的XMLHttpRequest请求post
报错:

No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

这是跨域访问问题。

1. 什么是跨域

跨域是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com页面去请求 www.google.com的资源。跨域的严格一点的定义是:只要 协议,域名,端口有任何一个的不同,就被当作是跨域

2. 为什么浏览器要限制跨域访问

原因就是安全问题:如果一个网页可以随意地访问另外一个网站的资源,那么就有可能在客户完全不知情的情况下出现安全问题。比如下面的操作就有安全问题:

用户访问www.mybank.com,登陆并进行网银操作,这时cookie啥的都生成并存放在浏览器;
用户突然想起件事,并迷迷糊糊地访问了一个邪恶的网站 www.xiee.com
这时该网站就可以在它的页面中,拿到银行的cookie,比如用户名,登陆token等,然后发起对www.mybank.com的操作。
如果这时浏览器不予限制,并且银行也没有做响应的安全处理的话,那么用户的信息有可能就这么泄露了。

3. 为什么要跨域

既然有安全问题,那为什么又要跨域呢? 有时公司内部有多个不同的子域,比如一个是location.company.com ,而应用是放在app.company.com , 这时想从 app.company.com去访问 location.company.com 的资源就属于跨域。

4 解决办法

定义BaseHandler,设置默认headers

import tornado.ioloop
import tornado.web
from tornado.web import RequestHandler

class BaseHandler(tornado.web.RequestHandler):
    def set_default_headers(self):
        self.set_header("Access-Control-Allow-Origin", "*")   # 这个地方可以写域名
        self.set_header("Access-Control-Allow-Headers", "x-requested-with")
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')

    def post(self):
        self.write('some post')

    def get(self):
        self.write('some get')

    def options(self):
        # no body
        self.set_status(204)
        self.finish()

然后所有的Handler类都继承BaseHandler即可,如:

class MyHandler(BaseHandler):
    def get(self):
        self.write('hello world')
    def post(self):
        self.write('hello world')

四. GET如何获取参数

比如这样的请求:http:localhost:8989?name=linxinfa&age=18,参数可以通过self.get_argument来获取

import tornado.ioloop
import tornado.web

# BaseHandler要自己定义,可参考上文
class MyHandler(BaseHandler):
    def get(self):
        name = self.get_argument('name', default = 'Tony')
        age = self.get_argument('age', default = '0') 

五. POST如何获取参数

通过self.request.body来获取,假设传过来的是json,那么self.request.body就是json字符串的二进制流,所以取出参数就是像下面这样:

import json
import tornado.ioloop
import tornado.web

# BaseHandler要自己定义,可参考上文
class MyHandler(BaseHandler):
	def post(self):
		jsonstr = str(self.request.body, encoding = "utf8")
		jd = json.loads(jsonstr)
		print(jd['name'], jd['age'])	

六. javascript执行GET和POST请求

可以使用XMLHttpRequest,例:

<button onclick='OnGetTestBtn()'>get_test</button>
<button onclick='OnPostTestBtn()'>post_test</button>
<script>
var ADDRESS = 'http://127.0.0.1:8458/'
function OnGetTestBtn()
{
	var request_url = ADDRESS + "?name=linxinfa&age=18";
    var request = new XMLHttpRequest();
    request.open('GET', request_url, true);
    request.send();
    request.onload = function(e) {
    	if (request.status === 200) {
    		// 假设服务端返回的数据是json串,所以这里使用JSON来解析
    		jd = JSON.parse(request.response);
    		
    	}
    	else{
            alert('失败,请重试!');
            window.history.back(-1);
        }
    }
}


function OnPostTestBtn()
{
	var request_url = ADDRESS;
    var request = new XMLHttpRequest();
    request.open('POST', request_url, true);
	send_data = {'name': 'linxinfa', 'age': 18 }
    request.send(JSON.stringify(send_data));
    request.onload = function(e) {
    	if (request.status === 200) {
    		// 假设服务端返回的数据是json串,所以这里使用JSON来解析
    		jd = JSON.parse(request.responseText);
    		
    	}
    	else{
            alert('失败,请重试!');
            window.history.back(-1);
        }
    }
    request.onerror = function(e){
       alert('请求失败:' + request.statusText)
    }
}
</script>

七. javascript动态创建列表

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        table {
            width: 500px;
            margin: 100px auto;
            border-collapse: collapse;
            text-align: center;
        }
        
        td,
        th {
            border: 1px solid #333;
        }
        
        thead tr {
            height: 40px;
            background-color: #ccc;
        }
    </style>
</head>

<body>
    <table cellspacing="0">
        <thead>
            <tr>
                <th>姓名</th>
                <th>科目</th>
                <th>成绩</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>

        </tbody>
    </table>
    <script>
        // 1.将每一行的数据作为一个对象,然后将这些对象封装在一个数组中(没有后台服务器的情况下,暂时采用这种方式添加)
        var datas = [{
                name: '李寻欢',
                subject: 'javascript',
                scores: 100
            }, {
                name: '周杰伦',
                subject: 'javascript',
                scores: 99
            }, {
                name: '陈奕迅',
                subject: 'javascript',
                scores: 98
            }, {
                name: '昼夜',
                subject: 'javascript',
                scores: 97
            }, {
                name: '柯南',
                subject: 'javascript',
                scores: 100
            }]
            // 2、添加行tr,利用for循环(i的数量与数组的长度有关)
            // 获取元素
        var tbody = document.querySelector('tbody');
        // 清理列表
		var childs = tbody.childNodes; 
        for(var i = childs .length - 1; i >= 0; i--) {
            tbody.removeChild(childs[i]);
        }
        // 创建列表
        for (var i = 0; i < datas.length; i++) {
            //创建行tr
            var tr = document.createElement('tr');
            //将新创建的行tr添加给tbody
            tbody.appendChild(tr);
            // 3、内层for循环,创建每一行中的所有单元格td,单元格td的数量与对象中的属性多少有关,故用for...in...
            for (var k in datas[i]) {
                // 创建td元素
                var td = document.createElement('td');
                // 将每个对象中的属性值传给td
                td.innerHTML = datas[i][k];
                //给tr添加td子元素
                tr.appendChild(td);
            }
            //4、在每一行的最后,添加一个td,内容为a标签‘删除’
            // 创建td新元素
            var td = document.createElement('td');
            //把a标签的‘删除’传给td
            td.innerHTML = "<a href='javascript:;'>删除</ a>";
            // 给tr添加上td
            tr.appendChild(td);
        }

        //以上,整个表格就创建完成了
        // 5、下面给表格添加点击‘删除’,就删除整行的事件
        // 获取元素
        var as = document.querySelectorAll('a');
        // 添加事件
        for (var i = 0; i < as.length; i++) {
            as[i].onclick = function() {
                tbody.removeChild(this.parentNode.parentNode);
            }
        }
    </script>
</body>
</html>

八. python执行get和post

1. get
import requests

r = requests.get(url)
print(r.text)
2. post
import json
import requests

data = {'name': 'linxinfa','age': '18' }
jsonStr = json.dumps(data)
jsonBytes =jsonStr.encode('utf-8')
r = requests.post(url, jsonBytes)
print(r.text)

九. 判断字符串是否是JSON格式

1. python
def is_json(txt):
    try:
        json.loads(txt)
    except ValueError:
        return False
    return True
2. javascript
function isJSON(str) {
    if (typeof str == 'string') {
        try {
            JSON.parse(str);
            return true;
        } catch(e) {
            return false;
        }
    }
    console.log('It is not a string!')    
}

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值