python 请求服务器的实现代码(http请求和https请求)
一、http请求
1、http请求方式:get和post
get一般用于获取/查询资源信息,在浏览器中直接输入url+请求参数点击enter之后连接成功服务器就能获取到的内容,post请求一般用于更新资源,通过form表单或者json、xml等其他形式提交给服务器端,然后等待服务器端给返回一个结果的方式(这个返回结果一般就是被修改之后的是否成功的状态,或者是修改后的最新数据table等)。
http请求,不论是get还是post请求,都会包含几个部分,分别是header,cookie,get会有param,post会有body。
这个可以通过fiddler里面抓包就可以拿到需要的Headers,一般需要设置的值可能有:
header = { "Host": "x.x.360.cn", "Authorization": "Basic: someValue", "Content-Type": r"application/json", "Connection": "keep-alive", "Proxy-Connection": "keep-alive", "Cookie": "xxxxxxxxx(备注:这里的具体值请自行填写,其他key对应的值也是一样)", "User-Agent": "360xxxxxx(备注:这里的信息也请自行抓到之后填写,不需要的话,可以不用填写)" }
针对正式环境和测试环境需要设置url的地址,以及Header的"Host"中的具体域名的方法如下:
(1)正式环境:url中的host也设置成域名,比如:http://%s/search/searchList的%s就替换成 域名,在headers中的"HOST"的键对应的value也是域名,比如说都是"x.y.360.cn"
(2)测试环境: url中的host设置成具体的IP,比如:http://%s/search/searchList的%s就替换成 10.108.225.234这样的具体IP(备注,这个IP就是你们平时开发上测试代码的机器),但是headers中的"HOST"的键对应的value必须得写成域名,比如"x.y.360.cn"
原因:因为一个IP地址对应的服务器上可能会有多个域名,因为可能会上多个不同业务的服务器代码,如此会有一个默认的域名,但是并不一定是你的这个业务对应的域名,所以一定要在headers中的"HOST"中指定域名才可以找到这个域名,从而找到其对应的接口,进行正确的调用。
进一步,对于一个IP地址对应的服务器,其上会有很多域名,这个是如何部署的呢?需要问一下服务器端的同学,比如说会有x.360.cn和x.y.360.cn,这个是如何进行配置的呢?具体原因是使用了nginx的配置://www.jb51.net/article/140826.htm;具体的内容就是指:一台nginx服务器多域名配置,然后客户端请求的时候,就能自动根据这个host找到对应的文件目录,然后找到对应处理方法,这个后续要再详细了解一下。
cookie信息都是在headers里面的"Cookie"键对应的value后面,这个可以通过日志或者抓包得到,注意,抓到的信息一定要原封不动的全部拿来用。
另外,这个cookie信息也可以通过其他方式获取,比如说,通过登录接口拿到cookie信息,再将cookie信息设置到后续需要的"Cookie"中。
具体的body的值,需要跟服务器端开发对应一下数据的加密方式,目前比较多的都是通过json格式的,需要确认的是几层json,比如我们的开发同学搞了两层json,导致我刚开始的时候就在最外面搞了一层json转换格式,结果请求的时候一直提示Resopnse 200,但是返回的errorMsg一直是错误请求。(备注:首先需要确认Response的Status是200的话,就说明已经跟服务器端连接上了,然后如果拿不到正确的数据,那就要分析是你的数据传送格式不正确,还是缺少了哪些内容,导致服务器端解析不出,或者无法给出你想要的内容)
一般的get请求的格式,一个参数的可能是这样的:http://xxx/search/YYYY?&kw=123456789,如果是多个参数的话:http://music.baidu.com/search?fr=ps&ie=utf-8&key=%E7%9C%8B%E8%A7%81%E4%BA%86,比如像百度音乐的这个url,在?后面都可以添加一个&,然后url其实也可以变成这样的格式:http://music.baidu.com/search?&fr=ps&ie=utf-8&key=%E7%9C%8B%E8%A7%81%E4%BA%86,但是实际上访问get到的都是相同的内容,也就是说服务器端解析的时候,返回的结果都是相同的内容;多个参数,就每个参数之间加一个&链接起来,但是注意,有些值传的时候可能需要进行urlencode编码,并且一定要在跟服务器端相同的编码的基础上进行urlencode编码(我自己碰到的坑:我的python程序用的编码方式是:gbk,我们服务器端的编码方式是utf-8,我最开始的时候,直接对中文进行了urlencode编码,但是得到的结果不是想要的,最后才发现原来我urlencode之后的码与服务器端urlencode之后的码不同,所以当然解不出了,那么就decode('gbk').encode('utf-8'),然后得到的内容再urlencode,之后才正确。。。所以都是坑)
备注1:需要了解一下get请求在服务器端是怎么处理的?post请求在服务器端又是如何处理的?这个需要另开一篇博客专门写一下。
备注2:关于编码方式,以及几种编码方式的转换(编码解码等),进行urlencode的具体方法,在python26的urllib中有urlencode方法,只能对dict进行编码,如果只是对字符串进行编码,需要使用urllib.quote()方法
比如:
>>> import urllib >>> xx = {'kw': '达达'} >>> urllib.urlencode(xx) 'kw=%B4%EF%B4%EF' >>> ss = File "<stdin>", line 1 ss = ^ SyntaxError: invalid syntax >>> >>> ss = '达达' >>> urllib.urlencode(ss) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python26\lib\urllib.py", line 1255, in urlencode raise TypeError TypeError: not a valid non-string sequence or mapping object >>> urllib.quote(ss) '%B4%EF%B4%EF'
查看当前处于什么编码格式:
>>> import sys >>> sys.getdefaultencoding() 'ascii'
编码及解码:
在python中使用decode和encode进行编码和解码,比如我们get到的str类型是gbk的,那就可以str.decode(''gbk'),之后再encode成我们想要的格式
一般情况下常用的编码格式主要有:utf8、gbk、gb2312;在python26中默认的编码是ascii,但是在python3.x中默认的编码是utf-8
后面再专门针对编码这块做一个大块的总结。
2、http请求端口、cookie,以及实现具体的get和post请求
http请求端口默认是80,如果不指定的话,默认走的就是80,否则就需要指定服务器端指定listen的端口。
cookie是什么?具体见://www.jb51.net/article/140830.htm, 主要内容:有两个Http头部和Cookie有关:Set-Cookie和Cookie。Set-Cookie由服务器发送,它包含在响应请求的头部中。它用于在客户端创建一个Cookie。Cookie头由客户端发送,包含在HTTP请求的头部中。注意,只有cookie的domain和path与请求的URL匹配才会发送这个cookie。
(1)httplib库――HTTP protocol client
切记:要从用户手册中学习!
httplib在python3.0中已经更名为http.client了。
class httplib.HTTPConnection(host[,port[,strict[,timeout]]])
class httplib.HTTPSConnection(host[,port[,key_file[,cert_file[,strict[,timeout]]]]]) ――这是HTTPConnection的一个子类,使用了SSL,用来跟安全服务器进行通信。默认的端口是443。key_file是一个pem格式的包含了密钥的文件,cert_file是一个pem格式的证书链文件。
然后这个httplib的HttpConnection的类调用之后,能够得到一个HTTPConnection的instance,就是一个HTTPConnection或者HTTPSConnection的一个对象,比如设置其名称为conn,之后利用这个conn的对象就可以继续走request(method,url[,body[,headers]])的请求,调用request方法之后,继续调用conn.getresponse(),然后返回一个HTTPResponse的实例对象,例如为res,然后调用res.getheaders()方法获取response的头部,得到的一个(header,value)的tuple,通过res.status就可以得到状态(200为OK,连接上的含义),res.read()就可以得到response的body信息,然后自己再针对body信息的类型,比如是json,就解析出来显示即可。
具体的使用例子用户手册中也说明了:
>>> import httplib >>> conn = httplib.HTTPConnection("www.python.org") >>> conn.request("GET", "/index.html") >>> r1 = conn.getresponse() >>> print r1.status, r1.reason 301 Moved Permanently >>> conn.request("GET", "/parrot.spam") >>> r2 = conn.getresponse() >>> print r2.status, r2.reason 301 Moved Permanently >>> conn2 = httplib.HTTPConnection("jia.360.cn") >>> conn2.request("GET", "/standard.html") >>> r3 = conn2.getresponse() >>> print r3.status 200 >>> data = r3.read() >>> print data <!Doctype html><html lang="zh-CN"><head>.......
以上例子中,先用的是用户手册的example中的例子,但是因为www.python.org被永久转移,所以返回的结果如上;所以选择了"jia.360.cn"的url,之后request中请求的是标准版摄像机的页面,即"/standard.html",之后就能够得到r3的结果,为200,说明连接OK了,之后就能通过r3.read()得到body的内容,通过r3.getheaders()就能获取到header的内容。
以上都是request方法中都是"GET"方法,换成"POST"需要传的内容会有一些差别,如下:
>>> import httplib, urllib >>> params = urllib.urlencode({'spam': 1, 'eggs': 2, 'bacon': 0}) >>> headers = {"Content-type": "application/x-www-form-urlencoded", ... "Accept": "text/plain"} >>> conn = httplib.HTTPConnection("musi-cal.mojam.com:80") >>> conn.request("POST", "/cgi-bin/query", params, headers) >>> response = conn.getresponse() >>> print response.status, response.reason 200 OK >>> data = response.read() >>> conn.close()
备注:以上代码也是运行不通过的,因为是比较久远的python版本的例子,主要需要注意的是:需要自己设置headers,在其中根据需要传递Cookie、Content-Type、Accept等信息,通过key-value的形式传递,具体的body中传递的信息,要注意是json格式的,还是通过urlencode编码等,格式一定要跟开发沟通清楚,否则会有错误请求的问题,之后得到response,并获取response的status、body、headers就与前面的"GET"method一样了。
(2)request库
request库是python的第三方库,官方文档地址:http://www.python-requests.org/en/master/user/quickstart/#make-a-request
get请求:
>>> r = requests.get('http://httpbin.org/get') >>> r <Response [200]> >>> r.text u'{\n "args": {}, \n "headers": {\n "Accept": "*/*", \n "Accept-Encoding": "gzip, deflate", \n "Host": "httpbin.org", \n "User-Agent": "python-requests/2.9.1"\n }, \n "origin": "218.30 .116.9", \n "url": "http://httpbin.org/get"\n}\n'
post请求:
>>> r = requests.post('http://httpbin.org/post', data={'key':'value'}) >>> r <Response [200]> >>> r.text u'{\n "args": {}, \n "data": "", \n "files": {}, \n "form": {\n "key": "value"\n }, \n "headers": {\n "Accept": "*/*", \n "Accept-Encoding": "gzip, deflate", \n "Content-Length": "9" , \n "Content-Type": "application/x-www-form-urlencoded", \n "Host": "httpbin.org", \n "User-Agent": "python-requests/2.9.1"\n }, \n "json": null, \n "origin": "218.30.116.185", \n "url": "http://httpbin.org/post"\n}\n'
我这里用的还是httplib的,request的后续有详细使用教程会补充上来。
二、https请求
1、https的请求方式:get和post
http和https的区别:
(1)url的前面是https://而不是http://,使用ssl进行加密/身份认证,并且http的默认端口是80,https的默认端口是443。
(2)因为有ssl的认证和加密,所以具体的底层的通信过程中会有不同,https的这一层在建立连接的时候,需要设置socket属性,socket属性的生成需要使用具体的方法调用,方法调用的参数需要指定:ca_certs=服务器端给提供的公钥证书即可。
然后如果还有客户端认证的话,那客户端也可以提供出自己的key_file,cert_file。
什么是ssl?
ssl的全称是(Secure Sockets Layer)安全套接层,另外还有TLS(Transport Layer Secure,传输层安全),这两种协议都是为网络提供安全和数据完整性的一种安全协议,在传输层对网络连接进行加密。
为什么要用这个?
防止数据以及网络连接的传输内容被截获,所以涉及到个人或者重要的信息等,都需要进行建立ssl连接,通过https的请求方式加密处理。
2、https请求端口、ssl建立,以及实现具体的get和post请求
post请求:
httpsConn = None try: httpsConn = httplib.HTTPSConnection(host) sock = socket.create_connection((httpsConn.host, httpsConn.port)) try: httpsConn.sock = ssl.wrap_socket(sock, ca_certs=CERT_FILE, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv3) #self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv3) except ssl.SSLError, e: print("Trying SSLv3.") try: httpsConn.sock = ssl.wrap_socket(sock, ca_certs=CERT_FILE, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv23) #self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv23) except ssl.SSLError, e: print("Trying SSLv23.") try: httpsConn.sock = ssl.wrap_socket(sock, ca_certs=CERT_FILE, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1) except ssl.SSLError, e: print("Trying TLSv1.") try: httpsConn.sock = ssl.wrap_socket(sock, ca_certs=CERT_FILE, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv2) except ssl.SSLError, e: print("Trying SSLv2.") httpsConn.request("POST", path, body, headers) res = httpsConn.getresponse() headers = {} for k, v in res.getheaders(): headers[k] = v return res.status, headers, res.read() except Exception, e: import traceback print traceback.format_exc() return e finally: if httpsConn: httpsConn.close
备注:
因为是客户端证书,所以没有使用注释的代码:#self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, ssl_version=ssl.PROTOCOL_SSLv3),这个程序中需要指定客户端的私钥密钥的文件,如果只有服务器端有私钥,客户端有公钥,则客户端的程序需要指定公钥文件,见代码:httpsConn.sock = ssl.wrap_socket(sock, ca_certs=CERT_FILE, cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_SSLv3),是通过ca_certs参数指定的,CERT_FILE是文件的路径,保证能够找到即可;如果是是一个文件夹下有多个文件,然后这多个文件都是需要用到的,比如A域名的证书和B域名的证书,A服务器在对接口处理请求的时候,会向B端发请求,如此客户端需要将A域名证书和B域名证书都添加进来,所以只要把文件夹路径设置成ca_certs参数的值即可。
另外,如果不确定SSL的版本,则需要尝试多个不同的SSL版本:ssl.PROTOCOL_TLSv1、ssl_version=ssl.PROTOCOL_SSLv2、ssl_version=ssl.PROTOCOL_SSLv23、ssl_version=ssl.PROTOCOL_SSLv3。
get请求的话,就将httpsConn.request("POST", path, body, headers)中的"POST"换成"GET"就好了,然后body设置为None即可。
3、ssl建立的过程中需要使用的证书(证书格式、证书生成、证书转换)、什么是服务器端/客户端校验?私钥公钥的概念
服务器端会有私钥和公钥,公钥会拿出来提供给客户端,在python的具体程序中,分别是key_file和cert_file,其中cert_file要提供给客户端。
python-cookbook中对建立ssl的连接的讲解见:http://python3-cookbook.readthedocs.io/zh_CN/latest/c11/p10_add_ssl_to_network_services.html :
以下是服务器端代码:
from socket import socket, AF_INET, SOCK_STREAM import ssl KEYFILE = 'server_key.pem' # Private key of the server CERTFILE = 'server_cert.pem' # Server certificate (given to client) def echo_client(s): while True: data = s.recv(8192) if data == b'': break s.send(data) s.close() print('Connection closed') def echo_server(address): s = socket(AF_INET, SOCK_STREAM) s.bind(address) s.listen(1) # Wrap with an SSL layer requiring client certs s_ssl = ssl.wrap_socket(s, keyfile=KEYFILE, certfile=CERTFILE, server_side=True ) # Wait for connections while True: try: c,a = s_ssl.accept() print('Got connection', c, a) echo_client(c) except Exception as e: print('{}: {}'.format(e.__class__.__name__, e)) echo_server(('', 20000))
之后是客户端连接服务器端的例子:
>>> from socket import socket, AF_INET, SOCK_STREAM >>> import ssl >>> s = socket(AF_INET, SOCK_STREAM) >>> s_ssl = ssl.wrap_socket(s, cert_reqs=ssl.CERT_REQUIRED, ca_certs = 'server_cert.pem') >>> s_ssl.connect(('localhost', 20000)) >>> s_ssl.send(b'Hello World?') 12 >>> s_ssl.recv(8192) b'Hello World?' >>>
备注:其中 ssl.wrap_socket(s,cert_reqs=ssl.CERT_REQUIRED,ca_certs = 'server_cert.pem') 的ca_certs就是需要在客户端指定的证书,这个是服务器给的公钥证书。
证书的格式:一般有der格式、pem格式,且格式不能单纯通过后缀名去进行判定,比如一个后缀名是crt,就认为其不是pem的格式是错误的。
证书转换:讲解证书转换的url地址:http://netkiller.github.io/cryptography/openssl/format.html
可以通过OpenSSL(OpenSSL的安装://www.jb51.net/softjc/575021.html)来生成证书、以及进行证书的格式转换,比如将der转成pem格式,或者将pem转成der格式的。如果你不确定你的证书的格式,可以将两种转换都尝试一下,因为如果原本就是pem格式的,希望通过der转成pem格式的命令调用之后,会有错误产生。