作为客户端与HTTP 服务交互

问题:

  你需要通过HTTP 协议以客户端的方式访问多种服务。例如,下载数据或者与基于REST 的API 进行交互

解决方案:

  对于简单的事情来说,通常使用urllib.request 模块就够了。例如,发送一个简单的HTTP GET 请求到远程的服务上,可以这样做:

 from os import linesep
from pprint import pprint
from urllib import request, parse
from ssl import SSLContext, PROTOCOL_SSLv23 #要查询的主页
url = 'https://httpbin.org/get' #查询的参数,也就是url中get请求的参数
parms = {
'name1' : 'value1',
'name2' : 'value2'
} #浏览器头部
header = {'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)'
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
} #把字典转换成get请求的字符串
querystring = parse.urlencode(parms) #使用ssl的协议
gcontext = SSLContext(PROTOCOL_SSLv23) #创建一个get请求
req = request.Request(url + '?' + querystring) #获取get请求响应的内容
response = request.urlopen(req, context=gcontext).read().decode('utf-8') #打印返回的json字符
print(response)

以上代码执行返回的结果为:

{
"args": {
"name1": "value1",
"name2": "value2"
},
"headers": {
"Accept-Encoding": "identity",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "Python-urllib/3.6"
},
"origin": "114.241.48.46",
"url": "https://httpbin.org/get?name1=value1&name2=value2"
}

如果你需要使用POST 方法在请求主体中发送查询参数,可以将参数编码后作为可选参数提供给urlopen() 函数,就像这样:

 from urllib import request, parse
from ssl import SSLContext, PROTOCOL_SSLv23 #要查询的主页
url = 'https://httpbin.org/post' #查询的参数,也就是url中get请求的参数
parms = {
'name1' : 'value1',
'name2' : 'value2'
} #浏览器头部
header = {'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)'
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
} #使用ssl的协议
gcontext = SSLContext(PROTOCOL_SSLv23) #构建post的查询字符串
querystring = parse.urlencode(parms) #构建post请求
req = request.Request(url, querystring.encode('ascii'), headers=header) #获取post请求响应的内容
response = request.urlopen(req, context=gcontext).read().decode('utf-8') #打印返回的json字符
print(response)

以上代码执行返回的结果为:

{
"args": {},
"data": "",
"files": {},
"form": {
"name1": "value1",
"name2": "value2"
},
"headers": {
"Accept-Encoding": "identity",
"Connection": "close",
"Content-Length": "",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"
},
"json": null,
"origin": "114.241.48.46",
"url": "https://httpbin.org/post"
}

如果需要交互的服务比上面的例子都要复杂, 也许应该去看看requests 库(https://pypi.python.org/pypi/requests)。例如,下面这个示例采用requests 库重新实现了上面的操作:

 import requests
from ssl import SSLContext, PROTOCOL_SSLv23 #需要访问的url主页
url = 'https://httpbin.org/post' parms = {
'name1' : 'value1',
'name2' : 'value2'
} #浏览器头部
header = {'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)'
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'
} #使用ssl的协议
gcontext = SSLContext(PROTOCOL_SSLv23) #构建post请求
req = requests.post(url=url, data=parms, headers=header) #获取post请求响应的内容
response = req.text #打印返回的json字符
print(response)

以上代码执行返回的结果为:

{
"args": {},
"data": "",
"files": {},
"form": {
"name1": "value1",
"name2": "value2"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Connection": "close",
"Content-Length": "",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36"
},
"json": null,
"origin": "114.241.48.46",
"url": "https://httpbin.org/post"
}

下面这个示例利用requests 库发起一个HEAD 请求,并从响应中提取出一些HTTP 头数据的字段:

 #获取请求头的信息
import requests resp = requests.head('https://www.jd.com') status = resp.status_code
content_type = resp.headers['content-type']
content_length = resp.headers['content-length']
content_expires = resp.headers['expires'] for k, v in resp.headers.items():
print("{:<25}:{:}".format(k, v)) #使用cookie请求
url = 'https://www.jd.com'
resp1 = requests.get(url)
resp2 = requests.get(url, cookies=resp1.cookies) #上传二进制文件
url = 'http://httpbin.org/post'
files = { 'file': ('data.csv', open('data.csv', 'rb')) }
r = requests.post(url, files=files)

创建TCP 服务器

问题:

  你想实现一个服务器,通过TCP 协议和客户端通信

解决方案:

  创建一个TCP 服务器的一个简单方法是使用socketserver 库。例如,下面是一个简单的应答服务器:

 from socketserver import BaseRequestHandler, TCPServer

 class EchoHandler(BaseRequestHandler):

     def handle(self):
print('Got connection from', self.client_address)
while True:
msg = self.request.recv(8192)
if not msg:
break
self.request.send(msg) if __name__ == "__main__": server = TCPServer(('', 20000), EchoHandler)
server.serve_forever()

  在这段代码中,你定义了一个特殊的处理类,实现了一个handle() 方法,用来为客户端连接服务。request 属性是客户端socket,client address 有客户端地址。为了测试这个服务器,运行它并打开另外一个Python 进程连接这个服务器:

 from socket import socket, AF_INET, SOCK_STREAM

 sock = socket(AF_INET, SOCK_STREAM)
server = ('', 20000)
sock.connect(server)
sock.send('Golang大战Python'.encode('utf-8'))
data = sock.recv(8192)
print(data.decode('utf-8'))

很多时候, 可以很容易的定义一个不同的处理器。下面是一个使用StreamRequestHandler 基类将一个类文件接口放置在底层socket 上的例子:

 from socketserver import StreamRequestHandler, TCPServer

 class EchoHandler(StreamRequestHandler):

     def handle(self):
print('Got connection from', self.client_address) for line in self.rfile:
self.wfile.write(line) if __name__ == "__main__":
server = TCPServer(('', 20000))
server.serve_forever()

  socketserver 可以让我们很容易的创建简单的TCP 服务器。但是,你需要注意的是,默认情况下这种服务器是单线程的,一次只能为一个客户端连接服务。如果你想处理多个客户端,可以初始化一个ForkingTCPServer 或者是ThreadingTCPServer 对象。例如:

 from socketserver import ThreadingTCPServer
if __name__ == '__main__':
serv = ThreadingTCPServer(('', 20000), EchoHandler)
serv.serve_forever()

直接使用socket 库来实现服务器也并不是很难。下面是一个使用socket 直接编程实现的一个服务器简单例子:

 from socket import socket, AF_INET, SOCK_STREAM

 def echo_handle(address, client_sock):
print('Got connection from {}'.format(address))
while True:
msg = client_sock.recv(8192)
if not msg:
break
client_sock.sendall(msg)
client_sock.close() def echo_server(address, backlog=5):
sock = socket(AF_INET, SOCK_STREAM)
sock.bind(address)
sock.listen(backlog)
while True:
client_sock, client_address = sock.accept()
echo_handle(client_address, client_sock) if __name__ == '__main__':
echo_server(('', 20000))

创建UDP 服务器

问题:

  你想实现一个基于UDP 协议的服务器来与客户端通信

解决方案:

  跟TCP 一样,UDP 服务器也可以通过使用socketserver 库很容易的被创建。例如,下面是一个简单的时间服务器:

 from socketserver import BaseRequestHandler, UDPServer
import time class TimeHandler(BaseRequestHandler):
def handle(self):
print('Got connection from', self.client_address)
msg, sock = self.request
resp = time.ctime()
sock.sendto(resp.encode('utf-8'), self.client_address) if __name__ == '__main__':
server = UDPServer(('', 20000), TimeHandler)
server.serve_forever()

我们来测试下这个服务器,首先运行它,然后打开另外一个Python 进程向服务器发送消息:

 from socket import socket, AF_INET, SOCK_DGRAM

 sock = socket(AF_INET, SOCK_DGRAM)
sock.sendto(b'', ('', 20000))
data = sock.recvfrom(8192)
print(data)

  UDPServer 类是单线程的,也就是说一次只能为一个客户端连接服务。实际使用中,这个无论是对于UDP 还是TCP 都不是什么大问题。如果你想要并发操作,可以实例化一ForkingUDPServer 或ThreadingUDPServer 对象:

 from socketserver import ThreadingUDPServer
if __name__ == '__main__':
serv = ThreadingUDPServer(('',20000), TimeHandler)
serv.serve_forever()

直接使用socket 来是想一个UDP 服务器也不难,下面是一个例子:

 from socket import socket, AF_INET, SOCK_DGRAM
import time def time_server(address):
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(address)
while True:
msg, addr = sock.recvfrom(8192)
print('Got message from', addr)
resp = time.ctime()
sock.sendto(resp.encode('utf-8'), addr) if __name__ == "__main__":
time_server(('', 20001))

通过CIDR 地址生成对应的IP 地址集

问题:

  你有一个CIDR 网络地址比如“123.45.67.89/27”,你想将其转换成它所代表的所有IP (比如,“123.45.67.64”, “123.45.67.65”, …, “123.45.67.95”))

解决方案:

  可以使用ipaddress 模块很容易的实现这样的计算。例如:

 import ipaddress

 net = ipaddress.ip_network('123.45.67.64/27')

 for i in net:
print(i) net6 = ipaddress.ip_network('12:3456:78:90ab:cd:ef01:23:30/125') for a in net6:
print(a)

以上代码执行返回的结果为:

123.45.67.64
123.45.67.65
123.45.67.66
123.45.67.67
123.45.67.68
123.45.67.69
123.45.67.70
..................
..................
123.45.67.95
12:3456:78:90ab:cd:ef01:23:30
12:3456:78:90ab:cd:ef01:23:31
12:3456:78:90ab:cd:ef01:23:32
12:3456:78:90ab:cd:ef01:23:33
12:3456:78:90ab:cd:ef01:23:34
12:3456:78:90ab:cd:ef01:23:35
12:3456:78:90ab:cd:ef01:23:36
12:3456:78:90ab:cd:ef01:23:37

创建一个简单的REST 接口

问题:

  你想使用一个简单的REST 接口通过网络远程控制或访问你的应用程序,但是你又不想自己去安装一个完整的web 框架

解决方案:

  构建一个REST 风格的接口最简单的方法是创建一个基于WSGI 标准(PEP3333)的很小的库,下面是一个例子:

 import cgi

 def notfound_404(environ, start_response):
start_response('404 Not Found', [ ('Content-type', 'text/plain') ])
return [b'Not Found'] class PathDispatcher:
def __init__(self):
self.pathmap = {} def __call__(self, environ, start_response):
path = environ['PATH_INFO']
params = cgi.FieldStorage(environ['wsgi.input'], environ=environ) method = environ['REQUEST_METHOD'].lower()
environ['params'] = {key: params.getvalue(key) for key in params}
handler = self.pathmap.get((method, path), notfound_404)
return handler(environ, start_response) def register(self, method, path, function):
self.pathmap[method.lower(), path] = function
return function

为了使用这个调度器,你只需要编写不同的处理器,就像下面这样:

 import time
_hello_resp = '''\
<html>
<head>
<title>Hello {name}</title>
</head>
<body>
<h1>Hello {name}!</h1>
</body>
</html>''' def hello_world(environ, start_response):
start_response('200 OK', [ ('Content-type','text/html')])
params = environ['params']
resp = _hello_resp.format(name=params.get('name'))
yield resp.encode('utf-8') _localtime_resp = '''\
<?xml version="1.0"?>
<time>
<year>{t.tm_year}</year>
<month>{t.tm_mon}</month>
<day>{t.tm_mday}</day>
<hour>{t.tm_hour}</hour>
<minute>{t.tm_min}</minute>
<second>{t.tm_sec}</second>
</time>''' def localtime(environ, start_response):
start_response('200 OK', [ ('Content-type', 'application/xml') ])
resp = _localtime_resp.format(t=time.localtime())
yield resp.encode('utf-8') if __name__ == '__main__':
from resty import PathDispatcher
from wsgiref.simple_server import make_server # Create the dispatcher and register functions
dispatcher = PathDispatcher()
dispatcher.register('GET', '/hello', hello_world)
dispatcher.register('GET', '/localtime', localtime) # Launch a basic server
httpd = make_server('', 8080, dispatcher)
print('Serving on port 8080...')
httpd.serve_forever()

要测试下这个服务器,你可以使用一个浏览器或urllib 和它交互。例如:

 from urllib.request import urlopen

 req = urlopen('http://localhost:8080/hello?name=Guido')
resp = req.read().decode('utf-8')
print(resp) req1 = urlopen('http://localhost:8080/localtime')
resp1 = req1.read().decode('utf-8')
print(resp1)

通过XML-RPC 实现简单的远程调用

问题:

  你想找到一个简单的方式去执行运行在远程机器上面的Python 程序中的函数或方法

解决方案: 

  实现一个远程方法调用的最简单方式是使用XML-RPC。下面我们演示一下一个实现了键-值存储功能的简单服务器:

 from xmlrpc.server import SimpleXMLRPCServer

 class KeyValueServer:
_rpc_methods_ = ['get', 'set', 'delete', 'exists', 'keys'] def __init__(self, address):
self._data = {}
self._serv = SimpleXMLRPCServer(address, allow_none=True)
for name in self._rpc_methods_:
self._serv.register_function(getattr(self, name)) def get(self, name):
return self._data[name] def set(self, name, value):
self._serv[name] = value def delete(self, name):
del self._data[name] def exists(self, name):
return name in self._data def keys(self):
return list(self._data) def serve_forever(self):
self._serv.serve_forever() if __name__ == "__main__":
kvserver = KeyValueServer(('', 30000))
kvserver.serve_forever()

下面我们从一个客户端机器上面来访问服务器:

 from xmlrpc.client import  ServerProxy

 s = ServerProxy('http://localhost:30000', allow_none=True)
s.set('foo', 'bar')
s.get('foo')

简单的客户端认证

问题:

  你想在分布式系统中实现一个简单的客户端连接认证功能,又不想像SSL 那样的复杂

解决方案:

  可以利用hmac 模块实现一个连接握手,从而实现一个简单而高效的认证过程。下面是代码示例:

 import hmac
import os def client_authenticate(connection, secret_key): message = connection.recv(32)
hash = hmac.new(secret_key, message)
digest = hash.digest()
connection.send(digest) def server_authenticate(connection, secret_key): message = os.urandom(32)
connection.send(message)
hash = hmac.new(secret_key, message)
digest = hash.digest()
response = connection.recv(len(digest)) return hmac.compare_digest(digest, response)

  基本原理是当连接建立后,服务器给客户端发送一个随机的字节消息(这里例子中使用了os.urandom() 返回值)。客户端和服务器同时利用hmac 和一个只有双方知道的密钥来计算出一个加密哈希值。然后客户端将它计算出的摘要发送给服务器,服务器通过比较这个值和自己计算的是否一致来决定接受或拒绝连接。摘要的比较需要使用hmac.compare digest() 函数。使用这个函数可以避免遭到时间分析攻击,不要用简单的比较操作符(==)。为了使用这些函数,你需要将它集成到已有的网络或消息代码中。例如,对于sockets,服务器代码应该类似下面:

 import hmac
import os def client_authenticate(connection, secret_key): message = connection.recv(32)
hash = hmac.new(secret_key, message)
digest = hash.digest()
connection.send(digest) def server_authenticate(connection, secret_key): message = os.urandom(32)
connection.send(message)
hash = hmac.new(secret_key, message)
digest = hash.digest()
response = connection.recv(len(digest)) return hmac.compare_digest(digest, response) #服务端 from socket import socket, AF_INET, SOCK_STREAM secret_key = b'golang fight python' def echo_handler(client_sock):
if not server_authenticate(client_sock, secret_key):
client_sock.close()
return while True:
msg = client_sock.recv(8192)
if not msg:
break
client_sock.send(msg) def echo_server(address):
s = socket(AF_INET, SOCK_STREAM)
s.bind(address)
s.listen(5) while True:
c, a = s.accept()
echo_handler(c) echo_server(('', 30050)) #客户端 from socket import socket, AF_INET, SOCK_STREAM secret_key = b'golang fight python' s = socket(AF_INET, SOCK_STREAM)
s.connect(('', 30050))
client_authenticate(s, secret_key)
s.send(b'Hello World')
resp = s.recv(8192)

在网络服务中加入SSL

问题:

  你想实现一个基于sockets 的网络服务,客户端和服务器通过SSL 协议认证并加密传输的数据

解决方案:

  ssl 模块能为底层socket 连接添加SSL 的支持。ssl.wrap socket() 函数接受一个已存在的socket 作为参数并使用SSL 层来包装它。例如,下面是一个简单的应答服务器,能在服务器端为所有客户端连接做认证

#服务端

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(5) s_ssl = ssl.wrap_socket(s, keyfile=KEYFILE, certfile=CERTFILE, server_side=True) while True:
try:
c, a = s_ssl.accept()
print('Got connection', c, a)
except Exception as e:
print('{}: {}'.format(e.__class__.__name__, e)) echo_server(('', 20010)) #客户端 from socket import socket, AF_INET, SOCK_STREAM
import ssl s = socket(AF_INET, SOCK_STREAM)
s_ssl = ssl.wrap_socket(s, ca_certs='server_cert.pem')
s_ssl.connect(('', 20010))
s_ssl.send(b'Hello world')
data = s_ssl.recv(8192)
print(data)
















网络与WEB 编程的更多相关文章

  1. 网络和Web编程

    一.以客户端的形式同HTTP服务交互 (1)使用urllib.request模块发送HTTP GET请求 from urllib import request,parse url = 'http:// ...

  2. 物联网网络编程、Web编程综述

    本文是基于嵌入式物联网研发工程师的视觉对网络编程和web编程进行阐述.对于专注J2EE后端服务开发的童鞋们来说,这篇文章可能稍显简单.但是网络编程和web编程对于绝大部分嵌入式物联网工程师来说是一块真 ...

  3. 物联网网络编程和web编程

    本文是基于嵌入式物联网研发project师的视觉对网络编程和web编程进行阐述. 对于专注J2EE后端服务开发的同学来说,这篇文章可能略微简单.可是网络编程和web编程对于绝大部分嵌入式物联网proj ...

  4. Web编程基础--HTML、CSS、JavaScript 学习之课程作业“仿360极速浏览器新标签页”

    Web编程基础--HTML.CSS.JavaScript 学习之课程作业"仿360极速浏览器新标签页" 背景: 作为一个中专网站建设出身,之前总是做静态的HTML+CSS+DIV没 ...

  5. python web编程-概念预热篇

    互联网正在引发一场革命??不喜欢看概念的跳过,注意这里仅仅是一些从python核心编程一书的摘抄 这正是最激动人心的一部分了,web编程 Web 客户端和服务器端交互使用的“语言”,Web 交互的标准 ...

  6. 全部编程皆为Web编程

    原文作者:Jeff Atwood   原文链接:http://blog.codinghorror.com/all-programming-is-web-programming Michael Brau ...

  7. 网络对抗——web基础

    网络对抗--web基础 实践内容 (1)Web前端HTML (2)Web前端javascipt (3)Web后端:MySQL基础:正常安装.启动MySQL,建库.创建用户.修改密码.建表 (4)Web ...

  8. 20145306 张文锦 网络攻防 web基础

    20145306 网络攻防 web基础 实验内容 WebServer:掌握Apache启停配置修改(如监听端口)前端编程:熟悉HTML+JavaScript.了解表单的概念编写不同的HTML网页,放入 ...

  9. Python 四大主流 Web 编程框架

    Python 四大主流 Web 编程框架 目前Python的网络编程框架已经多达几十个,逐个学习它们显然不现实.但这些框架在系统架构和运行环境中有很多共通之处,本文带领读者学习基于Python网络框架 ...

随机推荐

  1. 我的学习之路_第三十章_servlet

    servlet:小服务程序 servlet是JavaWeb体系中的三大核心(servlet/Filter/Listener)之一,而且是最主要的那个. 作用:接受请求,处理请求,做出响应 继承体系:s ...

  2. WinForm中AssemblyInfo.cs文件参数具体讲解

    在.NET中有一个配置文件AssemblyInfo.cs主要用来设定生成的有关程序集的常规信息dll文件的一些参数,下面是默认的AssemblyInfo.cs文件的内容具体介绍 //是否符合公共语言规 ...

  3. js&jquery跨域详解jsonp,jquery并发大量请求丢失回调bug

    URL  说明 是否允许通信 http://www.a.com/a.js http://www.a.com/b.js 同一域名下 允许 http://www.a.com/lab/a.js http:/ ...

  4. RecyclerView线性分割线

    由于recyclerview默认是没有分割线的,需要显示分割线的话,可以在布局里添加一条有背景色的View标签,或者通过ItemDecoration来实现,本文以后者为例. ItemDecoratio ...

  5. 启动Tomcat提示:指定的服务未安装

    新下载的Tomcat7.0 解压缩完了运行tomcat7.exe屏幕一闪就没了 运行tomcat7w.exe弹出个筐 指定的服务并未以已安装的服务存在 Unable to open the Servi ...

  6. 输入3个数a,b,c,按大小顺序输出

    题目:输入3个数a,b,c,按大小顺序输出 package com.li.FiftyAlgorthm; import java.util.Scanner; /** * 题目:输入3个数a,b,c,按大 ...

  7. vue vuex 提交 this.$store.commit({type: 'setSelectPro', selectPro: this.productId});

    1.store.commit({'type':'mutation','parameter':'value'}); store.dispatch('action'); 2.获取state保存的值 sto ...

  8. Centos 7部署大众点评CAT(一)——单服务器部署

    前一篇拙作上传的时间已经过去2个月了,中间并不是闲着...主要是忙着学习各种组件的安装,写了几篇安装心得存在硬盘里. 最近尝试了点评开源的CAT监控平台的安装,并且希望能够引入到工作中.在部署实践的过 ...

  9. luogu P3398 仓鼠找sugar [LCA]

    题目描述 小仓鼠的和他的基(mei)友(zi)sugar住在地下洞穴中,每个节点的编号为1~n.地下洞穴是一个树形结构.这一天小仓鼠打算从从他的卧室(a)到餐厅(b),而他的基友同时要从他的卧室(c) ...

  10. 学会用requirejs,5分钟足矣

    学会用requirejs,5分钟足矣 据说公司的项目较多的用到requirejs管理依赖,所以大熊同学挤出了5分钟休息时间学习了一下,现在分享一下.如果你想了解requirejs的实现原理,请绕道!如 ...