这是一个系列文章,你可以点击以下链接查看这个系列的其他文章。
CUPNET v0.2 设计大纲 - 自上而下,抗封锁软件定义的混合云(混乱云)网络架构设计构思详解
CUPNET v0.2 基础实现(0.5) - 搭建实验网络环境
CUPNET v0.2 基础实现(1) - 自动生成并推送Core节点的Wireguard VPN配置文件
简介
上一篇大致讲解了一下CUPNET的设计大纲,这是一个期望统合云上云下资源的混合云架构,主要使用Wireguard进行节点互联。如果你没看过上一篇文章,可以点击这里的传送门查看,里面大概阐述了我是如何设想这个混合云的架构的。
这个方案只考虑网络的互通互联,不考虑其他上层架构如何通过网络进行一体化部署,一体化计算和存储的方案之后或许也会专门写一两篇文章。
这篇文章将会描述如何将Core节点通过VPN连接起来。
这个架构其实就是一个简单的包含RR的iBGP,并不是很复杂。这篇文章需要讲的是上图的棕色线段落。可以看到Core节点是Full mesh(全连接)结构的,虽然图上未直接标识,但每个Core节点均与其他的所有Core节点建立了BGP Section。RR节点连接到Core节点,并且也与Core节点建立BGP Section。
Core节点与CE之间建立BGP协议,CE之间通过Core连接,这样可以实现全网的互联,也可以很顺利的打通VXLAN路由,实现大二层。通过外部Control节点也可以实现自动化配置全部设施的网络架构。对于CE与Core之间的连接,以及Control节点对于以下节点的控制,有空再进行规划和实验。
公网机器的Wireguard配置
点对点的Wireguard尽可能将AllowedIPs设置成::/0,0.0.0.0/0
,然后将Table设置成off,以便于所有IP都可以通过Wireguard隧道。
搭建点对点的Wireguard需要以下的信息:
- 本机主机密钥
- 对端主机公钥
- 本机主机点对点IPv4
- 本机主机点对点IPv6
- 对端主机点对点IPv4
- 对端主机点对点IPv6
- 本机主机监听端口
- 本机主机公网地址
- 对端主机公网地址
- 对端主机监听端口
根据以上参数可以直接拼出以下配置文件。
[Interface]
PrivateKey = {本机主机密钥}
Table = off
PostUp = ip addr add {本机主机点对点IPv4}/32 peer {对端主机点对点IPv4}/32 dev %i; ip addr add {本机主机点对点IPv6} peer {对端主机点对点IPv6} dev %i
ListenPort = {本机主机监听端口}
MTU = {MTU大小}
[Peer]
PublicKey = {对端主机公钥} #由对端主机生成传输而来
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = {对端主机公网地址}:{对端主机监听端口}
对于每个Core,我们需要建立前往对端Core的VPN,若规范化Core的参数,使用Core建立一个查询表,每个Core项包含以下基本参数:
- 密钥
- 公网地址
Wireguard的公钥可以直接通过密钥计算得出,而其他的参数通过规范化算法(例如规范前缀或是通过散列算法)也可以自动得出:
- 点对点IPv4
- 点对点IPv6
- 对端应监听端口(既然是唯一用于标识己方的端口,若己方使用本端口)
其实可以通过一个主机ID和一个主密钥生成主机密钥,然后再通过主机密钥生成公钥。这样的话对于每个主机我们需要收集以下信息:
- 主机ID
- 公网地址
那么就要求对于控制节点或者说是控制端,我们需要生成一个主密钥,可以使用任何字符串,但尽量选择安全系数较高的密钥。
使用Python配合donna25519库可以实现以上信息的生成。
# 目标是生成密钥、点对点IPv4、点对点IPv6、监听端口与统合这些信息的配置文件以及写入文件
import binascii
from email.headerregistry import Address
import donna25519 as curve25519
import sys, getopt
import base64
import hashlib
import re
def main(argv):
MainKey = ""
CoreID = ""
Mode = "key"
SelfPublicIPAddress=""
RemotePublicIPAddress=""
PeerID = ""
SelfConfig = ""
RemoteConfig = ""
HelpInfo = 'Usage: \n\
geninfo.py -k <Mainkey> -c <Self ID> -u <Remote ID> -i <Self Public IP> -r <Remote Peer IP> -m <cfg/key/ipv4/ipv6/port/cfgfile> --sconf=<SelfConfigFile> --rconf=<RemoteConfigFile>\n\
or:\n\
geninfo.py --mainkey=<Mainkey> --id=<Self ID> --remote-id=<Remote ID> --ip=<Self Public IP> --remote-ip=<Remote Peer IP> --mode=<cfg/key/ipv4/ipv6/port/cfgfile> --sconf=<SelfConfigFile> --rconf=<RemoteConfigFile>'
try:
opts, args = getopt.getopt(argv,"hk:c:i:m:u:r:",["help","mainkey=","id=","ip=","mode=","remote-id=","remote-ip=","sconf=","rconf="])
except getopt.GetoptError:
print(HelpInfo)
return()
for opt, arg in opts:
if opt in ("-h", "--help"):
print(HelpInfo)
sys.exit()
elif opt in ("-k", "--mainkey"):
MainKey = arg
elif opt in ("-c", "--id"):
CoreID = arg
elif opt in ("-i", "--ip"):
SelfPublicIPAddress = arg
elif opt in ("-m", "--mode"):
Mode = arg
elif opt in ("-u", "--remote-id"):
PeerID = arg
elif opt in ("-r", "--remote-ip"):
RemotePublicIPAddress = arg
elif opt in ("--sconf"):
SelfConfig = arg
elif opt in ("--rconf"):
RemoteConfig = arg
#print(MainKey,CoreID,PublicIPAddress,Mode)
#return()
CoreInfo = corenode(MainKey,CoreID)
PeerInfo = corenode(MainKey,PeerID)
if (MainKey == "") & (CoreID == ""):
print("Error!\n"+HelpInfo)
return()
if Mode == "key":
print(CoreInfo.PrivateKey())
elif Mode == "pub":
print(CoreInfo.PublicKey())
elif Mode == "ipv4":
print(CoreInfo.ipv4())
elif Mode == "ipv6":
print(CoreInfo.ipv6())
elif Mode == 'port':
print(CoreInfo.port())
elif Mode == 'cfg':
print(wgConfig(CoreInfo,PeerInfo,RemotePublicIPAddress))
elif Mode == 'cfgfile':
# 写入文件
with open(SelfConfig,'w') as SelfConfigfile:
SelfConfigfile.write(wgConfig(CoreInfo,PeerInfo,RemotePublicIPAddress))
with open(RemoteConfig,'w') as RemoteConfigfile:
RemoteConfigfile.write(wgConfig(PeerInfo,CoreInfo,SelfPublicIPAddress))
def wgConfig(SelfNode, PeerNode, RemoteAddress) -> str:
CfgInfo="\
[Interface]\n\
PrivateKey = " + SelfNode.PrivateKey() + "\n\
Table = off\n\
PostUp = \
ip addr add " + SelfNode.ipv4() + "/32 peer " + PeerNode.ipv4()+"/32 dev %i; \
ip addr add "+SelfNode.ipv6()+"/128 peer " + PeerNode.ipv6() + "/128 dev %i\n\
ListenPort = " + SelfNode.port() + "\n\
MTU = 1420\n\
\n\
[Peer]\n\
PublicKey = " + PeerNode.PublicKey() + "\n\
AllowedIPs = 0.0.0.0/0,::/0\n\
Endpoint = " + RemoteAddress + ":" + SelfNode.port()
return(CfgInfo)
class corenode:
def __init__(self, MainKey, CoreID):
self.MainKey=MainKey
self.CoreID=CoreID
def PrivateKey(self):
MidSec = self.midsec()
PrivateKey = curve25519.PrivateKey(MidSec[:32].encode('ascii'))
# 裁切32位并当做ascii编码成byte类型。
PrivateKey_base64 = binascii.b2a_base64(PrivateKey.private).decode('ascii').replace('\n','')
# donna25519输出的时候是byte,我们需要转换成base64之后转为字符串,再去除最后的换行符
return(PrivateKey_base64)
def PublicKey(self):
MidSec = self.midsec()
PublicKey = curve25519.PrivateKey(MidSec[:32].encode('ascii')).get_public()
PublicKey_base64 = binascii.b2a_base64(PublicKey.public).decode('ascii').replace('\n','')
return(PublicKey_base64)
def ipv4(self):
MidSec = self.midsec()
Ipv4InHex = MidSec[:4]
# 裁切4位
Ipv4InHexList = re.findall(r".{2}", Ipv4InHex)
# 分割为每两位的数组
Ipv4Formatted = "169.254."+".".join(str(int(x,16)) for x in Ipv4InHexList)
# 添加IPv4本地链路的地址前缀,格式化IPv4
return(Ipv4Formatted)
def ipv6(self):
MidSec = self.midsec()
Ipv6InHex = MidSec[:28]
# 裁切28位,IPv6有32位16进制数字,这里取28位作为后缀
Ipv6InHexList = re.findall(r".{4}", Ipv6InHex)
# 分割为每4位的数组
Ipv6Formatted = "fe80:"+":".join(str(x) for x in Ipv6InHexList)
# 添加IPv6本地链路前缀,格式化IPv6
return(Ipv6Formatted)
def port(self):
MidSec = self.midsec()
PortHex = MidSec[:4]
# 裁切4位,端口号有4位16进制数字(16bit)
Port = int(PortHex,16)
if Port < 10000:
if Port > 5535:
Port = (int(MidSec,16) % 6) * 10000 + Port
else:
Port = (int(MidSec,16) % 7) * 10000 + Port
# 为低于10000的端口添加到高于10000,避免低端口号
return(str(Port))
def midsec(self):
# 将主密钥与ID转为Base64之后,将字符串衔接,计算SHA256,并且取Hex字符串
key1=self.MainKey
key2=self.CoreID
key1_base64 = base64.b64encode(key1.encode('ascii'))
key2_base64 = base64.b64encode(key2.encode('ascii'))
return(hashlib.sha256(key1_base64+key2_base64).hexdigest())
if __name__ == "__main__":
main(sys.argv[1:])
以上脚本实现了简单的Wireguard配置生成,可以根据主密钥和双方的ID自动生成Peer信息。
公网机器的FRRouting配置
同理,FRRouting的Peer或许也可以这么处理。先看看FRR需要怎么配置。
router bgp {ASN}
bgp router-id {本机路由器ID}
neighbor fabric peer-group
neighbor fabric remote-as external
neighbor fabric bfd
neighbor {对端1链路本地IPv4} peer-group fabric
neighbor {对端1链路本地IPv6} peer-group fabric
neighbor {对端2链路本地IPv4} peer-group fabric
neighbor {对端2链路本地IPv6} peer-group fabric
...
!
address-family ipv4 unicast
network {本机广播IPv4地址块1}
network {本机广播IPv4地址块2}
...
exit-address-family
address-family ipv6 unicast
neighbor fabric activate
network {本机广播IPv6地址块1}
network {本机广播IPv6地址块2}
...
exit-address-family
!
address-family l2vpn evpn
neighbor fabric activate
advertise-all-vni
exit-address-family
!
以上方式要求对端列表与本机地址块,考虑Core是Full Mush架构,除了自身以外的所有Core机均需要加入到FRR的邻居里面,还需要添加自己的路由地址块,这需要使用一个数据库或者配置文件进行记录并且剔除自身的ID。
除此之外,ASN或许也可以进行自动生成。4200000000到4294967294的ASN是可以在私有网络内自由使用的。可以界定为42xxx{Port}
,使用自定义的三个数字加上一个自动生成的端口号自动生成ASN。
按照现在的需求,数据格式编排为如下结构。
{
"MainKey": "{这是主密钥}",
"CoreNodes":[
{
"id": "{这是节点1的ID}",
"router-id": "{这是节点1的router-id}",
"ip": "{这是节点1的公网IP}",
"network":[
"{这是节点1的网络1}",
"{这是节点1的网络2}"
...
]
},
{
"id": "{这是节点2的ID}",
"router-id": "{这是节点2的router-id}",
"ip": "{这是节点2的公网IP}",
"network":[
"{这是节点2的网络1}",
"{这是节点2的网络2}"
...
]
},
...
]
}
这份表是用于Control节点的,Control节点需要连接到各个机器进行配置,故还可以添加每个节点的控制方式。在0.2版本先只使用SSH的密钥连接进行控制。除此之外,我还希望脚本仍然保留自定义端口、LinkLocal地址和ASN的可能性。将以上内容添加到表中如下所示。
{
"MainKey": "{这是主密钥}",
"CoreNodes":[
{
"id": "{这是节点1的ID}",
"ip": "{这是节点1的公网IP}",
"linkto-port": "节点1的向外连接端口",
"asn": "4266660001",
"linkLocalv4": "",
"linkLocalv6": "",
"linkType": "Public",
"natSetting":{
"wgPort":{
"wanPort":"17777",
"lanPort":"17778"
}
},
"network":[
"{这是节点1的网络1}",
"{这是节点1的网络2}"
...
],
"controlMethod": "ssh",
"sshSetting":{
"address":"{连接ssh的地址}",
"port":"{连接ssh的端口}"
}
},
{
"id": "{这是节点2的ID}",
"ip": "{这是节点1的公网IP}",
"network":[
"{这是节点2的网络1}",
"{这是节点2的网络2}"
...
],
"controlMethod": "ssh",
"sshSetting":{
"address":"{连接ssh的地址}",
"port":"{连接ssh的端口}"
}
},
...
]
}
脚本就不一一贴出来了,晚点贴上Github地址。
Nat服务器的解决方案
对于一些在Nat后的服务器(国内也就Nat机子比较便宜一些了,其他性价比都难以言喻……),如上面列出的数据格式,这个方案也打算支持自动化配置Nat机子。
有两个可行的方法可以将Nat机并入到Core网络中:
- 不打开入站端口,与公网服务器进行连接并发送Keepalive包,维持UDP连接。
- 打开单一入站端口,与公网服务器进行Full mesh的Wireguard单一配置连接后再在此连接上搭建第二层隧道。
第一个选项对于Core网来说并不妥当,因为Core网我要求每个隧道均可以在双方各自主动地向对方发送连接,这样可以尽最大努力保证数据包能传送到对端。而第二个单端口选项需要在Wireguard上面再添加一层隧道,在现在的实现中我将会用Wireguard Over Wireguard套娃暂时解决这个问题。
对于Nat机子,配置此连接需要有以下信息:
- 映射到公网的服务端口
- 独立于上层连接的密钥对
- 独立于上层连接的本地链路地址
对端的连接信息
- 对端地址
- 对端连接
对于Wireguard这种Layer 3 VPN来说,只要将符合条件的IP路由到接口上,VPN会自动帮你把数据包妥当处理正确路由到对端。即便自己的链路IP地址和对端不是同一个IP段的,也可以顺利路由。
这种单端口的配置大概如下
Nat端
[Interface]
PrivateKey = {本机主机单端口专用密钥}
Address = {本机链路IP地址,推荐使用IPv6}
ListenPort = {本机主机NAT Full Mush内网监听端口,需要映射到公网}
MTU = {MTU大小}
[Peer]
PublicKey = {对端主机1单端口专用公钥}
AllowedIPs = {对端主机1链路IP地址,推荐使用IPv6}
Endpoint = {对端主机1公网地址}:{对端主机1Nat Full Mush公网监听端口}
[Peer]
PublicKey = {对端主机2单端口专用公钥}
AllowedIPs = {对端主机2链路IP地址,推荐使用IPv6}
Endpoint = {对端主机2公网地址}:{对端主机2Nat Full Mush公网监听端口}
如果如此配置,在上层的直连连接则需要使用以下配置。
[Interface]
PrivateKey = {本机主机密钥}
Table = off
PostUp = ip addr add {本机主机点对点IPv4}/32 peer {对端主机点对点IPv4}/32 dev %i; ip addr add {本机主机点对点IPv6} peer {对端主机点对点IPv6} dev %i
ListenPort = {本机主机监听端口}
MTU = {MTU大小}
[Peer]
PublicKey = {对端主机公钥}
AllowedIPs = 0.0.0.0/0,::/0
Endpoint = {对端主机NAT专用链路IP地址}:{对端主机监听端口}
可以见到,使用单端口连接的话,将会改变原先接口的对端Endpoint地址和密钥对信息,其他的信息基本不变。同时添加了Nat Full Mush专用的己方密钥对、对端密钥对、己方链路IP地址、对端链路IP地址等……总体逻辑流程如下:
发现与NAT机子Peer,重新计算一套信息用于生成Underlay VPN。
- 密钥对
- 链路IP
- 若自己为公网机器,生成端口
- 生成集合了全部机器的Underlay VPN配置文件
- 更改用于生成完全权限对等连接的配置信息中的Endpoint地址为Underlay地址
这个配置理论上可以做全网配置,即为即便拥有公网地址直接访问权限的主机也使用单一端口先进行Underlay隧道连接,再添加Overlay隧道套娃。我现在的实现是只局限于Nat与Nat或是Nat与公网机之间的连接。
为了和上层的连接信息有差异,此处的Wireguard密钥对配置和IP配置需要与先前有所不同。之前是使用主Key+ID生成的信息,此处再加上一个"VPN"字段进行生成,可以避免两者信息碰撞导致系统无法正确路由。具体实现不再演示。
推送方案
在解决了配置文件生成的问题后,下一个问题就是如何将配置推送到Core服务器上。若使用SSH的话,可以使用Python的paramiko包,若在机子上面安装服务端并接受通过HTTP或者gRpc一类协议推送配置文件,则需要制作一个API接口。
通过SSH进行管理
这个方式是使用SSH协议将配置文件传送到服务器上,然后执行重载服务或是重启服务的命令进行应用的。
先来看看如何使用paramiko包推送到服务器并应用配置文件的。
公钥密钥登录SSHClient
# 指定本地的RSA私钥文件,如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数
pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345')
# 建立连接
ssh = paramiko.SSHClient()
ssh.connect(hostname='192.168.2.129',
port=22,
username='super',
pkey=pkey)
# 执行命令
stdin, stdout, stderr = ssh.exec_command('df -hl')
# 结果放到stdout中,如果有错误将放到stderr中
print(stdout.read().decode())
# 关闭连接
ssh.close()
# 指定本地的RSA私钥文件,如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数
pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345')
# 建立连接
trans = paramiko.Transport(('192.168.2.129', 22))
trans.connect(username='super', pkey=pkey)
# 将sshclient的对象的transport指定为以上的trans
ssh = paramiko.SSHClient()
ssh._transport = trans
# 执行命令,和传统方法一样
stdin, stdout, stderr = ssh.exec_command('df -hl')
print(stdout.read().decode())
# 关闭连接
trans.close()
可以看到以上脚本使用密钥登陆了SSH客户端,然后运行了df -hl
命令,并输出了结果。除去直接在SSH里面通过标准输出写入文件以外,还可以使用SFTP方式将文件传输到服务端。
pkey = paramiko.RSAKey.from_private_key_file('/home/super/.ssh/id_rsa', password='12345')
# 实例化一个transport对象
trans = paramiko.Transport(('192.168.2.129', 22))
# 建立连接
trans.connect(username='super', pkey=pkey)
# 实例化一个 sftp对象,指定连接的通道
sftp = paramiko.SFTPClient.from_transport(trans)
# 发送文件
sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt')
# 下载文件
# sftp.get(remotepath, localpath)
trans.close()
假设储存了配置的字典为ConfigList
,字典键值为接口名称,值为配置信息,是一个隶属于server
类里面的变量;server
类内部也有一个SSHIP
和SSHPort
、User
变量提供了SSH的入口;同时还有一个全局变量privatekey
,记录了密钥的文件地址,则通过SFTP传输文件,SSH重启Wireguard服务的方式如下。
pkey=paramiko.RSAKey.from_private_key_file(privatekey)
class server:
ConfigList = {
"node1" : config1,
"node2" : config2
}
SSHIP='10.10.10.10'
SSHPort=22
User='root'
Remote = server()
trans = paramiko.Transport((Remote.SSHIP, Remote.SSHPort))
trans.connect(username=Remote.User,pkey=pkey)
sftp = paramiko.SFTPClient.from_transport(trans)
for x,y in Remote.ConfigList:
sftp.putfo(fl=y,remotepath="/etc/wireguard/"+x+".conf")
ssh = paramiko.SSHClient()
ssh._transport = trans
for x,y in Remote.ConfigList:
stdin, stdout, stderr = ssh.exec_command('wg-quick down '+x+';wg-quick up '+x)
print(stdout.read().decode())
trans.close()
至于FRR也是以相似逻辑编写脚本即可。
编写配置文件,在上一篇搭建的实验环境里面进行测试。
{
"MainKey": "3359F3",
"asnPrefix": "414",
"SSHKeyfile": "./id_rsa",
"CoreNodes":[
{
"id": "core1",
"router_id": "100.64.1.1",
"ip": "100.90.1.1",
"network4":[
"100.64.1.0/24"
],
"controlMethod": "ssh",
"sshSetting":{
"address":"100.90.1.1",
"port": 22,
"user":"root"
}
},
{
"id": "core2",
"router_id": "100.64.2.1",
"ip": "100.90.1.2",
"network4":[
"100.64.2.0/24"
],
"controlMethod": "ssh",
"sshSetting":{
"address":"100.90.1.2",
"port": 22,
"user":"root"
}
},
{
"id": "core3",
"router_id": "100.64.3.1",
"ip": "100.90.1.3",
"network4":[
"100.64.3.0/24"
],
"controlMethod": "ssh",
"sshSetting":{
"address":"100.90.1.3",
"port": 22,
"user":"root"
}
}
]
}
生成并导入RSA公钥,实现免密登录
ssh-keygen # 输入./id_rsa以在当前目录生成rsa密钥
ssh root@100.90.1.1
echo "ssh-rsa [你的密钥]" >> /root/.ssh/authorized_keys
ssh root@100.90.1.2
echo "ssh-rsa [你的密钥]" >> /root/.ssh/authorized_keys
ssh root@100.90.1.3
echo "ssh-rsa [你的密钥]" >> /root/.ssh/authorized_keys
给Docker容器创建各自的网络,并连接上容器
docker network create net-test-core1 --subnet=100.64.1.0/24
docker network create net-test-core2 --subnet=100.64.2.0/24
docker network create net-test-core3 --subnet=100.64.3.0/24
测试推送
编写API后端应用配置
传送给Core的数据不能包含主密钥,只能包含Core的Wireguard配置、FRR的配置。如上一篇文章提到的,以及这篇文章有所提及的,一个Core需要以下的信息:
单一的FRR配置
- 自己的ASN
- Peer的ASN
- Peer的本地链路地址
Wireguard配置组
- 自己的密钥
- 自己的本地链路地址
- 自己的访问端口(对方的监听端口)
- 对方的公钥
- 对方的本地链路地址
- 对方的IP地址
- 对方访问端口(自己的监听端口)
由于在推送控制端已经做过一次配置文件的实现了,Core端可以照搬一些内容。但每个信息都需要由推送段推送而不能经由主密钥生成。推送的数据结构可以如下设计:
{
"privateKey": "",
"RemotePort": "",
"asn": "",
"linkLocalv4":"",
"linkLocalv6":"",
"isNat": False,
"natConfig": {
"wgPort":{
"wanPort": 10000,
"lanPort": 10000
},
"linkLocalv6": "",
"PrivateKey_Value": "base64_text"
},
"wgPeers":[
{
"id": "",
"linkLocalv4":"",
"linkLocalv6":"",
"publicKey":"",
"IP":"",
"PeerlinkinPort":"",
"isNat": True,
"natConfig": {
"wgPort":{
"wanPort": 10000,
"lanPort": 10000
},
"linkLocalv6": "",
"PublicKey_Value": "base64_text"
}
}
]
}
仅针对Core节点互联,规划使用以下的方法搭建Core节点的API,为了基本安全性,每次请求都需要在Get请求中带上Token证明控制端的身份:
Http方法 | URL | 动作 |
---|---|---|
GET | http://[hostname]/auth?login=[ciphertext] | 使用预共享密钥向服务器请求Token,返回Token |
GET | http://[hostname]/core/list | 返回当前配置,Json格式 |
GET | http://[hostname]/core/status | 返回当前状态(连接启动状态、BGP状态),Json格式 |
GET & POST | http://[hostname]/core/config | 推送并覆盖之前的配置文件,Json格式 |
GET | http://[hostname]/core/config/apply | 应用/重启配置,需检查对比启动状态 |
服务端使用Flask搭建API服务端。关于Flask如何使用,详情的可以去查看教程,在这个实现里面只需要了解以下框架了解Get和Post请求的处理即可。
from flask import Flask, redirect, url_for, request, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template("login.html")
@app.route('/success/<name>')
def success(name):
return 'from success: welcome %s' % name
@app.route('/login',methods = ['POST', 'GET']) # 页面名
def login(): # 页面入口
if request.method == 'POST':
print(1)
user = request.form['nm'] # 获取POST参数
page_method = request.form('method') # 获取POST参数
if page_method == "1":
return redirect(url_for('success',name = user))
return 'Your name: ' + user + '\nPost request.'
else:
print(2)
user = request.args.get('nm') # 获取get参数
page_method = request.args.get('method') # 获取get参数
if page_method == "1":
return redirect(url_for('success',name = user))
# url_for的构建,第一个参数是url地址,默认为当前根目录,第二个是传入函数的参数
# redirect()是重定向到某个URL,参数为URL地址
return 'Your name: ' + user + '\nGet request.'
if __name__ == '__main__':
app.run()
具体的构建代码不贴出,之后会放上Github。