JS通过Thrift调用Python服务端过程(HTTP协议实现)
一、开始的思路
因为开始想要实现js与python的双向通信,因此查询有关技术发现
WebSocket
协议能够实现客户端和服务端之间的双向通信,而且实时性会很高,所以就考虑直接使用
WebSocket
来实现。
经过查看Thrift官方文档和示例,发现JavaScript能够支持两种协议,一种是TXHRTransport(也就是通过
http协议
),另一种是TWebSocketTransport(也就是通过
WebSocket协议
)。这两种协议都是
应用层
的协议,他们属于平行的两种协议,但是有所区别。
http协议是一种单向的通信协议,只能客户端发送请求,服务端给出响应。
服务端不能在没有请求的情况下给主动客户端发送消息
。websocket是一种双向通信技术,客户端发送请求之后,通信双方会建立一个长连接,因此无论是谁发送消息,另一方都可以接收到。但是,目前websocket并不能独立于http协议单独存在,因为建立连接之前,客户端会先向服务端发送一个http请求,这个请求的目的就是询问服务端是否支持websocket协议通信,希望服务端将协议切换到websocket协议模式,底层的连接实际上还是TCP。(我的理解,也不知道有没有错误)
如果websocket能够完成通信,那这个双向通信就成功了!
二、使用websocket尝试实现
2.1 Thrift文件
接口定义文件:test.thrift,通过命令能够自动生成客户端以及服务端代码
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
typedef string String
struct Person{
1:optional String name,
2:optional int age
}
exception DataException{
1:optional String message,
2:optional String callback,
3:optional String date
}
service PersonService{
Person getPersonByName(1:required String name) throws (1:DataException ex);
int insertPerson(1:required Person p) throws (1:DataException ex)
}
thrift生成的文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ow3PBjV-1662044240631)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/%E6%96%87%E4%BB%B6%E7%BB%93%E6%9E%84.png)]
2.2 客户端程序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>js_client</title>
</head>
<body>
<script src="./lib/thrift.js"></script>
<script src="./lib/gen-js/test_types.js"></script>
<script src="./lib/gen-js/PersonService.js"></script>
<button id="btn">点击发送请求</button>
<script>
var btn = document.getElementById("btn");
btn.onclick = function(){
const ws_uri = "ws://localhost:9999";
const transport = new Thrift.TWebSocketTransport(ws_uri);
const protocol = new Thrift.Protocol(transport);
const client = new PersonServiceClient(protocol);
transport.open();
console.log("clientStart");
person = client.getPersonByName("张三");
console.log(person);
}
</script>
</body>
</html>
客户端JavaScript实现需要三个依赖文件:test_types.js、PersonService.js、thrift.js,前两个是thrift生成的,thrift.js是thrift官方对js语言的一个实现。
2.3 服务端程序
Python需要先使用pip安装thrift模块,
pip install thrift
,然后在服务端导入TProtocol、TJSONProtocol等
PersonServiceHandler.py
import sys
from src.py.PersonService import Iface
sys.path.append("")
from py import ttypes
class PersonServiceHandler(Iface):
def getPersonByName(self, name):
print('JavaScript客户端传过来的数据是:' + name)
person = ttypes.Person()
person.name = name
person.age = 30
return person
def insertPerson(self, p):
if (p != None):
print("姓名:" + p.name)
print("年龄:" + p.age)
return 1
else:
return 0
test.py
from thrift.Thrift import TException
from thrift.protocol import TProtocol, TBinaryProtocol, TJSONProtocol
from thrift.server import TServer, THttpServer
from thrift.transport import TSocket, TTransport
from src.PersonServiceHandler import PersonServiceHandler
from src.py import PersonService
if __name__ == '__main__':
handler = PersonServiceHandler()
processor = PersonService.Processor(handler)
transport = TSocket.TServerSocket(host='localhost', port=8800)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TJSONProtocol.TJSONProtocolFactory()
server = TServer.TSimpleServer(processor,transport,tfactory,pfactory)
print("Starting python server...")
server.serve()
2.4 运行结果
客户端报错:JSON输入意外结束
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPEPM8yJ-1662044240632)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/ws_%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%8A%A5%E9%94%99.png)]
服务端报错:Unexpected character: b’G’
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xMY8cXmV-1662044240633)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/ws_%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%A5%E9%94%99.png)]
2.5 错误分析(暂未找到原因)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AWwP1w3P-1662044240633)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/ws_%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%85%B7%E4%BD%93%E6%8A%A5%E9%94%99%E4%BD%8D%E7%BD%AE.png)]
错误原因其实就是在readJSONSyntaxChar中对character和current进行了对比,
b’[’ ≠ b’G’
,因此报错,但是具体b’G’从什么地方出现的还没有找到,也不知道是什么具体含义,网上查找也没有相关解决办法,官方也没有对这个函数进行具体说明。
三、使用http尝试实现
3.1 客户端程序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>js_client</title>
</head>
<body>
<script src="./lib/thrift.js"></script>
<script src="./lib/gen-js/test_types.js"></script>
<script src="./lib/gen-js/PersonService.js"></script>
<button id="btn">点击发送请求</button>
<script>
var btn = document.getElementById("btn");
btn.onclick = function(){
const url = 'http://localhost:9999';
const transport = new Thrift.TXHRTransport(url);
const protocol = new Thrift.Protocol(transport);
const client = new PersonServiceClient(protocol);
transport.open();
console.log("clientStart");
person = client.getPersonByName("张三");
console.log(person);
}
</script>
</body>
</html>
3.2 服务端程序
from thrift.protocol import TJSONProtocol
from thrift.server import THttpServer
from thrift.transport import TSocket, TTransport
from py.PersonService import Iface,Processor
from py.ttypes import Person
class PersonServiceHandler(Iface):
def getPersonByName(self, name):
print("getPersonByName函数执行..." + name)
person = Person()
person.name = name
person.age = 30
return person
def insertPerson(self, p):
print('insertPerson函数执行...')
if(p is not None):
print(p.name)
print(p.age)
return 1
else:
return 0
if __name__ == '__main__':
handler = PersonServiceHandler()
processor = Processor(handler)
transport = TSocket.TServerSocket(port=9999)
tfactory = TTransport.TBufferedTransportFactory()
pfactory = TJSONProtocol.TJSONProtocolFactory()
port = 9999
server = THttpServer.THttpServer(processor, ("localhost", port), pfactory)
print('start server...')
server.serve()
3.3 运行结果
客户端:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0peyfmEi-1662044240633)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/CORS%E8%B7%A8%E5%9F%9F%E9%97%AE%E9%A2%98.png)]
关于CORS跨域问题
:发送请求时,如果请求方和被请求方的
协议、域名、端口号
有一个不一致,那就会产生跨域问题。跨域其实是浏览器的一种安全策略,如果请求不通过浏览器发送,那就不会存在跨域问题。浏览器在发送请求时会随机开一个端口用于发送请求,因此端口号时动态的,所以肯定会和服务端的端口号不一致,因而产生跨域问题。通常解决跨域问题的方式就是在服务端的响应报文的头中添加一个允许跨域的标志,也就是将
Access-Control-Allow-Origin 设置为 ‘*’
。
服务端:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SgngJ6Bj-1662044240634)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%8A%A5options%E9%94%99%E8%AF%AF.png)]
服务端报错:不支持OPTIONS请求。
通过上网知道,请求分为两种:
简单请求
和
复杂请求
。简单请求有GET,HEAD,POST,这种请求会直接发送,而复杂请求会先发送一个预检请求OPTIONS,服务端处理后,客户端接下来才会发送正式的请求。服务端此处报错就是因为服务端并没有方法对该OPTIONS请求做处理。
debug跟踪过程:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wugbO6oB-1662044240634)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/OPTIONS%E6%8A%A5%E9%94%99%E7%9A%84%E8%B0%83%E7%94%A8%E8%BF%87%E7%A8%8B.png)]
报错原因分析:
服务端没有提供do_OPTIONS方法
解决过程:Thrift官方提供了一个THttpServer.py文件,里面有一个RequestHandler类,这个类里面只有对POST请求的处理,因此就试了一下在里面添加do_OPTIONS方法,其具体代码如下:
def do_OPTIONS(self):
self.send_response(200, "ok")
self.send_header('Access-Control-Allow-Origin', '*')
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS, POST')
self.send_header("Access-Control-Allow-Headers", "X-Requested-With")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
然后运行了一下,客户端返回调用结果,服务端也正常运行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NfcASCTd-1662044240634)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/js%E8%B0%83%E7%94%A8%E6%AD%A3%E7%A1%AE%E7%BB%93%E6%9E%9C.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8uTzbj0M-1662044240635)(https://zsfq-picture.oss-cn-beijing.aliyuncs.com/img/JavaScript/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E6%AD%A3%E7%A1%AE%E5%93%8D%E5%BA%94.png)]
header(“Access-Control-Allow-Headers”, “X-Requested-With”)
self.send_header(“Access-Control-Allow-Headers”, “Content-Type”)
self.end_headers()
然后运行了一下,客户端返回调用结果,服务端也正常运行
[外链图片转存中...(img-NfcASCTd-1662044240634)]
[外链图片转存中...(img-8uTzbj0M-1662044240635)]
到此为止,JavaScript与Python之间使用Thrift实现了单向通信。Thrift具体的执行过程及实现方式还是没有搞清楚。