类Flask框架
使用WSGI开发框架
WSGl , WEB Server Gateway nerace,你是一种底层协议,它规定了服务器程序和应用程序
各自实现什么接口。 Python的实现称为wsgiref
Flask,基于WSG ,微框架
Django ,基于WSG ,开源的WEB框架
HTTP协议
安装httpd
使用httpd,观察http协议
[root@bogon ~]# yum -y install httpd
协议
http是一种无状态的面向连接的协议
同一个客户端的两次请求之间没有任何关系,从服务器端角度来说,它不知道这两个请求来自同一个客户端。
无状态,就是说不管从客户端来讲还是从服务端来讲根本没有办法区分发起的两次http发起请求,我要一个资源,在要一个资源,根本不知道这两个请求之间究竟是不是同一个浏览器发出来的
cookie
cookie
键值对信息。
浏览器发起每一请求时,都会把cookie信息发给服务器端。
是一种客户端、服务器端传递数据的技术。
服务端可以通过判断这些信息,来确定这次请求是否和之前的请求有关联。
一般来说cookie信息是在服务器端生成,返回给客户端的。
客户端可以自己设置cookie信息。
cookie从浏览器端来讲,如果你有我这个服务器的cookie的话,你每一次就应该把这个cookie给我顺带发回来,服务器看到这个标识才能确定是不是你,跟上次有没有关系,cookie技术是干这个事的,cookie一般来讲是由当你第一次去连接这个服务器的时候,服务器说我先看看你有没有cookie带过来,你没有cookie说明你是第一次访问我,我给你送一个标识,这个标识是放在你的cookie里的,这样当用户发送请求过来,服务器端要给他响应,响应回来是html,当然这里面还伴随http协议他是支持cookie的,也就是说看到的是网页还有一些你看不到的数据里面是有cookie的,这个cookie就被你的浏览器特殊客户端保存起来了,这个浏览器如果在对同样的这个网站发起请求,这个cookie如果没有过期的话,(如果快过期了会发送一个新的给你延长一下),这个cookie是会偷偷的在发送给服务器端的,服务器一看这个cookie这个标识还有效,就知道这个连接跟上一个连接是有关系的是同一个,下面就可以做一些判断了比如是同一个你有没有登录过呀,如果登录过是不是不用显示登录页面了,如果发现没有登录过,强制跳转到登录页面
URL组成
URL可以说就是地址, uniform resource locator统一资源定位符,每一个链接指向一个资源供客户端访问。
schema://host[:port#]/path/.../[;url-params] [?query-string][#anchor]
[;url-params] 这个东西没人用
[?query-string] 查询字符串非常重要,在url中关注的就是他了
[#anchor] 锚点,页面内的链接,一点锚点跳下面去了,一般链接跳到另一个页面去了,不用关心
例如,通过下面的URL访问网页
http://www.cnblogs.com/pathon/index.html?id-5&name-python
url的传输类型就是字符串没有数值类型,当数字来用需要自己来转
访问静态资源时,通过上面这个URL访问的是网站的某路径下的index.html文件,而这个文件对应磁盘上的真实的
文件。就会从磁盘上读取这个文件,并把文件的内容发回浏览器端
scheme模式、协议
http, ftp, https, file, mailto等等.mysql等都是类似这样写。
host:port
www.magedu.com:80 , 80端口是默认端口可以不写。域名会使用DNS解析,域名会解析成IP才能使用。实际上
会对解析后返回的IP的TCP的80端口发起访问。
/path/to/resource
path,指向资源的路径。这个路径是一个逻辑路径,跟相对路径还不一样,总之他告诉你通过这个东西就可以找到资源,可能会做映射,甚至重写技术 ,他跟真是的路径不一定真实的对应,
[?query-string] 就是下面这东西,在一个url后面用?分割,后面是谁等于谁中间用&连接起来,这个就叫做查询字符串,这东西太重要了做web开发基本上就是围绕这东西转,有这东西就可以传递一些参数给服务器端,传的就是参数,谁等于谁键值对不就是参数吗
?key1=value1 &key2-value2
query string ,查询字符串,问号分割,后面key-value形式,且使用&符号分割。
url用的才是最多的,格式就是你用什么协议呀,你访问什么地址什么端口啊,你用什么用户名和密码访问,然后你访问什么资源,资源用路径表示最方便,这地方还有一些参数
HTTP消息
消息分为Request, Response.
Request :浏览器向服务器发起的请求,这个请求是http协议的
Response :服务器对客户端请求的响应,也需要支持http协议
HTTP消息分为Request(请求), Response(响应),就是IO进和出,只不过在不同层次上来讲
请求和响应消息都由请求行、Header消息报头、Body消息正文组成。
请求
请求消息行:请求方法Method 请求路径 协议版本CRLF
# 请求头信息
GET / HTTP/1.1 # 协议的封装第一行有特殊意义大家叫请求消息行,第一行就这么写,第一部分GET叫请求方法Method,第二部分/请求路径,代表要请求什么资源,第三部分HTTP/1.1协议版本,不同协议传输方式可能不一样,关键是1.1支持长短连接,CRLF(CRLF回车换行符)
Host: www.cnblogs.com # 第二行叫head头,第二行一下整个合起来大家叫他请求头
User-Agent: Mozil1a/5.0 (Windoms NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 # 现在是在服务器端看的,这是远处浏览器给服务器发送的信息,这里面告诉你我是什么浏览器,当前是什么版本这个信息可以记录下来做日志分析
Accept: text/html,application/xhtml+xml,application/xml:q-0.9,*/*q-0.8
Accept-Language: zh-CN, zh;q=0.8, en-US;g=0.5, en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: 53gid2=10019286380004: 53revisit=1512357945900: 53uvid=1; onliner_zdfq72145423=0: ir # Cookie比较重要这里面带着id信息呢
Connection: keep-alive # HTTP/1.1版本就支持这个
Upgrade-Insecure-Requests: 1
请求方法Method
GET 请求获取URL对应的资源,比较纯粹就是拿资源,get也可以使用[?query-string]传参的方式推数据,当你在浏览器中打了一个地址就是要拿到这个资源
POST 提交数据至服务器端,一样要去服务器端发送请求,但这个请求推数据过去,比如说做登录窗口的时候不仅要获取页面还要提交数据
HEAD 和GET类似,不过不返回消息正文
常见传递信息的方式
1、 GET方法使用Query String,(Query String就是查询字符串,get方法通过这个方式来传数据)
http://www.cnblogs.com/pathon/index.html?id=5&name=python # ?id=5&name=python这里就是查询字符串,查一个id等于5名字叫python的数据,这时候其实就把这样的数据传给服务器端了,服务器端这时候调相应的应用程序,从数据库拿相应的数据返回回来,经常在网页连接中有这东西,告诉你id等于几或者配置等于几,第几页了
2、POST方法提交数据
http://127.0.0.1:9999/xxx/yyy?id=5&name=cnblogs # 这就是GET的写法,?id=5&name=magedu这些数据默认也是传过来了
使用表单提交数据,文本框input的name属性分别为age, weight,height
请求消息如下
POST /xxx/yyy?id=5&name=magedu HTTP/1.1 # 这个就是首行,不推荐这种写法,长度有限2百多个字符不能超,如果把密码也这样传太过明显,id=5&name=magedu链接写在首行了,这个查询字符串就也可以把这个内容带过去,这就是链接的一部分,查询字符串属于URL的一部分
HOST: 127.0.0.1:9999 # 这里是头head
content-length: 26 # 这里是头head
content-type: application/x-www-form-urlencoded # 这里是头head,下面两次回车换行符空一行
age=5&weight=80&height=170 # 这里是body正文,消息请求体,这里面随你放,就是post方法提交的数据,这就是表单
3、 URL中本身就包含着信息(早期认为POST和GET就是协议提供给我们的,认识方式一变理解层次就变了URL中本身就包含着信息的)
http://www.cnblogs.com/python/student/001 # 前面是域名没多说的,后面就有一种业务思想在里面python(班)/student(学员)/001(001号),这就是本身就有信息在了何必还要传呢,现在基本就是这种东西了,用url来表示资源,也就是说用这个东西就表示一个业务这个相当于发起GET请求,就是问我要python班里的学生001号他的信息的
响应
响应消息行:协议版本 状态码 消息描述 CRLF
# 响应头信息
HTTP/1.1 200 ок # 第一行,刚才是告诉我HTTP/1.1版本,现在我也用这个版本给你响应,然后给你响应一个200就是告诉你我成功的把信息回应了给你,见不到200系列就不可以认为服务器端给你成功返回你想要的信息
Date: Sat, 23 Dec 2017 12:03:17 GMT # 这一行一下成为相应部分的head,响应头
Content-Type: text/html: charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Cache-Control: private, max-age=10
Expires: Sat, 23 Dec 2017 12:03:27 CMT
Last-Modified: Sat, 23 Dec 2017 12:03:17 GMT
X-UA-Compatible: IE=10
X-Frame-Options: SAMEORIGIN
Content-Encoding: gzip
# 这个以下就是响应体
status code状态码
状态码在响应头第一行
1xx提示信息,表示请求已被成功接收,继续处理
2xx表示正常响应 ,服务器端成功的响应的客户端
200 正常返回了网页内容
3xx重定向
301 页面永久性移走,永久重定向。返回新的URL ,浏览器会根据返回的ur1发起新的request请求,这对搜索引擎太重要了,这表示你的网站整体迁移了,搜索引擎看到301马上就要记录下来省的下回指向一个老网站
302 临时重定向,搜索引擎一看临时跳转
304 资源未修改,浏览器使用本地缓存。资源重定向意思是你的资源就不要从我服务器来拿了,你的浏览器的缓存目录里已经有这个图片了就不要找我要了,图片更新有一个时间过期的问题
4xx客户端请求错误
404 Not Found,网页找不到,客户端请求的资源有错,没有资源就是404
400 请求语法错误
401 请求要求身份验证,一发请求就说对不起我这边有验证
403 服务器拒绝请求,你不该访问直接拒绝你不提供服务
5xx服务器端错误
500 服务器内部错误,说明单服务器,这东西往往是java写的Tomcat直接上的那种,中间没做反向反向代理直接报500错误
502 上游服务器错误,例如nginx反向代理的时候就会出现502,很重要,一看就知道这种网站一定是动静分离,前面一定有一个反向代理的
503 访问了服务器不存在的资源,503与404(404 Not Found)是同属一种网页状态出错码。前者是服务器不可访问时给客户端的一种状态,后者是访问了服务器不存在的资源
无状态,有连接和短连接
无状态,说过了,指的是服务器无法知道2次请求之间的联系,即使是前后2次同一个浏览器也没有任何数据能够判
断出是同一个浏览器的请求。后来可以通过cookie, session来判断。
无状态主要是从server端,前面解释过客户端的问题了,从服务端来看我根本不知道两次http请求之间有什么关系,不知道所以就叫无状态,对于现在动态网站开发里面讲的有状态都指定是我有办法知道两次连接是同一个这就叫有状态的开发,所以对于所有动态网页开发都要支持有状态否者就没法开发了
有连接,是因为它基于TCP协议,是面向连接的,需要3次握手、4次断开,
TCP连接就是有连接协议,面向连接的协议所以就叫有连接
短连接, Http 1.1之前,都是一个请求一个连接,而Tcp的连接创建销毁成本高,对服务器有很大的影响。所以,
自Http 1.1开始,支持keep-alive ,默认也开启,一个连接打开后,会保持一段时间(可设置) ,浏览器再访问该
服务器就使用这个Tcp连接,减轻了服务器压力,提高了效率。
短连接,在Http 1.0的时候,认为网络资源是很宝贵的,你不访问这个服务器就应该立马断开,当你访问一个连接的时候,这个连接给你传回响应的资源的时候你就应该断开,就不应该发起新的连接,下回你点的时候,等你看完在点第二连接这是一种很慢的连接
推荐图书《HTTP权威指南》
WSGI
WSG主要规定了服务器端和应程序间的接口。
这应该有三个角色
第一个角色Client端:浏览器
第二个角色server端:WSGI Server
这个server同时兼顾这他是一个http server不但可以接受用户发起的Socket请求,还可以把这些请求给你解析完,但是他只是一个server能做的只是给你解析,后面要写一个程序处理数据,对于WSGI Server来讲就是WSGI App
第二个角色App端: WSGI App
处理WSGI Server解析来的数据
http协议,进行通信
http请求从浏览器端发送过来之后到WSGI Server就是请求(request),server首先要支持http协议不然浏览器端发送过来的东西他根本就不理解但是在python中它使用了WSGI这种开发接口,也就是说用python这些库来开发的话最好大家都懂WSGI这门语言这样大家才能交流,所以这个http server他要兼具支持WSGI协议。http请求过来后被server解包之后给你的东西都是数字,字符串这些很容易理解的东西,否者都是字节流没法处理,拿到这些东西后server这里就是存粹的server要把这些解包后的东西传递给你写的符合WSGI标准的程序WSGI App,这个程序写了好多的函数好多的类,然后里面终于把这个数据给处理完了之后按照他的要求这个数据首先要把响应(Response消息)消息头传回去就是为了通知客户端我现在要响应你200呢还是404呢要告诉你这个请求我究竟有没有很好的处理掉,浏览器说你现在是200是ok的,已经准备好了接受你要发来的html网页就是文本文件的内容,先不说css,js这些这些锦上添花的东西,所以WSGI App程序先把响应头发送过来,然后大部队(大部队就是Response body里面包含了完整的html信息)就过来了,还没完WSGI App的返回过程是WSGI App首先把文本文件内容传给server要必须做一个转换把文本包装成http协议的消息(文本),因为文本文件必须先在http协议上跑,然后传给浏览器一看http的进行解析掉http剩下的就是传的文本这些文本是html的进行渲染,不是有个表格吗,不是要显示个红色吗渲染就行了,这是整个的一个处理过程,对于其他的web开发来讲无非就是App中间的通信协议这块换掉,不用WSGI了
应用程序端
1、应用程序应该是一个可调用对象
Pyhon中应该是函数、类、实现了eall法的类的实例
server调用应用程序,要求应用程序端必须是一个可调用对象,这是python中的方法,他要调用应用程序并送参进来,如果是不可调用对象参数往哪塞,在python中的可调用对象实现无非就是下面2、中的三种
2、这个可调用对象应该接收两个参数
只要在python中符合这三种方式,他都是满足了WSGI的基本要求的这样一个可调用对象
#函数实现
def application(environ,start_response): #调一个函数,整个函数至少包含environ一个参数,这个参数我就给你赛进去了
pass
#类实现
class Application: # 如果写成一个类,这个类构造方法里面也要environ传参,类实例化后括号相当于调用,调用的是传进来
def __init__(self,environ,start_response):
pass
#类实现
class Application:
def __call__(self,environ,start_response): # 这种方式__call__可调用对象,可调用对象相当于也在environ传参
pass
# start_response 这就是服务端提供的一个回写的东西,这个参数意思是,反正在WSGI App进行逻辑处理后生成的内容,最后返回之前要在server做协议封装,server这边给你传两个东西第一个是要请求解析的东西塞给environ了,第二个未来准备写回去的东西,内容(主要是http状态码,报文头)交给这个对象给server返回回去就行了,server给你封装成http协议了,然后再用最底层的socket一个字节一个字节给你传回去
3、以上的可调用对象实现,都必须返回正文的可迭代对象。
意思是有一个文本要返回就是html内容,请写成return [html(内容)],哪怕只有一个内容请你写到里面去,这是他的要求,可迭代对象就行了放元组也没问题,容易忘掉,直接return一个字符串给他会抛异常
res_str = b'cnblogs.com
' # 比如这里有一个字符串要返回回去
# 函数实现
def application(environ,start_response): # 这里请求进来了
return [res_str] # 紧接着return 一个字符串放到一个可迭代对象中去返回回去
# 类实现
class Application:
def __init__(self,environ,start_response):
pass
def __iter__(self):
yield res_str # 返回一个生成器本身就是一个迭代器,所以是一个可迭代对象
# 类实现
class Application:
def __call__(self,environ,start_response):
return [res_str]
参数
environ和start_response两个参数名可以是任何合法名,但是一般默认都是这2个名字
environ是包含Http请求信息的dict对象
environ的东西这个环境里面就是把你传过来的那些数据全解析后包装起来就放到这里去,他传过来的都是键值对,是一个字典,environ里面有一些重要的东西在下面表格中
名称 | 含义 |
---|---|
REQUEST_METHOD | 请求方法, GET、POST等 (请求方法是什么?非常重要) |
PATH_INFO | URL中的路径部分 (URL中的路径信息) |
QUERY_STRING | 查询字符串 (URL后面?后面那个谁等于谁,传参用的非常重要) |
SERVER_NAME,SERVER_PORT | 服务器名、端口 |
HTTP_HOST | 地址和端口 |
SERVER_PROTOCOL | 协议 |
HTTP_USER_AGENT | UserAgent信息(互联网开发这些信息非常重要) |
start_response一个可调用对象,有3个参数,定义如下:
start_response(status, response_headers, exc_info-None)
第一个参数status,是状态码,例如208 0K,你现在想让我给你发一个头部信息head回去,这个头部你要告诉我现在你想让我发回去这个状态码是多少
第一个参数response_headers,是一个元素为二元组的列表,例如('Content-Type', 'text/plain;charset=utf-8') 这个二元组告诉你Content-Type内容的类型,给你发回去的是一个text纯文本,charset=utf-8字符集是utf-8,填头信息
第三个参数exc_info-None,在错误处理的时候使用,异常信息
start_response应该在返回可迭代对象之前调用,因为它返回的是Response Header,返回的可迭代
对象是Response Body.
服务器端
服务器程序需要调用符合上述定义的可调用对象,传入environ, starturesponse,拿到返回的可迭代对象,返回给(浏览器端)客户端。
简单的web程序开发wsgiref
wsgire这个一个WSCI参考实现库
到这里就完成了一个简单的WEB程序开发。
WEB服务器
本质上就是一个TCP服务器,监听在特定端口上
支持HTTP协议,能够将HTTP请求报文进行解析,能够把响应数据进行HTTP协议的报文封装并返回浏览器端。
实现了WSGI协议,该协议约定了和应用程序之间接口(参看PEP333 , https://www.pthon.org/dev/peps/pep-0333
APP应用程序
遵从WSGI协议
本身是一个可调用对象
调用start_response返回响应头部
返回包含正文的可迭代对象
参考库意思就是生产环境别用实验写练习用的
wsgire.simple_server实现一简单的WSGI HTTP服务器。
simple_server(简单的服务,一个简单的http server)
wsgiref.simple_server.make_server(host,port,app,server_class=WSGIServer,handier_class=WSCGkequesthandler)启动一个WSGI服务器,让这个app去处理相关的数据然后
simple_server提供了一个方法叫做make_server,里面把主机端口告诉他tcp绑定就可以提供一个http server,app的意思是跟哪个app关联发现有用户请求过来需要调这个app,然后响应(response)一些信息回来
wsgiref.simple_server.demo_app(envion,start_response) 一个函数,小巧完整的WSGI的应用程序的实现
wsgiref.simple_server.demo_app他提供了一个demo_app我们什么都不用写,他提供了一个app,这个app是一个很小的函数实现的,但他很完整实现了整个WSGI协议要求
返回文本的例子
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
import wsgiref
from wsgiref.simple_server import make_server,demo_app # 导入make_server进行绑定,导入demo_app小巧完整的WSGI的应用程序的实现
ip = '192.168.161.203'
port = 9999
server = make_server(ip,port,demo_app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找demo_app
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# 服务启动后访问192.168.161.203:9999打印结果
# Hello world! 这是print("Hello world!", file=stdout)他打印的内容
#这些打印的信息是
# h = sorted(environ.items()) # 把他所有的environ.items()键值对排个序,把你传进来的environ
# for k,v in h: # 依次把键值对打印出来
# print(k,'=',repr(v), file=stdout) # 然后这些东西都属于stdout的了getvalue全部拿来返回回去,以下打印的全都是
# 这样实现的,所有信息都打印了不考虑安全
# AMD_ENTRYPOINT = 'vs/server/remoteExtensionHostProcess'
# APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL = 'true'
# COLORTERM = 'truecolor'
# CONTENT_LENGTH = ''
# CONTENT_TYPE = 'text/plain'
# GATEWAY_INTERFACE = 'CGI/1.1'
# GIT_ASKPASS = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass.sh'
# HISTCONTROL = 'ignoredups'
# HISTSIZE = '1000'
# HOME = '/root'
# HOSTNAME = 'localhost.localdomain'
# HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
# HTTP_ACCEPT_ENCODING = 'gzip, deflate'
# HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9'
# HTTP_CONNECTION = 'keep-alive'
# HTTP_HOST = '192.168.161.203:9999'
# HTTP_UPGRADE_INSECURE_REQUESTS = '1'
# HTTP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
# LANG = 'zh_CN.UTF-8'
# LESSOPEN = '||/usr/bin/lesspipe.sh %s'
# LOGNAME = 'root'
# LS_COLORS = 'rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:'
# MAIL = '/var/spool/mail/root'
# PATH = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'
# PATH_INFO = '/'
# PIPE_LOGGING = 'true'
# PWD = '/root'
# QUERY_STRING = ''
# REMOTE_ADDR = '192.168.161.193'
# REMOTE_HOST = ''
# REQUEST_METHOD = 'GET'
# SCRIPT_NAME = ''
# SERVER_NAME = 'localhost.localdomain'
# SERVER_PORT = '9999'
# SERVER_PROTOCOL = 'HTTP/1.1'
# SERVER_SOFTWARE = 'WSGIServer/0.2'
# SHELL = '/bin/bash'
# SHLVL = '5'
# SSH_CLIENT = '192.168.161.193 8747 22'
# SSH_CONNECTION = '192.168.161.193 8747 192.168.161.203 22'
# TERM = 'xterm-256color'
# TERM_PROGRAM = 'vscode'
# TERM_PROGRAM_VERSION = '1.47.3'
# USER = 'root'
# VERBOSE_LOGGING = 'true'
# VSCODE_GIT_ASKPASS_MAIN = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass-main.js'
# VSCODE_GIT_ASKPASS_NODE = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/node'
# VSCODE_GIT_IPC_HANDLE = '/run/user/0/vscode-git-f64a3e4d20.sock'
# VSCODE_IPC_HOOK_CLI = '/tmp/vscode-ipc-468555df-a2ea-4a76-a02d-df0b0a09b1d9.sock'
# XDG_RUNTIME_DIR = '/run/user/0'
# XDG_SESSION_ID = '646'
# _ = '/usr/bin/python3'
# wsgi.errors = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
# wsgi.file_wrapper = <class 'wsgiref.util.FileWrapper'>
# wsgi.input = <_io.BufferedReader name=4>
# wsgi.multiprocess = False
# wsgi.multithread = True
# wsgi.run_once = False
# wsgi.url_scheme = 'http'
# wsgi.version = (1, 0)
返回网页的例子
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
import wsgiref
from wsgiref.simple_server import make_server,demo_app # 导入make_server进行绑定,导入demo_app小巧完整的WSGI的应用程序的实现
def application(environ:dict,start_response): # 这个函数忘了就从demo_app源码中复制进来,改改名字,改改代码块,这样级别上就写完了
print(type(environ)) # 打印一下拿进来的类型,他是一个字典
html = "<h1>Hello world欢迎你</h1>" #这段数据开发中要关注的,这属于业务逻辑,下面的都是固定套路, 给你一个html文本,html看他是宽松的还是严格的,宽松的标准要求要有一个<h1>开的有一个</h1>闭的
start_response("200 OK", [('Content-Type','text/html; charset=utf-8')]) # start_response这是个函数,这个函数是约定好的
return [html.encode("utf-8")] # 返回一定要妨到可迭代对象中
ip = '192.168.161.203'
port = 9999
server = make_server(ip,port,application) # application改改名字,make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找application
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
server.server_close() # 关掉
# 服务启动后访问192.168.161.203:9999打印结果
# Hello world欢迎你 这是html = "<h1>Hello world欢迎你</h1>,return [html.encode("utf-8")]返回的内容
# 浏览器按F12进入调试
# 调到Network从新加载,name有一个主机地址192.168.161.203点一下,这是返回的东西
# Headers 指的是头部
# General (一般)
# Request URL: http://192.168.161.203:9999/ # 从哪个服务器传过来的,自己请求的
# Request Method: GET # 提交的是GET方法
# Status Code: 200 OK # 服务器回应你是200 ok没有出错
# Remote Address: 192.168.161.203:9999 # 远程地址
# Referrer Policy: no-referrer-when-downgrade
# Response Headers (响应头部) view source(查看源可以看到HTTP/1.0 200 OK 他的头部)
# Content-Length: 29 # 总共长度29个字节
# Content-Type: text/plain; charset=utf-8 # 你要求text/plain按照文本显示的这里要start_response函数修改为text/html,要求使用utf-8否者中文是乱码
# Date: Tue, 11 Aug 2020 04:20:54 GMT # 日期
# Server: WSGIServer/0.2 CPython/3.6.8 # 暴露服务器信息了
# Request Headers (请求头部) view source(查看源可以看到GET / HTTP/1.1 他的头部)
# Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 # 我能接受的类型和其他参数
# Accept-Encoding: gzip, deflate # 我能接受压缩编码
# Accept-Language: zh-CN,zh;q=0.9 # 语言中文
# Cache-Control: max-age=0 #如果是no-cache,表示不ache
# Connection: keep-alive # 我这是保持连接的,http1.1就是保持连接的
# Host: 192.168.161.203:9999 # 主机
# Upgrade-Insecure-Requests: 1
# User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 # 用户使用的终端
# # referer: http://192.168.161.203:9999 # 这是从哪次调过来的,这里没有显示,如果盗链就靠他
# Preview 指的是预览
# <h1>Hello world欢迎你</h1>
# Response 指的是Response body(响应体)
# <h1>Hello world欢迎你</h1> # 这个html,有的大多数都是javs script脚本,或者html和javs script混合使用
simple_server只是参考用,不能用于生产
测试用命令
curl -I http://192.168.142.1:9999/xxx?id=5
curl -x POST http://192.168.142.1:9999/yyy -d '{"x":2}'
-l使用HEAD方法
-x指定方法,-d传输数据
WSGI服务器作用
- 监听HTTP服务端口( TCPServer ,默认端口80 )
- 接收浏览器端的HTTP请求并解析封装成(字典)environ环境数据
- 负责调用应用程序,将environ和start response方法传入
- 将应用程序响应的正文封装成HTTP响应报文返回浏览器端
WSGI APP应用程序端
1、应用程序应该是一个可调用对象
Python中应该是函数、类、实现了_call-方法的类的实例
2、这个可调用对象应该接收两个参数
# 函数实现 # 函数可调用
def application(environ,start_response):
return[res_str] # 要包装成一个可迭代对象一个列表
# 类实现 # 类本身是可调用的,这个类的实例可迭代
class Application:
def __init__(self, environ,start_response):
pass
def __iter__(self): #实现此方法,对象即可迭代
yield res_str # yield拨一下转一下
# 类实现 # 类实例化后的这个实例是可调用的
class Application:
def __call__(self, environ,start_response): # 与函数方法相似
return [res_str]
environ和start_response这两个参数名可以是任何合法名,但是一般默认都是这2个名字。
应用程序端还有些其他的规定,暂不用关心
类Flask框架实现
从现在开始,我们将一步步完成一个WSGI的WEB框架,从而了解WEB框架的内部机制。
WSGI请求environ处理
WSGI服务器程序会帮我们处理HTTP请求报文(进行解析放到字典里),但是提供的environ还是一个用起来不方便的字典
# 遍历出的 environ字典中的内容
http://127.0.0.1:9999/python/index.html?id=1234&name=tom
('SERVER_PROTOCOL','HTTP/1.1')
('wsgi.url_scheme', 'http')
('HTTP_HOST', '127.0.0.1:9999')
('SERVER_PORT','9999')
('REMOTE_ADDR', '127.0.0.1') # 请求的地址
('REQUEST_METHOD', 'GET') # 方法
('CONTENT_TYPE', 'text/plain') # 重要,内容类型
('PATH_INFO', '/python/index.html') # 重要,网页资源路径拿到了
('QUERY_STRING', 'id=1234&name=tom') # url请求的查询字符串,拿到了
('HTTP_USER_AGENT','Mozilla/5.0 (windows NT 6.1) Applewebkit/537.36(KHTML,like Gecko)Maxthon/5.0 Chrome/55.0.2883.75 Safari/537.36') # USER_AGENT不多说了,用户设备信息
# PATH_INFO 和 QUERY_STRING 这两个拿到就可以请求不同的资源以及不同的数据
QUERY_STRING查询字符串的解析
WSG1服务器程序处理过HTTP报文后,返回一个字典,可以得到查询字符串('QUERY_STRING', 'id=1234&name=tom')。这个键值对用起来不方便。
1、编程序解析
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
# http://172.17.38.217:9999?id=1&name=tom&age=20 # url的传输类型就是字符串没有数值类型,当数字来用需要自己来转
def simple_server(environ,start_response): # 自己实现的demp_app
# for k,v in environ.items(): # 可以把environ中所有的键值对信息拿出来,遍历environ先items排序返回一个k,v二元组解开
# print(k,v) # 打印出来
# print('-'*30)
query_string = environ.get('QUERY_STRING') # 只拿environ中其中一个键值对QUERY_STRING键对应的值,拿出(解析)查询字符串,
print(query_string) # 这个query_string拿出来是字符串'id=1&name=tom&age=20'拿到的这东西不方便,需要解析
这个方法写完不用
d = {} # 空字典
for item in query_string.split('&'): #split按&符来切割,拿到键值对,还不是我们想要的 用切割遍历的方式解析
k,_,v = item.partition('=') # partition返回三元组('id','=','1')
# print(k,v) # 拿到一个键值对,妨到字典里最好,不要_,只打印k,v
d[k]=v # 生成字典
# d = {query_string.split('&')} # query_string.split('&')拿到一个列表
d = {k:v for k,_,v in map(lambda x: x.partition('='),query_string.split('&'))} # for 遍历一个map集合,返回k:v的字典 。map()第二的参数是可迭代序列,对query_string指定&做切割split,返回一个列表做第一个参数的参数,第一个参数是一个函数partition对第二个参数的结果做指定=符号做切割,返回三元组在map集合中,map()第一个参数接受函数名,后面参数接受可迭代序列,返回一个集合
status = '200 ok'
headers = [('Content-Type','text/plain; charset=utf-8')]
start_response(status,headers)
# ret = [("%s: %s
" % (key,value)).encode("utf-8")for key,value in environ.items()]
ret = [query_string.encode()] # query_string拿到是个字符串类型,把这个字符串返回出去,headers这里是utf-8,query_string.encode()这里也就是utf-8
return ret # 返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素(字符串)
class A:
def __init__(self,name,age):
pass
def __call__(self,environ,start_response):
pass
ip = '172.17.38.217'
port = 9999
server = make_server(ip,port,simple_server) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是simple_server
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# 服务启动后访问 http://172.17.38.217:9999?id=1&name=tom&age=20打印结果
# id=1&name=tom&age=20
# id 1
# name tom
# age 20
# 他打印的内容
#这些打印的信息是
# h = sorted(environ.items()) # 把他所有的environ.items()键值对排个序,把你传进来的environ
# for k,v in h: # 依次把键值对打印出来
# print(k,'=',repr(v), file=stdout) # 然后这些东西都属于stdout的了getvalue全部拿来返回回去,以下打印的全都是
# 这样实现的,所有信息都打印了不考虑安全
# AMD_ENTRYPOINT = 'vs/server/remoteExtensionHostProcess'
# APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL = 'true'
# COLORTERM = 'truecolor'
# CONTENT_LENGTH = ''
# CONTENT_TYPE = 'text/plain'
# GATEWAY_INTERFACE = 'CGI/1.1'
# GIT_ASKPASS = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass.sh'
# HISTCONTROL = 'ignoredups'
# HISTSIZE = '1000'
# HOME = '/root'
# HOSTNAME = 'localhost.localdomain'
# HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
# HTTP_ACCEPT_ENCODING = 'gzip, deflate'
# HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9'
# HTTP_CONNECTION = 'keep-alive'
# HTTP_HOST = '192.168.161.203:9999'
# HTTP_UPGRADE_INSECURE_REQUESTS = '1'
# HTTP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
# LANG = 'zh_CN.UTF-8'
# LESSOPEN = '||/usr/bin/lesspipe.sh %s'
# LOGNAME = 'root'
# LS_COLORS = 'rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:'
# MAIL = '/var/spool/mail/root'
# PATH = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'
# PATH_INFO = '/'
# PIPE_LOGGING = 'true'
# PWD = '/root'
# QUERY_STRING = ''
# REMOTE_ADDR = '192.168.161.193'
# REMOTE_HOST = ''
# REQUEST_METHOD = 'GET'
# SCRIPT_NAME = ''
# SERVER_NAME = 'localhost.localdomain'
# SERVER_PORT = '9999'
# SERVER_PROTOCOL = 'HTTP/1.1'
# SERVER_SOFTWARE = 'WSGIServer/0.2'
# SHELL = '/bin/bash'
# SHLVL = '5'
# SSH_CLIENT = '192.168.161.193 8747 22'
# SSH_CONNECTION = '192.168.161.193 8747 192.168.161.203 22'
# TERM = 'xterm-256color'
# TERM_PROGRAM = 'vscode'
# TERM_PROGRAM_VERSION = '1.47.3'
# USER = 'root'
# VERBOSE_LOGGING = 'true'
# VSCODE_GIT_ASKPASS_MAIN = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass-main.js'
# VSCODE_GIT_ASKPASS_NODE = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/node'
# VSCODE_GIT_IPC_HANDLE = '/run/user/0/vscode-git-f64a3e4d20.sock'
# VSCODE_IPC_HOOK_CLI = '/tmp/vscode-ipc-468555df-a2ea-4a76-a02d-df0b0a09b1d9.sock'
# XDG_RUNTIME_DIR = '/run/user/0'
# XDG_SESSION_ID = '646'
# _ = '/usr/bin/python3'
# wsgi.errors = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
# wsgi.file_wrapper = <class 'wsgiref.util.FileWrapper'>
# wsgi.input = <_io.BufferedReader name=4>
# wsgi.multiprocess = False
# wsgi.multithread = True
# wsgi.run_once = False
# wsgi.url_scheme = 'http'
# wsgi.version = (1, 0)
2、使用cgi模块
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
import cgi # 使用cgi
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
# http://172.17.38.217:9999?id=1&name=tom&age=20 # url的传输类型就是字符串没有数值类型,当数字来用需要自己来转
def simple_server(environ,start_response): # 自己实现的demp_app
# for k,v in environ.items(): # 可以把environ中所有的键值对信息拿出来,遍历environ先items排序返回一个k,v二元组解开
# print(k,v) # 打印出来
# print('-'*30)
query_string = environ.get('QUERY_STRING') # 只拿environ中其中一个键值对QUERY_STRING键对应的值,拿出(解析)查询字符串,
print(query_string) # 这个query_string拿出来是字符串'id=1&name=tom&age=20'拿到的这东西不方便,需要解析
# 这个方法写完不用
# d = {} # 空字典
# for item in query_string.split('&'): #split按&符来切割,拿到键值对,还不是我们想要的 用切割遍历的方式解析
# k,_,v = item.partition('=') # partition返回三元组('id','=','1')
# # print(k,v) # 拿到一个键值对,妨到字典里最好,不要_,只打印k,v
# d[k]=v # 生成字典
# # d = {query_string.split('&')} # query_string.split('&')拿到一个列表
# d = {k:v for k,_,v in map(lambda x: x.partition('='),query_string.split('&'))} # for 遍历一个map集合,返回k:v的字典 。map()第二的参数是可迭代序列,对query_string指定&做切割split,返回一个列表做第一个参数的参数,第一个参数是一个函数partition对第二个参数的结果做指定=符号做切割,返回三元组在map集合中,map()第一个参数接受函数名,后面参数接受可迭代序列,返回一个集合
qs = cgi.parse_qs(query_string) # parse_qs解析查询字符串,他做的事跟上面我们写的一样
print(qs)
# 打印结果
# {'id': ['1'], 'name': ['tom'], 'age': ['20']} # 他也是打印的k,v但是他加了中括号,中括号的意思是这个值有多个,这就是多值的问题了
status = '200 ok'
headers = [('Content-Type','text/plain; charset=utf-8')]
start_response(status,headers)
# ret = [("%s: %s
" % (key,value)).encode("utf-8")for key,value in environ.items()]
ret = [query_string.encode()] # query_string拿到是个字符串类型,把这个字符串返回出去,headers这里是utf-8,query_string.encode()这里也就是utf-8
return ret # 返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素(字符串)
class A:
def __init__(self,name,age):
pass
def __call__(self,environ,start_response):
pass
ip = '172.17.38.217'
port = 9999
server = make_server(ip,port,simple_server) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是simple_server
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# 服务启动后访问 http://172.17.38.217:9999?id=1&name=tom&age=20打印结果
# id=1&name=tom&age=20
# id 1
# name tom
# age 20
# 他打印的内容
#这些打印的信息是
# h = sorted(environ.items()) # 把他所有的environ.items()键值对排个序,把你传进来的environ
# for k,v in h: # 依次把键值对打印出来
# print(k,'=',repr(v), file=stdout) # 然后这些东西都属于stdout的了getvalue全部拿来返回回去,以下打印的全都是
# 这样实现的,所有信息都打印了不考虑安全
# AMD_ENTRYPOINT = 'vs/server/remoteExtensionHostProcess'
# APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL = 'true'
# COLORTERM = 'truecolor'
# CONTENT_LENGTH = ''
# CONTENT_TYPE = 'text/plain'
# GATEWAY_INTERFACE = 'CGI/1.1'
# GIT_ASKPASS = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass.sh'
# HISTCONTROL = 'ignoredups'
# HISTSIZE = '1000'
# HOME = '/root'
# HOSTNAME = 'localhost.localdomain'
# HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
# HTTP_ACCEPT_ENCODING = 'gzip, deflate'
# HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9'
# HTTP_CONNECTION = 'keep-alive'
# HTTP_HOST = '192.168.161.203:9999'
# HTTP_UPGRADE_INSECURE_REQUESTS = '1'
# HTTP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
# LANG = 'zh_CN.UTF-8'
# LESSOPEN = '||/usr/bin/lesspipe.sh %s'
# LOGNAME = 'root'
# LS_COLORS = 'rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:'
# MAIL = '/var/spool/mail/root'
# PATH = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'
# PATH_INFO = '/'
# PIPE_LOGGING = 'true'
# PWD = '/root'
# QUERY_STRING = ''
# REMOTE_ADDR = '192.168.161.193'
# REMOTE_HOST = ''
# REQUEST_METHOD = 'GET'
# SCRIPT_NAME = ''
# SERVER_NAME = 'localhost.localdomain'
# SERVER_PORT = '9999'
# SERVER_PROTOCOL = 'HTTP/1.1'
# SERVER_SOFTWARE = 'WSGIServer/0.2'
# SHELL = '/bin/bash'
# SHLVL = '5'
# SSH_CLIENT = '192.168.161.193 8747 22'
# SSH_CONNECTION = '192.168.161.193 8747 192.168.161.203 22'
# TERM = 'xterm-256color'
# TERM_PROGRAM = 'vscode'
# TERM_PROGRAM_VERSION = '1.47.3'
# USER = 'root'
# VERBOSE_LOGGING = 'true'
# VSCODE_GIT_ASKPASS_MAIN = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass-main.js'
# VSCODE_GIT_ASKPASS_NODE = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/node'
# VSCODE_GIT_IPC_HANDLE = '/run/user/0/vscode-git-f64a3e4d20.sock'
# VSCODE_IPC_HOOK_CLI = '/tmp/vscode-ipc-468555df-a2ea-4a76-a02d-df0b0a09b1d9.sock'
# XDG_RUNTIME_DIR = '/run/user/0'
# XDG_SESSION_ID = '646'
# _ = '/usr/bin/python3'
# wsgi.errors = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
# wsgi.file_wrapper = <class 'wsgiref.util.FileWrapper'>
# wsgi.input = <_io.BufferedReader name=4>
# wsgi.multiprocess = False
# wsgi.multithread = True
# wsgi.run_once = False
# wsgi.url_scheme = 'http'
# wsgi.version = (1, 0)
3、使用urllib库(python3就用这个库)
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from urllib.parse import parse_qs
# http://172.17.38.217:9999?id=1&name=tom&age=20 # url的传输类型就是字符串没有数值类型,当数字来用需要自己来转
def simple_server(environ,start_response): # 自己实现的demp_app
# for k,v in environ.items(): # 可以把environ中所有的键值对信息拿出来,遍历environ先items排序返回一个k,v二元组解开
# print(k,v) # 打印出来
# print('-'*30)
query_string = environ.get('QUERY_STRING') # 只拿environ中其中一个键值对QUERY_STRING键对应的值,拿出(解析)查询字符串,
print(query_string) # 这个query_string拿出来是字符串'id=1&name=tom&age=20'拿到的这东西不方便,需要解析
# 这个方法写完不用
# d = {} # 空字典
# for item in query_string.split('&'): #split按&符来切割,拿到键值对,还不是我们想要的 用切割遍历的方式解析
# k,_,v = item.partition('=') # partition返回三元组('id','=','1')
# # print(k,v) # 拿到一个键值对,妨到字典里最好,不要_,只打印k,v
# d[k]=v # 生成字典
# # d = {query_string.split('&')} # query_string.split('&')拿到一个列表
# d = {k:v for k,_,v in map(lambda x: x.partition('='),query_string.split('&'))} # for 遍历一个map集合,返回k:v的字典 。map()第二的参数是可迭代序列,对query_string指定&做切割split,返回一个列表做第一个参数的参数,第一个参数是一个函数partition对第二个参数的结果做指定=符号做切割,返回三元组在map集合中,map()第一个参数接受函数名,后面参数接受可迭代序列,返回一个集合
qs = parse_qs(query_string) # 这里就不要用cgi了,已经从urllib.parse导入了。parse_qs解析查询字符串,他做的事跟上面我们写的一样
# qs = parse_qsl(query_string) # 返回二元组列表,一般不用,都用parse_qs返回字典
print(qs)
# 打印结果
# http://172.17.38.217:9999?id=1&name=tom&age=20 访问第一次
# {'id': ['1'], 'name': ['tom'], 'age': ['20']} # urllib.parse解析出来的与cgi一模一样。他也是打印的k,v但是他加了中括号,中括号的意思是这个值有多个,这就是多值的问题了
# http://172.17.38.217:9999?id=1&name=tom&age=20&age=30 访问第二次,age同一个k,等于两个值就必须用添加进两个值
# {'id': ['1'], 'name': ['tom'], 'age': ['20', '30']} # age中列表有两个值,这就是多值
# http://172.17.38.217:9999?id=1&name=tom,jerry&age=20&age=30 访问第三次
# {'id': ['1'], 'name': ['tom,jerry'], 'age': ['20', '30']} # name中列表有'tom,jerry'这表示是一个值
status = '200 ok'
headers = [('Content-Type','text/plain; charset=utf-8')]
start_response(status,headers)
# ret = [("%s: %s
" % (key,value)).encode("utf-8")for key,value in environ.items()]
ret = [query_string.encode()] # query_string拿到是个字符串类型,把这个字符串返回出去,headers这里是utf-8,query_string.encode()这里也就是utf-8
return ret # 返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素(字符串)
class A:
def __init__(self,name,age):
pass
def __call__(self,environ,start_response):
pass
ip = '172.17.38.217'
port = 9999
server = make_server(ip,port,simple_server) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是simple_server
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# 服务启动后访问 http://172.17.38.217:9999?id=1&name=tom&age=20打印结果
# id=1&name=tom&age=20
# id 1
# name tom
# age 20
# 他打印的内容
#这些打印的信息是
# h = sorted(environ.items()) # 把他所有的environ.items()键值对排个序,把你传进来的environ
# for k,v in h: # 依次把键值对打印出来
# print(k,'=',repr(v), file=stdout) # 然后这些东西都属于stdout的了getvalue全部拿来返回回去,以下打印的全都是
# 这样实现的,所有信息都打印了不考虑安全
# AMD_ENTRYPOINT = 'vs/server/remoteExtensionHostProcess'
# APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL = 'true'
# COLORTERM = 'truecolor'
# CONTENT_LENGTH = ''
# CONTENT_TYPE = 'text/plain'
# GATEWAY_INTERFACE = 'CGI/1.1'
# GIT_ASKPASS = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass.sh'
# HISTCONTROL = 'ignoredups'
# HISTSIZE = '1000'
# HOME = '/root'
# HOSTNAME = 'localhost.localdomain'
# HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
# HTTP_ACCEPT_ENCODING = 'gzip, deflate'
# HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9'
# HTTP_CONNECTION = 'keep-alive'
# HTTP_HOST = '192.168.161.203:9999'
# HTTP_UPGRADE_INSECURE_REQUESTS = '1'
# HTTP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
# LANG = 'zh_CN.UTF-8'
# LESSOPEN = '||/usr/bin/lesspipe.sh %s'
# LOGNAME = 'root'
# LS_COLORS = 'rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:'
# MAIL = '/var/spool/mail/root'
# PATH = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'
# PATH_INFO = '/'
# PIPE_LOGGING = 'true'
# PWD = '/root'
# QUERY_STRING = ''
# REMOTE_ADDR = '192.168.161.193'
# REMOTE_HOST = ''
# REQUEST_METHOD = 'GET'
# SCRIPT_NAME = ''
# SERVER_NAME = 'localhost.localdomain'
# SERVER_PORT = '9999'
# SERVER_PROTOCOL = 'HTTP/1.1'
# SERVER_SOFTWARE = 'WSGIServer/0.2'
# SHELL = '/bin/bash'
# SHLVL = '5'
# SSH_CLIENT = '192.168.161.193 8747 22'
# SSH_CONNECTION = '192.168.161.193 8747 192.168.161.203 22'
# TERM = 'xterm-256color'
# TERM_PROGRAM = 'vscode'
# TERM_PROGRAM_VERSION = '1.47.3'
# USER = 'root'
# VERBOSE_LOGGING = 'true'
# VSCODE_GIT_ASKPASS_MAIN = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass-main.js'
# VSCODE_GIT_ASKPASS_NODE = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/node'
# VSCODE_GIT_IPC_HANDLE = '/run/user/0/vscode-git-f64a3e4d20.sock'
# VSCODE_IPC_HOOK_CLI = '/tmp/vscode-ipc-468555df-a2ea-4a76-a02d-df0b0a09b1d9.sock'
# XDG_RUNTIME_DIR = '/run/user/0'
# XDG_SESSION_ID = '646'
# _ = '/usr/bin/python3'
# wsgi.errors = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
# wsgi.file_wrapper = <class 'wsgiref.util.FileWrapper'>
# wsgi.input = <_io.BufferedReader name=4>
# wsgi.multiprocess = False
# wsgi.multithread = True
# wsgi.run_once = False
# wsgi.url_scheme = 'http'
# wsgi.version = (1, 0)
environ的解析--webob库
环境数据有很多,都是存在字典中的,字典的存取方式没有对象的属性访问方便。
使用第三方库webob ,可以把环境数据的解析、封装成对象。
webob简介
Python下,webob可以对WSGI请求的数据进行解析,并提供对响应进行高级封装的库(这个库很强)。
# 安装库
pip3 install webob
官网文档webob.org
webob.Request对象(请求模块)
将环境参数解析并封装成request对象
GET方法,发送的数据是URL中Query string ,在Request Header中。
request.GET就是一个字典MultiDict,里面就封装着查询字符串。
POST方法, "提交"的数据是放在Request Body里面,但是也可以同时使用Query String.
request.POST可以获取Request Body中的数据,也是个字典MultiDict.
不关心什么方法提交,只关心数据,可以使用request.params ,它里面是所有提交数据的封装。
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from webob import Request, Response #从webob导入, Request(请求模块),Response(响应模块)
# http://172.17.38.217:9999?id=1&name=tom&age=20 # url的传输类型就是字符串没有数值类型,当数字来用需要自己来转
def simple_app(environ,start_response): # 自己实现的demp_app
request = Request(environ) # Request可以解决到environ传进来的环境变量
# Request 源码
# class Request(AdhocAttrMixin, BaseRequest):
# """ The default request implementation """
# BaseRequest 源码 __init__初始化的时候可以对environ进行解析,通过看源码知道Request可以解析environ
# class BaseRequest(object):
# # The limit after which request bodies should be stored on disk
# # if they are read in (under this, and the request body is stored
# # in memory):
# request_body_tempfile_limit = 10 * 1024
# _charset = None
# def __init__(self, environ, charset=None, unicode_errors=None,
# decode_param_names=None, **kw):
# 下面方法我们可以用面向对象属性的方式就可以获得
# query_string = request.query_string # 这个消亡不用,还要解析麻烦,query_string请求到查询字符串
method = request.method # method请求到使用方法
print(method)
# print(query_string,method) # query_string可以看看,还要解析比较麻烦
# 打印结果
# id=1&name=tom,jerry&age=20&age=30 GET # id=1&name=tom,jerry&age=20&age=30(query_string请求到查询字符串),GET(method请求到使用方法)
print(request.GET) # GET通过type(request.GET)看到GET其实是个字典,拿数据直接拿就行了
# 打印结果
# GET([('id', '1'), ('name', 'tom,jerry'), ('age', '20'), ('age', '30')]) # 拿到了解析过的查询字符串
print(type(request.GET)) # GET通过type(request.GET)看到GET其实是个字典
# 打印结果
# <class 'webob.multidict.GetDict'> # multidict(多值字典的).GetDict(字典)
print(request.POST) # 跟GET类型一样dict(字典),只不过可以存多值
# 打印结果
# <NoVars: Not an HTML form submission (Content-Type: text/plain)> # 需要表单数据, 他说没有数据你提交了
print(request.path) # 路径,地址端口号后面,查询字符串前面,这里没写表示请求的是网站的根目录(跟文件的根目录两码事)
# 打印结果
# / # 如果url加上路径http://172.17.38.217:9999/user/?id=1&name=tom&age=20 ,返回的就是/user/
print('params = {}'.format(request.params)) # 如果GET和POST数据都要,遍历两个比较麻烦,就用params,所有数据,参数
# 打印结果
# params = NestedMultiDict([('id', '1'), ('name', 'tom,jerry'), ('age', '20'), ('age', '30')])
print(request.headers) # 请求头,有时候还是要用一下的
# 打印结果
# <webob.headers.EnvironHeaders object at 0x7f4ad1e423c8>
status = '200 ok'
headers = [('Content-Type','text/plain; charset=utf-8')]
start_response(status,headers)
# ret = [("%s: %s
" % (key,value)).encode("utf-8")for key,value in environ.items()]
ret = [] # 什么都不给你们. query_string拿到是个字符串类型,把这个字符串返回出去,headers这里是utf-8,query_string.encode()这里也就是utf-8
return ret # 返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素(字符串)
class A:
def __init__(self,name,age):
pass
def __call__(self,environ,start_response):
pass
ip = '172.17.38.217'
port = 9999
server = make_server(ip,port,simple_app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是simple_server
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# 服务启动后访问 http://172.17.38.217:9999?id=1&name=tom&age=20打印结果
# id=1&name=tom&age=20
# id 1
# name tom
# age 20
# 他打印的内容
#这些打印的信息是
# h = sorted(environ.items()) # 把他所有的environ.items()键值对排个序,把你传进来的environ
# for k,v in h: # 依次把键值对打印出来
# print(k,'=',repr(v), file=stdout) # 然后这些东西都属于stdout的了getvalue全部拿来返回回去,以下打印的全都是
# 这样实现的,所有信息都打印了不考虑安全
# AMD_ENTRYPOINT = 'vs/server/remoteExtensionHostProcess'
# APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL = 'true'
# COLORTERM = 'truecolor'
# CONTENT_LENGTH = ''
# CONTENT_TYPE = 'text/plain'
# GATEWAY_INTERFACE = 'CGI/1.1'
# GIT_ASKPASS = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass.sh'
# HISTCONTROL = 'ignoredups'
# HISTSIZE = '1000'
# HOME = '/root'
# HOSTNAME = 'localhost.localdomain'
# HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9'
# HTTP_ACCEPT_ENCODING = 'gzip, deflate'
# HTTP_ACCEPT_LANGUAGE = 'zh-CN,zh;q=0.9'
# HTTP_CONNECTION = 'keep-alive'
# HTTP_HOST = '192.168.161.203:9999'
# HTTP_UPGRADE_INSECURE_REQUESTS = '1'
# HTTP_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'
# LANG = 'zh_CN.UTF-8'
# LESSOPEN = '||/usr/bin/lesspipe.sh %s'
# LOGNAME = 'root'
# LS_COLORS = 'rs=0:di=38;5;27:ln=38;5;51:mh=44;38;5;15:pi=40;38;5;11:so=38;5;13:do=38;5;5:bd=48;5;232;38;5;11:cd=48;5;232;38;5;3:or=48;5;232;38;5;9:mi=05;48;5;232;38;5;15:su=48;5;196;38;5;15:sg=48;5;11;38;5;16:ca=48;5;196;38;5;226:tw=48;5;10;38;5;16:ow=48;5;10;38;5;21:st=48;5;21;38;5;15:ex=38;5;34:*.tar=38;5;9:*.tgz=38;5;9:*.arc=38;5;9:*.arj=38;5;9:*.taz=38;5;9:*.lha=38;5;9:*.lz4=38;5;9:*.lzh=38;5;9:*.lzma=38;5;9:*.tlz=38;5;9:*.txz=38;5;9:*.tzo=38;5;9:*.t7z=38;5;9:*.zip=38;5;9:*.z=38;5;9:*.Z=38;5;9:*.dz=38;5;9:*.gz=38;5;9:*.lrz=38;5;9:*.lz=38;5;9:*.lzo=38;5;9:*.xz=38;5;9:*.bz2=38;5;9:*.bz=38;5;9:*.tbz=38;5;9:*.tbz2=38;5;9:*.tz=38;5;9:*.deb=38;5;9:*.rpm=38;5;9:*.jar=38;5;9:*.war=38;5;9:*.ear=38;5;9:*.sar=38;5;9:*.rar=38;5;9:*.alz=38;5;9:*.ace=38;5;9:*.zoo=38;5;9:*.cpio=38;5;9:*.7z=38;5;9:*.rz=38;5;9:*.cab=38;5;9:*.jpg=38;5;13:*.jpeg=38;5;13:*.gif=38;5;13:*.bmp=38;5;13:*.pbm=38;5;13:*.pgm=38;5;13:*.ppm=38;5;13:*.tga=38;5;13:*.xbm=38;5;13:*.xpm=38;5;13:*.tif=38;5;13:*.tiff=38;5;13:*.png=38;5;13:*.svg=38;5;13:*.svgz=38;5;13:*.mng=38;5;13:*.pcx=38;5;13:*.mov=38;5;13:*.mpg=38;5;13:*.mpeg=38;5;13:*.m2v=38;5;13:*.mkv=38;5;13:*.webm=38;5;13:*.ogm=38;5;13:*.mp4=38;5;13:*.m4v=38;5;13:*.mp4v=38;5;13:*.vob=38;5;13:*.qt=38;5;13:*.nuv=38;5;13:*.wmv=38;5;13:*.asf=38;5;13:*.rm=38;5;13:*.rmvb=38;5;13:*.flc=38;5;13:*.avi=38;5;13:*.fli=38;5;13:*.flv=38;5;13:*.gl=38;5;13:*.dl=38;5;13:*.xcf=38;5;13:*.xwd=38;5;13:*.yuv=38;5;13:*.cgm=38;5;13:*.emf=38;5;13:*.axv=38;5;13:*.anx=38;5;13:*.ogv=38;5;13:*.ogx=38;5;13:*.aac=38;5;45:*.au=38;5;45:*.flac=38;5;45:*.mid=38;5;45:*.midi=38;5;45:*.mka=38;5;45:*.mp3=38;5;45:*.mpc=38;5;45:*.ogg=38;5;45:*.ra=38;5;45:*.wav=38;5;45:*.axa=38;5;45:*.oga=38;5;45:*.spx=38;5;45:*.xspf=38;5;45:'
# MAIL = '/var/spool/mail/root'
# PATH = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'
# PATH_INFO = '/'
# PIPE_LOGGING = 'true'
# PWD = '/root'
# QUERY_STRING = ''
# REMOTE_ADDR = '192.168.161.193'
# REMOTE_HOST = ''
# REQUEST_METHOD = 'GET'
# SCRIPT_NAME = ''
# SERVER_NAME = 'localhost.localdomain'
# SERVER_PORT = '9999'
# SERVER_PROTOCOL = 'HTTP/1.1'
# SERVER_SOFTWARE = 'WSGIServer/0.2'
# SHELL = '/bin/bash'
# SHLVL = '5'
# SSH_CLIENT = '192.168.161.193 8747 22'
# SSH_CONNECTION = '192.168.161.193 8747 192.168.161.203 22'
# TERM = 'xterm-256color'
# TERM_PROGRAM = 'vscode'
# TERM_PROGRAM_VERSION = '1.47.3'
# USER = 'root'
# VERBOSE_LOGGING = 'true'
# VSCODE_GIT_ASKPASS_MAIN = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/extensions/git/dist/askpass-main.js'
# VSCODE_GIT_ASKPASS_NODE = '/root/.vscode-server/bin/91899dcef7b8110878ea59626991a18c8a6a1b3e/node'
# VSCODE_GIT_IPC_HANDLE = '/run/user/0/vscode-git-f64a3e4d20.sock'
# VSCODE_IPC_HOOK_CLI = '/tmp/vscode-ipc-468555df-a2ea-4a76-a02d-df0b0a09b1d9.sock'
# XDG_RUNTIME_DIR = '/run/user/0'
# XDG_SESSION_ID = '646'
# _ = '/usr/bin/python3'
# wsgi.errors = <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>
# wsgi.file_wrapper = <class 'wsgiref.util.FileWrapper'>
# wsgi.input = <_io.BufferedReader name=4>
# wsgi.multiprocess = False
# wsgi.multithread = True
# wsgi.run_once = False
# wsgi.url_scheme = 'http'
# wsgi.version = (1, 0)
MultiDict(多值字典)
MultiDict允许一个key存了好几个值
from webob.multidict import MultiDict # 拿出多值字典的类
md = MultiDict() # 类实例化
md.add('a',1) # 添加键和值,k和v
md.add('b',2)
md.add('b',3)
# print(md)
# 打印结果
# MultiDict([('a', 1), ('b', 2), ('b', 3)]) # 一个k可以存入多值
md.add(1,100) # 键(k)可哈希
md.add(2,200)
# print(md)
# 打印结果
# TypeError: argument of type 'int' is not iterable # 大多数经常看到key是个名称多数都是字符串,你只要不迭代你想怎么用怎么用print(md[1])这样就可以把值取出,如果在迭代中发现一个key不是字符串,他就抛异常,他说这东西不可迭代
print(md[1]) # 1是一个k对应一个值吧是的一个字典,
# 打印结果
# 100 # 拿出来的1对应的值
print(md['a']) # 'a'没什么说的取1
print(md['b']) # 取多值中的最后一个值,'b'取的是3
# 打印结果
# 1
# 3
print(md.get('c',2000)) # get一个key还可以写一个缺省值,如果这个key不存在自动生成一个字典,然后访问
# 打印结果
# 2000
print(md.get('b')) # 跟print(md['b'])结果一样
# 打印结果
# 3
# print(md.getone('b')) # getone取的方式是(有,且只能有一个),他说你要一个结果你还是个多值,你还是俩就出问题了
# 打印结果
# KeyError: "Multiple values match 'b': [2, 3]"
# print(md.getone('c')) # 没有c抛异常
# 打印结果
# KeyError: "Key not found: 'c'"
print(md.getone('a')) # 这个就正常了
# 打印结果
# 1
print(md.getall('b')) # b存的是一个列表,因为需要add添加,不能元组剩下了的只剩列表了
# 打印结果
# [2, 3]
这些方式就可以知道他查询字符串是怎么处理的
webob.Response对象(响应模块)
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from webob import Request, Response #从webob导入, Request(请求模块),Response(响应模块)
# http://172.17.38.217:9999?id=1&name=tom&age=20 # url的传输类型就是字符串没有数值类型,当数字来用需要自己来转
def simple_app(environ,start_response): # 自己实现的demp_app
request = Request(environ) # Request可以解决到environ传进来的环境变量
# Request 源码
# class Request(AdhocAttrMixin, BaseRequest):
# """ The default request implementation """
# BaseRequest 源码 __init__初始化的时候可以对environ进行解析,通过看源码知道Request可以解析environ
# class BaseRequest(object):
# # The limit after which request bodies should be stored on disk
# # if they are read in (under this, and the request body is stored
# # in memory):
# request_body_tempfile_limit = 10 * 1024
# _charset = None
# def __init__(self, environ, charset=None, unicode_errors=None,
# decode_param_names=None, **kw):
# 下面方法我们可以用面向对象属性的方式就可以获得
# query_string = request.query_string # 这个消亡不用,还要解析麻烦,query_string请求到查询字符串
method = request.method # method请求到使用方法
print(method)
# print(query_string,method) # query_string可以看看,还要解析比较麻烦
# 打印结果
# id=1&name=tom,jerry&age=20&age=30 GET # id=1&name=tom,jerry&age=20&age=30(query_string请求到查询字符串),GET(method请求到使用方法)
print(request.GET) # GET通过type(request.GET)看到GET其实是个字典,拿数据直接拿就行了
# 打印结果
# GET([('id', '1'), ('name', 'tom,jerry'), ('age', '20'), ('age', '30')]) # 拿到了解析过的查询字符串
print(type(request.GET)) # GET通过type(request.GET)看到GET其实是个字典
# 打印结果
# <class 'webob.multidict.GetDict'> # multidict(多值字典的).GetDict(字典)
print(request.POST) # 跟GET类型一样dict(字典),只不过可以存多值
# 打印结果
# <NoVars: Not an HTML form submission (Content-Type: text/plain)> # 需要表单数据, 他说没有数据你提交了
print(request.path) # 路径,地址端口号后面,查询字符串前面,这里没写表示请求的是网站的根目录(跟文件的根目录两码事)
# 打印结果
# / # 如果url加上路径http://172.17.38.217:9999/user/?id=1&name=tom&age=20 ,返回的就是/user/
print('params = {}'.format(request.params)) # 如果GET和POST数据都要,遍历两个比较麻烦,就用params,所有数据,参数
# 打印结果
# params = NestedMultiDict([('id', '1'), ('name', 'tom,jerry'), ('age', '20'), ('age', '30')])
print(request.headers) # 请求头,有时候还是要用一下的
# 打印结果
# <webob.headers.EnvironHeaders object at 0x7f4ad1e423c8> #这是一个对象来自webob下的headers的EnvironHeaders(环境的头)
res = Response() #(响应) 这个可调用对象需要有一个__call__方法,我们可以直接返回值
# Response源码
# def __init__(self, body=None, status=None, headerlist=None, app_iter=None,
# content_type=None, conditional_response=None, charset=_marker,
# **kw): # 第一个参数传的body,正文是什么告诉他
# if app_iter is None: # 如果app_iter迭代是个None的话
# if body is None:
# body = b'' # 这给你返回一个b
# elif body is not None:
# raise TypeError(
# "You may only give one of the body and app_iter arguments")
# if status is None: # status如果没设定,缺省就是'200 OK'
# self._status = '200 OK'
# else:
# self.status = status
# def __call__(self, environ, start_response): # environ环境注入进来做一些总之就是前面说的那些方式处理
# """
# WSGI application interface
# """
# if self.conditional_response:
# return self.conditional_response_app(environ, start_response)
# headerlist = self._abs_headerlist(environ) # headerlist如果是个列表,self._abs_headerlist(environ)做一下这个头,environ这东西多数都是头信息,处理完有一个headerlist
# start_response(self.status, headerlist) #headerlist处理完照单全收继续作为头返回回去,status状态值也返回回去
# if environ['REQUEST_METHOD'] == 'HEAD': # 如果environ['REQUEST_METHOD']是HEAD
# # Special case here...
# return EmptyResponse(self._app_iter) # 就返回一个空响应,给你一个空列表
# return self._app_iter #最后要一个可迭代对象,他其实就是刚才在这里面塞的一大堆你要让他发回去的数据,这就是我们WSGI APP的要求吗
print(res.status_code) # 默认状态码是200
# 打印结果
# 200
print(res.status) # 状态码200对应的描述ok
# 打印结果
# 200 OK
print(res.headers) # 返回一个对象
# 打印结果
# ResponseHeaders([('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')])
print(res.headerlist) # 把headers返回的结果,返回一个list
# 打印结果
# [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
# status = '200 ok'
# headers = [('Content-Type','text/plain; charset=utf-8')]
# start_response(res.status,res.headerlist) # 这里就可以不用status和headers我们自己填的,用他返回回来的res.status和res.headerlist
# ret = [("%s: %s
" % (key,value)).encode("utf-8")for key,value in environ.items()]
# ret = [] # 什么都不给你们. query_string拿到是个字符串类型,把这个字符串返回出去,headers这里是utf-8,query_string.encode()这里也就是utf-8
# return ret # 返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素(字符串)
res.body='<h1>hello</h1>'.encode() # 这里注意需要添加encode()函数,描述:以指定的编码格式编码字符串,默认编码为 'utf-8'。(否者产生错误不能是一个test对象:You cannot set Response.body to a text object (use Response.text)(您不能将Response.body设置为文本对象(使用Response.text)))
return res(environ,start_response) # res = Response()这里创建了一个实例,这个实例会调用的,这个实例再调用中会做一些事情,然后这个return res直接return回去了,这个res不调用res()他就不会对应__call__方法,__call__方法需要return回来一个可迭代对象iter但是没有,把res(environ,start_response)传进来,这两个传来__call__方法就调用,__call__方法会调用start_response()这个参数
class A:
def __init__(self,name,age):
pass
def __call__(self,environ,start_response):
pass
ip = '172.17.55.69'
port = 9999
server = make_server(ip,port,simple_app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是simple_server
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# 服务启动后访问 http://172.17.38.217:9999?id=1&name=tom&age=20打印结果
webob.dec 装饰器
有了这个装饰器,用起来就方便多了,这个装饰器可以
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from webob import Request, Response #从webob导入, Request(请求模块),Response(响应模块)
from webob.dec import wsgify # webob.dec 导入这个装饰器
# http://172.17.38.217:9999?id=1&name=tom&age=20 # url的传输类型就是字符串没有数值类型,当数字来用需要自己来转
def simple_app(environ,start_response): # 自己实现的demp_app
request = Request(environ) # Request可以解决到environ传进来的环境变量
# Request 源码
# class Request(AdhocAttrMixin, BaseRequest):
# """ The default request implementation """
# BaseRequest 源码 __init__初始化的时候可以对environ进行解析,通过看源码知道Request可以解析environ
# class BaseRequest(object):
# # The limit after which request bodies should be stored on disk
# # if they are read in (under this, and the request body is stored
# # in memory):
# request_body_tempfile_limit = 10 * 1024
# _charset = None
# def __init__(self, environ, charset=None, unicode_errors=None,
# decode_param_names=None, **kw):
# 下面方法我们可以用面向对象属性的方式就可以获得
# query_string = request.query_string # 这个消亡不用,还要解析麻烦,query_string请求到查询字符串
method = request.method # method请求到使用方法
print(method)
# print(query_string,method) # query_string可以看看,还要解析比较麻烦
# 打印结果
# id=1&name=tom,jerry&age=20&age=30 GET # id=1&name=tom,jerry&age=20&age=30(query_string请求到查询字符串),GET(method请求到使用方法)
print(request.GET) # GET通过type(request.GET)看到GET其实是个字典,拿数据直接拿就行了
# 打印结果
# GET([('id', '1'), ('name', 'tom,jerry'), ('age', '20'), ('age', '30')]) # 拿到了解析过的查询字符串
print(type(request.GET)) # GET通过type(request.GET)看到GET其实是个字典
# 打印结果
# <class 'webob.multidict.GetDict'> # multidict(多值字典的).GetDict(字典)
print(request.POST) # 跟GET类型一样dict(字典),只不过可以存多值
# 打印结果
# <NoVars: Not an HTML form submission (Content-Type: text/plain)> # 需要表单数据, 他说没有数据你提交了
print(request.path) # 路径,地址端口号后面,查询字符串前面,这里没写表示请求的是网站的根目录(跟文件的根目录两码事)
# 打印结果
# / # 如果url加上路径http://172.17.38.217:9999/user/?id=1&name=tom&age=20 ,返回的就是/user/
print('params = {}'.format(request.params)) # 如果GET和POST数据都要,遍历两个比较麻烦,就用params,所有数据,参数
# 打印结果
# params = NestedMultiDict([('id', '1'), ('name', 'tom,jerry'), ('age', '20'), ('age', '30')])
print(request.headers) # 请求头,有时候还是要用一下的
# 打印结果
# <webob.headers.EnvironHeaders object at 0x7f4ad1e423c8> #这是一个对象来自webob下的headers的EnvironHeaders(环境的头)
res = Response() #(响应) 这个可调用对象需要有一个__call__方法,我们可以直接返回值
# Response源码
# def __init__(self, body=None, status=None, headerlist=None, app_iter=None,
# content_type=None, conditional_response=None, charset=_marker,
# **kw): # 第一个参数传的body,正文是什么告诉他
# if app_iter is None: # 如果app_iter迭代是个None的话
# if body is None:
# body = b'' # 这给你返回一个b
# elif body is not None:
# raise TypeError(
# "You may only give one of the body and app_iter arguments")
# if status is None: # status如果没设定,缺省就是'200 OK'
# self._status = '200 OK'
# else:
# self.status = status
# def __call__(self, environ, start_response): # environ环境注入进来做一些总之就是前面说的那些方式处理
# """
# WSGI application interface
# """
# if self.conditional_response:
# return self.conditional_response_app(environ, start_response)
# headerlist = self._abs_headerlist(environ) # headerlist如果是个列表,self._abs_headerlist(environ)做一下这个头,environ这东西多数都是头信息,处理完有一个headerlist
# start_response(self.status, headerlist) #headerlist处理完照单全收继续作为头返回回去,status状态值也返回回去
# if environ['REQUEST_METHOD'] == 'HEAD': # 如果environ['REQUEST_METHOD']是HEAD
# # Special case here...
# return EmptyResponse(self._app_iter) # 就返回一个空响应,给你一个空列表
# return self._app_iter #最后要一个可迭代对象,他其实就是刚才在这里面塞的一大堆你要让他发回去的数据,这就是我们WSGI APP的要求吗
print(res.status_code) # 默认状态码是200
# 打印结果
# 200
print(res.status) # 状态码200对应的描述ok
# 打印结果
# 200 OK
print(res.headers) # 返回一个对象
# 打印结果
# ResponseHeaders([('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')])
print(res.headerlist) # 把headers返回的结果,返回一个list
# 打印结果
# [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')]
# status = '200 ok'
# headers = [('Content-Type','text/plain; charset=utf-8')]
# start_response(res.status,res.headerlist) # 这里就可以不用status和headers我们自己填的,用他返回回来的res.status和res.headerlist
# ret = [("%s: %s
" % (key,value)).encode("utf-8")for key,value in environ.items()]
# ret = [] # 什么都不给你们. query_string拿到是个字符串类型,把这个字符串返回出去,headers这里是utf-8,query_string.encode()这里也就是utf-8
# return ret # 返回要求可迭代对象,正文就是这个列表的元素,可以是一个元素(字符串)
res.body='<h1>hello</h1>'.encode() # 这里注意需要添加encode()函数,描述:以指定的编码格式编码字符串,默认编码为 'utf-8'。(否者产生错误不能是一个test对象:You cannot set Response.body to a text object (use Response.text)(您不能将Response.body设置为文本对象(使用Response.text)))
return res(environ,start_response) # res = Response()这里创建了一个实例,这个实例会调用的,这个实例再调用中会做一些事情,然后这个return res直接return回去了,这个res不调用res()他就不会对应__call__方法,__call__方法需要return回来一个可迭代对象iter但是没有,把res(environ,start_response)传进来,这两个传来__call__方法就调用,__call__方法会调用start_response()这个参数
# 装饰器
@wsgify # 这个装饰器的调用本质上是在把我们上面写的 res = Response()重新帮我们写了一遍,封装到他自己的__call__魔术方法里面去了,就做了这事。这个装饰器帮你把app这样的一个函数在外面相当于给你包一下,相当于再调用(environ,start_response)这种东西,他最后拿到的environ这个东西还给你解析成request这样的东西
def app(request:Request): # 一个请求对应一个响应我不用关心start_response这个,作为一个编程者关心的是给你一个http请求然后你给我响应一个Response回来,看def simple_app(environ,start_response):这种东西给的不是求需,要给一个environ字典,还要给一个start_response函数给你调用,最后回来还要return可迭代对象,这种东西不好用,这种工作装饰器帮我们做了。 ->:常常出现在python函数定义的函数名后面,为函数添加元数据,描述函数的返回类型,从而方便开发人员使用
print('*'*20)
return b'<h1>hello1</h1>' # 传的是bytes
return Response('<h1>hello1</h1>') # 装饰器提供的Response是无参实例化,这个无参实例化相当于200 ok,头也写死了,缺省,如果不想让他写死就重新自己构建一个Response对象由他来给你返回去,装饰器一看你自己是Response他就用你的了。用装饰器封装之后,在这里返回Response对象实例, 把Response丢回去
return '<h2>python.org</h2>' # 这里是string字符类型,它可以有很多的东西进来,这都是wsgify这个装饰器帮我们忙的很多类型他都支持,它里面做了很多判断,装饰器发现类型不是Response如果是字符串就把你变成bytes用Response把你包起来,如果是bytes的话就直接给你封装了,如果是Response装饰器就不提你封装了就这个意思
# @wsgify装饰器源码
# class wsgify(object): # 这个类一旦初始化完成,这个def app(request:Request):就变成wsgify这个东西的实例,我们要的是和调用对象wsgify类要实现__call__方法
# RequestClass = Request # 这个Request就是from webob import Request,这是在wsgify这里类里面做的
# def __init__(self, func=None, RequestClass=None, # 调用装饰器后,函数名会传递到func=None这儿来
# args=(), kwargs=None, middleware_wraps=None):
# self.func = func # 这个app函数就被self.func他接收了
# ...
# def __call__(self, req, *args, **kw): # 来看看他的__call__方法,第一次参数给的是environ,第二个参数给的是start_response,start_response被*args收集
# """Call this as a WSGI application or with a request"""
# func = self.func
# if func is None: # 这里如果self.func函数没有
# if args or kw:
# raise TypeError(
# "Unbound %s can only be called with the function it "
# "will wrap" % self.__class__.__name__)
# func = req
# return self.clone(func) # self.func函数没有的话,他就给你克隆一个函数,不管,下面的才是重要的
# if isinstance(req, dict): # req如果等于字典dict,下面就是在处理wsgi
# if len(args) != 1 or kw:
# raise TypeError(
# "Calling %r as a WSGI app with the wrong signature" %
# self.func)
# environ = req # 你是req但是本质上你就是environ
# start_response = args[0] # *args收集了一个元素,索引为0把元组第一个元素拿出来给start_response现在明白了怎么调用的了
# req = self.RequestClass(environ) # RequestClass = Request 用webob中的Request来处理environ
# req.response = req.ResponseClass() # 这是以防万一调用人偷懒response没给他就用req.ResponseClass(),他在req上动态增加一个属性response,然后拿到response的实例req.ResponseClass()赋值给他req.response,比如 return '<h2>python.org</h2>'这不是response,万一你不写response他不能用字符串来调用resp(environ, start_response) 这两个参数,调用不了
# try:
# args, kw = self._prepare_args(None, None)
# resp = self.call_func(req, *args, **kw)
# except HTTPException as exc:
# resp = exc
# if resp is None: # 如果response是none,这个resp是调用self.call_func得到的resp = self.call_func(req, *args, **kw),这个call_func就调func = self.func他,func他不就是我们传递进来的app吗对吧
# ## FIXME: I'm not sure what this should be?
# resp = req.response
# if isinstance(resp, text_type): # 如果resp他不是none,他是text_type字符串
# resp = bytes_(resp, req.charset) # 如果是字符串给你转成bytes
# if isinstance(resp, bytes): # 如果resp是bytes
# body = resp
# resp = req.response
# resp.write(body) # 拿到一个 response把body内容写进去
# if resp is not req.response: # 如果resp是你自己创建出来的,就跟我req.response = req.ResponseClass()的不一样
# resp = req.response.merge_cookies(resp) # 如果不一样,如果你设置了cookies,他就把cookies给你填进来
# return resp(environ, start_response) # resp他一定是response,要么是你自己创建的response,要么就从req.ResponseClass() 这个类实例化出来一个response,这里跟我们 res = Response(), return res(environ,start_response) 这里写的一样
# else:
# args, kw = self._prepare_args(args, kw)
# return self.call_func(req, *args, **kw)
class App:
# def __init__(seif): # 没想好写什么不要了
# pass
@wsgify
def __call__(self,request:Request):
print('*'*20)
# return b'<h1>hello1</h1>'
# return Response('<h1>hello1</h1>')
return '<h2>python.org</h2>'
if __name__ == '__main__':
ip = '172.17.55.69'
port = 9999
# server = make_server(ip,port,app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是装饰器返回的app
server = make_server(ip,port,App()) # 现在是调用这个类一定要有括号的,因为这个类实例化之后才是可调用对象,因为这东西可以__call__,方法的本质就是函数,只不过类对我们来说以后可以扩展,扩展起来比函数方便点,他不过是一种封装形式的变化而已
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# 一个函数的封装毕竟还是太过简单,他只能完成一些简单的东西,为什么要用类,是因为后面要做更加复杂的调用,比如说下面要做路由了,因为发现所有的请求只有 def __call__(self,request:Request):这一个函数处理,我们想把不同的url交给不同的函数去处理,最简单的方法就是加判断if url 等于这个,if url 等于哪个就可以指向不同的函数去处理,这才是动态网页技术的核心技术,请求资源不同调用不同的函数,只不过要有解决并发的问题,使用多线程呢,多进程呢,异步,webserver也是tcp的server
路由功能实现
路由route
什么是路由?
简单说,就是路怎么走。就是按照不同的路径分发数据。
URL就是不同资源的路径,不同的路径应该对应不同的应用程序来处理。
所以,代码中要增加对路径的分析处理。
增加路由
一个简单的需求
路径 | 内容 |
---|---|
如果是个 / | 返回欢迎内容 |
如果是个 /python | 返回Hello Python |
其他路径 | 返回404 |
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from webob import Request, Response #从webob导入, Request(请求模块),Response(响应模块)
from webob.dec import wsgify # webob.dec 导入这个装饰器
class App:
# def __init__(seif): # 没想好写什么不要了
# pass
@wsgify
def __call__(self,request:Request):
print('request.path = ',request.path)
print('request.path type',type(request.path))
# http://172.17.55.69:9999/?id=1&name=tom&age=20
# 返回结果:request.path = / # web站点的根目录,是web站点能管理的根的东西,不是文件系统的根目录
# http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# 返回结果: request.path = /index.html
res = Response()
if request.path == "/":
print('-'*10,'request.pash == "/"')
res.body = "<h1>Hello</h1>".encode()
# 访问:http://172.17.55.69:9999/?id=1&name=tom&age=20
# 返回结果:Hello
elif request.path == "/python":
res.body = "<h1>Hello Python</h1>".encode()
# 访问:http://http://172.17.55.69:9999/python?id=1&name=tom&age=20
# 返回结果:Hello Python
else:
res.status_code = 404
res.body = "Not Found".encode()
# 访问:http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# 返回结果:Not Found
print('*'*20)
# return b'<h1>hello1</h1>'
# return Response('<h1>hello1</h1>')
return res # 我们这样写路由系统是起作用的
if __name__ == '__main__':
ip = '172.17.55.69'
port = 9999
# server = make_server(ip,port,app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是装饰器返回的app
server = make_server(ip,port,App()) # 现在是调用这个类一定要有括号的,因为这个类实例化之后才是可调用对象,因为这东西可以__call__,方法的本质就是函数,只不过类对我们来说以后可以扩展,扩展起来比函数方便点,他不过是一种封装形式的变化而已
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
# server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
server.handle_request()
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# ------------------------------------------------------ 进一步改进,加上函数-----------------------------------------------
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from webob import Request, Response #从webob导入, Request(请求模块),Response(响应模块)
from webob.dec import wsgify # webob.dec 导入这个装饰器
def index(request:Request): # 以后显示首页的,有时候可能要借用request中给我们的内容想做一些处理
res = Response()
res.body = "<h1>Hello</h1>".encode()
return res
def showpython(request:Request):
res = Response()
res.body = "<h1>Hello Python</h1>".encode()
return res
def notfound(request:Request):
res = Response()
res.status_code = 404
res.body = "Not Found".encode()
return res
class App:
# def __init__(seif): # 没想好写什么不要了
# pass
@wsgify
def __call__(self,request:Request):
print('request.path = ',request.path)
print('request.path type',type(request.path))
# http://172.17.55.69:9999/?id=1&name=tom&age=20
# 返回结果:request.path = / # web站点的根目录,是web站点能管理的根的东西,不是文件系统的根目录
# http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# 返回结果: request.path = /index.html
if request.path == "/":
return index(request)
# 访问:http://172.17.55.69:9999/?id=1&name=tom&age=20
# 返回结果:Hello
elif request.path == "/python":
return showpython(request)
# 访问:http://172.17.55.69:9999/python?id=1&name=tom&age=20
# 返回结果:Hello Python
else:
return notfound(request)
# 访问:http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# 返回结果:Not Found
print('*'*20)
if __name__ == '__main__':
ip = '172.17.55.69'
port = 9999
# server = make_server(ip,port,app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是装饰器返回的app
server = make_server(ip,port,App()) # 现在是调用这个类一定要有括号的,因为这个类实例化之后才是可调用对象,因为这东西可以__call__,方法的本质就是函数,只不过类对我们来说以后可以扩展,扩展起来比函数方便点,他不过是一种封装形式的变化而已
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
# server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
server.handle_request()
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# ------------------------------------------------------ 在改进,使用字典映射-----------------------------------------------
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from webob import Request, Response #从webob导入, Request(请求模块),Response(响应模块)
from webob.dec import wsgify # webob.dec 导入这个装饰器
def index(request:Request): # 以后显示首页的,有时候可能要借用request中给我们的内容想做一些处理
res = Response()
res.body = "<h1>Hello</h1>".encode()
return res
def showpython(request:Request):
res = Response()
res.body = "<h1>Hello Python</h1>".encode()
return res
def notfound(request:Request):
res = Response()
res.status_code = 404
res.body = "Not Found".encode()
return res
# 这个路由用起来不方便,这个路由相当于写死,要是再写,不知道要写多少个,既然路径等于一个东西,这不就是典型的映射,字典就诞生了,这时候用字典来描述他就更好了
ROUTETABLE = {
"/" : index,
"/python" : showpython
}
class App:
# def __init__(seif): # 没想好写什么不要了
# pass
@wsgify
def __call__(self,request:Request):
# 这些不要了
# print('request.path = ',request.path)
# print('request.path type',type(request.path))
# # http://172.17.55.69:9999/?id=1&name=tom&age=20
# # 返回结果:request.path = / # web站点的根目录,是web站点能管理的根的东西,不是文件系统的根目录
# # http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# # 返回结果: request.path = /index.html
# if request.path == "/":
# return index(request)
# # 访问:http://172.17.55.69:9999/?id=1&name=tom&age=20
# # 返回结果:Hello
# elif request.path == "/python":
# return showpython(request)
# # 访问:http://172.17.55.69:9999/python?id=1&name=tom&age=20
# # 返回结果:Hello Python
# else:
# return notfound(request)
# # 访问:http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# # 返回结果:Not Found
# print('*'*20)
return ROUTETABLE.get(request.path,notfound)(request) # 这东西返回一个函数,函数要调用把requese送进去就完了 从ROUTETABLE字典中找,每次只进来一回使用.get,get找不到是notfound,字典key是request.path
if __name__ == '__main__':
ip = '172.17.55.69'
port = 9999
# server = make_server(ip,port,app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是装饰器返回的app
server = make_server(ip,port,App()) # 现在是调用这个类一定要有括号的,因为这个类实例化之后才是可调用对象,因为这东西可以__call__,方法的本质就是函数,只不过类对我们来说以后可以扩展,扩展起来比函数方便点,他不过是一种封装形式的变化而已
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
# server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
server.handle_request()
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉
# ------------------------------------------------------ 在改进,使用注册-----------------------------------------------
# 开启httpd服务,关闭firewalld防火墙,使用http://协议访问
# import cgi # 使用cgi(cgi过期了用urllib提供的)
from wsgiref.simple_server import make_server # 导入make_server进行绑定,demo_app小巧完整的WSGI的应用程序自己实现实现
from webob import Request, Response #从webob导入, Request(请求模块),Response(响应模块)
from webob.dec import wsgify # webob.dec 导入这个装饰器
def index(request:Request): # 以后显示首页的,有时候可能要借用request中给我们的内容想做一些处理
res = Response()
res.body = "<h1>Hello</h1>".encode()
return res
def showpython(request:Request):
res = Response()
res.body = "<h1>Hello Python</h1>".encode()
return res
def notfound(request:Request):
res = Response()
res.status_code = 404
res.body = "Not Found".encode()
return res
# 路由表,这就是一个最简单的路由过程了,但是这个路由过程非常死板
ROUTETABLE = {}
# 路由硬编码
# 路由逻辑写死到了代码中,这样不好。
# 好的办法,写到配置文件,动态加载。
# 写一个注册函数,解决路由硬编码(字典值写死了),加注册就能完成加到字典里去,什么时候注册问题都不大
def register(path, handler):
ROUTETABLE[path] = handler
# 注册过程,在用的时候要保证他已经注册过了
register('/',index)
register('/python',showpython)
class App:
# def __init__(seif): # 没想好写什么不要了
# pass
@wsgify
def __call__(self,request:Request):
# 这些不要了
# print('request.path = ',request.path)
# print('request.path type',type(request.path))
# # http://172.17.55.69:9999/?id=1&name=tom&age=20
# # 返回结果:request.path = / # web站点的根目录,是web站点能管理的根的东西,不是文件系统的根目录
# # http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# # 返回结果: request.path = /index.html
# if request.path == "/":
# return index(request)
# # 访问:http://172.17.55.69:9999/?id=1&name=tom&age=20
# # 返回结果:Hello
# elif request.path == "/python":
# return showpython(request)
# # 访问:http://172.17.55.69:9999/python?id=1&name=tom&age=20
# # 返回结果:Hello Python
# else:
# return notfound(request)
# # 访问:http://172.17.55.69:9999/index.html?id=1&name=tom&age=20
# # 返回结果:Not Found
# print('*'*20)
print(ROUTETABLE)
return ROUTETABLE.get(request.path,notfound)(request) # 这东西返回一个函数,函数要调用把requese送进去就完了 从ROUTETABLE字典中找,每次只进来一回使用.get,get找不到是notfound,字典key是request.path
if __name__ == '__main__':
ip = '172.17.55.69'
port = 9999
# server = make_server(ip,port,app) # make_server封装一个tcp server他要理解httpd 绑定ip,port未来启动一个监听,拿到用户请求后找我们自己定义的app是装饰器返回的app
server = make_server(ip,port,App()) # 现在是调用这个类一定要有括号的,因为这个类实例化之后才是可调用对象,因为这东西可以__call__,方法的本质就是函数,只不过类对我们来说以后可以扩展,扩展起来比函数方便点,他不过是一种封装形式的变化而已
# make_server源码
# def make_server(
# host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
# ):
# """Create a new WSGI server listening on `host` and `port` for `app`"""
# server = server_class((host, port), handler_class) # 调这个server_class类告诉你他是一个WSGIServer,这里的参数有个handler_class,在写tcp每一个请求来的时候的时候转给handler函数处理,这个http请求也是一个tcp请求
# server.set_app(app) # 调这个set_app方法
# return server
# ----
# def set_app(self,application):
# self.application = application # 把传入的application设置到 self.application上去
# ---- demo_app 源码 首先拿到server回调server一调用就传递两个参数,把environ信息拿到后做相应的处理,第一步start_response头返回回去,然后return [stdout.getvalue().encode("utf-8")] 返回正文
# def demo_app(environ,start_response): # demo_app不管哪种实现方式总之要接受这两个参数,environ(这里面就是各种头信息内容),start_response,
# from io import StringIO # 存内存玩一下。返回的内容放在内存中
# stdout = StringIO()
# print("Hello world!", file=stdout)
# print(file=stdout)
# h = sorted(environ.items()) # 把环境变量传进来排序打印一下,把你传进来的environ
# for k,v in h: # h是排序后的然后遍历返回一个k,v二元组解开
# print(k,'=',repr(v), file=stdout) # 解开以后打印一下
# start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) # 报文码和信息头传回去, [('Content-Type','text/plain; charset=utf-8')]是一个列表,里面是一个二元组来模拟一个字典
# return [stdout.getvalue().encode("utf-8")] # stdout从标准输出上拿到getvalue这些信息然后给他编码传回去,必需返回一个可迭代对象,可迭代对象里面的东西就是正文的内容,做"utf-8"的编码了
try:
# server.serve_forever() # server.serve_forever()永久, server.handle_request()执行一次
server.handle_request()
except Exception as e: # 有异常就捕获去
print(e)
except KeyboardInterrupt:
print('stop')
server.server_close() # 关掉