基于RestClient的OpenSIPS呼叫处理方法
一、前言
OpenSIPS是一个开源的SIP(会话初始协议)服务器,用于构建通信系统,在大并发、WebRTC、信令处理、高可靠性、媒体代理等方方面面有非常强的支撑能力。在OpenSIPS的丰富模块集合中,rest_client模块为OpenSIPS提供了向RESTful Web服务发起HTTP请求的能力。
以下是有关rest_client模块的详细描述:
二、概述
rest_client模块允许你从OpenSIPS脚本中发起HTTP/HTTPS请求,与外部系统(如数据库、其他服务等)交互。使用这个模块,你可以将OpenSIPS集成到更广泛的生态系统中,从而实现更复杂的用例。
当我们提到OpenSIPS与外部系统的集成,实际上我们是指OpenSIPS有能力与其他非SIP系统交互。rest_client模块提供了一个方便的桥梁,让OpenSIPS可以通过RESTful API与各种外部服务进行交互。
1. 集成与外部数据库
尽管OpenSIPS本身支持多种数据库连接(如MySQL、PostgreSQL、NoSQL数据库等),但有时你可能需要与支持RESTful API的数据库进行交互。例如,你可能想从一个NoSQL数据库中获取数据,该数据库提供了HTTP API而不是传统的SQL接口。
2. 动态路由
你可以使用rest_client从外部服务获取路由决策,从而在OpenSIPS中动态决定呼叫的目标或下一跳。但不建议这么用,尽量预加载模式,而不是次次向外部发请求,才是我们使用OpenSIPS的意义。
3. 认证与授权
对于一些高级的认证方案(例如OAuth),你可能需要与一个认证服务器交互。rest_client可以帮助你完成这个过程。
4. 与其他微服务的交互
现代的应用架构经常采用微服务的模式。rest_client模块可以使OpenSIPS轻松地与这些微服务进行交互,从而增强其功能。
5. 异步操作
为了不阻塞OpenSIPS的主流程,你可以使用OpenSIPS的异步能力与rest_client结合,这样即使HTTP请求有延迟或需要较长时间来获得响应,也不会影响OpenSIPS的主要操作
三、具体操作
OpenSIPS的配置:
route{
if (is_method("INVITE")) {
route(call_control);
}
}
route[call_control] {
$var(call-id)=$ci;
$var(from-uri)=$fu;
$var(from-user-id)=$fU;
$avp(from-display-name)=$fn;
$var(to-uri)=$tu;
$var(to-user-id)=$rU;
$avp(to-display-name)=$tn;
$var(from-domain)=$fd;
$var(to-domain)=$rd;
$avp(agent)=$ua;
$var(webserver)="http://127.0.0.1:8082";
$var(request_body)='{"call-id":"'+$var(call-id)+'",'+
'"from-uri":"'+$var(from-uri)+'",'+
'"from-user-id":"'+$var(from-user-id)+'",'+
'"from-display-name":'+$avp(from-display-name)+','+
'"to-uri":"'+$var(to-uri)+'",'+
'"to-user-id":"'+$var(to-user-id)+'",'+
'"to-display-name":'+$avp(to-display-name)+','+
'"from-domain":"'+$var(from-domain)+'",'+
'"agent":"'+$avp(agent)+'",'+
'"source-ip":"'+$si+'",'+
'"method":"'+$rm+'",'+
'"to-domain":"'+$var(to-domain)+
'"}';
if (is_method("INVITE")) {
xlog("L_INFO","在$cfg_line行处理,往$var(webserver)发送-->$var(request_body)");
async(rest_post("$var(webserver)/api/callcontrol",$var(request_body),"application/json", $var(response), $var(rtype),$var(rcode)), load_balance);
}
if (is_method("REGISTER")) {
xlog("L_INFO","在$cfg_line行处理,往$var(webserver)发送-->$var(request_body)");
async(rest_post("$var(webserver)/api/callcontrol",$var(request_body),"application/json", $var(response), $var(rtype),$var(rcode)), load_balance);
}
}
RestfulApi实现:
Rest API接口服务器,这次我们选择的是基于Golang开发GIN WEB服务框架
Gin是一个用Go(或称Golang)编写的HTTP web框架。由于其简洁的API、高性能和与Go语言的天然兼容性,它已经成为Go社区中最受欢迎的web框架之一。以下是基于Golang的Gin服务器的主要优势:
1. 性能
速度:Gin是一个非常快速的框架。它的路由器基于httprouter,被认为是Go中最快的HTTP路由器之一。
低内存占用:Go语言本身就为并发和高性能设计,与C和Java相比,它通常使用更少的内存和CPU。
2. 简洁性
简单的API:Gin提供了一个直观而简洁的API,使开发人员能够迅速上手并构建强大的web应用程序。
中间件支持:像其他的web框架一样,Gin也支持中间件,使开发者可以轻松地插入功能,如日志、身份验证和跨站请求伪造(CSRF)防护。
3. 并发
Goroutines:Go语言的并发模型基于Goroutines,这些轻量级的线程使得Gin可以轻松处理数千甚至数百万的并发请求。
4. 模块化设计
可扩展:Gin允许你使用插件和中间件扩展其功能,这使得你可以为特定的应用程序或服务创建一个定制的Gin实例。
5. JSON验证
Gin内置支持JSON请求体的绑定和验证,这大大简化了API开发的过程。
6. 错误处理
Gin提供了一个方便的方法来捕获和处理错误,这有助于保持代码的整洁和可读性。
7. 社区与生态系统
Gin拥有一个活跃的社区,这意味着你可以容易地找到插件、中间件和解决常见问题的资源。
与其他Go库的兼容性:由于Gin是用Go编写的,它可以与Go生态系统中的其他库和工具轻松集成。
8. 跨平台
与Go语言的其他产品一样,用Gin编写的应用程序也可以轻松地跨多个平台和操作系统进行部署。
结论
Gin是一个功能丰富、高性能的web框架,非常适合那些寻求速度和效率,并希望利用Go强大并发功能的开发者。无论是构建微服务、API还是完整的web应用程序,Gin都是一个出色的选择。
话不多说,基于Gin的代码片段呈现
main.go
func main() {
//开启个协程进行监听是否ctrl+C关闭服务
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
log.Printf("启动Gin服务")
go func() { initdb.PostgresSQL() }()
go func() { initdb.Redis() }()
//启动GIN监听服务器端口
r := router.SetupRouter()
r.Run(config.Gin_listen())
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-done
log.Printf("接收到ctrl+C信号,程序开始关闭中...\n")
}
router.go
func SetupRouter() *gin.Engine {
r := gin.Default()
r.Use(gin.Recovery())
log.Println("启动Gin服务成功")
r.POST("/api/callcontrol", control.Callcontrol) //呼叫逻辑控制
return r
}
model.go
type InviteBody struct {
Source_ip string `json:"source-ip"`
Call_id string `json:"call-id"`
From_uri string `json:"from-uri"`
From_user_id string `json:"from-user-id"`
Display_name string `json:"from-display-name"`
To_uri string `json:"to-uri"`
To_user_id string `json:"to-user-id"`
To_display_name string `json:"to-display-name"`
From_domain string `json:"from-domain"`
To_domain string `json:"to-domain"`
Agent string `json:"agent"`
Method string `json:"method"`
}
control.go
func Callcontrol(c *gin.Context) {
var inviteBody model.InviteBody
if err := c.ShouldBindJSON(&inviteBody); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
jsonData, err := json.MarshalIndent(inviteBody, "", " ")
if err != nil {
c.JSON(500, gin.H{"error": "Failed to marshal data"})
return
}
log.Printf("Invite消息请求内容:%s", string(jsonData))
............(更多的逻辑代码自己编写)
}
然后我们就可以看到了两方协作,OpenSIPS向Restful发起请求后的输出:
[opsCall]2023/09/01 11:19:11 callcontrol.go:22: Invite消息请求内容:{
"source-ip": "72.167.53.131",
"call-id": "1021992399-1605103198-803483289",
"from-uri": "sip:5556@1.2.3.4",
"from-user-id": "5556",
"from-display-name": "",
"to-uri": "sip:0046812111496@1.2.3.4",
"to-user-id": "0046812111496",
"to-display-name": "",
"from-domain": "1.2.3.4",
"to-domain": "1.2.3.4",
"agent": "Linksys-SPA942",
"method": "INVITE"
}
接下来,我们就可以根据获得的JSON结果,通过代码逻辑分析这一通的呼叫是呼入还是呼出,该如何进行变化号码,然后是转发给哪个SIP中继,还是拒绝呼叫,还是送到数据库去验证,还是.....(各种玩法都会产生出来)
总之,通过这种模式可以极大的扩展后续的各种业务。有时候在OpenSIPS上进行数据库方面的操作不是很方便,有时候需要进行各种逻辑判断,如果通过这种rest client方式进行接口对接方式来处理整个呼叫流程,那么将是一种非常方便快捷的来处理呼叫流程的实现方法。
譬如如果是这个IP过来的不允许呼叫,那么返回
{"code":403,"reason":"IP Auth Failed"}
Opensips有json模块进行解析后,将返回的json结果进行解码后,根据403就可以直接返回一个结果:
send_reply("403", "Forbidden");
总之:
Opensips与Gin的强强联合可以将系统整体的呼叫并发发挥到极致。并且可以通过nginx代理转发模式分别转到多个Gin接口服务器上。性能不再是约束了。
其实像Gin(Golang系)一样强悍的API接口服务器还有很多种,譬如OpenResty(Lua系),SpringBoot(Java系)等等都可以实现这类的接口服务,如果你擅长PHP也是可以实现的。