12. 网络编程的奇淫巧技
- 简单的网络交互
通过HTTP协议以客户端的方式访问多种服务,通常使用 urllib.request 模块就够了
# Base URL being accessed
url = ‘http://httpbin.org/get’ # Dictionary of query parameters (if any) parms = { ‘name1’ : ‘value1’, ‘name2’ : ‘value2′ } # Encode the query string querystring = parse.urlencode(parms) # Make a GET request and read the response u = request.urlopen(url+’?’ + querystring) resp = u.read() |
如果需要使用POST方法
from urllib import request, parse
# Base URL being accessed url = ‘http://httpbin.org/post’ # Dictionary of query parameters (if any) parms = { ‘name1’ : ‘value1’, ‘name2’ : ‘value2’ } # Encode the query string querystring = parse.urlencode(parms) # Make a POST request and read the response u = request.urlopen(url, querystring.encode(‘ascii’)) resp = u.read() |
如果需要提供一些自定义的HTTP头
from urllib import request, parse
… # Extra headers headers = { ‘User-agent’ : ‘none/ofyourbusiness’, ‘Spam’ : ‘Eggs’ } req = request.Request(url, querystring.encode(‘ascii’), headers=headers) # Make a request and read the response u = request.urlopen(req) resp = u.read() |
当然如果希望使用更为复杂的操作,可以使用requests 库
import requests
# Base URL being accessed url = ‘http://httpbin.org/post’ # Dictionary of query parameters (if any) parms = { ‘name1’ : ‘value1’, ‘name2’ : ‘value2’ } # Extra headers headers = { ‘User-agent’ : ‘none/ofyourbusiness’, ‘Spam’ : ‘Eggs’ } resp = requests.post(url, data=parms, headers=headers) # Decoded text returned by the request text = resp.text |
其中resp.text 带给我们的是以Unicode解码的响应文本。但是,如果去访问 resp.content ,就会得到原始的二进制数据。另一方面,如果访问 resp.json ,那么就会得到JSON格式的响应内容。
比如requests 库发起一个HEAD请求
import requests
resp = requests.head(‘http://www.python.org/index.html’) status = resp.status_code last_modified = resp.headers[‘last-modified’] content_type = resp.headers[‘content-type’] content_length = resp.headers[‘content-length’] |
基本认证登录
import requests
resp = requests.get(‘http://pypi.python.org/pypi?:action=login’, auth=(‘user’,’password’)) |
用requests上传内容
import requests
url = ‘http://httpbin.org/post’ files = { ‘file’: (‘data.csv’, open(‘data.csv’, ‘rb’)) } r = requests.post(url, files=files) |
Request还支持了OAuth这些认证
http://docs.python-requests.org/
- 创建简单的服务器
这里看下简单的服务器,利用TCP协议和客户端进行通信
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__’: serv = TCPServer((”, 20000), EchoHandler) serv.serve_forever() |
定义了一个特殊的处理类,实现了一个 handle() 方法
request 属性是客户端socket,client_address 有客户端地址
这种情况下,我们创建的服务器是单线程的,如果希望处理多个客户端
可以利用ForkingTCPServer 或者是 ThreadingTCPServer
或者使用线程池
if __name__ == ‘__main__’:
from threading import Thread NWORKERS = 16 serv = TCPServer((”, 20000), EchoHandler) for n in range(NWORKERS): t = Thread(target=serv.serve_forever) t.daemon = True t.start() serv.serve_forever() |
- IP_ADDRESS
将CIDR直接转换为IP 列表,可以为
>>> net = ipaddress.ip_network(‘123.45.67.64/27’)
>>> net IPv4Network(‘123.45.67.64/27’) >>> for a in net: … print(a) … 123.45.67.64 |
其本质是ipaddress.ip_address的数组
>>> a = ipaddress.ip_address(‘123.45.67.69’)
>>> a in net True |
而这个在实际使用中,经常需要配合str()使用
- 简单的REST接口
这里我们构建一个简单的REST接口通过网络远程控制或访问你的应用程序
利用WSGI标准(PEP 3333)这样的一个很小的库
# resty.py
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() |
整体接口如下
import cgi
def wsgi_app(environ, start_response): pass |
environ 属性是一个字典,包含了从web服务器如Apache[参考Internet RFC 3875]提供的CGI接口中获取的值,environ[‘REQUEST_METHOD’] 代表请求类型如GET、POST、HEAD等。 environ[‘PATH_INFO’] 表示被请求资源的路径。 调用 cgi.FieldStorage() 可以从请求中提取查询参数并将它们放入一个类字典对象中以便后面使用。
start_response 参数是一个为了初始化一个请求对象而必须被调用的函数。 第一个参数是返回的HTTP状态值,第二个参数是一个(名,值)元组列表,用来构建返回的HTTP头。
然后返回字符串序列
def wsgi_app(environ, start_response):
pass start_response(‘200 OK’, [(‘Content-type’, ‘text/plain’)]) resp = [] resp.append(b’Hello World\n’) resp.append(b’Goodbye!\n’) return resp 或者,你还可以使用 yield : def wsgi_app(environ, start_response): pass start_response(‘200 OK’, [(‘Content-type’, ‘text/plain’)]) yield b’Hello World\n’ yield b’Goodbye!\n’ |
- 远程调用
远程调用函数可以使用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._data[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() # Example if __name__ == ‘__main__’: kvserv = KeyValueServer((”, 15000)) kvserv.serve_forever() |
生成一个SimpleXMLRPCServer,然后利用register_function注册函数
之后就serve_forever()函数不断监听
对应的使用就好比
>>> s = ServerProxy(‘http://localhost:15000’, allow_none=True)
>>> s.set(‘foo’, ‘bar’) >>> s.set(‘spam’, [1, 2, 3]) >>> s.keys() [‘spam’, ‘foo’] >>> s.get(‘foo’) ‘bar’ >>> s.get(‘spam’) [1, 2, 3] >>> s.delete(‘spam’) >>> s.exists(‘spam’) False >>> |