Python全栈开发之18、cookies、session和ajax等相关知识
一、cookies
本质为在浏览器端保存的键值对,由服务端写在浏览器端,以后每次请求的时候,浏览器都携带着cookie来访问,cookies的使用之处非常多,比如用户验证,登陆界面,右侧菜单隐藏,控制页面列表显示条数等,已经后面的session都是基于cookie的。cookie从设置方面来说可以由tronado和前端js设置
tornado设置(普通字符串,tronado做了分割等处理)
self.cookies
self.get_cookie('k1')
self.set_cookie('k2', 'v2')
浏览器js设置
document.cookie
document.cookie.split(";") 获取所有的cookie列表
document.cookie = "k3=66" 设置
document.cookie = "k3=66;path='/"
下面来看一下用tornado设置的代码
#!/usr/bin/env python # coding=utf-8 import tornado.web import tornado.ioloop class IndexHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): print(self.cookies) print(self.get_cookie('k1')) self.set_cookie('k2', '999') self.render('index.html') settings = { 'template_path': 'views', 'static_path': 'statics', } application = tornado.web.Application([ (r'/index', IndexHandler), ], **settings) if __name__ == '__main__': application.listen(8888) tornado.ioloop.IOLoop.instance().start()
下面看一下html代码里面是怎么通过js来设置的
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> function setCookieBySeconds(name, value, expires) { var current_date = new Date(); current_date.setDate(current_date.getSeconds()+expires); document.cookie = name + '= ' + value +';expires=' + current_date.toUTCString(); } function setCookieByDays(name, value, expires) { var current_date = new Date(); current_date.setDate(current_date.getDate()+expires); document.cookie = name + '= ' + value +';expires=' + current_date.toUTCString(); } //此外还可以导入jquery.cookie.js后通过 // $.cookie('k1','v1',{expires:7});设置过期时间为7天 </script> </body> </html>
上面说的是最基本的cookie,采用的是键值对,稍微高级一点做法对value进行加密,下如果进行加密的话,先来看一下加密流程
加密cookie流程
服务端
v1=base64加密(v1)
v1| v1+时间戳+自定义密钥 之后在经过类似md5加密变成下面的
v1| 加密字符串 | 时间戳 保存到客户端
客户端来请求,服务端先base64解密拿到 v1然后根据客户端发来的时间戳+v1+自定义密钥再次生成加密字符串和客户端发来的加密字符串进行对比,看是否一致。
这里看一下tornado设置和获取加密的方法,set_secure_cookie,get_secure_cookie,此外记得在settings里面配置cookie_secret密钥就行,具体演示和上面的类似,就不具体的演示了。
二、session
cookie保存单一键值对,如果需要保存其他内容,则需要写多个cookie,而每次请求的话都会发送所有的cookie这样的话,会造成网络拥堵,此外,cookies是保存在浏览器端,如果保存用户名密码的cookie也放在浏览器端的话,也不够安全,这样就引出了session,session是人为生成的。session也是基于cookie来做的,但是只在浏览器端生成一个cookie(随机字符串,sessionId,token),而在服务端也保存着这段cookie,此外服务端还根据这段cookie可以生成一个字典,字典里面就可以放置用户的其他信息,服务端将这段cookie(sessionId)写到浏览器端,以后浏览器端来访问的时候,服务端根据sessionId从其相应的字典里面获取相应的信息来做相应用户认证机制。session可以保存在全局变量里,放在数据库,文件里面,memcached radis但是不能放在局部变量里。下面看下自定义的session的代码实现
import tornado.web import tornado.ioloop import hashlib import time CONTAINER = { } class Session: def __init__(self, handler): self.handler = handler self.random_str = '' @staticmethod def __generate_str(): obj = hashlib.md5() obj.update(bytes(str(time.time()), encoding='utf-8')) return obj.hexdigest() def __getitem__(self, item): random_str = self.handler.get_cookie('__session__') if not random_str: return None if random_str not in CONTAINER.keys(): return None return CONTAINER[random_str].get(item, None) def __setitem__(self, key, value): random_str = self.handler.get_cookie('__session__') if not random_str: random_str = Session.__generate_str() # self.handler.set_cookie('__session__', random_str) CONTAINER[random_str] = {} else: if random_str not in CONTAINER.keys(): random_str = Session.__generate_str() CONTAINER[random_str] = {} self.random_str = random_str CONTAINER[self.random_str][key] = value self.handler.set_cookie('__session__', self.random_str) # 为什么放在这里,cookie可能失效?放在上面失效也一样 def __delitem__(self, key): random_str = self.handler.get_cookie('__session__') if not random_str: return None if random_str not in CONTAINER.keys(): return None del CONTAINER[random_str][key]
Tornado框架中,默认执行Handler的get/post等方法之前默认会执行 initialize方法,所以可以通过自定义一个basehandler类实现initialize的方法,使得所有在继承basehandler的类中,可以自行的完成一个初始化的工作。
三、Xss和csrf
Xss跨站脚本攻击
csrf跨站请求伪造
get请求的时候,会给浏览器发一个id(cookie),浏览器post请求的时候,携带这个id,然后服务端对其做验证,如果没有这个id的话,就禁止浏览器提交内容。下面来看一下在tornado里面怎么设置,首先需要在settings里面配置 'xsrf_cookies': True,如果这样配置的话,浏览器发送post请求的话这样设置之后,Tornado 将拒绝请求参数中不包含正确的_xsrf
值的 post/put/delete 请求,如果没有携带相应的id(session)则会禁止访问。{% raw xsrf_form_html() %}
是新增的,目的就在于实现上面所说的授权给前端以合法请求。
这里主要看下html里面的代码,用js获取_xsrf对应的session,然后用jquery的ajax发送post请求,并且将_xsrf的session也发送过去。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="/sss/jquery-1.12.4.js"></script> <!--<script src="{{ static_url('jquery-1.12.4.js') }}" ></script>--> </head> <body> <!--{{ xsrf_form_html() }}--> {% raw xsrf_form_html() %} <input type="button" value="ajax_csrf" onclick="SubmitCsrf();"> <script> function getCookie(name) { var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } function SubmitCsrf() { var nid = getCookie('_xsrf'); console.log(nid); $.post({ url: '/csrf', data:{'k1':'v1', "_xsrf":nid}, success:function (callback) { console.log(callback); } }); } </script> </body> </html>
这样经过上面的处理之后,服务端就只需要写简单的handler类就可以了,这里就不再多叙述了。
四、随机验证码图片
登陆注册的时候,需要验证码的功能,原理为在后台自动创建一张随机图片,然后通过img标签输出到前端。这里我们需要安装一个pillow的模块,相应的生成随机验证代码文件如下,此外还需要一个字体文件
#!/usr/bin/env python # coding:utf-8 import random from PIL import Image, ImageDraw, ImageFont, ImageFilter _letter_cases = "abcdefghjkmnpqrstuvwxy" # 小写字母,去除可能干扰的i,l,o,z _upper_cases = _letter_cases.upper() # 大写字母 _numbers = ''.join(map(str, range(3, 10))) # 数字 init_chars = ''.join((_letter_cases, _upper_cases, _numbers)) def create_validate_code(size=(120, 30), chars=init_chars, img_type="GIF", mode="RGB", bg_color=(255, 255, 255), fg_color=(0, 0, 255), font_size=18, font_type="Monaco.ttf", length=4, draw_lines=True, n_line=(1, 2), draw_points=True, point_chance=2): ''' @todo: 生成验证码图片 @param size: 图片的大小,格式(宽,高),默认为(120, 30) @param chars: 允许的字符集合,格式字符串 @param img_type: 图片保存的格式,默认为GIF,可选的为GIF,JPEG,TIFF,PNG @param mode: 图片模式,默认为RGB @param bg_color: 背景颜色,默认为白色 @param fg_color: 前景色,验证码字符颜色,默认为蓝色#0000FF @param font_size: 验证码字体大小 @param font_type: 验证码字体,默认为 ae_AlArabiya.ttf @param length: 验证码字符个数 @param draw_lines: 是否划干扰线 @param n_lines: 干扰线的条数范围,格式元组,默认为(1, 2),只有draw_lines为True时有效 @param draw_points: 是否画干扰点 @param point_chance: 干扰点出现的概率,大小范围[0, 100] @return: [0]: PIL Image实例 @return: [1]: 验证码图片中的字符串 ''' width, height = size # 宽, 高 img = Image.new(mode, size, bg_color) # 创建图形 draw = ImageDraw.Draw(img) # 创建画笔 def get_chars(): '''生成给定长度的字符串,返回列表格式''' return random.sample(chars, length) def create_lines(): '''绘制干扰线''' line_num = random.randint(*n_line) # 干扰线条数 for i in range(line_num): # 起始点 begin = (random.randint(0, size[0]), random.randint(0, size[1])) # 结束点 end = (random.randint(0, size[0]), random.randint(0, size[1])) draw.line([begin, end], fill=(0, 0, 0)) def create_points(): '''绘制干扰点''' chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100] for w in range(width): for h in range(height): tmp = random.randint(0, 100) if tmp > 100 - chance: draw.point((w, h), fill=(0, 0, 0)) def create_strs(): '''绘制验证码字符''' c_chars = get_chars() strs = ' %s ' % ' '.join(c_chars) # 每个字符前后以空格隔开 font = ImageFont.truetype(font_type, font_size) font_width, font_height = font.getsize(strs) draw.text(((width - font_width) / 3, (height - font_height) / 3), strs, font=font, fill=fg_color) return ''.join(c_chars) if draw_lines: create_lines() if draw_points: create_points() strs = create_strs() # 图形扭曲参数 params = [1 - float(random.randint(1, 2)) / 100, 0, 0, 0, 1 - float(random.randint(1, 10)) / 100, float(random.randint(1, 2)) / 500, 0.001, float(random.randint(1, 2)) / 500 ] img = img.transform(size, Image.PERSPECTIVE, params) # 创建扭曲 img = img.filter(ImageFilter.EDGE_ENHANCE_MORE) # 滤镜,边界加强(阈值更大) return img, strs 随机生成验证码图片模块
下面首先来看一下html文件,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="statics/jquery-1.12.4.js"></script> </head> <body> <form action="login" method="post"> <input type="text", name="code"> <img src="/check_code" onclick="ChangeCode();" id="imgcode"> <input type="submit" value="submit"> <span>{{status}}</span> </form> <script> function ChangeCode() { var code = document.getElementById('imgcode'); code.src += '?' } </script> </body> </html>
再来看一下,服务端是怎么写的
class CheckCodeHandler(BaseHandler): def get(self, *args, **kwargs): import io import check_code mstream = io.BytesIO() img, code = check_code.create_validate_code() img.save(mstream, 'GIF') self.session['CheckCode'] = code self.write(mstream.getvalue())
这里需要注意一点,要记得将验证码写到session中经行保存,这样后面可以进行验证。这样一个简单的随机码验证图片就完成了。
五、自定义分页类
分页的功能很常见,下面将其整理为一个类,类似session类一样,以后使用只需稍作修改在页面嵌入为原生字符串即可。
class Pagenation: def __init__(self, current_page, all_item, each_item): all_pager, c = divmod(all_item, each_item) if c > 0: all_pager += 1 if current_page == '': current_page = 1 self.current_page = int(current_page) # 当前页 self.all_pages = all_pager # 总的页面数 self.each_item = each_item # 每页显示的item数 @property def start_item(self): # 当前页的起始item位置 return (self.current_page - 1) * self.each_item @property def end_item(self): # 当前页结束item位置 return self.current_page * self.each_item @property def start_end_span(self): # 获取开始和结束页的具体数字 if self.all_pages < 10: start_page = 1 # 起始页 end_page = self.all_pages + 1 # 结束页 else: # 总页数大于10 if self.current_page < 5: start_page = 1 end_page = 11 else: if (self.current_page + 5) < self.all_pages: start_page = self.current_page - 4 end_page = self.current_page + 5 + 1 else: start_page = self.all_pages - 10 end_page = self.all_pages + 1 return start_page, end_page def generate_str_page(self): list_page = [] start_page, end_page = self.start_end_span if self.current_page == 1: # 上一页 prev = '<li><a class="pre-page" href="javascript:void(0);">上一页</a></li>' else: prev = '<li><a class="pre-page" href="/index/%s">上一页</a></li>' % (self.current_page - 1,) list_page.append(prev) for p in range(start_page, end_page): # 1-10 if p == self.current_page: temp = '<li><a class="li-page" href="/index/%s">%s</a></li>' % (p, p) else: temp = '<li><a href="/index/%s">%s</a></li>' % (p, p) list_page.append(temp) if self.current_page == self.all_pages: # 下一页 nex = '<li><a class="next-page" href="javascript:void(0);">下一页</a></li>' else: nex = '<li><a class="next-page" href="/index/%s">下一页</a></li>' % (self.current_page + 1,) list_page.append(nex) # 跳转 jump = """<input type='text' /><a onclick="Jump('%s',this);">GO</a>""" % ('/index/') script = """<script> function Jump(baseUrl,ths){ var val = ths.previousElementSibling.value; if(val.trim().length>0){ location.href = baseUrl + val; } } </script>""" list_page.append(jump) list_page.append(script) str_page = "".join(list_page) return str_page
六、AJAX
为什么使用ajax,局部刷新,减少请求中发送的数据
AJAX,Asynchronous JavaScript and XML (异步的JavaScript和XML),一种创建交互式网页应用的网页开发技术方案。
- 异步的JavaScript:
使用 【JavaScript语言】 以及 相关【浏览器提供类库】 的功能向服务端发送请求,当服务端处理完请求之后,【自动执行某个JavaScript的回调函数】。以上请求和响应的整个过程是【偷偷】进行的,页面上无任何感知。 - XML
XML是一种标记语言,是Ajax在和后台交互时传输数据的格式之一,但是现在使用的很少,基本都是使用json来做数据交换
利用AJAX可以做:
1、注册时,输入用户名自动检测用户是否已经存在。
2、登陆时,提示用户名密码错误
3、删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面DOM中将数据行也删除。
首先来看一下一种用iframe标签模拟ajax请求
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <div> <p>请输入要加载的地址:<span id="currentTime"></span></p> <p> <input id="url" type="text" /> <input type="button" value="刷新" onclick="LoadPage();"> </p> </div> <div> <h3>加载页面位置:</h3> <iframe id="iframePosition" style="width: 100%;height: 500px;"></iframe> </div> <script type="text/javascript"> window.onload= function(){ var myDate = new Date(); document.getElementById('currentTime').innerText = myDate.getTime(); }; function LoadPage(){ var targetUrl = document.getElementById('url').value; document.getElementById("iframePosition").src = targetUrl; } </script> </body> </html> iframe模拟ajax
如今使用ajax其实就是使用浏览器(除了老旧的IE6,IE5)的XmlHttpRequest对象来完成的。
下面是相应的实例,
a. void open(String method,String url,Boolen async) 用于创建请求 参数: method: 请求方式(字符串类型),如:POST、GET、DELETE... url: 要请求的地址(字符串类型) async: 是否异步(布尔类型) b. void send(String body) 用于发送请求 参数: body: 要发送的数据(字符串类型) c. void setRequestHeader(String header,String value) 用于设置请求头 参数: header: 请求头的key(字符串类型) vlaue: 请求头的value(字符串类型) d. String getAllResponseHeaders() 获取所有响应头 返回值: 响应头数据(字符串类型) e. String getResponseHeader(String header) 获取响应头中指定header的值 参数: header: 响应头的key(字符串类型) 返回值: 响应头中指定的header对应的值 f. void abort() 终止请求 XmlHttpRequest对象的主要方法:
a. Number readyState 状态值(整数) 详细: 0-未初始化,尚未调用open()方法; 1-启动,调用了open()方法,未调用send()方法; 2-发送,已经调用了send()方法,未接收到响应; 3-接收,已经接收到部分响应数据; 4-完成,已经接收到全部响应数据; b. Function onreadystatechange 当readyState的值改变时自动触发执行其对应的函数(回调函数) c. String responseText 服务器返回的数据(字符串类型) d. XmlDocument responseXML 服务器返回的数据(Xml对象) e. Number states 状态码(整数),如:200、404... f. String statesText 状态文本(字符串),如:OK、NotFound... XmlHttpRequest对象的主要属性:
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <h1>XMLHttpRequest - Ajax请求</h1> <input type="button" onclick="XhrGetRequest();" value="Get发送请求" /> <input type="button" onclick="XhrPostRequest();" value="Post发送请求" /> <script src="/statics/jquery-1.12.4.js"></script> <script type="text/javascript"> function GetXHR(){ var xhr = null; if(XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject("Microsoft.XMLHTTP"); } return xhr; } function XhrPostRequest(){ var xhr = GetXHR(); // 定义回调函数 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ // 已经接收到全部响应数据,执行以下操作 var data = xhr.responseText; console.log(data); } }; // 指定连接方式和地址----文件方式 xhr.open('POST', "/test/", true); // 设置请求头 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8'); // 发送请求 xhr.send('n1=1;n2=2;'); } function XhrGetRequest(){ var xhr = GetXHR(); // 定义回调函数 xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ // 已经接收到全部响应数据,执行以下操作 var data = xhr.responseText; console.log(data); } }; // 指定连接方式和地址----文件方式 xhr.open('get', "/test/", true); // 发送请求 xhr.send(); } </script> </body> </html> XmlHttpRequest发送ajax请求
jQuery.get(...) 所有参数: url: 待载入页面的URL地址 data: 待发送 Key/value 参数。 success: 载入成功时回调函数。 dataType: 返回内容格式,xml, json, script, text, html jQuery.post(...) 所有参数: url: 待载入页面的URL地址 data: 待发送 Key/value 参数 success: 载入成功时回调函数 dataType: 返回内容格式,xml, json, script, text, html jQuery.getJSON(...) 所有参数: url: 待载入页面的URL地址 data: 待发送 Key/value 参数。 success: 载入成功时回调函数。 jQuery.getScript(...) 所有参数: url: 待载入页面的URL地址 data: 待发送 Key/value 参数。 success: 载入成功时回调函数。 jQuery.ajax(...) 部分参数: url:请求地址 type:请求方式,GET、POST(1.9.0之后用method) headers:请求头 data:要发送的数据 contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8") async:是否异步 timeout:设置请求超时时间(毫秒) beforeSend:发送请求前执行的函数(全局) complete:完成之后执行的回调函数(全局) success:成功之后执行的回调函数(全局) error:失败之后执行的回调函数(全局) accepts:通过请求头发送给服务器,告诉服务器当前客户端课接受的数据类型 dataType:将服务器端返回的数据转换成指定类型 "xml": 将服务器端返回的内容转换成xml格式 "text": 将服务器端返回的内容转换成普通文本格式 "html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。 "script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式 "json": 将服务器端返回的内容转换成相应的JavaScript对象 "jsonp": JSONP 格式 使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数 如果不指定,jQuery 将自动根据HTTP包MIME信息返回相应类型(an XML MIME type will yield XML, in 1.4 JSON will yield a JavaScript object, in 1.4 script will execute the script, and anything else will be returned as a string converters: 转换器,将服务器端的内容根据指定的dataType转换类型,并传值给success回调函数 $.ajax({ accepts: { mycustomtype: 'application/x-some-custom-type' }, // Expect a `mycustomtype` back from server dataType: 'mycustomtype' // Instructions for how to deserialize a `mycustomtype` converters: { 'text mycustomtype': function(result) { // Do Stuff return newresult; } }, }); jQuery Ajax 方法列表 Jquery之ajax方法
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Jquery - Ajax请求</h1> <input type="button" onclick="XhrGetRequest();" value="Get发送请求" /> <input type="button" onclick="XhrPostRequest();" value="Post发送请求" /> <script src="/statics/jquery-1.12.4.js"></script> <script > function XhrGetRequest() { $.get({ url: "/test/", data: {'k1': 'v1'}, success:function (callback) { console.log(callback) } }) } function XhrPostRequest() { $.post({ url: "/test/", data: {'k1':'v1'}, success:function (callback) { console.log(callback) } }) } </script> </body> </html> jquery发送ajax请求
七、跨域ajax
由于浏览器存在同源策略机制,本域脚本只能读写本域内的资源,而无法访问其它域的资源,请求的发送和响应是可以进行的,但是浏览器不接受而已,但是浏览器同源策略并不是对所有的请求均制约:
- 制约: XmlHttpRequest
- 不制约: img、iframe、script等具有src属性的标签
如果要实现跨域请求,有两种方法,jsonp和cors(跨域资源共享)下面来看一下怎么用这两种方法来实现跨域请求
1.JSONP实现跨越请求
jsonp的原理是利用script标签的src实现跨域。这种方法非常巧妙,但是有一个问题那就是只能发get请求
下面来看一下文件这里本地用了两个域w1,和w2,下面来看一下具体的文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" value="Ajax" onclick="DoAjax();"> <input type="button" value="JsonpAjax" onclick="JsonpAjax();"> <script src="/statics/jquery-1.12.4.js"></script> <script> function func(arg) { console.log(arg) } function DoAjax() { $.ajax({ url: 'http://w2.com:8002/index', type: 'POST', data: {'k1': 'v1'}, success:function (arg) { console.log(arg) } }); } function JsonpAjax() { // var tag = document.createElement("script"); // tag.src = 'http://w2.com:8002/index?callback=func'; // document.head.appendChild(tag); // document.head.removeChild(tag); $.ajax({ url:'http://w2.com:8002/index', dataType: 'jsonp', jsonp: 'callback', jsonpCallBack: 'func' }) } </script> </body> </html> w1下面的index用来请求w2
#!/usr/bin/env python # coding=utf-8 import tornado.web import tornado.ioloop class IndexHandler(tornado.web.RequestHandler): def get(self): func = self.get_argument('callback') self.write('%s([11,22])' % func) def post(self, *args, **kwargs): self.write('w2.post') settings = { 'template_path': 'views', 'static_path': 'statics', "static_url_prefix": '/statics/', } application = tornado.web.Application([ (r'/index', IndexHandler), ], **settings) if __name__ == '__main__': application.listen(8002) tornado.ioloop.IOLoop.instance().start() w2下面的start文件
#!/usr/bin/env python # coding=utf-8 import tornado.web import tornado.ioloop class IndexHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): self.write('w1.post') settings = { 'template_path': 'views', 'static_path': 'statics', "static_url_prefix": '/statics/', } application = tornado.web.Application([ (r'/index', IndexHandler), ], **settings) if __name__ == '__main__': application.listen(8001) tornado.ioloop.IOLoop.instance().start() w1下的start文件
2、CORS
随着技术的发展,现在的浏览器可以支持主动设置从而允许跨域请求,即:跨域资源共享(CORS,Cross-Origin Resource Sharing),其本质是设置响应头,使得浏览器允许跨域请求。这种做法是去请求的域不用做修改,只需被请求的域需要修改。
说cors之前,先要分清楚什么是简单请求和复杂请求。
条件:
1
、请求方式:HEAD、GET、POST
2
、请求头信息:
Accept
Accept
-
Language
Content
-
Language
Last
-
Event
-
ID
Content
-
Type
对应的值是以下三个中的任意一个
application
/
x
-
www
-
form
-
urlencoded
multipart
/
form
-
data
text
/
plain
注意:同时满足以上两个条件时,则是简单请求,否则为复杂请求
简单请求: 一次请求
非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,
只有“预检”通过后才再发送一次请求用于数据传输。
1、简单请求
简单请求由于没有预检,所以比较简单,只需要在被请求的一方设置响应头 Access-Control-Allow-Origin,value为请求响应的域名,如果写上一个*的话,代表所有的域都可以来这里请求。
2、非简单请求
- “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
- “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
- “预检”缓存时间,服务器设置响应头:Access-Control-Max-Age 设置完成后再该时间里只需通过一次预检
- 默认获取到的所有响应头只有基本信息,如果想要获取自定义的响应头,则需要再服务器端设置Access-Control-Expose-Headers。
此外在跨域请求中,默认情况下,HTTP Authentication信息,Cookie头以及用户的SSL证书无论在预检请求中或是在实际请求都是不会被发送。
如果想要发送:
- 浏览器端:XMLHttpRequest的withCredentials为true
- 服务器端:Access-Control-Allow-Credentials为true
- 注意:此时服务器端响应的 Access-Control-Allow-Origin 不能是通配符 *
下面来看一下代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="button" value="Ajax" onclick="DoAjax();"/> <input type="button" value="DoAjaxComplex" onclick="DoAjaxComplex();"/> <script src="/statics/jquery-1.12.4.js"></script> <script> function DoAjax() { $.ajax({ url: 'http://w2.com:8002/cors', type: 'POST', data: {'k1': 'v1'}, success: function (arg) { console.log(arg); } }); } function DoAjaxComplex() { $.ajax({ url: 'http://w2.com:8002/cors', type: 'PUT', headers: {'h1': 'xxoo'}, data: {'k1': 'v1'}, xhrFields:{withCredentials: true}, success: function (arg) { console.log(arg); } }); } </script> </body> </html> w1下的html文件
#!/usr/bin/env python # coding=utf-8 import tornado.web import tornado.ioloop class IndexHandler(tornado.web.RequestHandler): def get(self): func = self.get_argument('callback') self.write('%s([11,22])' % func) def post(self, *args, **kwargs): self.write('w2.post') class CorsHandler(tornado.web.RequestHandler): def get(self): self.write('{"status": 1, "message": "get"}') def post(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "*") self.write('{"status": 1, "message": "post"}') def options(self, *args, **kwargs): # 允许put方法来 # self.set_header('Access-Control-Allow-Origin', "*") self.set_header('Access-Control-Allow-Origin', "http://w1.com:8001") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Allow-Headers', "h1,h2") self.set_header('Access-Control-Allow-Credentials', "true") self.set_header('Access-Control-Max-Age', 10) def put(self, *args, **kwargs): print(self.cookies) self.set_cookie('k1', 'kkk') self.set_header('Access-Control-Allow-Origin', "http://w1.com:8001") self.set_header('Access-Control-Allow-Credentials', "true") self.set_header('xxoo', "seven") self.set_header('bili', "daobidao") self.set_header('Access-Control-Expose-Headers', "xxoo,bili") self.write('ok') settings = { 'template_path': 'views', 'static_path': 'statics', "static_url_prefix": '/statics/', } application = tornado.web.Application([ (r'/index', IndexHandler), (r'/cors', CorsHandler), ], **settings) if __name__ == '__main__': application.listen(8002) tornado.ioloop.IOLoop.instance().start() w2的start文件
八、上传文件
form表单上传文件的时候一定要记得加上 enctype="multipart/form-data"
<div class="publish"> <form action="publish" method="post" enctype="multipart/form-data"> <div class="title"> <label class="title-label">标题:</label> <input type="text" name="title" > </div> <div class="content1"> <label class="content-label">内容:</label> <textarea name="content"></textarea> </div> <input type="file" name='up_picture'> <input class="submit" type="submit" value="提交"> </form> </div> class PubHandler(account.BaseHanlder): def get(self, *args, **kwargs): USER_INFO['is_login'] = self.session.get_value("is_login") USER_INFO['username'] = self.session.get_value("username") if USER_INFO['is_login']: self.render('publish/publish.html', user_info=USER_INFO) else: self.write('请先登录') def post(self): title = self.get_argument('title', None) content = self.get_argument('content', None) if self.request.files: file_pic = self.request.files['up_picture'][0] # print(file_pic, file_pic['filename'], file_pic['body']) file_path = os.path.join('static', 'pic', 'upload', file_pic['filename']) # 上传的图片保存 print(file_path) with open(file_path, 'wb') as f: f.write(file_pic['body']) form表单上传文件
下面来看一下几种ajax上传
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="file" id="img"> <input type="button" onclick="Uploadfile();" value="提交"> <script> function Uploadfile() { var fileobj = document.getElementById('img').files[0]; var form = new FormData(); form.append('file_img',fileobj); var xhr = new XMLHttpRequest(); xhr.open("post", '/file', true); xhr.send(form); } </script> </body> </html> XmlHttpReq上传文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="file" id="img"> <input type="button" onclick="Uploadfile();" value="提交"> <script src="/statics/jquery-1.12.4.js"></script> <script> function Uploadfile() { var fileobj = $('#img')[0].files[0]; // var form = new FormData(); form.append('file_img',fileobj); $.ajax({ type: 'POST', url: '/file', data: form, processData: false, // 这两行需要加上,不然jquery会自动对传输的数据进行转换 contentType:false, success:function (callback) { console.log(callback) } }) } </script> </body> </html> jquery上传文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .hide{ display: none; } </style> </head> <body> <form id="my_form" name="form" action="/file" method="POST" enctype="multipart/form-data"> <input type="file" id="img" name="file_img"> <input type="button" onclick="redirect();" value="提交"> <iframe id="my_iframe" name="my_iframe" src="" class="hide"></iframe> </form> <script src="/statics/jquery-1.12.4.js"></script> <script> function redirect() { document.getElementById('my_iframe').onload = callBk; document.getElementById('my_form').target = 'my_iframe'; document.getElementById('my_form').submit(); } function callBk() { var t = $("#my_iframe").contents().find('body').text(); console.log(t) } </script> </body> </html> 利用iframe上传文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="/statics/jquery-1.12.4.js"></script> </head> <body> <script type="text/javascript"> $(document).ready(function () { $("#formsubmit").click(function () { var iframe = $('<iframe name="postiframe" id="postiframe" style="display: none"></iframe>'); $("body").append(iframe); var form = $('#theuploadform'); form.attr("action", "/file"); form.attr("method", "post"); form.attr("encoding", "multipart/form-data"); form.attr("enctype", "multipart/form-data"); form.attr("target", "postiframe"); form.attr("file", $('#userfile').val()); form.submit(); $("#postiframe").load(function () { iframeContents = this.contentWindow.document.body.innerHTML; $("#textarea").html(iframeContents); }); return false; }); }); </script> <form id="theuploadform"> <input id="userfile" name="file_img" size="50" type="file" /> <input id="formsubmit" type="submit" value="Send File" /> </form> <div id="textarea"> </div> </body> </html> 基于iframe实现ajax上传
class UploadHandeler(BaseHandler): def get(self, *args, **kwargs): # self.render("Xhr_img.html") # self.render("jquery_img.html") # self.render("iframe_img.html") self.render("iframe_ajax_img.html") def post(self, *args, **kwargs): if self.request.files: file_img = self.request.files['file_img'][0] with open(file_img['filename'], 'wb') as f: f.write(file_img['body']) self.write('ok') 服务端处理类