HTTP客户端
- 网关名:请务必确保每个网关中,该名称是全局唯一的
1.1.实时数据配置
- 启用与否:是否启用HTTP实时数据上传功能
- 接口类型:POST/GET
- 超时:HTTP请求的超时时间
- 变值更新:仅在数据值发生变化时,才进行上传操作
- 推荐此选项,降低网络传输开销
- 周期:向实时接口推送数据的周期
- 定时发送:多长时间发送一次全部数据,无论值是否发生变更
- 实时URL:接收网关推送数据的接口地址
- 需要上传的数据点,请用户在完成上述配置后,再自行配置
1.2.控制接口配置
- 启用与否:是否启用HTTP控制接口的功能
- 接口类型:POST/GET
- 超时:HTTP请求的超时时间
- 执行间隔:如果下发内容包含多个时,设置下一个子对象前的休眠
- 执行记录:经过控制接口成功执行的操作,是否记录在本地
- 在控制频繁的场景下,不推荐开启此选项
- 控制URL:接收网关的询问请求,以及回复结果的接口
- 此端控制点无需配置,只需要下发的控制ID在网关内存在即可
1.3.接口的数据格式
GET/POST方式,Body数据格式:
[
{
"gateway": "gw1",
"id": "Group1_Tag1",
"rinfo": "读取成功",
"rvalue": "17.230000",
"rstatus": "ok",
"rtime": 1739931138
},
{
"gateway": "gw1",
"id": "Group1_Tag2",
"rinfo": "读取成功",
"rvalue": "0.000000",
"rstatus": "ok",
"rtime": 1739931138
}
]
控制接口
下发内容
[
{
"Id":"Group1_Tag1",
"Type":"write",
"Setv":"999"
},
{
"Id":"Group1_Tag2",
"Type":"write",
"Setv":"334"
}
]
正确回复:
[
{
"gateway":"gw1",
"id": "Group5_CTag_49",
"wstatus": "ok",
"wtime": 1739936081,
"err": ""
},
{
"gateway": "gw1",
"id": "Group8_CTag_49",
"wstatus": "ok",
"wtime": 1739936083,
"err": ""
}
]
错误回复:
[
{
"gateway": "gw1",
"id": "group9_A0",
"wstatus": "no",
"wtime": 1739935416,
"err": "此协议不支持写操作"
},
{
"gateway": "gw1",
"id": "group1_A2",
"wstatus": "no",
"wtime": 1739935416,
"err": "请求ID不存在内部对象表中"
}
]
1.4.测试工具
- 在官网下载"HTTP-服务端模拟工具",或者使用下述代码:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'D:\WinPyQt5.9-32bit-3.5.3.1\ecric6pwd\gateway\tools\http_server.ui'
#
# Created by: PyQt5 UI code generator 5.15.10
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread,pyqtSignal
from PyQt5.QtWidgets import QMessageBox
import http.server
class MyHandler(http.server.BaseHTTPRequestHandler):
qthread_http=None
ctl_value=None
real=None
ctl=None
def my_init(qthread_http):
MyHandler.qthread_http=qthread_http
#统一错误时的回复
def error_response(self):
self.send_response(400)#状态码
self.wfile.write(b'{"status":"error"}')#返回消息
#尝试从http请求中,提取出"附带的数据"
def get_post_data(self):
if('Content-Length' not in self.headers):
return None
content_length = int(self.headers['Content-Length'])
if(content_length!=0):
post_data = self.rfile.read(content_length).decode('utf8')
#parsed_data = urllib.parse.parse_qs(post_data)#一般示例中,有以下"代码",不适用
return post_data
else:
return None
def return_data(self,type_str,data):
send_data={}
send_data["type"]=type_str
send_data["data"]=data
MyHandler.qthread_http._signal.emit(send_data)
#对路径进行预先判断
def operator_path(self):
if MyHandler.real !=None:
real_path='/'+ MyHandler.real
else:
real_path='/'
if MyHandler.ctl != None:
ctl_path = '/' + MyHandler.ctl
else:
ctl_path = '/'
return real_path,ctl_path
def read_ok_heads(self):
self.send_response(200)#状态码
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
def do_GET(self):
real_path,ctl_path=self.operator_path()
print("real_path:",real_path)
print("ctl_path:",ctl_path)
if self.path == real_path:
print("GET-访问实时接口路径:",real_path)
post_data=self.get_post_data()
if(post_data==None):
print("REAL请求非法,推出");
return
print("post_data:",post_data)
self.return_data("real",post_data)
self.read_ok_heads()
elif self.path == ctl_path:
print("GET-访问控制接口路径:",ctl_path)
post_data=self.get_post_data()#获取对端请求的数据
print("post_data:",post_data)
if(post_data==None):
return
if MyHandler.ctl_value==None:#如果ui没有设置下方数据,则退出
return
self.send_response(200)#状态码
self.send_header('Content-type', 'text/plain; charset=utf-8')
self.end_headers()
if "gain_control" in post_data:#请求,下方控制内容
self.wfile.write(MyHandler.ctl_value.encode('utf-8'))#必须是字节格式
else:
self.return_data("ctl",post_data)
else:
self.error_response()
def do_POST(self):
real_path,ctl_path=self.operator_path()
print("real_path:",real_path)
print("ctl_path:",ctl_path)
#取出对方post数据的方法
if self.path == real_path:
#取出对方post数据的方法
print("POST-访问实时接口路径:",real_path)
post_data=self.get_post_data()
self.return_data("real",post_data)
self.read_ok_heads()
elif self.path == ctl_path:
print("POST-访问控制接口路径:",ctl_path)
post_data=self.get_post_data()#获取对端请求的数据
print("post_data:",post_data)
if(post_data==None):
return
if MyHandler.ctl_value==None:#如果ui没有设置下方数据,则退出
return
self.read_ok_heads()
if "gain_control" in post_data:#请求,下方控制内容
self.wfile.write(MyHandler.ctl_value.encode('utf-8'))#必须是字节格式
else:
self.return_data("ctl",post_data)
class Http_Server_Thread(QtCore.QThread):
_signal=pyqtSignal(dict)#对接接口线程的信号
def __init__(self,http_server_ui):
super().__init__()
self.http_server_ui=http_server_ui
self._signal.connect(self.http_server_ui.show_thread_view)
self.start_stop_mark="stop"
self.server_port=80
self.httpd=None
def run(self):
print("http_server thread 运行")
server_address = ('', self.server_port)
self.httpd = http.server.HTTPServer(server_address,MyHandler)
#更新回调类中的参数
MyHandler.my_init(self)
while True:
print("while循环中");
self.httpd.handle_request()
print("退出循环")
def thread_recv(self,data):
if(data["type"]=="start_stop"):
self.server_port=int(data["port"])#更新下次启动的端口
if data["ctl"]=="start":#更新启停标记
self.start_stop_mark="start"
#self.run()
self.start()
else:
self.start_stop_mark="stop"
self.httpd.server_close()
self.terminate()
self.wait()
elif data["type"]=="set_ctl_value":
print("更新下发数据")
MyHandler.ctl_value=data["ctl_value"]
print("展示主线程的数据:",data)
class Ui_Http_server(QtWidgets.QDialog):
_signal=pyqtSignal(dict)#对接接口线程的信号
def __init__(self):
super().__init__()
self.setupUi()
self.signal_connect()
self.child_thread_create()
self.qthread_http_status="no"
#子线程创建相关
def child_thread_create(self):
self.qthread_http=Http_Server_Thread(self)#创建线程对象
self.qthread_http._signal.connect(self.show_thread_view)#接受"历史线程返回的数据",进行状态提示
self.qthread_http.finished.connect(self.show_thread_stop)
self._signal.connect(self.qthread_http.thread_recv)
def signal_connect(self):
self.start_stop_pushButton.clicked.connect(self.start_stop_http)
self.send_ctl_pushButton.clicked.connect(self.set_ctl_send_data)
def setupUi(self):
self.setObjectName("Form")
self.resize(1348, 877)
self.gridLayoutWidget = QtWidgets.QWidget(self)
self.gridLayoutWidget.setGeometry(QtCore.QRect(20, 30, 1291, 811))
self.gridLayoutWidget.setObjectName("gridLayoutWidget")
self.gridLayout = QtWidgets.QGridLayout(self.gridLayoutWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.server_port_label = QtWidgets.QLabel(self.gridLayoutWidget)#服务器端口的label
self.server_port_label.setObjectName("server_port_label")
self.gridLayout.addWidget(self.server_port_label, 0, 0, 1, 1)
self.server_port_lineEdit = QtWidgets.QLineEdit(self.gridLayoutWidget)#服务器端口
self.server_port_lineEdit.setObjectName("server_port_lineEdit")
self.gridLayout.addWidget(self.server_port_lineEdit, 0, 1, 1, 1)
self.start_stop_label = QtWidgets.QLabel(self.gridLayoutWidget)#停启服务器的label
self.start_stop_label.setObjectName("start_stop_label")
self.gridLayout.addWidget(self.start_stop_label, 0, 2, 1, 1)
self.start_stop_pushButton = QtWidgets.QPushButton(self.gridLayoutWidget)#停启服务器
self.start_stop_pushButton.setObjectName("start_stop_pushButton")
self.gridLayout.addWidget(self.start_stop_pushButton, 0, 3, 1, 1)
self.upload_url_label = QtWidgets.QLabel(self.gridLayoutWidget)#上传主题是否启用
self.upload_url_label.setObjectName("upload_url_label")
self.gridLayout.addWidget(self.upload_url_label, 1, 0, 1, 1)
self.upload_lineEdit = QtWidgets.QLineEdit(self.gridLayoutWidget)#上传主题
self.upload_lineEdit.setObjectName("upload_lineEdit")
self.gridLayout.addWidget(self.upload_lineEdit, 1, 1, 1, 1)
self.ctl_url_label = QtWidgets.QLabel(self.gridLayoutWidget)#控制主题的label
self.ctl_url_label.setObjectName("ctl_url_label")
self.gridLayout.addWidget(self.ctl_url_label, 1, 2, 1, 1)
self.ctl_url_lineEdit = QtWidgets.QLineEdit(self.gridLayoutWidget)#控制主题
self.ctl_url_lineEdit.setObjectName("ctl_url_lineEdit")
self.gridLayout.addWidget(self.ctl_url_lineEdit, 1, 3, 1, 1)
self.send_ctl_pushButton = QtWidgets.QPushButton(self.gridLayoutWidget)#下发控制主题的按钮
self.send_ctl_pushButton.setObjectName("send_ctl_pushButton")
self.gridLayout.addWidget(self.send_ctl_pushButton, 2, 0, 1, 1)
self.send_ctl_plainTextEdit = QtWidgets.QPlainTextEdit(self.gridLayoutWidget)#显示下发内容的text
self.send_ctl_plainTextEdit.setObjectName("send_ctl_plainTextEdit")
self.gridLayout.addWidget(self.send_ctl_plainTextEdit, 2, 1, 1, 1)
self.real_recv_label = QtWidgets.QLabel(self.gridLayoutWidget)#接收内容的label
self.real_recv_label.setObjectName("real_recv_label")
self.gridLayout.addWidget(self.real_recv_label, 2, 2, 2, 1)
self.real_recv_plainTextEdit = QtWidgets.QPlainTextEdit(self.gridLayoutWidget)#显示接收内容的text
self.real_recv_plainTextEdit.setObjectName("real_recv_plainTextEdit")
self.gridLayout.addWidget(self.real_recv_plainTextEdit, 2, 3, 2, 1)
self.ctl_reply_label = QtWidgets.QLabel(self.gridLayoutWidget)#显示控制回复的label
self.ctl_reply_label.setObjectName("ctl_reply_label")
self.gridLayout.addWidget(self.ctl_reply_label, 3, 0, 1, 1)
self.ctl_reply_plainTextEdit = QtWidgets.QPlainTextEdit(self.gridLayoutWidget)#显示控制回复的text
self.ctl_reply_plainTextEdit.setObjectName("ctl_reply_plainTextEdit")
self.gridLayout.addWidget(self.ctl_reply_plainTextEdit, 3, 1, 1, 1)
self.retranslateUi()
#QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("Ui_Http_server", "HTTP服务器模拟工具"))
self.upload_url_label.setText(_translate("Ui_Http_server", "上传主题"))
self.server_port_label.setText(_translate("Ui_Http_server", "服务器端口"))
self.ctl_reply_label.setText(_translate("Ui_Http_server", "下发回复"))
self.real_recv_label.setText(_translate("Ui_Http_server", "实时数据展示"))
self.send_ctl_pushButton.setText(_translate("Ui_Http_server", "设置下发数据"))
self.ctl_url_label.setText(_translate("Ui_Http_server", "控制主题"))
self.start_stop_pushButton.setText(_translate("Ui_Http_server", "停启"))
self.start_stop_label.setText(_translate("Ui_Http_server", "停启服务器"))
def start_stop_http(self):
print("停启服务器")
#1.提示
if(self.qthread_http_status=="no"):
change=QMessageBox.information(self,"提示","是否要启动服务器",QMessageBox.Yes|QMessageBox.No)
else:
change=QMessageBox.information(self,"提示","是否要停止服务器服务器",QMessageBox.Yes|QMessageBox.No)
if change==QMessageBox.No:
return
#2.1.输入内容合理性检查
if self.server_port_lineEdit.text()=="":
QMessageBox.information(self,"提示","端口未配置",QMessageBox.Yes)
return
#2.2.检测接口路径是否输入:
if self.upload_lineEdit.text() =="" and self.ctl_url_lineEdit.text()=="":
QMessageBox.information(self,"提示","至少设置实时、控制主题中任一个",QMessageBox.Yes)
return
#4.接口路径,直接通过类变量进行更新
if self.upload_lineEdit.text() != "":
MyHandler.real=self.upload_lineEdit.text()
if self.ctl_url_lineEdit.text()!="":
MyHandler.ctl=self.ctl_url_lineEdit.text()
#4.发送数据至线程,并更新状态字
send_data={}
send_data["type"]="start_stop"
send_data["port"]=self.server_port_lineEdit.text()#启停的端口
if(self.qthread_http_status=="no"):
self.qthread_http_status="ok"
send_data["ctl"]="start"
else:
self.qthread_http_status="no"
send_data["ctl"]="stop"
self._signal.emit(send_data)
def set_ctl_send_data(self):
print("设置控制接口回复内容")
send_data={}
send_data["type"]="set_ctl_value"#设置控制下发的值
send_data["ctl_value"]=self.send_ctl_plainTextEdit.toPlainText()
self._signal.emit(send_data)
def show_thread_view(self,data):
print("展示线程中发来的数据:",data)
if data["type"]=="real":
self.real_recv_plainTextEdit.setPlainText(data["data"])
elif data["type"]=="ctl":
self.ctl_reply_plainTextEdit.setPlainText(data["data"])
def show_thread_stop(self):
print("线程已停止")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = Ui_Http_server()
ui.show()
sys.exit(app.exec_())
- 确保已安装python环境,推荐python版本3.10
- 安装依赖包:pip install pyqt5
- 上述脚本,仅用于测试,请勿在生产环境中使用
1.5.可能报错
1.Server returned nothing (no headers, no data)
http服务器未按约定进行响应