网络应用(8):http的封装与使用

之前讲过http的协议,怎么约定请求或响应的行、头、体,也介绍怎么使用curl来完成http的请求。这一次,再接再厉,换一个角度换一些角色,再次说http的封装与使用。反正目的只有一个:加深对http协议的理解。

(1)tcp的实现

说http的实现,非讲tcp不可(为什么?后面会解释),而之前讲tcp协议的使用,用到了socket,用socket就能通信,就能做tcp想做的事情。

那socket是什么东西?

用你的话来理解,socket是一个通道吗?因为它能通信啊。“socket是一个通道”已经是抓到重点了,虽然不完整,因为socket在成为通道前,并不是通道,它只是一个等待使用的值。但是,“突出重点”的抽象是如此重要,不要追求完整或完美。socket,是网络结点(ip:port),可以代表数据通道,通过它就可以在结点间传送数据。 可以想象,网络上的一切数据交换,都由socket完成。

socket并不只是一个抽象的概念,它还是实实在在的代码实现,但你能看到的是二进制代码,它已经给别人实现了,而且存在于各个平台,比如windows、unix、ios、android等,那它在这些平台是什么样的存在?它又是谁实现的呢?

结合wiki等网页的信息:

套接字起源于20世纪70年代加州大学伯克利分校版本的Unix,所以,这个套接字称为“伯克利套接字”或“BSD套接字”。 BSD sockets,是所有其它平台的socket的祖先,包括windows/liunx,还有某些语言使用的socket比如python、java等。bsd socket用c语言实现,有对应的c层静态库(或动态库)。

windows的socket是Martin Hall、Mark Towfiq(从属于Microsoft)等人的版权,有两个大版本,即winsock1跟winsock2,对应的库文件是wsock32.lib/ws2_32.lib,你在windows平台上就是在使用这个库文件进行socket操作。

好了,具体某些平台是谁写了socket的实现,就不深究了,只是说,感谢前辈们的贡献。

那问题来了,socket实现了什么?

socket实现了tcp协议(还有udp),这个很重要,对于http来说也很重要,因为http在协议栈上位于应用层,依赖于传输层的tcp协议,也就是说,http最终要用tcp来传输数据,而socket包括了tcp的实现,你说重不重要?

所以,http协议依靠socket来传输数据,但依靠还依靠,不代表http要理socket层的事情。

(1)http的实现

http跟tcp不同,http并不关心数据怎么传来传去,反正能传就行,这个是tcp层的事情,那http还要考虑什么?就是请求与响应的格式啊,而且是人能读懂的格式,http的要求已经很人性化了,这个对话已经很友好了(不像tcp的数据)。http报文的格式之前介绍了的!

对于http请求或响应数据,包括行、头、体,其中行与头是少不了的。那行头体的格式由谁来封装,注意,这不是socket的事情,socket不管这个,那谁管?

工具curl管http格式封装(也管数据收发),java的HttpClient管http格式封装,python的urllib也管http格式封装,还有很多工具类或库都能封装http协议(注意,说http就只有http协议,不包括tcp,因为tcp是另一层的事情),当然,你自己写一个http的格式封装,也没有问题。

简单来说,http的实现其实并不包括怎么传输的事情,那是tcp层(socket)的事情,http的实现重点在于本身协议的封装与实现,比如怎么组装请求头、怎么调用接口收发数据、怎么解释数据、GET或POST方式封装、url编码,等等。你会发现有很多零散的知识点,很多实现还跟业务关系密切,这也是http是应用层协议的表现。

对于你,如无必要,优先考虑已经稳定的http封装实现吧,具体看你是什么平台进行对应的选择。

(2)http的使用

之前讲http的结构跟怎么使用curl时,其实就是通过curl使用了http。是的,我们并不会直接“使用http”,而是通过工具类来使用http,你可以自己写工具类比如弄一个httpclient的类出来,也可以使用一些表现良好的现有的工具类,建议用后者。

这一次,我用python库的一些工具,比如urllib或httplib,通过它们来使用http协议。

一般使用http,就是要向服务器发出请求,那服务器怎么来?你自然可以找一堆服务器出来,只要有对应的url就可以,实际上你也只是请求某个url。但是,为了显得高级一点,我这里自行定制一个web服务器,用来接收http请求。

如何定制web服务器?

小程在介绍“PHP程序”的使用时,把apache给引了进来,apache是一个web服务器,可以接受你的http请求。另一方面,apache可以跟PHP解析器、python解析器等进行交互,可以根据http请求,执行一些脚本代码(像php脚本或python脚本等)。

这里就演示一下,怎么发起一个http请求到apache,apache再执行python脚本,返回http响应。

(a)让apache能启用python

按之前讲解的知识,apache项目的目录是:/Library/WebServer/Documents,之前php脚本就是放在这里。但是,对于python脚本,解释它的是cgi,这些脚本要放在cgi项目目录,那个目录是:/Library/WebServer/CGI-Executables,所以你写的python脚本要放在这个目录。 先不管那么多,在CGI-Executables目录下,创建一个hello.py,随便写点什么,比如:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

title="小程"
info="你好,first py script,关注微信公众号'广州小程'"
htmlcontent="<html><head><meta charset='utf-8'><title>%s</title></head><body><h2>%s</h2></body></html>" % (title,info)
print("Content-type:text/html")
print("")
print(htmlcontent)

然后打开浏览器来请求一下这个文件吧(注意请求cgi-bin目录),结果是这样的:
网络应用(8):http的封装与使用

显然,这不是我想看到呀,我不是要看脚本代码,我是想看到脚本代码被解释后的内容!

如果客户端比如浏览器,向apache请求了python或php脚本,而apache没有找到对应的脚本解释器的话(注意apache自己是不懂脚本解释的),apache会把这个脚本的内容完整地返回给浏览器,这时源码暴露无遗,这很可能不是你想看到的事情。

所以,让apache找到脚本解释器(这里是cgi),是至关重要的一步。

不再讲apache的启用了,直接讲,怎么样才能让apache执行python脚本呢?你可能还记得,让apache调用php程序的话,要把apache的配置文件httpd.conf改一下(/private/etc/apache2目录下),也就是“LoadModule phpx_module...”这一行解除注释,就可以调用php程序了,那对于python是不是也这样改呢?是的,bingo,也要解除注释,这一句是:

#LoadModule cgi_module libexec/apache2/mod_cgi.so

把前面的#去掉并保存一下即可,注意重启一下apache:

sudo apachectl restart

这时,apache就会加载mod_cgi.so,并用cgi来解释python脚本。再用浏览器请求一次,看到这样的返回:
网络应用(8):http的封装与使用

简单来说,cgi就是用来解释python脚本的,apache启用它就解决了python脚本解释的问题,更多cgi的东西不说了。

(b)服务器处理http请求

至此,apache已经可以执行我写的python脚本了,那我写这个hello.py来做什么呢?当然是处理浏览器发过来的http请求了。所以,把hello.py再修改一下,让它根据请求的参数值,返回不同的内容给浏览器,比如这样:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import cgi

title="广州小程"
info=""
paras = cgi.FieldStorage()
if paras.has_key('secret'):
    if paras['secret'].value=='free':
        info="<h1>welcome, you are so clever! secret is %s</h1>" % paras['secret'].value
    else:  
        info="<h1>wrong! secret is NOT '%s'</h>" % paras['secret'].value
else:
    info="<h1>NO! 请加上secret参数!</h1>"
htmlcontent="<html><head><meta charset='utf-8'><title>%s</title></head><body>%s</body></html>" % (title,info)
print("Content-type:text/html")
print("")

脚本中使用cgi.FieldStorage获取请求参数,对这个参数给出了不同的提示。用浏览器请求一下,传递不同的参数,可以看这样的展示:
网络应用(8):http的封装与使用
网络应用(8):http的封装与使用
网络应用(8):http的封装与使用

至此,一切都是美好的,服务器脚本定好了,能应对请求了,浏览器也看到返回内容了,可是,我不是教你怎么做web服务啊,我想说的是,怎么使用http协议的呀!

(c)在python中使用http协议

这个才是重点内容。这里演示写一个客户端,用python里面的类,完成http请求,以表示,我已经会使用http协议了!

分别用urllib2跟httplib来演示。

import urllib2
url='http://localhost/cgi-bin/hello.py?secret=free'
req=urllib2.Request(url)
res=urllib2.urlopen(req)
res=res.read()
print(res)

执行上面的代码,使用urllib2这个http协议封装类,发起请求,返回是这样的:
网络应用(8):http的封装与使用

import httplib
url='http://localhost/cgi-bin/hello.py?xx'
conn=httplib.HTTPConnection('localhost')
conn.request(method='GET', url=url)
res=conn.getresponse()
res=res.read()
conn.close()
print(res)

这一段代码就形象很多了,先用HTTPConnection建立起链接(指定ip跟端口,端口省略意思就是用默认的80即web服务器的默认端口,另外我的localhost是固定到本机的),然后发起request,再取响应,一气呵成。执行结果是这样的:
网络应用(8):http的封装与使用

以上请求都是get方式,post也是很容易的,你研究一下嘛。好了,已经会用http协议了,至少是用上了。

总结一下吧,本文主要讲怎么使用http协议,为了说明白这个操作,讲了tcp跟http的大道理,还讲了怎么让apache解释python脚本,最后那一点演示才是重点,你至少知道有urllib这样的类可以使用http协议了吧!


网络应用(8):http的封装与使用

上一篇:来自shell脚本的Javascript


下一篇:如何执行Python CGI脚本?