python 请求服务器的实现代码(http请求和https请求)

yipeiwu_com4年前服务器

一、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的配置:/post/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是什么?具体见:/post/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/

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的安装:https://www.jb51.net/softjc/575021.html)来生成证书、以及进行证书的格式转换,比如将der转成pem格式,或者将pem转成der格式的。如果你不确定你的证书的格式,可以将两种转换都尝试一下,因为如果原本就是pem格式的,希望通过der转成pem格式的命令调用之后,会有错误产生。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持【听图阁-专注于Python设计】。

相关文章

Spring Cloud微服务架构简介

Spring Cloud微服务架构简介

什么是微服务微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自...

使用pycharm在本地开发并实时同步到服务器

使用pycharm在本地开发并实时同步到服务器

本教程使用的是python 3   和django为事例 打开pycharm file  ->   new project...  ->...

Linux系统中设置多版本PHP共存配合Nginx服务器使用

应用环境 LNMP的环境,当前PHP版本5.3.8,遇到一个应用需求只支持PHP 5.2.x,又希望保持现有应用还是用PHP 5.3.8。也就是说需要两个版本的PHP同时存在,供ngin...

PHP 服务器配置(使用Apache及IIS两种方法)

一、使用Apache≡ PHP 5.2.5 的安装 ≡1、到其官方站点下载 php-5.2.5-Win32.zip 并解压(据说:不要下载及使用它的Installer,这种方式虽然很自动...

用PHP实现多服务器共享SESSION数据的方法

PHP 实现多服务器共享 SESSION 数据˂!-- google 的广告条 2005年09月20日换位置 唉,22号被停了.郁闷,没作弊呀 11.27日重开了 ˂!...