mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
Merge pull request #4436 from marcus-j-davies/allow-ws-headers
Feat: Add ability to set headers for WebSocket client
This commit is contained in:
commit
208dd2a457
@ -40,6 +40,99 @@
|
||||
|
||||
(function() {
|
||||
|
||||
const headerTypes = [
|
||||
/*
|
||||
{ value: "Accept", label: "Accept", hasValue: false },
|
||||
{ value: "Accept-Encoding", label: "Accept-Encoding", hasValue: false },
|
||||
{ value: "Accept-Language", label: "Accept-Language", hasValue: false },
|
||||
*/
|
||||
{ value: "Authorization", label: "Authorization", hasValue: false },
|
||||
/*
|
||||
{ value: "Content-Type", label: "Content-Type", hasValue: false },
|
||||
{ value: "Cache-Control", label: "Cache-Control", hasValue: false },
|
||||
*/
|
||||
{ value: "User-Agent", label: "User-Agent", hasValue: false },
|
||||
/*
|
||||
{ value: "Location", label: "Location", hasValue: false },
|
||||
*/
|
||||
{ value: "other", label: RED._("node-red:httpin.label.other"),
|
||||
hasValue: true, icon: "red/images/typedInput/az.svg" },
|
||||
]
|
||||
|
||||
const headerOptions = {};
|
||||
const defaultOptions = [
|
||||
{ value: "other", label: RED._("node-red:httpin.label.other"),
|
||||
hasValue: true, icon: "red/images/typedInput/az.svg" },
|
||||
"env",
|
||||
];
|
||||
/*
|
||||
headerOptions["accept"] = [
|
||||
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||
{ value: "text/html", label: "text/html", hasValue: false },
|
||||
{ value: "application/json", label: "application/json", hasValue: false },
|
||||
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
|
||||
headerOptions["accept-encoding"] = [
|
||||
{ value: "gzip", label: "gzip", hasValue: false },
|
||||
{ value: "deflate", label: "deflate", hasValue: false },
|
||||
{ value: "compress", label: "compress", hasValue: false },
|
||||
{ value: "br", label: "br", hasValue: false },
|
||||
{ value: "gzip, deflate", label: "gzip, deflate", hasValue: false },
|
||||
{ value: "gzip, deflate, br", label: "gzip, deflate, br", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["accept-language"] = [
|
||||
{ value: "*", label: "*", hasValue: false },
|
||||
{ value: "en-GB, en-US, en;q=0.9", label: "en-GB, en-US, en;q=0.9", hasValue: false },
|
||||
{ value: "de-AT, de-DE;q=0.9, en;q=0.5", label: "de-AT, de-DE;q=0.9, en;q=0.5", hasValue: false },
|
||||
{ value: "es-mx,es,en;q=0.5", label: "es-mx,es,en;q=0.5", hasValue: false },
|
||||
{ value: "fr-CH, fr;q=0.9, en;q=0.8", label: "fr-CH, fr;q=0.9, en;q=0.8", hasValue: false },
|
||||
{ value: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", label: "zh-CN, zh-TW; q = 0.9, zh-HK; q = 0.8, zh; q = 0.7, en; q = 0.6", hasValue: false },
|
||||
{ value: "ja-JP, jp", label: "ja-JP, jp", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["content-type"] = [
|
||||
{ value: "text/css", label: "text/css", hasValue: false },
|
||||
{ value: "text/plain", label: "text/plain", hasValue: false },
|
||||
{ value: "text/html", label: "text/html", hasValue: false },
|
||||
{ value: "application/json", label: "application/json", hasValue: false },
|
||||
{ value: "application/octet-stream", label: "application/octet-stream", hasValue: false },
|
||||
{ value: "application/pdf", label: "application/pdf", hasValue: false },
|
||||
{ value: "application/xml", label: "application/xml", hasValue: false },
|
||||
{ value: "application/zip", label: "application/zip", hasValue: false },
|
||||
{ value: "multipart/form-data", label: "multipart/form-data", hasValue: false },
|
||||
{ value: "audio/aac", label: "audio/aac", hasValue: false },
|
||||
{ value: "audio/ac3", label: "audio/ac3", hasValue: false },
|
||||
{ value: "audio/basic", label: "audio/basic", hasValue: false },
|
||||
{ value: "audio/mp4", label: "audio/mp4", hasValue: false },
|
||||
{ value: "audio/ogg", label: "audio/ogg", hasValue: false },
|
||||
{ value: "image/bmp", label: "image/bmp", hasValue: false },
|
||||
{ value: "image/gif", label: "image/gif", hasValue: false },
|
||||
{ value: "image/jpeg", label: "image/jpeg", hasValue: false },
|
||||
{ value: "image/png", label: "image/png", hasValue: false },
|
||||
{ value: "image/tiff", label: "image/tiff", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
headerOptions["cache-control"] = [
|
||||
{ value: "max-age=0", label: "max-age=0", hasValue: false },
|
||||
{ value: "max-age=86400", label: "max-age=86400", hasValue: false },
|
||||
{ value: "no-cache", label: "no-cache", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
*/
|
||||
headerOptions["user-agent"] = [
|
||||
{ value: "Mozilla/5.0", label: "Mozilla/5.0", hasValue: false },
|
||||
...defaultOptions,
|
||||
];
|
||||
|
||||
function getHeaderOptions(headerName) {
|
||||
const lc = (headerName || "").toLowerCase();
|
||||
let opts = headerOptions[lc];
|
||||
return opts || defaultOptions;
|
||||
}
|
||||
|
||||
function ws_oneditprepare() {
|
||||
$("#websocket-client-row").hide();
|
||||
$("#node-input-mode").on("change", function() {
|
||||
@ -192,7 +285,8 @@
|
||||
value: "",
|
||||
label:RED._("node-red:websocket.sendheartbeat"),
|
||||
validate: RED.validators.number(/*blank allowed*/true) },
|
||||
subprotocol: {value:"",required: false}
|
||||
subprotocol: {value:"",required: false},
|
||||
headers: { value: [] }
|
||||
},
|
||||
inputs:0,
|
||||
outputs:0,
|
||||
@ -200,6 +294,9 @@
|
||||
return this.path;
|
||||
},
|
||||
oneditprepare: function() {
|
||||
|
||||
const node = this;
|
||||
|
||||
$("#node-config-input-path").on("change keyup paste",function() {
|
||||
$(".node-config-row-tls").toggle(/^wss:/i.test($(this).val()))
|
||||
});
|
||||
@ -214,14 +311,114 @@
|
||||
if (!heartbeatActive) {
|
||||
$("#node-config-input-hb").val("");
|
||||
}
|
||||
|
||||
const hasMatch = function (arr, value) {
|
||||
return arr.some(function (ht) {
|
||||
return ht.value === value
|
||||
});
|
||||
}
|
||||
|
||||
const headerList = $("#node-input-headers-container").css('min-height', '150px').css('min-width', '450px').editableList({
|
||||
addItem: function (container, i, header) {
|
||||
const row = $('<div/>').css({
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'flex'
|
||||
}).appendTo(container);
|
||||
const propertNameCell = $('<div/>').css({ 'flex-grow': 1 }).appendTo(row);
|
||||
const propertyName = $('<input/>', { class: "node-input-header-name", type: "text", style: "width: 100%" })
|
||||
.appendTo(propertNameCell)
|
||||
.typedInput({ types: headerTypes });
|
||||
|
||||
const propertyValueCell = $('<div/>').css({ 'flex-grow': 1, 'margin-left': '10px' }).appendTo(row);
|
||||
const propertyValue = $('<input/>', { class: "node-input-header-value", type: "text", style: "width: 100%" })
|
||||
.appendTo(propertyValueCell)
|
||||
.typedInput({
|
||||
types: getHeaderOptions(header.keyType)
|
||||
});
|
||||
|
||||
const setup = function(_header) {
|
||||
const headerTypeIsAPreset = function(h) {return hasMatch(headerTypes, h) };
|
||||
const headerValueIsAPreset = function(h, v) {return hasMatch(getHeaderOptions(h), v) };
|
||||
|
||||
const {keyType, keyValue, valueType, valueValue} = header;
|
||||
|
||||
if(keyType == "other") {
|
||||
propertyName.typedInput('type', keyType);
|
||||
propertyName.typedInput('value', keyValue);
|
||||
} else if (headerTypeIsAPreset(keyType)) {
|
||||
propertyName.typedInput('type', keyType);
|
||||
} else {
|
||||
propertyName.typedInput('type', "other");
|
||||
propertyName.typedInput('value', keyValue);
|
||||
}
|
||||
|
||||
if(valueType == "other" || valueType == "env" ) {
|
||||
propertyValue.typedInput('type', valueType);
|
||||
propertyValue.typedInput('value', valueValue);
|
||||
} else if (headerValueIsAPreset(propertyName.typedInput('type'), valueType)) {
|
||||
propertyValue.typedInput('type', valueType);
|
||||
} else {
|
||||
propertyValue.typedInput('type', "other");
|
||||
propertyValue.typedInput('value', valueValue);
|
||||
}
|
||||
}
|
||||
setup(header);
|
||||
|
||||
propertyName.on('change', function (event) {
|
||||
propertyValue.typedInput('types', getHeaderOptions(propertyName.typedInput('type')));
|
||||
});
|
||||
|
||||
},
|
||||
sortable: true,
|
||||
removable: true
|
||||
});
|
||||
if (node.headers) {
|
||||
for (let index = 0; index < node.headers.length; index++) {
|
||||
const element = node.headers[index];
|
||||
headerList.editableList('addItem', node.headers[index]);
|
||||
}
|
||||
}
|
||||
},
|
||||
oneditsave: function() {
|
||||
|
||||
const node = this;
|
||||
|
||||
if (!/^wss:/i.test($("#node-config-input-path").val())) {
|
||||
$("#node-config-input-tls").val("_ADD_");
|
||||
}
|
||||
if (!$("#node-config-input-hb-cb").prop("checked")) {
|
||||
$("#node-config-input-hb").val("0");
|
||||
}
|
||||
|
||||
const headers = $("#node-input-headers-container").editableList('items');
|
||||
|
||||
node.headers = [];
|
||||
headers.each(function(i) {
|
||||
const header = $(this);
|
||||
const keyType = header.find(".node-input-header-name").typedInput('type');
|
||||
const keyValue = header.find(".node-input-header-name").typedInput('value');
|
||||
const valueType = header.find(".node-input-header-value").typedInput('type');
|
||||
const valueValue = header.find(".node-input-header-value").typedInput('value');
|
||||
node.headers.push({
|
||||
keyType, keyValue, valueType, valueValue
|
||||
})
|
||||
|
||||
});
|
||||
},
|
||||
oneditresize: function(size) {
|
||||
const dlg = $("#dialog-form");
|
||||
const expandRow = dlg.find('.node-input-headers-container-row');
|
||||
let height = dlg.height() - 5;
|
||||
if(expandRow && expandRow.length){
|
||||
const siblingRows = dlg.find('> .form-row:not(.node-input-headers-container-row)');
|
||||
for (let i = 0; i < siblingRows.size(); i++) {
|
||||
const cr = $(siblingRows[i]);
|
||||
if(cr.is(":visible"))
|
||||
height -= cr.outerHeight(true);
|
||||
}
|
||||
$("#node-input-headers-container").editableList('height',height);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -299,8 +496,15 @@
|
||||
<span data-i18n="inject.seconds"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-row" style="margin-bottom:0;">
|
||||
<label><i class="fa fa-list"></i> <span data-i18n="httpin.label.headers"></span></label>
|
||||
</div>
|
||||
<div class="form-row node-input-headers-container-row">
|
||||
<ol id="node-input-headers-container"></ol>
|
||||
</div>
|
||||
<div class="form-tips">
|
||||
<p><span data-i18n="[html]websocket.tip.url1"></span></p>
|
||||
<span data-i18n="[html]websocket.tip.url2"></span>
|
||||
<p><span data-i18n="[html]websocket.tip.url2"></span></p>
|
||||
<span data-i18n="[html]websocket.tip.headers"></span>
|
||||
</div>
|
||||
</script>
|
||||
|
@ -58,6 +58,7 @@ module.exports = function(RED) {
|
||||
node.isServer = !/^ws{1,2}:\/\//i.test(node.path);
|
||||
node.closing = false;
|
||||
node.tls = n.tls;
|
||||
node.upgradeHeaders = n.headers
|
||||
|
||||
if (n.hb) {
|
||||
var heartbeat = parseInt(n.hb);
|
||||
@ -96,6 +97,42 @@ module.exports = function(RED) {
|
||||
tlsNode.addTLSOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
// We need to check if undefined, to guard against previous installs, that will not have had this property set (applies to 3.1.x setups)
|
||||
// Else this will be breaking potentially
|
||||
if(node.upgradeHeaders !== undefined && node.upgradeHeaders.length > 0){
|
||||
options.headers = {};
|
||||
for(let i = 0;i<node.upgradeHeaders.length;i++){
|
||||
const header = node.upgradeHeaders[i];
|
||||
const keyType = header.keyType;
|
||||
const keyValue = header.keyValue;
|
||||
const valueType = header.valueType;
|
||||
const valueValue = header.valueValue;
|
||||
|
||||
const headerName = keyType === 'other' ? keyValue : keyType;
|
||||
let headerValue;
|
||||
|
||||
switch(valueType){
|
||||
case 'other':
|
||||
headerValue = valueValue;
|
||||
break;
|
||||
|
||||
case 'env':
|
||||
headerValue = RED.util.evaluateNodeProperty(valueValue,valueType,node);
|
||||
break;
|
||||
|
||||
default:
|
||||
headerValue = valueType;
|
||||
break;
|
||||
}
|
||||
|
||||
if(headerName && headerValue){
|
||||
options.headers[headerName] = headerValue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var socket = new ws(node.path,node.subprotocol,options);
|
||||
socket.setMaxListeners(0);
|
||||
node.server = socket; // keep for closing
|
||||
|
@ -516,7 +516,8 @@
|
||||
"path1": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Empfänger (Listener) kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt.",
|
||||
"path2": "Dieser Pfad ist relativ zu <code>__path__</code>.",
|
||||
"url1": "URL sollte ws:// oder wss:// Schema verwenden und auf einen vorhandenen WebSocket-Listener verweisen.",
|
||||
"url2": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt."
|
||||
"url2": "Standardmäßig enthält <code>payload</code> die Daten, die über einen WebSocket gesendet oder von einem WebSocket empfangen werden. Der Client kann so konfiguriert werden, dass er das gesamte Nachrichtenobjekt als eine JSON-formatierte Zeichenfolge (string) sendet oder empfängt.",
|
||||
"headers": "Header werden nur während des Protokollaktualisierungsmechanismus übermittelt, von HTTP auf das WS/WSS-Protokoll."
|
||||
},
|
||||
"status": {
|
||||
"connected": "Verbunden __count__",
|
||||
|
@ -586,7 +586,8 @@
|
||||
"path1": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The listener can be configured to send or receive the entire message object as a JSON formatted string.",
|
||||
"path2": "This path will be relative to <code>__path__</code>.",
|
||||
"url1": "URL should use ws:// or wss:// scheme and point to an existing websocket listener.",
|
||||
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string."
|
||||
"url2": "By default, <code>payload</code> will contain the data to be sent over, or received from a websocket. The client can be configured to send or receive the entire message object as a JSON formatted string.",
|
||||
"headers": "Headers are only submitted during the Protocol upgrade mechanism, from HTTP to the WS/WSS Protocol."
|
||||
},
|
||||
"status": {
|
||||
"connected": "connected __count__",
|
||||
|
@ -583,7 +583,8 @@
|
||||
"path1": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. L'écouteur peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON.",
|
||||
"path2": "Ce chemin sera relatif à <code>__path__</code>.",
|
||||
"url1": "L'URL doit utiliser le schéma ws:// ou wss:// et pointer vers un écouteur websocket existant.",
|
||||
"url2": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. Le client peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON."
|
||||
"url2": "Par défaut, <code>payload</code> contiendra les données à envoyer ou à recevoir d'un websocket. Le client peut être configuré pour envoyer ou recevoir l'intégralité de l'objet message sous forme de chaîne au format JSON.",
|
||||
"headers": "Les en-têtes ne sont soumis que lors du mécanisme de mise à niveau du protocole, de HTTP vers le protocole WS/WSS."
|
||||
},
|
||||
"status": {
|
||||
"connected": "__count__ connecté",
|
||||
|
@ -586,7 +586,8 @@
|
||||
"path1": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
|
||||
"path2": "このパスは <code>__path__</code> の相対パスになります。",
|
||||
"url1": "URLには ws:// または wss:// スキーマを使用して、存在するwebsocketリスナを設定してください。",
|
||||
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。"
|
||||
"url2": "標準では <code>payload</code> がwebsocketから送信、受信されるデータを持ちます。クライアントはJSON形式の文字列としてメッセージ全体を送信、受信するよう設定できます。",
|
||||
"headers": "ヘッダーは、HTTP から WS/WSS プロトコルへのプロトコル アップグレード メカニズム中にのみ送信されます。"
|
||||
},
|
||||
"status": {
|
||||
"connected": "接続数 __count__",
|
||||
|
@ -451,7 +451,8 @@
|
||||
"path1": "표준으로는 <code>payload</code> 가 websocket에서 송신, 수신된 데이터를 기다립니다. 클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
|
||||
"path2": "This path will be relative to <code>__path__</code>.",
|
||||
"url1": "URL에는 ws:// 또는 wss:// 스키마를 사용하여, 존재하는 websocket리스너를 설정해 주세요.",
|
||||
"url2": "표준으로는 <code>payload</code> 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다."
|
||||
"url2": "표준으로는 <code>payload</code> 가 websocket에서 송신,수신될 데이터를 기다립니다.클라이언트는 JSON형식의 문자열로 메세지전체를 송신, 수신하도록 설정할 수 있습니다.",
|
||||
"headers": "헤더는 HTTP에서 WS/WSS 프로토콜로 프로토콜 업그레이드 메커니즘 중에만 제출됩니다."
|
||||
},
|
||||
"status": {
|
||||
"connected": "접속 수 __count__",
|
||||
|
@ -573,7 +573,8 @@
|
||||
"path1": "Por padrão, a <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O ouvinte pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON.",
|
||||
"path2": "Este caminho será relativo a <code>__path__</code>.",
|
||||
"url1": "A URL deve usar o esquema ws:// ou wss:// e apontar para um ouvinte de websocket existente.",
|
||||
"url2": "Por padrão, <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O cliente pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON."
|
||||
"url2": "Por padrão, <code>carga útil</code> conterá os dados a serem enviados ou recebidos de um websocket. O cliente pode ser configurado para enviar ou receber todo o objeto de mensagem como uma cadeia de caracteres formatada em JSON.",
|
||||
"headers": "Os cabeçalhos são enviados apenas durante o mecanismo de atualização do protocolo, do HTTP para o protocolo WS/WSS."
|
||||
},
|
||||
"status": {
|
||||
"connected": "conectado __count__",
|
||||
|
@ -475,7 +475,8 @@
|
||||
"path1": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Слушатель может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.",
|
||||
"path2": "Путь будет относительно <code>__path__</code>.",
|
||||
"url1": "URL должен использовать схему ws:// или wss:// и указывать на существующего слушателя websocket.",
|
||||
"url2": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON."
|
||||
"url2": "По умолчанию <code>payload</code> будет содержать данные, которые будут отправлены или получены из websocket. Клиент может быть настроен на отправку или получение всего объекта сообщения в виде строки в формате JSON.",
|
||||
"headers": "Заголовки передаются только во время механизма обновления протокола с HTTP на протокол WS/WSS."
|
||||
},
|
||||
"status": {
|
||||
"connected": "подключен __count__",
|
||||
|
@ -576,7 +576,8 @@
|
||||
"path1": "默认情况下,<code>payload</code>将包含要发送或从Websocket接收的数据。侦听器可以配置为以JSON格式的字符串发送或接收整个消息对象.",
|
||||
"path2": "这条路径将相对于 <code>__path__</code>.",
|
||||
"url1": "URL 应该使用ws://或者wss://方案并指向现有的websocket侦听器.",
|
||||
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象."
|
||||
"url2": "默认情况下,<code>payload</code> 将包含要发送或从Websocket接收的数据。可以将客户端配置为以JSON格式的字符串发送或接收整个消息对象.",
|
||||
"headers": "标头仅在协议升级机制期间提交,从 HTTP 到 WS/WSS 协议."
|
||||
},
|
||||
"status": {
|
||||
"connected": "已连接数量 __count__",
|
||||
|
@ -471,7 +471,8 @@
|
||||
"path1": "預設情況下,<code>payload</code>將包含要發送或從Websocket接收的資料。偵聽器可以配置為以JSON格式的字串發送或接收整個消息物件.",
|
||||
"path2": "這條路徑將相對於 <code>__path__</code>.",
|
||||
"url1": "URL 應該使用ws://或者wss://方案並指向現有的websocket監聽器.",
|
||||
"url2": "預設情況下,<code>payload</code> 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件."
|
||||
"url2": "預設情況下,<code>payload</code> 將包含要發送或從Websocket接收的資料。可以將使用者端配置為以JSON格式的字串發送或接收整個消息物件.",
|
||||
"headers": "標頭僅在協定升級機制期間提交,從 HTTP 到 WS/WSS 協定."
|
||||
},
|
||||
"status": {
|
||||
"connected": "連接數 __count__",
|
||||
|
Loading…
x
Reference in New Issue
Block a user