我被要求创建一个应用程序来管理通常位于 NAT 或防火墙后面的设备,因此无法从外部访问。
幸运的是,该设备的创建者想到了这一点,所以他们创建了一个 API,可以连接到外部服务器并保持连接有效,同时还可以通过同一连接接收请求 - 如果我理解手册的话。 有问题的设备是 Videosec 人脸识别设备,协议称为“LAPI”(v1 或 v2),我能找到的有关该协议的所有信息都在这里:http ://videosec.com/support/Firmware/oet/OET-213H_API-JSON.pdf
不幸的是,它不起作用。我一打开设备就收到了心跳请求,但我不知道它是否确认了我的 HTTP 200 响应,而且我发送的任何请求都没有得到满足——只是被设备忽略,并不断发送心跳。
我的问题是 - 我这样做对吗?我是否足够了解 HTTP 保持活动(持久)连接?这是我的服务器套接字代码。它是用 PHP 编写的,但我也尝试过 Python:
<?php
header_remove();
$host = "(public ip)";
$port = 5196;
set_time_limit(0);
if (!extension_loaded('sockets')) {
die('The sockets extension is not loaded.');
}
$socket = socket_create(AF_INET, SOCK_STREAM, 0) or die("Could not create socket\n");
$result = socket_bind($socket, $host, $port) or die("Could not bind to socket\n");
$result = socket_listen($socket, 3) or die("Could not set up socket listener\n");
function getLocalTime()
{
$dt = new DateTime("now", new DateTimeZone('Europe/Belgrade'));
return $dt->format('Y-m-d H:i:s');
}
function returnok($spawn, $url = "/LAPI/V1.0/PACS/Controller/HeartReportInfo")
{
$rsobj = [
"ResponseURL" => $url,
"Code" => 0,
"Data" => [
"Time" => getLocalTime()
]
];
$resobj = json_encode($rsobj, JSON_UNESCAPED_SLASHES) . PHP_EOL;
$response = "HTTP/1.1 200 Ok" . PHP_EOL;
$response.= "Content-Length: " . strlen($resobj) . PHP_EOL;
$response.= "Content-Type: application/json" . PHP_EOL;
$response.= "Connection: keep-alive" . PHP_EOL;
$response.= "X-Frame-Options: SAMEORIGIN" . PHP_EOL;
$response.= $resobj . PHP_EOL;
echo $response;
if (!socket_write($spawn, $response, strlen($response))) {
echo "Could not write output\n";
}
}
function writeback($spawn, $url, $method = "GET")
{
$output = $method . " " . $url . " HTTP/1.1" . PHP_EOL;
$output.= "Content-Type: application/json" . PHP_EOL;
$output.= "Connection: keep-alive" . PHP_EOL;
$output.= PHP_EOL;
echo $output;
if (!socket_write($spawn, $output, strlen($output))) {
echo "Could not write output\n";
}
}
$spa = true;
while ($spa) {
$spawn = socket_accept($socket) or die("Could not accept incoming connection\n");
if ($input = socket_read($spawn, 16 * 1048576)) {
$input = trim($input);
if ($spawn) {
returnok($spawn);
sleep(7);
writeback($spawn, "/LAPI/V1.0/PeopleLibraries/BasicInfo");
} else {
$spa = false;
}
}
socket_close($spawn);
}
socket_close($socket);
?>
交换的内容如下:
RECV: POST /LAPI/V1.0/PACS/Controller/HeartReportInfo HTTP/1.1
Host: (redacted):5196
Content-Type: application/json
Content-Length: 180
Connection: keep-alive
{
"RefId": "(redacted)",
"Time": "2011-12-01 00:38:07",
"NextTime": "2011-12-01 00:38:37",
"DeviceCode": "(redacted)",
"DeviceType": 1
}
SEND: HTTP/1.1 200 OK
Content-Length: 108
Content-Type: application/json
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
{"ResponseURL":"/LAPI/V1.0/PACS/Controller/HeartReportInfo","Code":0,"Data":{"Time":"2024-11-26 02:09:06"}}
SEND: GET /LAPI/V1.0/PeopleLibraries/BasicInfo HTTP/1.1
Content-Type: application/json
Connection: keep-alive
RECV: POST /LAPI/V1.0/PACS/Controller/HeartReportInfo HTTP/1.1
Host: (redacted):5196
Content-Type: application/json
Content-Length: 180
Connection: keep-alive
{
"RefId": "(redacted)",
"Time": "2011-12-01 00:38:16",
"NextTime": "2011-12-01 00:38:46",
"DeviceCode": "(redacted)",
"DeviceType": 1
}
SEND: HTTP/1.1 200 OK
Content-Length: 108
Content-Type: application/json
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
{"ResponseURL":"/LAPI/V1.0/PACS/Controller/HeartReportInfo","Code":0,"Data":{"Time":"2024-11-26 02:09:15"}}
通过单个套接字进行双向HTTP 似乎相当不寻常 - 它肯定是 Videosec 的一项超越标准 HTTP“持久连接(保持活动)”的定制发明 - 但是:
您的请求/响应标头和正文之间缺少一个空行。解析器需要知道标头的结束位置;这由两个连续的换行符表示。例如:
这甚至适用于没有主体(和/或没有标题)的请求或响应;它们仍然必须以空行结尾。
(我怀疑您
X-Frame-Options:
在这里是否需要,因为响应不会发送到 Web 浏览器,因此从这个意义上来说没有“框架”。同样,您的 GET 请求不需要,Content-Type:
因为它们没有内容;通常只有响应或 POST 才需要此标头。想要接收JSON 的 GET 将使用Accept:
来指示这一点。)另外:在这种情况下不要使用
PHP_EOL
。该常量是特定于平台的,这与您对网络协议的要求完全相反。例如,HTTP 指定使用 CR+LF(\r\n
或\x0D\x0A
)换行符,无论平台如何(某些服务器恰好接受裸 LF,其他服务器则不接受)。