Socket网络编程

Socket网络编程

客户端与服务端

协议栈

protocol stack复杂的网络建立在简单网络服务的基础之上

应用层

  • 用API获取一个JSON文档

    import requests
    
    def geocode(address):
        parameter = {'address': address, 'sensor': 'false'}
        base = 'http://maps.googleapis.com/maps/api/geocode/json'
        respose = requests.get(base, params=parameter)
        ans = respose.json()
        print(ans['result'][0]['geometry']['location'])
    

编码与解码

if __name__=='__main__':

    # decode
    in_byte = b'\xff\xfe4\x001\x003\x00 \x00i\x00s\x00 \x00i\x00n\x00.\x00'
    in_char = in_byte.decode('utf-16')
    print(repr(in_char))  # repr()将对象转化为解释器可读取的对象

    # encode
    out_char = 'We copy you down. Eagle.\n'
    out_byte = out_char.encode('utf-8')
    with open('eagle.txt', 'wb') as f:
        f.write(out_byte)

IP地址

  • 主机名转IP(DNS)

    import socket
    
    if __name__=='__main__':
        hostname = 'www.baidu.com'
        addr = socket.gethostbyname(hostname)
        print("The IP address of {} is {}".format(hostname, addr))
    

RFC文档

RFC文档


UDP

多路复用

multiplexing

  • 需要为两台主机间传送大量数据包打上标签, 这样就可以将表示网页的数据包和利用电子邮件的数据包区分开来, 而这两种数据包也可以与该机器正在进行其他网络会话使用的数据包分隔开。

可靠传输

reliable transport

  • 对两台主机间独立传输的数据包流发生的任何错误, 都需要进行修复。 而丢失的数据包也需要进行重传, 直到将其成功发送至目的地址。 另外, 如果数据包达到时顺序错乱, 则要将这些数据包重组回正确的顺序。 最后, 要丢弃重复的数据包i, 以保证数据流中的信息没有冗余。 提供这些保证的特性叫做可靠传输

查询域名服务器端口号

>>>import socket
>>>socket.getservbyname('domain')
# 53

套接字

  • 底层, python标准库对兼容POSIX操作系统网络操作的底层系统调用进行了封装, 并为所有普通的原始调用提供了一个简单的基于对象的接口。
  • 每当调用socket.socket对象的方法请求使用该套接字的系统调用时, 该对象都会自动使用内部维护的套接字整数标识符

混杂客户端

promiscuous

  • 不考虑地址是否正确, 接受并处理所有收到的数据包的网络监听客户端

  • 解决方案

    • 设计唯一标识符

    • 检查数据包的地址与请求数据包的地址是否相同

    • == 或者 connect()
      

关闭挂起服务器

ctrl + z # 暂停服务器
ctrl + c # 暂停服务器

连接UDP套接字

UDP套接字名是IP接口和UDP端口号组成的二元组

  • 可以使用sendto()指定每个数据包的目标地址, 然后使用recvfrom()接受相应
  • 也可以在创建了套接字以后使用connect()将其与目标地址连接, 然后使用send()recv()进行通信(只支持同时与一台服务器交互的情况)

套接字选项

选项 说明
SO_BROADCAST 允许发送并接受UDP广播数据包
SO_DONTROUTE 数据包不经由网关发送
SO_TYPE 返回套接字类型
  • UDP–>SOCK_DGRAM
  • TCP–>SOCK_STREAM

使用UDP情况

  • 实现一个已经使用了UDP的协议
  • 设计对时间要求十分苛刻的媒体流
  • 设计适用LAN子网多播的应用程序
  • 客户端与服务器之间不存在长时间连接的情况下

TCP

TCP套接字的含义

  • 被动套接字( passive socket), 又称监听套接字( listening packet )
    • 维护了IP地址端口号
  • 主动套接字( active socket), 又称连接套接字( connected socket )
    • 绑定特定IP地址特定端口号

唯一标识主动套接字

local_ip
local_port

remote_ip
remote_port   # remote 远程

connect()

  • UDP的connect()调用只是对绑定套接字进行了设置, 设置了后续send()recv()调用所要使用的默认远程地址, 不会导致任何错误
  • TCP的connect()是真实的网络操作, 会在要通信的客户端和服务器之间进行三次握手, 这意味着connect()是有可能失败的

send()

网络栈可能碰到的三种情况

  • 要发送的数据可能立即被本地系统的网络栈接受
    • send()会立即返回。
  • 网卡正忙, 套接字发送缓冲区已满, 系统不愿为其分配更多空间
    • send()默认情况下会直接阻塞进程, 暂停应用进程
  • 介于上面两种情况之间, 发送缓冲区几乎满, 但尚有空间
    • send()会立即返回从数据串开始处起已经被接受的字节数, 剩余数据尚未被处理

最后一种情况的存在, 调用send()需要检查返回值, 还需要在一个循环内进行send()调用

# sendall()方法 该方法用C语言实现
# 因为在循环中释放了GIL锁, 因此其他线程在数据发送完成前不会竞争资源
bytes_sent = 0
while bytes_sent < len(message):
    message_remaining = message[bytes_sent:]
    bytes_sent += s.send(message_remaining)

recv()

没有相应的库封装

  • 如果没有任何数据, 那么recv()会阻塞程序, 直到有数据传到
  • 如果接受缓冲区的内的数据已经完整就绪, 那么recv()会接受所需的所有数据
  • 如果接受缓冲区里只有recv()需要返回的部分数据, 那么, 即使这并非所需的全部内容, 也会立即返回缓冲区中已有的数据。

流套接字类型

  • 监听套接字(listening packet):服务器通过监听套接字设定的某个端口用于监听连接请求
  • 连接套接字(connected socket):用于表示服务器与某一特定客户端正在进行的会话

bind()

  • 运行bind()来声明一个特定的端口

listen()

  • 希望套接字接听,此时才真正决定了程序要作为服务器

getsockname()

  • 同时适用于监听套接字和连接套接字, 可以用于获取套接字正在使用的绑定TCP端口

getpeername()

  • 获取连接套接字对应的客户端地址

SO_REUSEADDR

  • 指明应用程序能够使用一些网络客户端之间的,连接正在关闭的端口

阻塞

如果一个协议没有严格要求服务器在客户端 请求发送完成后才读取完整的请求, 然后再返回完整的相应,就会,发送阻塞

解决办法: 手动关闭程序,重新进行编写, 以改进程序的设计

  • 大量数据避免死锁的两种方案
    • 客户端和服务器可以通过套接字选项将阻塞关闭
    • 程序可以使用某种技术同时处理来自多个输入的数据—>多线程

shutdown()

关闭双向套接字中任一方向的通信连接

  • SHUT_WR: 调用方将不再向套接字写入数据
  • SHUT_RD: 关闭接受方向的套接字流
  • SHUT_RDWR: 将套接字两个方向的通信都关闭

makefile()

返回一个Python文件对象,该对象实际上会在底层调用recv()和send()

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
hasattr(sock, 'read')
# False
f = sock.makefile()
hasattr(f, ,'read')
# True

套接字名与DNS

套接字方法

  • mysocket.accept():由TCP流的监听套接字调用。
    • 第一项是新建的连接至远程地址的套接字
    • 第二项是已连接的远程地址
  • mysocket.bind(address):将特定的本地地址分配给套接字
  • mysocket.connect(address):通过套接字发送的数据会被传输至特定的远程地址
  • mysocket.getpeername:返回了与套接字连接的远程地址
  • mysocket.getsockname:返回了套接字自身的本地端点地址
  • mysocket.recvfrom(...):用于UDP, 返回一个二元组
    • 数据的字符
    • 数据的来源地址
  • mysocket.sendto(data, address):未连接的UDP端口使用该方法向特定远程地址发送数据

套接字的五个坐标

创建及部署套接字的步骤:

import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('localhost, 1060'))

  • 地址族(address family)—>AF_INET
  • 套接字类型(socket type)—>SOCK_DGRAM/STREAM
  • 协议(protocol)—IPPROTO_TCP/UDP
  • IP地址
  • 端口号

现代地址解析

  • getaddrinfo:将用户指定的主机名和端口号转换为可供套接字方法使用的地址

    from pprint import pprint
    infolist = socket.getaddrinfo('gatech.edu', 'www')
    pprint(infolist)
    
    
    
  • 为服务器绑定端口

    from  socket import getaddrinfo
    getaddrinfo(None, 'smtp', 0, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
    
    
  • 连接服务

    getaddrinfo('ftp.kernel.org', 'ftp', 0, socket.SOCK_STREAM, 0,
               socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
    
    
  • 请求规范的主机名

    getaddrinfo('iana.org', 'www', 0, socket.SOCK_STREAM, 0,
               socket.AI_ADDRCONFIG | socket.AI_V4MAPPED | socket.AI_CANONNAME)
    
    
    getaddrinfo()标记 说明
    AI_ALL IPv4地址重写为与之对应的IPv6地址
    AI_NUMERICHOST 将主机名字符串作为字面IPv4或IPv6地址来解析
    AI_NUMERICSERV 禁用’www’符号形式端口名, 采用’80’端口号

DNS协议

whois [域名]
# sudo apt-get install whois 下载


网络数据与错误

计算机的内存芯片和网卡都支持将字节作为通用传输单元

字节

0b1100010
# 98
0b1100010 == 0o142 == 98 == 0x62
# True

字符串

ASCII: American Standard Code for Information Interchange

for i in range(32, 128, 32):
    print(' '.join(chr(j) for j in range(i ,i+32)))
#  ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ 

编码(Encoding)

将真正的Unicode字符串转换为字节字符串

解码(Decoding)

将字节字符串转换为真正的Unicode字符

二进制数

  • 大端法(big-endian)

  • 小端法(little-endian)

    import struct
    struct.pack(', 4253) # 小
    # b'\x9d\x10\x00\x00'
    struct.pack('>i', 4253) # 大
    # b'\x00\x00\x10\x9d'
    struct.unpack('>i', b'\x00\x00\x10\x9d')
    # (4253,)
    
    

shutdown()

  • 在客户端和服务器的套接字上都调用shutdown(),看上去对称, 也能提高冗余性

pickle()

  • 将文本命令与数据混合使用

    import pickle
    pickle.dumps([5, 6, 7])
    # b'\x80\x03]q\x00(K\x05K\x06K\x07e.'
    
    

zlib()

>>> import zlib
>>> data = zlib.compress(b'Python') + b'.' + zlib.compress(b'zlib') + b'.'
>>> data
b'x\x9c\x0b\xa8,\xc9\xc8\xcf\x03\x00\x08\x97\x02\x83.x\x9c\xab\xca\xc9L\x02\x00\x04d\x01\xb2.'
>>> len(data)
28


网络异常

  • OSError: socket模板的可能抛出的主要错误
  • socket.gaierror: 该异常在getaddrinfo无法找到提供的名称或服务时被抛出
  • socket.timeout: 有时我们会决定为套接字设定超时参数, 而不希望永远等待

捕捉与报告网络异常

  • granular: 针对每个网络调用

  • blanket: 特定的多个连续网络操作结合起来

    import sys
    ...
    try:
        deliver_updated_keyfiles(...)
    except (socket.error, socket.gaierror) as e:
        print("cannot deliver remote keyfiles: {}".format(e), file=sys.stderr)
        exit(1)
    
    except:
        FatalError("cannot send replies: {}".format(e))
    
    

TLS/LLS

TLS: Transport Layer Security 传输层安全协议

LLS: Secure Sockets Layer 安全套接层

TLS无法保护的信息

  • 本机与远程主机的地址都是可见的, 地址信息在每个数据包的IP头信息中以纯文本的形式表示
  • 客户端与服务器的端口号同样在每个TCP头信息中可见
  • 客户端为了获取服务器的IP地址, 可能会先进行DNS查询。该查询在通过网络发送时也是可见的

证书:certificate CA列表

公钥:public key

签名:signature CA为证书加上的数学标记

TLS特别关注的路两种类型的字段: notBefore日期/notAfter日期 表示有效期

临时证书


临时:intermedia

  • 某些机构希望他们的服务器只使用有效期只有几天或几个星期的短期证书
  • CA会颁发临时证书,有效期较长,CA会保存私钥,并使用私钥作为用户可见的证书签名, 这一做法形成了一条证书链(certificate chain)或是信任链(chain of trust)

TLS负载移除

  • 方案一: 使用一个单独的守护进程或服务提供TSL支持
  • 方案二: 直接在Python编写的服务器代码中使用TLS功能的OpenSSL库

方案一更易于升级与修改

默认上下文

  • 第一步: 创建一个TLS上下文context对象, 该对象保存了我们对证书认证与加密算法选择的偏好设置
  • 第二步: 调用上下文对象的wrap _socket方法, 表示让OpenSSL库负责控制我们的TCP连接, 然后与通信对方交换必要握手信息, 并建立加密连接
  • 第三步: 使用wrap_socket()调用返回ssl_sock对象, 进行所以后续通信

ssl.create_default_context()常见上下文对象的目的, 可将第一个参数设置成Purpose.SERVER_AUTH,表示该上下文对象为客户端所使用, 用于验证连接服务器

cafile 脚本验证远程证书时信任的证书机构


套接字包装的变体

ssl.wrap_socket4个缺点

  • 效率较低
  • 无法提供真正的上下文的灵活性
  • 向下兼容导致允许使用较弱的加密算法
  • 没有主机名检查, 无法提供真正的安区性

完美前向安全

  • 手动选择加密算法

支持TLS的协议

协议 说明
http.client 构造HTTPSConnection对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext
smtplib 构造SMTP_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext
poplib 构造POP3_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext
imaplib 构造IMAP_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext
ftplib 构造FTP_TLS对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext
nntplib 构造NNTP_SSL对象时,把构造器中的context关键字设置为一个自己配置过的SSLContext

了解TLS细节

  • getpeercert():返回一个Python字典, 包含了证书选出的字段
  • cipher():返回OpenSSL与通信对象的TLS实现最终协商确认
  • compression():返回正在使用的压缩算法名称或是Python的单例对象None

服务器架构

部署

业界广泛使用的方法: 在服务器前端配置一个负载均衡器(load balancer),客户端直接连接到负载均衡器, 然后由负载均衡器将连接请求转发至实际的服务器

部署(deployment)

  • 定义:使用某种方式在物理或虚拟机器上运行服务器代码
  • 分类
    • 通过两次fork()创建一个Unix守护进程, 安排进行系统级的日志操作, 支持配置文件以及提供启动/关闭/重启的相关机制
    • “十二要素应用(The Twelve-Factor App)”, 提倡只实现服务器程序必需功能的最小集合

Paas

Platform as a Service “平台即服务”

  • 将应用程序的几十个甚至几百个副本配置在一个公共域名和TCP负载均衡器下, 然后将所有输出日志聚集起来分析
  • Docker
  • supervisord工具

Trace模块

测试每一行代码花费的时间

python3 -m trace --tg --ignore-dir=/usr filename.py ""


多线程与多进程服务器

  • 优点: 直接使用单线程服务器的代码, 创建多个线程运行它的多份副本
  • 缺点: 服务器能够同时通信的客户端数量受操作系统并发机制规模的限制

accept()函数

每个线程都可以拥有服务器监听套接字的一个副本, 并运行自己的accept()函数。 操作系统会将每个新的客户端连接交由任何运行了accept()函数并处于等待的线程来处理。 如果所有线程都处于繁忙状态的话, 操作系统会将该连接置于队列中, 直到某个线程空闲为止


异步服务器

  • 实现原理:首先,网络栈提供了一个系统调用, 支持进程为等待整个客户端套接字列表中的套接字而阻塞, 而不是等待一个单独的客户端套接字
  • 特点:可以将一个套接字配置为非阻塞套接字
  • 非阻塞套接字在进行send()recv()调用时永远不会阻塞调用进程
  • 操作系统支持
    • POSIXselect()调用—>效率低
    • Linuxpoll()调用
    • BSDd的epoll()调用

asyncio框架

回调风格

  • 涉及文件系统上的文件读取或是对数据库等后端服务的查询, 代码要处理两个方向上的数据传输
  • asyncio框架既会负责服务器与客户端之间的数据发送和接收,也会负责服务器与文件系统或数据库之间的数据发送和接收。
  • 此时可能会在回调方法中构造一些futures对象, 用于更深一层的回调, 以供数据库或文件系统的I/O最终完成时触发

协程风格 (coroutine)

  • 协程是一个函数, 它在进行I/O操作时不会阻塞, 而是会暂停, 并将控制权转移回调用方
  • Python语言支持协程的一种标准形式就是生成器(generator)——在内部包含一个或多个yield语句的函数

高性能解决方案

  • 首先使用异步的回调对象或协程来编写服务, 并通过某个异步框架来启动服务。
  • 然后再回过头来配置一些运行服务器的操作系统, 检查操作系统的CPU内核数目
  • 有多少CPU内核, 就启动多少个事件循环

inetd守护进程

在该服务的inted.conf配置文件中将第4个字段设为nowait

1060 stream tcp nowait brandon /usr/bin/python3 /usr/bin/python3 filenam.py


缓存与消息队列

Memcached

内存缓存守护进程(memory cache deamon), Memcached将安装它的服务器上的空闲RAM与一个很大的近期最少使用(LRU)的缓存结合使用

使用步骤
  • 在每台空闲内存的服务器上都运行一个Memcached守护进程
  • 将所有Memcached守护进程的IP地址与端口号列出, 并将该列表发送给所有将要使用Memcached的客户端
  • 客户端程序现在可以访问一个组织级的速度极快的键值缓存
安装Memcached
pip install python3-memcached

简单的交互
import memcached
mc = memcached.Client(["127.0.0.1:11211"])
mc.set('user:19', "Simple is better than complex")
# True
mc.get('user:19')
# "Simple is better than complex"

解决脏数据的3个办法
  • Memcached允许我们为缓存中的每一项设置一个过期时间, 到达这个时间时, Memcached会负责悄悄将这些项丢掉
  • 如果能够建立从信息标识到缓存中包含该标识的键的映射, 那么就可以在脏数据出现后主动移除这些缓存项
  • 当缓存中的记录不可用时, 我们可以重写并使用新内容代替该条记录, 而不是简单地移除该记录
散列与分区

当Memcached客户端得到包含了多个Memcached实例的列表时, 会根据每个键的字符串值的散列值对Memcached数据库进行分区(shard), 由计算出的散列值决定用Memcached集群中的哪台服务器来存储特定的记录

alpha
 server0 36355  0.36
 server1 23667  0.23
 server2 29893  0.29
 server3 12486  0.12

hash
 server0 25750  0.25
 server1 25587  0.25
 server2 25657  0.25
 server3 25407  0.25

md5
 server0 25626  0.25
 server1 25600  0.25
 server2 25496  0.25
 server3 25679  0.25

消息队列

消息(message)

消息队列协议允许我们发送可靠的数据块, 注意不是数据报(datagram)

  • 可靠自动传输:一条消息要么被完好无损的传输至目的地, 要么完全不传输。
  • 封帧:使用消息队列的客户端从来都不需要在接收到完整的消息之前一直在循环中不断调用recv()这样的函数
  • 应用场景
    • 使用自己的电子邮箱地址在一个网站注册新账号时
    • 作为自定义远程过程调用(RPC, Remote Procedure Call)
    • 经常需要将一些大容量的事件数据作为小型的有效消息流集中存储在消息队列中并进行分析
  • 特点:具有混合安排并匹配所有客户端与服务器或发布者与订阅者进程的能力
  • 拓扑结构
    • 管道(pipeline):生产者创建消息, 然后将消息提交至队列中, 消费者从队列中接受消息
    • 发布者-订阅者(publisher-subscriber)或扇出(fanout):订阅者设置一个过滤器, 通过某种特定的格式限定有兴趣的消息范围
    • 请求-响应(request-reply)模式:消息需要往返, 将能够在某台机器上大量运行的多个轻量级线程与数据库服务端或文件服务器连接起来的一种很好的方式
  • AMQP协议
    • ZMQ(Zero Message Queue):提供智能消息机制的任务交给了每个消息客户端程序来完成, 而没有交给集中式服务器来处理

HTTP客户端

客户端库

pip install gunicorn httpbin requests
gunicorn httpbin:app

Requests和urllib

  • Requests支持gzipdeflate两种压缩格式的HTTP响应, 而urllib则不支持

HTTP消息组成

  • 第一行包含一个方法名和一个请求的文档名;在响应消息中, 第一行包含了返回码和描述信息
  • 第二部分包含零个或多个头信息, 每个头信息由一个名称/一个冒号/一个键组成
  • 第三部分是一个可选的消息体

封帧的方法

  • 提供一个Content-Length头,Content-Length的值是一个十进制整数, 表示消息体包含的字节数
  • 在头信息中指定Transfer-Encodin头, 并将其设置为chunked
  • 服务器可以指定Connection: close, 然后就能够随意发送任意大小的消息体

方法

GET

请求服务器将请求路径指向的文档作为响应发送回浏览器

不包含消息体, 附加到请求路径后面的任何参数都至只能修改返回后的文档,而不能修改服务器上的数据

POST

提交新文档/评论以及数据库行

两次运行同一个POST会在服务器上进行两次相同的操作,因此既不能将POST操作的结果存入缓存以提高后续重复操作的速度,也不能在没有接收到响应的时候自动重试POST


其余的方法简单分为两大类

1、类似于GET

2、类似于POST

OPTIONS

请求与给定路径匹配的HTTP头的值

HEAD

请求服务器做好一切发送资源的准备, 但只发送头信息


PUT(幂等)

向服务器发送一个新的文档, 该文档上传后就会存在于指定的路径上

DELETE(幂等)

请求服务器删除指定的路径及所有存在与该路径下内容


TRACE

用于调试

CONNECT

将所使用的协议从HTTP切换为其他协议


状态码

状态码 说明
200~300 成功
300~400 重定向,不包含消息体
400~500 客户端的请求无法被识别或非法,包含消息体
500~600 服务器错误导致了一些意外错误,包含消息体
200 OK

请求成功。如果是POST操作, 表明已经对服务器产生了预期的影响

301 Moved Permanently

尽管路径合法,但是该路径已经不是所请求资源目前的官方路径了。

客户端若要获取响应,应请求Location头中给出的URL。

如果客户端希望将URL存入缓存, 则所有后续的请求都会直接忽略旧URL, 直接转向新URL

303 See Other

通过某个路径请求资源时, 客户端可以通过使用GET方法对响应信息的Location头中给出的URL进行请求, 以获取响应结果, 但是对该资源的后续请求仍然需要通过当前请求路径来完成

任何使用POST正确提交的表单都应该返回303状态码, 这样就能通过安全/幂等GET方法获取客户端实际看到的页面了

304 Not Modified

不需要在响应中包含文档内容, 原因在于请求头指出了客户端已经在缓存中存储了所请求文档的最新版本

307 Temporary Redirect

无论客户端使用GET或POST方法发起了什么样的请求,都需要使用响应的Location头中给出的另一个URL重新发送请求

400 Bad Request

这不是一个合法的HTTP请求

403 Forbidden

客户端没有在请求中向服务器提供正确的密码、cookie或其他验证数据来证明客户端有访问服务器的权限

404 Not Found

路径没有指向一个已经存在的资源

405 Method Not Allowed

服务器能够识别方法和路径,但是该方法无法应用于该路径

500 Server Error

服务器希望完成请求,但是由于某些内部错误,暂时无法请求

501 Not Implemented

服务器无法识别请求中给出的HTTP方法

502 Bad Gateway

请求的服务器是一个网关或代理,它无法连接到真正为该请求路径提供响应的服务器


自动重定向

>>> import requests
>>> r = requests.get('http://httpbin.org/status/301')
>>> (r.status_code, r.url)
(200, 'http://httpbin.org/get')
>>> r.history
[<Response [301]>, <Response [302]>]

关闭重定向

>>>r = requests.get('http://httpbin.org/status/301',
                    allow_redirects=False)
>>> r.raise_for_status()
>>> (r.status_code, r.url, r.headers['Location'])
(301, 'http://httpbin.org/status/301', '/redirect/1')

自动补齐/删除前缀

>>> r = requests.get('https://baidu.com/')
>>> r.url
'http://www.baidu.com/'
>>> r = requests.get('http://www.github.com/')
>>> r.url
'https://github.com/'

cookie

cookie是一个键值对。任何从服务器发送至客户端的成功响应中都可以传输cookie


HTTP服务器

WSGI

Web服务器网关接口(Web Server Gateway)

_call_()

  • 第一个参数是environ,用于接收一个字典,字典中提供的键值对是旧式的CGI环境变量集合的扩展
  • 第二个参数是start_response(),本身也可以被调用,WSGI应用程序通过该方法来声明响应头信息
  • 被调用后,app函数可以开始生成字符串,也可以返回一个可迭代对象

四种设计模式

  • 运行一个使用python编写的服务器,服务器代码中可以直接调用WSGI接口
  • 配置mod_wsgi并运行Apache,在一个独立的WSGIDaemonProcess中运行Python代码,由mod_wsgi启动守护进程
  • 在后端运行一个类似于GunicornPythonHTTP服务器,然后在前端运行一个既能返回静态文件,又能对Python编写的动态资源服务进行反向代理的Web服务器(Apache/nginx)
  • 在最前端运行一个纯粹的反向代理(如Varnish),在该反向代理后端运行Apche或nginx,在最后端运行Python编写的HTTP服务器

基于CPython的3个运行时特性,即解释器占用内存大/解释器运行慢/全局解释器锁(GIL)禁止多个线程同时运行Python字节码


PaaS

平台即服务(Platform as a Service)提供商,Docker支持用户在本地的Linux机器上运行Heroku风格的容器


REST

Representational State Transfer 表述性状态转移

四个接口规范

  • 使用URL来标识资源
  • 通过操作资源的俄表述形式来操作资源
  • 消息具备自描述性
  • 超媒体即应用状态引擎

WebOb

WebOb的Response类提供了content_typecharset两个独立的属性, 允许用户将Content-Type头信息的两部分(text/plain; charset=utf-8)看作两个独立的值进行处理


万维网

解析与构造URL

>>> from urllib.parse import urlsplit
>>> u = urlsplit('https://www.google.com/search?q=apod&btnI=yes')
>>> tuple(u)
('https', 'www.google.com', '/search', 'q=apod&btnI=yes', '')
>>> u.scheme
'https'
>>> u.netloc
'www.google.com'
>>> u.path
'/search'
>>> u.query
'q=apod&btnI=yes'
>>> u.fragment
''


网络位置(network location)
>>> u = urlsplit('https://brandon:atigdng@localhost:8000/')
>>> u.netloc
'brandon:atigdng@localhost:8000'
>>> u.username
'brandon'
>>> u.password
'atigdng'
>>> u.hostname
'localhost'
>>> u.port
8000


相对URL
>>> from urllib.parse import urljoin
>>> base = 'http://tools.itef.org/html/rfc3986'
>>> urljoin(base, 'rfc7320')
'http://tools.itef.org/html/rfc7320'
>>> urljoin(base, '.')
'http://tools.itef.org/html/'
>>> urljoin(base, '..')
'http://tools.itef.org/'
>>> urljoin(base, '/dailydose/')
'http://tools.itef.org/dailydose/'
>>> urljoin(base, '?version=1.0')
'http://tools.itef.org/html/rfc3986?version=1.0'
>>> urljoin(base, '#section-5.4')
'http://tools.itef.org/html/rfc3986#section-5.4'
# 斜杠问题
>>> urljoin('http://tools.itef.org/html/rfc3986', 'rfc7320')
'http://tools.itef.org/html/rfc7320'
>>> urljoin('http://tools.itef.org/html/rfc3986/', 'rfc7320')
'http://tools.itef.org/html/rfc3986/rfc7320'
# 不确定HTTP(s)
>>> urljoin(base, '//www.google.com/search?q=apod')
'http://www.google.com/search?q=apod'

你可能感兴趣的:(笔记)