使用Python通过JSON与Web套接字进行通信

13
为了更好地理解Websockets如何在基本的hello-world之外使用,我设定了一个任务:使用Websockets和JSON从页面获取一些数据(因为gitxiv的源代码是可用的,我选择查看http://gitxiv.com/day/2015/12/31)。
通过Python连接到这个Websocket似乎很简单。
from websocket import create_connection
import websocket
import pprint

websocket.enableTrace(True)
ws=create_connection("ws://gitxiv.com/sockjs/212/2aczpiim/websocket")
result = ws.recv()
print "Received '%s'" % result
result = ws.recv()
print "Received '%s'" % result

我不是很清楚ws:// URL中的变量,比如“212”。运行此代码似乎可靠地建立连接(尽管有可能因未正确设置变量而导致服务器拒绝合作)。现在,如果我观察Firefox和gitxiv页面之间的通信,我会看到在WebSocket连接之后,服务器发送了以下内容。
o
a["{\"server_id\":\"0\"}"]

上面的脚本得到了相同的响应,所以似乎连接已经成功。

然而,这就是我跌倒的地方。通信的下一步是我的浏览器向Web服务发送大量信息,比如这行:

"["{\"msg\":\"connect\",\"version\":\"1\",\"support\":[\"1\",\"pre2\",\"pre1\"]}"]"

使用ws.send()直接发送这些行会导致“破碎的帧”。仅发送以下内容:
controlstr='{"msg":"connect","version":"1","support":["1","pre2","pre1"]}';
ws.send(controlstr)

结果会发送出看起来像这样的内容:

send: '\x81\xbd\xef\x17F8\x945+K\x885|\x1a\x8cx(V\x8at2\x1a\xc350]\x9dd/W\x815|\x1a\xde5j\x1a\x9cb6H\x80e2\x1a\xd5Ld\t\xcd;dH\x9drt\x1a\xc356J\x8a&de\x92'

我遇到了一个不同的错误:

'a["{\\"msg\\":\\"error\\",\\"reason\\":\\"Bad request\\"}"]'

因此,似乎我向websocket发送JSON消息的方式有问题。是否有人知道它期望的格式以及如何使用websocket/websocket-client实现它?欢迎任何澄清/建议。
我要发送的JSON消息是Firefox的Websocket开发工具报告的那些:这里是一个截图:

Firefox Web Developer Tool report


212只是URL的一部分,因此有点随意。你从哪里获取发送字符串?我更愿意生成数据结构,然后通过json.dumps将其转换为json。我认为这些消息来自Meteor框架,所以你可以在那里寻找协议。但你应该验证它们是否真的来自Meteor。 - syntonym
我通过使用开发者工具WebSocket扩展程序来查找Firefox发送的流量,简单地获取了发送字符串,遵循相同的原则,相同的事情应该导致相同的结果。212确实会有所变化(每次都会得到一个新的URL),但我不确定这会产生什么影响。听起来可能与Meteor有关 - 谢谢! - Soz
啊,抱歉我是指 '\x81\xbd\xef\x17F8...'。另外,看起来你确实需要发送一个 JSON 列表,其中包含一个字符串(该字符串再次是有效的 JSON)。请确保正确转义引号。 - syntonym
那个奇怪的 \x81 等字符是 Python 打印出来的内容(websocket.enableTrace(True) 这一行代码会导致它详细记录每个发送和接收的消息)。显然,ws.send() 在内部进行了这种转换。 - Soz
看起来 WebSocket 客户端对有效负载进行了一些二进制处理,这就是所打印的内容。除非您想深入了解 WebSocket 的具体细节,否则我认为该输出并没有什么帮助。 Werner 在下面实现了“包含单个字符串的数组,该字符串是有效的 JSON”,看起来很有前途。 - syntonym
Werner的方法确实可行。你说得很对,那个输出可能只是误导性的。 - Soz
1个回答

15

如果您仔细观察通过浏览器发送的内容,会发现它是:

["{\"msg\":\"connect\"}"]

这看起来非常像一组JSON字符串的数组。实际上,如果您尝试复制它:


This looks an awful lot like an array of JSON strings. Indeed, if you try to replicate it:

ws.send(json.dumps([json.dumps({'msg': 'connect', 'version': '1', 'support': ['1', 'pre2', 'pre1']})]))

你会看到你已连接。这是我的完整代码:

import json
import pprint
import websocket
from websocket import create_connection

websocket.enableTrace(True)
ws = create_connection('ws://gitxiv.com/sockjs/212/2aczpiim/websocket')

result = ws.recv()
print('Result: {}'.format(result))

result = ws.recv()
print('Result: {}'.format(result))

ws.send(json.dumps([json.dumps({'msg': 'connect', 'version': '1', 'support': ['1', 'pre2', 'pre1']})]))
result = ws.recv()
print('Result: {}'.format(result))

我曾尝试过使用json.dumps,但没有效果。我没有想到可以嵌套使用它们,例如json.dumps([json.dumps({stuff})])。这是一个神奇而又神秘的解决方案!(你知道这是否是常见的做法吗?) - Soz
1
@Soz,就我个人而言,我之前没有见过这种情况。直觉上,我会认为在数组中使用实际的JSON更有意义(例如[{msg1},{msg2}]),但也许开发人员有他们的理由。 - syntonym
1
老实说,对我来说这似乎有点疯狂。我唯一能想到的原因是他们有一个Web客户端,可能会同时发送多个消息,并指定每个消息必须是JSON编码的。不过真的很奇怪,因为我本来会只指定消息是消息的JSON编码数组,例如 json.dumps([{'msg': 'connect'}, {'msg': 'frobnosticate'}]),而不是JSON编码对象的JSON编码列表。我不确定是否能够提出一个好的理由(尽管我可以虚构其他几个有意义的理由)。 - Wayne Werner
对我来说似乎非常疯狂 - Michael Paccione

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接