CS/BS
- CS Client/Server
- BS Browser/Server
- 浏览器只不过是一种特殊的客户端
- 使用HTTP(s)协议
- 解析渲染HTML
- BS开发分类 :
- 客户端开发/前端开发 HTML CSS JavaScript
- 服务器开发/后端开发 Python使用WSGI Django Flask Tornado
HTTP
- HTTP是无状态协议
- 同一客户端的两次请求之间没有任何关系
- 服务器无法分辨两个请求是否来自同一浏览器
cookies
- 键值对信息,用于解决HTTP协议无状态的问题
- 浏览器每次向发起请求,都会携带cookies信息
- 服务器可通过判断cookies,来确定不同得请求之间是否有关联
- cookies信息一般由服务器生成 返回给浏览器,但浏览器也可以自己设置cookies信息
URL
- Uniform Resource Locator 统一资源定位符 简而言之就是地址
- 格式如下
schema://host[:port]/path/.../[:url-params][?query-string][#anchor]
- 例如 :
https://cn.bing.com/search?q=python&qs=n&form=QBRE
- http协议默认80端口
- https协议默认443端口
- /path/to/resource 指向资源路径
- ?q=python&qs=n&form=QBRE query String 查询字符串 问好分割 后面的key=value形式,用&分割
无状态 有连接 短连接
- 无状态 : 服务器无法分辨两个请求是否来自同一浏览器
- 有连接 : HTTP基于TCP,是面向连接的,需要三次握手,四次断开
- 短连接 :
- HTTP1.1之前,一个请求一个连接 但TCP连接的创建和销毁成本太高,对服务器有很大影响
- HTTP1.1开始支持keep-alive,一个链接打开后会保持一段时间,减轻了服务器的压力
WSGI
相关参考 : http://python.jobbole.com/87361/
wsgiref库
from wsgiref.simple_server import make_server
def app(environ,start_response):
qstr=environ.get('QUERY_STRING')
qd={k:v for k,_,v in map(lambda x:x.partition('='),qstr.split('&'))}
print(123,qstr,'\n',456,qd)
status='200 OK'
headers=[('Content-Type','text/html;charset=utf-8')]
start_response(status,headers)
res = '<h1>中文Test Page</h1>'.encode()
return [res]
#app要求可调用,传两个参数,返回一个可迭代对象
ip='127.0.0.1'
port=80
server=make_server(ip,port,app)
server.serve_forever()
----------------------------------------------------------------------
浏览器访问http://127.0.0.1/?a=1&b=2&a=3&123=456&fg=中文,jk
服务器端输出信息 :
123 a=1&b=2&a=3&123=456&fg=%E4%B8%AD%E6%96%87,jk
127.0.0.1 - - [26/Jun/2018 10:55:18] "GET /?a=1&b=2&a=3&123=456&fg=%E4%B8%AD%E6%96%87,jk HTTP/1.1" 200 24
456 {'a': '3', 'b': '2', '123': '456', 'fg': '%E4%B8%AD%E6%96%87,jk'}
urllib库 : 解析url查询字符串
from urllib import parse
qstr='a=1&b=2&a=3&123=456&fg=中文,jk'
qd=parse.parse_qs(qstr)
print(123,qstr,'\n',456,qd)
-----------------------------------
123 a=1&b=2&a=3&123=456&fg=中文,jk
456 {'a': ['1', '3'], 'b': ['2'], '123': ['456'], 'fg': ['中文,jk']}
webob库
- environ数据保存在字典中,不方便操作
- 使用webob库可将environ数据转化为request对象
- 用Request解析environ :
from wsgiref.simple_server import make_server
from webob import Request
def app(environ,start_response):
r=Request(environ)
print('r.headers : {}\nr.method : {}\nr.path : {}\nr.query_string : {}\nr.GET : {}\nr.POST : {}\nr.params : {}'.format(
r.headers,
r.method,
r.path,
r.query_string,
r.GET,
r.POST,
r.params))
status='200 OK'
headers=[('Content-Type','text/html;charset=utf-8')]
start_response(status,headers)
res = '<h1>中文Test Page</h1>'.encode()
return [res]
ip='127.0.0.1'
port=80
server=make_server(ip, port,app)
server.serve_forever()
--------------------------------------------------------------------
浏览器访问http://127.0.0.1/?a=1&b=2&a=3&123=456&fg=中文,jk
服务器端输出信息 :
r.headers : <webob.headers.EnvironHeaders object at 0x0000021244A96748>
r.method : GET
r.path : /
r.query_string : a=1&b=2&a=3&123=456&fg=%E4%B8%AD%E6%96%87,jk
r.GET : GET([('a', '1'), ('b', '2'), ('a', '3'), ('123', '456'), ('fg', '中文,jk')])
r.POST : <NoVars: Not an HTML form submission (Content-Type: text/plain)>
r.params : NestedMultiDict([('a', '1'), ('b', '2'), ('a', '3'), ('123', '456'), ('fg', '中文,jk')])
127.0.0.1 - - [26/Jun/2018 11:33:39] "GET /?a=1&b=2&a=3&123=456&fg=%E4%B8%AD%E6%96%87,jk HTTP/1.1" 200 24
r.headers : <webob.headers.EnvironHeaders object at 0x0000021244A968D0>
r.method : GET
r.path : /favicon.ico
r.query_string :
r.GET : GET([])
r.POST : <NoVars: Not an HTML form submission (Content-Type: text/plain)>
r.params : NestedMultiDict([])
127.0.0.1 - - [26/Jun/2018 11:33:39] "GET /favicon.ico HTTP/1.1" 200 24
- MultiDict :
from webob.multidict import MultiDict
md=MultiDict()
md.add('1', 'value')
md.add('1', 'value2')#add函数可以形成一键多值
md['2']='123'#建索引赋值和update会将多值变为单值,
md['2']='234'
md['a']='123asd'
md['b']='bn'
md.add('b', 'value')
md.add('b', 'value2')
print("md : {}\nmd.getall('1') : {}\nmd.get('b') : {}\nmd.getone('a') : {}".format(md,md.getall('1'),md.get('b'),md.getone('a')))
for k,v in md.items():
print('k: {},v: {}'.format(k,v))
--------------------------------------------------------------------
md : MultiDict([('1', 'value'), ('1', 'value2'), ('2', '234'), ('a', '123asd'), ('b', 'bn'), ('b', 'value'), ('b', 'value2')])
md.getall('1') : ['value', 'value2'] #返回列表
md.get('b') : value2
md.getone('a') : 123asd #要求键值'a'有且只能有一个值,否则会报错
k: 1,v: value
k: 1,v: value2
k: 2,v: 234
k: a,v: 123asd
k: b,v: bn
k: b,v: value
k: b,v: value2
- Response :
from wsgiref.simple_server import make_server
from webob import Response,Request
def app(environ:dict,start_response):
req=Request(environ)
res=Response()
res.status_code=200
print(res.content_type,'\n',res.status,'\n',res.headerlist)
html = '<h1>中文Test Page</h1>'.encode()
res.body=html
return res(environ, start_response)
ip='127.0.0.1'
port=80,
server=make_server(ip, port,app)
server.serve_forever()
--------------------------------------------------------------------
text/html
200 OK
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
127.0.0.1 - - [26/Jun/2018 15:52:03] "GET /?a=1&b=2&a=3&123=456&fg=%E4%B8%AD%E6%96%87,jk HTTP/1.1" 200 24
text/html
200 OK
[('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
127.0.0.1 - - [26/Jun/2018 15:52:03] "GET /favicon.ico HTTP/1.1" 200 24
wsgify装饰器 :
- 该装饰器装饰的函数要求有一个参数,此参数为webob.Request类型,且是对字典environ的对象化后的实例
- 返回值是webob.Response类型,若为str或bytes类,会经过str–>bytes–>Response的转化
from wsgiref.simple_server import make_server
from webob import Request,Response
from webob.dec import wsgify
@wsgify
def app(request:Request)->Response:
res=Response('<h1>中文测试Test Page</h1>')
return res
ip='127.0.0.1'
port=80
server=make_server(ip, port,app)
server.serve_forever()
路由
- URL路径与处理函数(或静态文件)的映射
- 使用字典,key为path,value为handle函数
- 为更好地匹配url路径.引入正则表达式,key由静态的path改为正则匹配模式pattern
- 考虑到匹配顺序问题,将字典换为二元组(compiled(pattern),handle)
- 再次细分 可将浏览器请求方法也加入
from wsgiref.simple_server import make_server
from webob import Response,Request
from webob.dec import wsgify
from webob.exc import HTTPNotFound
import re
# from functools import partialmethod
#小技巧,字典属性化
class AttrDict:
def __init__(self,d):
self.__dict__.update(d if isinstance(d,dict) else {})
def __setattr__(self):
raise NotImplementedError
class Router:
##ROUTETABLE每一项是由 请求方法 编译过的正则表达式 与处理函数组成
#(method,compilde_pattern,handle)
ROUTETABLE=[]
#注册路由,装饰器更新ROUTETABLE名单
@classmethod
def route(cls,method,pattern):
def wrapper(handler):
cls.ROUTETABLE.append( (method.upper(),re.compile(pattern),handler))
return handler
return wrapper
# get = partialmethod(route,'GET')
@classmethod
def get(cls,pattern):
return cls.route('GET',pattern)
@classmethod
def post(cls,pattern):
return cls.route('POST',pattern)
@classmethod
def head(cls,pattern):
return cls.route('HEAD',pattern)
@Router.get(r'^/$') #匹配网站根目录
@Router.route('GET',r'^/(?P<id>\d+)$') #匹配数字
def indexhandle(request:Request):
print(request.groups,request.groupdict,sep='\n')
# return Response('<h1>Welcome to Index Page id={}</h1>'.format(request.groupdict.get('id',0)))
r= AttrDict(request.groupdict)
id = r.id if hasattr(r, 'id') else ''
return Response('<h1>Welcome to Index Page id={}</h1>'.format(id))
@Router.get(r'^/python$')
def pythonhandler(request:Request):
res=Response()
# res.charset='utf-8'
res.body='<h1>Welcome to Python Page</h1>'.encode()
return res
class App:
_Router=Router
@wsgify
def __call__(self,request:Request):
for method,pattern,handler in self._Router.ROUTETABLE:
#not method表示未指定请求方法,则支持全部方法
if not method or request.method.upper() in method:
matcher=pattern.match(request.path)
if matcher:
request.groups=matcher.groups()
request.groupdict=matcher.groupdict()
return handler(request)
raise HTTPNotFound('<h1>404</h1>')
if __name__ == '__main__':
ip='127.0.0.1'
port=80
server=make_server(ip, port, App())
try:
server.serve_forever()
except KeyboardInterrupt:
server.shutdown()
server.server_close()