Compare commits

..
142 Commits
Author SHA1 Message Date
heavyrain.lee 0bcf2fea7b update version to 0.37 2020-01-08 14:56:36 +08:00
heavyrain.lee 60730c57cb update sdk test case 2020-01-08 14:49:21 +08:00
heavyrain.lee 3b61f8f6dd 添加频道获取自己信息的接口 2020-01-08 11:40:59 +08:00
heavyrain.lee e6ca9f5c78 更新channel接口,订阅/取消订阅只能逐个操作 2020-01-06 12:02:54 +08:00
heavyrain.lee 95beca8e05 fix build error 2020-01-05 23:47:57 +08:00
heavyrain.lee 7c4fe831b3 优化聊天室逻辑,添加聊天室空闲退出时间和发消息重新加入配置 2020-01-05 12:36:36 +08:00
heavyrain.lee e4da3780f5 添加频道sdk 2020-01-04 17:04:43 +08:00
heavyrain.lee 998f01a584 默认关掉设备在线状态回调 2020-01-04 14:51:46 +08:00
heavyrain.lee d71644c209 添加设置/获取备注api,添加用户在线状态回调功能 2020-01-04 14:47:27 +08:00
imhao183 70c390dea9 添加设置/获取备注api,添加用户在线状态回调功能 2020-01-04 14:45:49 +08:00
imhao183 41d326d40f 禁止私聊添加对机器人/管理员/物联网设备的例外 2020-01-04 14:35:34 +08:00
x86 1dbf431e4d 处理好友请求间隔 2020-01-02 10:15:25 +08:00
imhao183 0c0d712869 update 2019-12-30 16:00:14 +08:00
imhao183 73015c7a17 更新黑名单逻辑 2019-12-30 15:20:59 +08:00
imhao183 9baa1f9b23 修正编译错误,优化部分逻辑 2019-12-30 08:54:46 +08:00
imhao183 190da8934f 修改配置,增加如下功能
1,添加黑名单处理策略,发送失败或者忽略掉对方。
2,添加好友再次请求的时间间隔配置和好友请求被拒绝后再次请求的时间间隔配置
2019-12-30 08:48:17 +08:00
heavyrain.lee 9d1031594e 清除重复devicetoken时对包名进行判断,如果不同包名的client不清除重复推送token 2019-12-28 18:18:40 +08:00
imhao183 1d987c1569 remove useless files 2019-12-27 09:43:02 +08:00
imhao183 ac225b1541 添加退出群组api接口 2019-12-27 09:41:44 +08:00
imhao183 11375133ba 禁止发送消息给被封禁用户 2019-12-26 21:18:34 +08:00
imhao183 91b209d0fa 添加是否允许陌生人聊天的配置 2019-12-26 19:35:15 +08:00
imhao183 216860c573 update 2019-12-24 12:32:24 +08:00
imhao183 c7579877a1 修正sdk好友和频道接口错误 2019-12-24 11:43:47 +08:00
imhao183 6c5354d17b 优化session处理 2019-12-18 11:37:39 +08:00
imhao183 c98bd1fb9b update version to 0.36 2019-12-18 10:54:08 +08:00
imhao183 fe525e9483 优化用户session逻辑 2019-12-18 09:02:59 +08:00
imhao183 6acea9b140 创建群时同步操作 2019-12-17 14:39:49 +08:00
imhao183 4beb573052 add ignore 2019-12-17 14:36:49 +08:00
imhao183 220e010193 update version to 0.35 2019-12-13 13:02:27 +08:00
imhao183 865888824c Revert "Fix race condition during SensitiveFilter update"
This reverts commit ab7b20871a.
2019-12-13 12:58:07 +08:00
imhao183 583df110fd Revert "Fix potential GZIPOutputStream resource leak on exception"
This reverts commit 6c7d2ab3ad.
2019-12-13 12:57:59 +08:00
imhao183 f204bd693e Revert "fix build error"
This reverts commit 5cd2e7affe.
2019-12-13 12:57:46 +08:00
x86 b6be1ca045 fix pull message issue 2019-12-10 23:34:15 +08:00
imhao183 beb403a9f7 添加搜索类型 2019-12-06 10:36:26 +08:00
imhao183 00f1187200 添加群主进群时,修改群主的的状态问题 2019-12-05 15:42:20 +08:00
heavyrain.lee 7ee490cc1b update version to 0.34 2019-12-03 13:54:17 +08:00
imhao183 3dc0ae7a47 update 2019-12-03 08:24:15 +08:00
imhao183 994a8b5c4a 添加服务器为客户缓存消息数量的配置 2019-12-03 08:10:38 +08:00
imhao183 5cd2e7affe fix build error 2019-12-02 15:21:04 +08:00
heavyrain.lee f29ffe4cd2 Merge pull request #124 from k81/wildfirechat
Fix race condition during SensitiveFilter update and potential resource leak of GZIPOutputStream on exception
2019-12-02 14:43:55 +08:00
gitlab-runner 6c7d2ab3ad Fix potential GZIPOutputStream resource leak on exception 2019-11-30 23:05:28 +08:00
gitlab-runner ab7b20871a Fix race condition during SensitiveFilter update 2019-11-30 17:27:56 +08:00
imhao183 6883418e22 解决okhttp内存泄漏问题 2019-11-30 14:25:41 +08:00
fengyun 32f7266a5f add deleted property 2019-11-30 13:11:25 +08:00
fengyun 35db76e9cd set use deleted 2019-11-30 13:11:18 +08:00
heavyrain.lee 4f6aa87b9f update proto 2019-11-30 13:10:53 +08:00
fengyun f5816e20dd add get user group list and destroy user function 2019-11-30 11:46:52 +08:00
heavyrain2012 e72ae59e49 update version to 0.33 2019-11-28 17:33:35 +08:00
heavyrain2012 f0c2fd82ca 调整部分文件包名 2019-11-28 14:20:34 +08:00
heavyrain2012 5a588c8268 update 2019-11-27 12:57:07 +08:00
heavyrain2012 2adc030d6f 修改日志缺陷 2019-11-27 11:09:07 +08:00
heavyrain2012 b0ca8ed2b7 update 2019-11-27 10:39:32 +08:00
heavyrain2012 f9d5c861ce 优化用户session 2019-11-27 10:30:39 +08:00
heavyrain2012 9aaaa71ce9 清除掉重复token 2019-11-26 14:44:25 +08:00
heavyrain2012 73a971bde3 同步已读设置时,如果是排除掉已经解散的群组和销毁的频道 2019-11-25 18:50:00 +08:00
heavyrain2012 5942eb7b15 update version to 0.32 2019-11-25 12:12:06 +08:00
heavyrain2012 b97fea1841 添加索引 2019-11-25 11:22:36 +08:00
heavyrain2012 33637229d6 限制多端登陆 2019-11-25 11:19:53 +08:00
heavyrain2012 af366fcdb2 update proto 2019-11-25 11:19:44 +08:00
heavyrain2012 c9089c5d51 性能优化 2019-11-25 11:18:44 +08:00
imndx 3afc6a2da9 update readme 2019-11-24 20:37:45 +08:00
heavyrain2012 12d22e78bf 根据devicetoken进行排重 2019-11-21 20:51:52 +08:00
heavyrain2012 bcf969e56f 兼容旧客户端 2019-11-21 12:20:53 +08:00
heavyrain2012 b3a95c0310 update 2019-11-21 10:24:16 +08:00
heavyrain2012 ae863ab6e4 update version 2019-11-21 10:12:19 +08:00
heavyrain2012 688ca4e31d 黑名单功能 2019-11-21 09:36:41 +08:00
heavyrain2012 724b6dddd6 消息推送显示详情配置错误 2019-11-21 08:08:20 +08:00
heavyrain.lee bf3b30eb2c remove invalid message 2019-11-18 21:51:33 +08:00
heavyrain.lee 4f0545dfc1 update version to 0.30 2019-11-18 15:00:39 +08:00
heavyrain.lee c1b9a81e12 重构黑名单,修改代码 2019-11-18 14:53:20 +08:00
heavyrain.lee 029b6ed691 重构黑名单,修改协议message 2019-11-18 14:52:19 +08:00
heavyrain.lee 4fdefca33d 修改拉取离线消息最大包大小为512KB 2019-11-18 11:07:50 +08:00
heavyrain.lee e90ca145be 添加设置群管理员的api接口 2019-11-14 16:11:33 +08:00
heavyrain.lee bf3d3c308d Update README.md 2019-11-13 20:28:23 +08:00
heavyrain.lee bee2f3c6ef 转移群主问题修复 2019-11-13 15:27:23 +08:00
heavyrain.lee d4e13e4578 删除敏感词后立即生效 2019-11-09 16:55:11 +08:00
heavyrain2012 66486b019f update 2019-11-04 20:30:48 +08:00
heavyrain.lee 6510204fe6 Merge pull request #104 from wildfirechat/dependabot/maven/broker/org.eclipse.paho-org.eclipse.paho.client.mqttv3-1.2.1
Bump org.eclipse.paho.client.mqttv3 from 1.0.2 to 1.2.1 in /broker
2019-11-03 20:36:09 +08:00
heavyrain.lee b4b736c0c4 Merge pull request #105 from k81/wildfirechat
remove duplicated code in reloadUserMessageMaps()
2019-11-03 20:35:23 +08:00
heavyrain.lee c4e8c5811c fix error 2019-11-03 17:02:21 +08:00
k 804b2fc610 remove duplicated code in reloadUserMessageMaps() 2019-11-03 10:55:57 +08:00
dependabot[bot] 0a7487a144 Bump org.eclipse.paho.client.mqttv3 from 1.0.2 to 1.2.1 in /broker
Bumps org.eclipse.paho.client.mqttv3 from 1.0.2 to 1.2.1.

Signed-off-by: dependabot[bot] <[email protected]>
2019-11-02 09:16:14 +00:00
heavyrain.lee 4ae9de0628 去掉sun的私有api 2019-11-02 08:44:47 +08:00
heavyrain.lee 04d0f717d8 精确搜索时,只搜索用户名和电话号码,不搜索昵称 2019-10-31 09:47:36 +08:00
heavyrain2012 b3ddda78a1 update version to 0.29 2019-10-23 21:07:43 +08:00
heavyrain2012 066e74be5f fix sdk error 2019-10-23 21:04:27 +08:00
heavyrain.lee f9c2cf8e31 update 2019-10-22 11:52:11 +08:00
heavyrain.lee d3fc625299 update 2019-10-22 09:57:26 +08:00
imndx 905763b6e3 update readme 2019-10-19 17:35:45 +08:00
imndx 877b7bc217 fix typo 2019-10-19 17:35:19 +08:00
heavyrain.lee d8b7f6a8de Update README.md 2019-10-11 16:12:48 +08:00
heavyrain.lee 579d3b529f 更新说明 2019-10-10 20:16:36 +08:00
heavyrain.lee 065a79adc3 调整代码 2019-10-10 20:14:17 +08:00
imndx 5ed45ef414 fix message content issue. fix wildfirechat/android-chat#184 2019-10-10 15:35:03 +08:00
heavyrain.lee ba671b3370 update 2019-10-09 20:22:02 +08:00
heavyrain.lee e975f20a6b alter voip token length to 240 2019-10-09 20:21:02 +08:00
heavyrain.lee 70bdebd4a0 update version to 0.27 2019-09-24 08:26:35 +08:00
heavyrain.lee 409078801e update 2019-09-24 08:23:25 +08:00
heavyrain2012 06fed7de52 update version to 0.26 2019-09-13 23:12:05 +08:00
heavyrain2012 da2428a9f9 modify windows bash error 2019-09-13 23:09:29 +08:00
heavyrain.lee 83b6ae5229 修改pc等平台pushtype越界问题 2019-09-07 14:50:34 +08:00
heavyrain.lee 4b7804cdfd update 2019-09-02 21:54:03 +08:00
heavyrain.lee d075b5212f 修改用户信息和频道信息时,改成同步修改 2019-09-01 22:16:49 +08:00
heavyrain.lee 636c8ce219 管理员撤回消息 2019-08-30 22:17:17 +08:00
heavyrain.lee 091d7fa3ec 解决乱码问题 2019-08-24 17:56:29 +08:00
heavyrain.lee 59356e5b52 获取敏感词列表按照逆序排列 2019-08-23 11:38:42 +08:00
heavyrain.lee 882bf29e63 1,更新群主时更新群成员信息中的群主状态
2,在敏感词列表中添加id列
2019-08-23 08:39:29 +08:00
heavyrain.lee f3eef4dd6b 添加修改群组信息接口 2019-08-17 10:30:39 +08:00
heavyrain.lee 898983c6e5 rm useless code 2019-08-10 22:14:46 +08:00
heavyrain.lee dae5d032fa 添加拉取历史消息开关。设置默认消息漫游和拉取都为关闭,这样无法删除问题就不会再出现 2019-08-09 13:44:44 +08:00
heavyrain.lee ff76622796 1, 修改撤回逻辑 2019-08-09 09:00:14 +08:00
heavyrain.lee a6fd11a243 修复部分接口 2019-08-08 08:28:00 +08:00
heavyrain2012 cf983be44c Update README.md 2019-08-07 23:48:59 +08:00
heavyrain.lee 1a72de0e85 修改群组反复添加删除群成员的问题 2019-08-06 22:55:52 +08:00
heavyrain.lee bf70a1e80e 退出群组时,删掉群相关用户设置 2019-08-06 22:55:41 +08:00
heavyrain.lee 512d797d4d Revert "jdbc character set to utf8mb4"
This reverts commit 608184ef9e.
2019-08-03 22:27:29 +08:00
heavyrain.lee 4a46dd1dde 添加组播消息 2019-08-03 20:43:27 +08:00
heavyrain lee 7494cdc071 update 2019-08-02 13:24:30 +08:00
heavyrain.lee 608184ef9e jdbc character set to utf8mb4 2019-08-02 13:16:15 +08:00
heavyrain2012 eb4d8a9b68 Update README.md 2019-07-30 18:22:03 +08:00
heavyrain.lee 6b92ae383c add weixin qrcode 2019-07-30 18:20:27 +08:00
heavyrain.lee 5fee857c4a add system settings 2019-07-30 07:54:49 +08:00
heavyrain2012 53b7139c30 Update README.md 2019-07-29 15:37:27 +08:00
imndx 83e864d667 update readme 2019-07-28 19:15:26 +08:00
imndx b88297e6de update readme 2019-07-26 22:52:39 +08:00
heavyrain lee 4aa2f46011 添加默认敏感词 2019-07-23 08:26:16 +08:00
heavyrain lee 1fb3c757e2 撤回消息时,先删除旧消息,再插入新消息 2019-07-21 22:29:24 +08:00
heavyrain lee 3f640b6c14 sync with commercial 2019-07-21 15:11:24 +08:00
heavyrain lee 49eb57af00 update version to 0.25 2019-07-21 13:09:07 +08:00
heavyrain lee 6bd6a80e71 添加是否接收历史消息的开关 2019-07-21 12:20:26 +08:00
heavyrain lee 38b84f0fa5 解决封禁用户无法踢用户下线的问题 2019-07-21 12:18:38 +08:00
heavyrain lee 1f46debd0c Fix test case error 2019-07-21 11:44:54 +08:00
heavyrain.lee 37998e76d1 添加漫游开关 2019-07-19 20:52:25 +08:00
heavyrain.lee 80b5508533 添加消息漫游开关 2019-07-19 20:51:52 +08:00
heavyrain lee a646cc8227 去掉启动时预读数据,加快启动速度 2019-07-17 00:36:17 +08:00
heavyrain lee 92692adf52 解决群组成员反复添加删除错误问题 2019-07-17 00:31:21 +08:00
heavyrain lee b2cfc2b99b add metrics for user channel 2019-07-16 21:56:51 +08:00
heavyrain lee 86e08f6151 Remove useless code 2019-07-16 21:32:55 +08:00
heavyrain lee 8d852eb2d1 退出群组时,删掉收藏的群组 2019-07-15 22:36:19 +08:00
heavyrain lee 8bef018911 转让群主后更新群组信息时间戳 2019-07-13 18:50:23 +08:00
heavyrain lee 5e13419c28 修改群管理逻辑错误 2019-07-13 18:31:08 +08:00
heavyrain.lee 503a7ae18f 取消管理员 2019-07-12 09:17:11 +08:00
1149 changed files with 10698 additions and 444772 deletions
+5
View File
@@ -49,8 +49,13 @@ maven-metadata.xml
/bin/
broker/config/git.properties
distribution/src/main/resources/git.properties
license/target
server/target
broker/h2db
broker/media
common/target
server/target
sdk/target
moments/target
monitor
wildfire_jmeter
+28 -15
View File
@@ -1,18 +1,19 @@
## 野火IM解决方案
野火IM是一套跨平台、全开源的即时通讯解决方案,主要包含以下内容。
野火IM是一套跨平台、核心功能开源的即时通讯解决方案,主要包含以下内容。
| 仓库 | 说明 | 备注 |
| ------------------------------------------------------------ | ------------------------------------------------------- | ---------------------------------------------- |
| [android-chat](https://github.com/wildfirechat/android-chat) | 野火IM Android SDK源码和App源码 | 可以很方便地进行二次开发,或集成到现有应用当中 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | 野火IM iOS SDK源码和App源码 | 可以很方便地进行二次开发,或集成到现有应用当中 |
| [pc-chat](https://github.com/wildfirechat/pc-chat) | 基于[Electron](https://electronjs.org/)开发的PC平台应用 | |
| [proto](https://github.com/wildfirechat/proto) | 野火IM的协议栈实现 | |
| [server](https://github.com/wildfirechat/server) | IM server | |
| [app server](https://github.com/wildfirechat/app_server) | 应用服务端 | |
| [robot_server](https://github.com/wildfirechat/robot_server) | 机器人服务端 | |
| [push_server](https://github.com/wildfirechat/push_server) | 推送服务器 | |
| [docs](https://github.com/wildfirechat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明 | |
| 仓库 | 说明 | 备注 |
| ------------------------------------------------------------ | ------------------------------------------------------- | ---- |
| [android-chat](https://github.com/wildfirechat/android-chat) | 野火IM Android SDK源码和App源码 |可以很方便地进行二次开发,或集成到现有应用当中 |
| [ios-chat](https://github.com/wildfirechat/ios-chat) | 野火IM iOS SDK源码和App源码 |可以很方便地进行二次开发,或集成到现有应用当中 |
| [pc-chat](https://github.com/wildfirechat/pc-chat) | 基于[Electron](https://electronjs.org/)开发的PC平台应用 | |
| [web-chat](https://github.com/wildfirechat/web-chat) | Web平台的Demo, [体验地址](http://web.wildfirechat.cn) | |
| [wx-chat](https://github.com/wildfirechat/wx-chat) | 微信小程序平台的Demo | |
| [server](https://github.com/wildfirechat/server) | IM server | |
| [app server](https://github.com/wildfirechat/app_server) | 应用服务端 | |
| [robot_server](https://github.com/wildfirechat/robot_server) | 机器人服务端 | |
| [push_server](https://github.com/wildfirechat/push_server) | 推送服务器 | |
| [docs](https://github.com/wildfirechat/docs) | 野火IM相关文档,包含设计、概念、开发、使用说明 | | | |
# server
本工程为野火IM 社区版IM服务软件。野火IM作为一个通用的即时通讯解决方案,可以集成到各种应用中。请阅读[docs](http://docs.wildfirechat.cn)或下载服务器[发布版本](https://github.com/wildfirechat/server/releases)
@@ -20,11 +21,18 @@
开发一套IM系统真的很艰辛,请路过的朋友们给点个star,支持我们坚持下去🙏🙏🙏🙏🙏
### 联系我们
问题讨论请加群:822762829
微信公众号:
> 商务合作请优先采用邮箱和我们联系。技术问题请到[野火IM论坛](http://bbs.wildfirechat.cn/)发帖交流
1. heavyrain.lee 邮箱: heavyrain.lee@wildfirechat.cn 微信:wildfirechat
2. imndx 邮箱: imndx@wildfirechat.cn 微信:wfchat
### 问题交流
1. 如果大家发现bug,请在GitHub提issue
2. 其他问题,请到[野火IM论坛](http://bbs.wildfirechat.cn/)进行交流学习
3. 微信公众号
<img src="http://static.wildfirechat.cn/wx_wfc_qrcode.jpg" width = 50% height = 50% />
@@ -35,6 +43,11 @@
![野火IM](http://static.wildfirechat.cn/download_qrcode.png)
#### 加入野火官方体验交流群
使用野火IM移动客户端扫码下面二维码(不是使用微信扫码),和野火IM的爱好者一起畅谈野火IM的问题和经验:
![野火IM官方交流群](http://static.wildfirechat.cn/wildfirechat_official_group.jpeg)
#### 应用截图
![ios-demo](http://static.wildfirechat.cn/ios-demo.gif)
+4
View File
@@ -1,3 +1,7 @@
<!-- # 本配置文件为debug使用,修改这里不会在Release包中生效。Release中包含的配置文件在 ${Porject_Path}/distribution/src/main/resources目录下-->
<hazelcast
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.hazelcast.com/schema/config
+60 -4
View File
@@ -1,9 +1,18 @@
#
#
# 本配置文件为debug使用,修改这里不会在Release包中生效。Release中包含的配置文件在 ${Porject_Path}/distribution/src/main/resources目录下
#
#
#*********************************************************************
# Server configuration
#*********************************************************************
#服务器的接入IP。给客户端提供是${server.ip}和${http_port}。
#客户端会从${http_port}端口获取到长链接端口。
server.ip 192.168.1.100
#客户端会从${http_port}端口获取到长链接端口。这个地址一定要改成客户端可以访问到的IP地址
# (如果您部署云服务器上或者具有独立公网出口的服务器上,请改为对应的公网IP;如果您部署在在内网环境下,在内网使用,这个地方改成内网地址)
server.ip 0.0.0.0
##原生客户端长链接端口
port 1883
@@ -26,7 +35,7 @@ host 0.0.0.0
##本地绑定端口
local_port 80
##是否使用内置DB。0使用mysql;1使用h2db
##是否使用内置DB。0使用mysql;1使用h2db;2使用mysql+mongodb。社区版只支持0和1,专业版支持2
embed.db 0
##服务器管理接口密钥
@@ -34,6 +43,7 @@ http.admin.secret_key 123456
##服务器API接口参数是否检查时间。当设置为false时,所有的请求会检查时间的有效性;当设置为true时,可以在http.admin.secret_key保持不变的情况下,使用固定的服务API签名
##nonce = "76616", timestamp = "1558350862502", sign = "b98f9b0717f59febccf1440067a7f50d9b31bdde"
##这个地方上线前要改为false
http.admin.no_check_time true
##客户端协议栈密钥,需要与客户端协议栈文件libemqq.cc文件中的aes_key值保持一致,16进制byte用逗号分割。这个密钥对安全性帮助不大,客户不要修改
@@ -42,6 +52,34 @@ client.proto.secret_key 0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0
##用来生产im token的私钥,只在服务器使用,客户端不用。正式使用时为了安全一定要修改这个值,切记切记
token.key testim
##首次登录,是否接收之前的历史消息。0 不接收;1接收。
message.roaming 0
##是否开启拉取远程历史消息
message.remote_history_message 0
##服务器为每个用户缓存的消息数量。这个值改得太大,拉取消息时间变长,另外会占用大量内存。
message.max_queue 1024
##是否禁止陌生人聊天
message.disable_stranger_chat false
##黑名单处理策略,0 发送失败,返回被拉黑的错误码;1 发送成功但消息被服务器直接丢弃;
message.blacklist.strategy 0
##好友请求重复发送的间隔,单位是毫秒,默认是7天,0为不限制
friend.repeat_request_duration 604800000
##好友请求被拒绝后再次发送的间隔,单位是毫秒,默认是30天,0为不限制
friend.reject_request_duration 2592000000
## 聊天室观众空闲退出时间,单位为毫秒,默认为15分钟,0为永远不退出
chatroom.participant_idle_time 900000
## 用户向聊天室发送消息,自动加入聊天室
chatroom.rejoin_when_active true
#*********************************************************************
# hazelcase configuration
#*********************************************************************
@@ -86,6 +124,9 @@ qiniu.bucket_favorite_domain http://cdn2.wildfirechat.cn
# 本地媒体服务器配置。
local.media.storage.root media
# 是否支持多端登陆
server.multi_endpoint false
#*********************************************************************
# Push server configuration
#*********************************************************************
@@ -94,11 +135,26 @@ push.android.server.address http://localhost:8085/android/push
##苹果推送服务器地址
push.ios.server.address http://localhost:8085/ios/push
#*********************************************************************
# User online status event callback
#*********************************************************************
##用户在线状态事件回调地址
#user.online_status_callback http://localhost:8888/user/online_event
#*********************************************************************
# MongoDB configuration
#*********************************************************************
#专业版支持,社区版请忽略
#mongodb.client_uri mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]
#mongodb.client_uri mongodb://host1:27017,host2:27017,host3:27017/wfchat?replicaSet=myReplicaSet
mongodb.client_uri mongodb://wfc:123456@localhost:27017/wfchat
mongodb.database wfchat
mongodb.data_expire_days 1096
#*********************************************************************
# Sensitive configuration
#*********************************************************************
##文本敏感词过滤处理方法,0 发送失败;1 发送成功但消息被服务器直接丢弃;2 命中的敏感词被替换成***
##文本敏感词过滤处理方法,0 发送失败;1 发送成功但消息被服务器直接丢弃;2 命中的敏感词被替换成***;3 记录并继续发送
sensitive.filter.type 0
#*********************************************************************
+10
View File
@@ -0,0 +1,10 @@
DROP TABLE IF EXISTS `t_settings`;
CREATE TABLE `t_settings` (
`id` int(11) NOT NULL PRIMARY KEY,
`value` varchar(64) NOT NULL,
`desc` varchar(128) NOT NULL
);
insert into t_settings(`id`, `value`,`desc`) values (1, '3000', '最大群成员数量');
+1
View File
@@ -0,0 +1 @@
alter table `t_sensitiveword` add column `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT;
+3
View File
@@ -0,0 +1,3 @@
alter table `t_user_session` modify column `_voip_token` varchar(240) DEFAULT '';
+2
View File
@@ -0,0 +1,2 @@
alter table t_friend add column `_blacked` tinyint DEFAULT 0;
update t_friend set `_blacked` = 1 where `_state` = 2;
+1
View File
@@ -0,0 +1 @@
alter table `t_user_session` ADD INDEX `session_token_index` ( `_token` );
+1
View File
@@ -0,0 +1 @@
alter table t_friend_request add index `friend_request_uid_index` (`_friend_uid`);
+1
View File
@@ -0,0 +1 @@
alter table `t_user_session` ADD INDEX `session_uid_index` ( `_uid` );
+1
View File
@@ -0,0 +1 @@
alter table `t_user_session` add column `_deleted` tinyint DEFAULT 0;
+1
View File
@@ -0,0 +1 @@
alter table `t_user` add column `_deleted` tinyint DEFAULT 0;
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
DROP TABLE IF EXISTS `t_settings`;
CREATE TABLE `t_settings` (
`id` int(11) NOT NULL PRIMARY KEY,
`value` varchar(64) NOT NULL,
`desc` varchar(128) NOT NULL default ""
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci;
insert into t_settings(`id`, `value`,`desc`) values (1, "3000", "最大群成员数量");
+1
View File
@@ -0,0 +1 @@
alter table `t_sensitiveword` add column `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT;
+3
View File
@@ -0,0 +1,3 @@
alter table `t_user_session` modify column `_voip_token` varchar(240) DEFAULT '';
+2
View File
@@ -0,0 +1,2 @@
alter table t_friend add column `_blacked` tinyint DEFAULT 0 COMMENT "0, normal; 1, blacked";
update t_friend set `_blacked` = 1 where `_state` = 2;
@@ -0,0 +1 @@
alter table `t_user_session` ADD INDEX `session_token_index` ( `_token` );
+1
View File
@@ -0,0 +1 @@
alter table t_friend_request add index `friend_request_uid_index` (`_friend_uid`);
+1
View File
@@ -0,0 +1 @@
alter table `t_user_session` ADD INDEX `session_uid_index` ( `_uid` );
+1
View File
@@ -0,0 +1 @@
alter table `t_user_session` add column `_deleted` tinyint DEFAULT 0;
+1
View File
@@ -0,0 +1 @@
alter table `t_user` add column `_deleted` tinyint DEFAULT 0;
Executable → Regular
+9 -27
View File
@@ -5,7 +5,7 @@
<relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.24</version>
<version>0.37</version>
</parent>
<artifactId>moquette-broker</artifactId>
@@ -31,12 +31,6 @@
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>cn.wildfirechat</groupId>
<artifactId>client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>cn.wildfirechat</groupId>
<artifactId>common</artifactId>
@@ -110,28 +104,11 @@
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.version}</version>
<classifier>linux-x86_64</classifier>
<!--
<!--
<classifier>${os.detected.name}-${os.detected.arch}</classifier>
-->
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-jvm</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>com.librato.metrics</groupId>
<artifactId>metrics-librato</artifactId>
<version>5.1.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
@@ -164,7 +141,7 @@
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>[0.9.5.3,)</version>
<version>0.9.5.4</version>
</dependency>
<!-- needs extra dependencies: objenesis & hamcrest -->
@@ -184,7 +161,7 @@
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.0.2</version>
<version>1.2.1</version>
</dependency>
<dependency>
@@ -250,6 +227,11 @@
<version>5.2.4</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.1</version>
</dependency>
</dependencies>
@@ -57,7 +57,7 @@ public class PushServer {
}
public void pushMessage(PushMessage pushMessage, String deviceId, String pushContent) {
LOG.info("try to delivery push diviceId = {}, pushContent", deviceId, pushContent);
LOG.info("try to delivery push diviceId = {}, pushContent = {}", deviceId, pushContent);
executorService.execute(() ->{
try {
pushMessageInternel(pushMessage, deviceId, pushContent);
@@ -22,6 +22,8 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import cn.wildfirechat.common.ErrorCode;
import java.nio.charset.StandardCharsets;
import static cn.wildfirechat.common.ErrorCode.ERROR_CODE_SUCCESS;
/**
@@ -84,7 +86,7 @@ abstract public class Action {
if (request instanceof FullHttpRequest) {
FullHttpRequest fullHttpRequest = (FullHttpRequest) request;
byte[] bytes = Utils.readBytesAndRewind(fullHttpRequest.content());
String content = new String(bytes);
String content = new String(bytes, StandardCharsets.UTF_8);//contribute by JiaRG from github
Gson gson = new Gson();
T t = gson.fromJson(content, cls);
return t;
@@ -57,7 +57,7 @@ public class RouteAction extends Action {
try {
bytes = Base64.getDecoder().decode(str);
} catch (IllegalArgumentException e) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
sendResponse(response, ErrorCode.ERROR_CODE_INVALID_DATA, null, base64Response);
return true;
}
@@ -65,7 +65,7 @@ public class RouteAction extends Action {
byte[] cbytes = Base64.getDecoder().decode(cid);
cbytes = AES.AESDecrypt(cbytes, "", true);
if (cbytes == null) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
sendResponse(response, ErrorCode.ERROR_CODE_INVALID_DATA, null, base64Response);
return true;
}
cid = new String(cbytes);
@@ -74,7 +74,7 @@ public class RouteAction extends Action {
byte[] ubytes = Base64.getDecoder().decode(uid);
ubytes = AES.AESDecrypt(ubytes, "", true);
if (ubytes == null) {
sendResponse(response, ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH, null, base64Response);
sendResponse(response, ErrorCode.ERROR_CODE_INVALID_DATA, null, base64Response);
return true;
}
uid = new String(ubytes);
@@ -82,7 +82,7 @@ public class RouteAction extends Action {
MemorySessionStore.Session session = sessionsStore.sessionForClientAndUser(uid, cid);
if (session == null) {
ErrorCode errorCode = sessionsStore.createNewSession(uid, cid, true, true);
ErrorCode errorCode = sessionsStore.loadActiveSession(uid, cid);
if (errorCode != ErrorCode.ERROR_CODE_SUCCESS) {
sendResponse(response, errorCode, null, base64Response);
return true;
@@ -50,7 +50,7 @@ abstract public class AdminAction extends Action {
Long ts;
try {
ts = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
} catch (Exception e) {
e.printStackTrace();
return ErrorCode.INVALID_PARAMETER;
}
@@ -0,0 +1,58 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.pojos.InputGetAlias;
import cn.wildfirechat.pojos.InputGetFriendList;
import cn.wildfirechat.pojos.OutputGetAlias;
import cn.wildfirechat.pojos.OutputStringList;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.model.FriendData;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import java.util.ArrayList;
import java.util.List;
@Route(APIPath.Friend_Get_Alias)
@HttpMethod("POST")
public class AliasGetAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputGetAlias input = getRequestBody(request.getNettyRequest(), InputGetAlias.class);
List<FriendData> dataList = messagesStore.getFriendList(input.getOperator(), null, 0);
List<String> list = new ArrayList<>();
OutputGetAlias out = new OutputGetAlias(input.getOperator(), input.getTargetId());
for (FriendData data : dataList) {
if (data.getFriendUid().equals(input.getTargetId())) {
out.setAlias(data.getAlias());
break;
}
}
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok(out);
response.setContent(new Gson().toJson(result));
}
return true;
}
}
@@ -0,0 +1,49 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputGetFriendList;
import cn.wildfirechat.pojos.InputUpdateAlias;
import cn.wildfirechat.pojos.OutputStringList;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import com.xiaoleilu.loServer.model.FriendData;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import java.util.ArrayList;
import java.util.List;
@Route(APIPath.Friend_Set_Alias)
@HttpMethod("POST")
public class AliasPutAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputUpdateAlias input = getRequestBody(request.getNettyRequest(), InputUpdateAlias.class);
ErrorCode errorCode = messagesStore.setFriendAliasRequest(input.getOperator(), input.getTargetId(), input.getAlias(), new long[1]);
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(errorCode);
response.setContent(new Gson().toJson(result));
}
return true;
}
}
@@ -0,0 +1,98 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.*;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.IMTopic;
import win.liyufan.im.UUIDGenerator;
import java.util.concurrent.Executor;
@Route(APIPath.Create_Channel)
@HttpMethod("POST")
public class CreateChannelAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputCreateChannel inputCreateChannel = getRequestBody(request.getNettyRequest(), InputCreateChannel.class);
if (inputCreateChannel != null
&& !StringUtil.isNullOrEmpty(inputCreateChannel.getName())
&& !StringUtil.isNullOrEmpty(inputCreateChannel.getOwner())) {
if(StringUtil.isNullOrEmpty(inputCreateChannel.getTargetId())) {
inputCreateChannel.setTargetId(messagesStore.getShortUUID());
}
RPCCenter.getInstance().sendRequest(inputCreateChannel.getOwner(), null, IMTopic.CreateChannelTopic, inputCreateChannel.toProtoChannelInfo().toByteArray(), inputCreateChannel.getOwner(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
byte[] data = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(data);
String secret = new String(data);
sendResponse(response, null, new OutputCreateChannel(inputCreateChannel.getTargetId(), secret));
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
}, true);
return false;
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -0,0 +1,97 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputDestroyUser;
import cn.wildfirechat.pojos.InputOutputUserInfo;
import cn.wildfirechat.pojos.OutputCreateUser;
import cn.wildfirechat.pojos.OutputGetIMTokenData;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import win.liyufan.im.IMTopic;
import win.liyufan.im.UUIDGenerator;
import java.util.Base64;
import java.util.concurrent.Executor;
@Route(APIPath.Destroy_User)
@HttpMethod("POST")
public class DestroyUserAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputDestroyUser inputDestroyUser = getRequestBody(request.getNettyRequest(), InputDestroyUser.class);
if (inputDestroyUser != null
&& !StringUtil.isNullOrEmpty(inputDestroyUser.getUserId())) {
WFCMessage.IDBuf idBuf = WFCMessage.IDBuf.newBuilder().setId(inputDestroyUser.getUserId()).build();
RPCCenter.getInstance().sendRequest(inputDestroyUser.getUserId(), null, IMTopic.DestroyUserTopic, idBuf.toByteArray(), inputDestroyUser.getUserId(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ErrorCode errorCode1 = ErrorCode.fromCode(result[0]);
if (errorCode1 == ErrorCode.ERROR_CODE_SUCCESS) {
//ba errorcode qudiao
byte[] data = new byte[result.length -1];
for (int i = 0; i < data.length; i++) {
data[i] = result[i+1];
}
String token = Base64.getEncoder().encodeToString(data);
sendResponse(response, null, null);
} else {
sendResponse(response, errorCode1, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
}, true);
return false;
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -9,6 +9,7 @@
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.pojos.OutputStringList;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
@@ -36,7 +37,7 @@ public class FriendRelationGetAction extends AdminAction {
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputGetFriendList inputGetFriendList = getRequestBody(request.getNettyRequest(), InputGetFriendList.class);
List<FriendData> dataList = messagesStore.getFriendList(inputGetFriendList.getUserId(), 0);
List<FriendData> dataList = messagesStore.getFriendList(inputGetFriendList.getUserId(), null, 0);
List<String> list = new ArrayList<>();
for (FriendData data : dataList) {
if (data.getState() == inputGetFriendList.getStatus()) {
@@ -44,7 +45,7 @@ public class FriendRelationGetAction extends AdminAction {
}
}
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.ok(list);
RestResult result = RestResult.ok(new OutputStringList(list));
response.setContent(new Gson().toJson(result));
}
return true;
@@ -41,7 +41,7 @@ public class GetIMTokenAction extends AdminAction {
String userId = input.getUserId();
WFCMessage.GetTokenRequest getTokenRequest = WFCMessage.GetTokenRequest.newBuilder().setUserId(userId).setClientId(input.getClientId()).build();
WFCMessage.GetTokenRequest getTokenRequest = WFCMessage.GetTokenRequest.newBuilder().setUserId(userId).setClientId(input.getClientId()).setPlatform(input.getPlatform() == null ? 0 : input.getPlatform()).build();
RPCCenter.getInstance().sendRequest(userId, input.getClientId(), IMTopic.GetTokenTopic, getTokenRequest.toByteArray(), userId, TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
@@ -0,0 +1,61 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputGetUserInfo;
import cn.wildfirechat.pojos.InputOutputUserInfo;
import cn.wildfirechat.pojos.SystemSettingPojo;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
@Route(APIPath.Get_System_Setting)
@HttpMethod("POST")
public class GetSystemSettingAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return false;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
SystemSettingPojo inputUserId = getRequestBody(request.getNettyRequest(), SystemSettingPojo.class);
if (inputUserId != null) {
SystemSettingPojo output = messagesStore.getSystemSetting(inputUserId.id);
response.setStatus(HttpResponseStatus.OK);
RestResult result;
if (output == null) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_NOT_EXIST);
} else {
result = RestResult.ok(output);
}
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -0,0 +1,63 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputGetGroup;
import cn.wildfirechat.pojos.InputUserId;
import cn.wildfirechat.pojos.OutputGroupIds;
import cn.wildfirechat.pojos.PojoGroupInfo;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList;
import java.util.Set;
@Route(APIPath.Get_User_Groups)
@HttpMethod("POST")
public class GetUserGroupsAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputUserId inputUserId = getRequestBody(request.getNettyRequest(), InputUserId.class);
if (inputUserId != null
&& (!StringUtil.isNullOrEmpty(inputUserId.getUserId()))) {
Set<String> groupIds = messagesStore.getUserGroupIds(inputUserId.getUserId());
response.setStatus(HttpResponseStatus.OK);
OutputGroupIds outputGroupIds = new OutputGroupIds();
outputGroupIds.setGroupIds(new ArrayList<>(groupIds));
RestResult result = RestResult.ok(outputGroupIds);
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -0,0 +1,85 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputAddGroupMember;
import cn.wildfirechat.pojos.InputModifyGroupInfo;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route(APIPath.Group_Modify_Info)
@HttpMethod("POST")
public class ModifyGroupInfoAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputModifyGroupInfo inputAddGroupMember = getRequestBody(request.getNettyRequest(), InputModifyGroupInfo.class);
if (inputAddGroupMember.isValide()) {
RPCCenter.getInstance().sendRequest(inputAddGroupMember.getOperator(), null, IMTopic.ModifyGroupInfoTopic, inputAddGroupMember.toProtoGroupRequest().toByteArray(), inputAddGroupMember.getOperator(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
sendResponse(response, null, null);
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
}, true);
return false;
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -0,0 +1,90 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.BroadMessageData;
import cn.wildfirechat.pojos.BroadMessageResult;
import cn.wildfirechat.pojos.MultiMessageResult;
import cn.wildfirechat.pojos.MulticastMessageData;
import com.google.gson.Gson;
import com.hazelcast.util.StringUtil;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route(APIPath.Msg_Multicast)
@HttpMethod("POST")
public class MulticastMessageAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
MulticastMessageData sendMessageData = getRequestBody(request.getNettyRequest(), MulticastMessageData.class);
if (MulticastMessageData.isValide(sendMessageData)) {
RPCCenter.getInstance().sendRequest(sendMessageData.getSender(), null, IMTopic.MultiCastMessageTopic, sendMessageData.toProtoMessage().toByteArray(), sendMessageData.getSender(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
long messageId = byteBuf.readLong();
long timestamp = byteBuf.readLong();
sendResponse(response, null, new MultiMessageResult(messageId, timestamp));
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
}, true);
return false;
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -0,0 +1,64 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputGetUserInfo;
import cn.wildfirechat.pojos.InputOutputUserInfo;
import cn.wildfirechat.pojos.SystemSettingPojo;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.internal.StringUtil;
@Route(APIPath.Put_System_Setting)
@HttpMethod("POST")
public class PutSystemSettingAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
SystemSettingPojo inputUserId = getRequestBody(request.getNettyRequest(), SystemSettingPojo.class);
if (inputUserId != null
&& (!StringUtil.isNullOrEmpty(inputUserId.value) || !StringUtil.isNullOrEmpty(inputUserId.desc))) {
boolean success = messagesStore.updateSystemSetting(inputUserId.id, inputUserId.value, inputUserId.desc);
response.setStatus(HttpResponseStatus.OK);
RestResult result;
if (!success) {
result = RestResult.resultOf(ErrorCode.ERROR_CODE_SERVER_ERROR);
} else {
result = RestResult.ok();
}
response.setContent(new Gson().toJson(result));
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -0,0 +1,85 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputKickoffGroupMember;
import cn.wildfirechat.pojos.InputQuitGroup;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route(APIPath.Group_Member_Quit)
@HttpMethod("POST")
public class QuitGroupMemberAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputQuitGroup inputQuitGroup = getRequestBody(request.getNettyRequest(), InputQuitGroup.class);
if (inputQuitGroup.isValide()) {
RPCCenter.getInstance().sendRequest(inputQuitGroup.getOperator(), null, IMTopic.QuitGroupTopic, inputQuitGroup.toProtoGroupRequest().toByteArray(), inputQuitGroup.getOperator(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
sendResponse(response, null, null);
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
}, true);
return false;
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -0,0 +1,85 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package com.xiaoleilu.loServer.action.admin;
import cn.wildfirechat.common.APIPath;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.InputAddGroupMember;
import cn.wildfirechat.pojos.InputSetGroupManager;
import com.google.gson.Gson;
import com.xiaoleilu.loServer.RestResult;
import com.xiaoleilu.loServer.annotation.HttpMethod;
import com.xiaoleilu.loServer.annotation.Route;
import com.xiaoleilu.loServer.handler.Request;
import com.xiaoleilu.loServer.handler.Response;
import io.moquette.persistence.RPCCenter;
import io.moquette.persistence.TargetEntry;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import win.liyufan.im.IMTopic;
import java.util.concurrent.Executor;
@Route(APIPath.Group_Set_Manager)
@HttpMethod("POST")
public class SetGroupManagerAction extends AdminAction {
@Override
public boolean isTransactionAction() {
return true;
}
@Override
public boolean action(Request request, Response response) {
if (request.getNettyRequest() instanceof FullHttpRequest) {
InputSetGroupManager inputAddGroupMember = getRequestBody(request.getNettyRequest(), InputSetGroupManager.class);
if (inputAddGroupMember.isValide()) {
RPCCenter.getInstance().sendRequest(inputAddGroupMember.getOperator(), null, IMTopic.SetGroupManagerTopic, inputAddGroupMember.toProtoGroupRequest().toByteArray(), inputAddGroupMember.getOperator(), TargetEntry.Type.TARGET_TYPE_USER, new RPCCenter.Callback() {
@Override
public void onSuccess(byte[] result) {
ByteBuf byteBuf = Unpooled.buffer();
byteBuf.writeBytes(result);
ErrorCode errorCode = ErrorCode.fromCode(byteBuf.readByte());
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
sendResponse(response, null, null);
} else {
sendResponse(response, errorCode, null);
}
}
@Override
public void onError(ErrorCode errorCode) {
sendResponse(response, errorCode, null);
}
@Override
public void onTimeout() {
sendResponse(response, ErrorCode.ERROR_CODE_TIMEOUT, null);
}
@Override
public Executor getResponseExecutor() {
return command -> {
ctx.executor().execute(command);
};
}
}, true);
return false;
} else {
response.setStatus(HttpResponseStatus.OK);
RestResult result = RestResult.resultOf(ErrorCode.INVALID_PARAMETER);
response.setContent(new Gson().toJson(result));
}
}
return true;
}
}
@@ -45,7 +45,7 @@ abstract public class ChannelAction extends Action {
Long ts;
try {
ts = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
} catch (Exception e) {
e.printStackTrace();
return ErrorCode.INVALID_PARAMETER;
}
@@ -47,7 +47,7 @@ abstract public class RobotAction extends Action {
Long ts;
try {
ts = Long.parseLong(timestamp);
} catch (NumberFormatException e) {
} catch (Exception e) {
e.printStackTrace();
return ErrorCode.INVALID_PARAMETER;
}
@@ -51,6 +51,7 @@ abstract public class ActionHandler extends SimpleChannelInboundHandler<FullHttp
this.doAction(ctx, request, response);
}
} catch (Exception e) {
e.printStackTrace();
Action errorAction = ServerSetting.getErrorAction(ServerSetting.MAPPING_ERROR);
request.putParam(UnknownErrorAction.ERROR_PARAM_NAME, e);
response.setContent(e.toString());
@@ -15,14 +15,16 @@ public class FriendData implements Serializable {
private String friendUid;
private String alias;
private int state;
private int blacked;
private long timestamp;
public FriendData(String userId, String friendUid, String alias, int state, long timestamp) {
public FriendData(String userId, String friendUid, String alias, int state, int blacked, long timestamp) {
this.userId = userId;
this.friendUid = friendUid;
this.alias = alias;
this.state = state;
this.blacked = blacked;
this.timestamp = timestamp;
}
@@ -61,6 +63,14 @@ public class FriendData implements Serializable {
this.state = state;
}
public int getBlacked() {
return blacked;
}
public void setBlacked(int blacked) {
this.blacked = blacked;
}
public long getTimestamp() {
return timestamp;
}
@@ -60,10 +60,6 @@ public final class BrokerConstants {
public static final String NETTY_SO_KEEPALIVE_PROPERTY_NAME = "netty.so_keepalive";
public static final String NETTY_CHANNEL_TIMEOUT_SECONDS_PROPERTY_NAME = "netty.channel_timeout.seconds";
public static final String NETTY_EPOLL_PROPERTY_NAME = "netty.epoll";
public static final String METRICS_ENABLE_PROPERTY_NAME = "use_metrics";
public static final String METRICS_LIBRATO_EMAIL_PROPERTY_NAME = "metrics.librato.email";
public static final String METRICS_LIBRATO_TOKEN_PROPERTY_NAME = "metrics.librato.token";
public static final String METRICS_LIBRATO_SOURCE_PROPERTY_NAME = "metrics.librato.source";
public static final String STORAGE_CLASS_NAME = "storage_class";
@@ -92,6 +88,8 @@ public final class BrokerConstants {
public static final String PUSH_ANDROID_SERVER_ADDRESS = "push.android.server.address";
public static final String PUSH_IOS_SERVER_ADDRESS = "push.ios.server.address";
public static final String USER_ONLINE_STATUS_CALLBACK = "user.online_status_callback";
public static final String HZ_Cluster_Node_External_IP = "node_external_ip";
public static final String HZ_Cluster_Node_External_Long_Port = "node_external_long_port";
public static final String HZ_Cluster_Node_External_Short_Port = "node_external_short_port";
@@ -100,7 +98,7 @@ public final class BrokerConstants {
public static final String HTTP_SERVER_API_NO_CHECK_TIME = "http.admin.no_check_time";
public static final String CLIENT_PROTO_SECRET_KEY = "client.proto.secret_key";
public static final String TOKEN_SECRET_KEY = "token.key ";
public static final String TOKEN_SECRET_KEY = "token.key";
public static final String EMBED_DB_PROPERTY_NAME = "embed.db";
@@ -110,6 +108,25 @@ public final class BrokerConstants {
public static final String MESSAGE_Forward_Url = "message.forward.url";
public static final String SERVER_MULTI_ENDPOINT = "server.multi_endpoint";
public static final String MONGODB_Client_URI = "mongodb.client_uri";
public static final String MONGODB_Database = "mongodb.database";
public static final String MONGODB_Data_Expire_Days = "mongodb.data_expire_days";
public static final String MESSAGE_ROAMING = "message.roaming";
public static final String MESSAGE_Remote_History_Message = "message.remote_history_message";
public static final String MESSAGE_Max_Queue = "message.max_queue";
public static final String MESSAGE_Disable_Stranger_Chat = "message.disable_stranger_chat";
public static final String MESSAGE_Blacklist_Strategy = "message.blacklist.strategy";
public static final String FRIEND_Repeat_Request_Duration = "friend.repeat_request_duration";
public static final String FRIEND_Reject_Request_Duration = "friend.reject_request_duration";
public static final String CHATROOM_Participant_Idle_Time = "chatroom.participant_idle_time";
public static final String CHATROOM_Rejoin_When_Active = "chatroom.rejoin_when_active";
private BrokerConstants() {
@@ -41,7 +41,7 @@ public class CreateChannelHandler extends GroupHandler<WFCMessage.ChannelInfo> {
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
WFCMessage.ModifyUserSettingReq modifyUserSettingReq = WFCMessage.ModifyUserSettingReq.newBuilder().setScope(kUserSettingMyChannels).setKey(request.getTargetId()).setValue("1").build();
mServer.internalRpcMsg(fromUser, null, modifyUserSettingReq.toByteArray(), 0, fromUser, PutUserSettingTopic, false);
byte[] data = request.getTargetId().getBytes();
byte[] data = request.getSecret().getBytes();
ackPayload.ensureWritable(data.length).writeBytes(data);
return ErrorCode.ERROR_CODE_SUCCESS;
} else {
@@ -0,0 +1,38 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.imhandler;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.WFCMessage;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.moquette.spi.impl.security.TokenAuthenticator;
import io.netty.buffer.ByteBuf;
import win.liyufan.im.IMTopic;
@Handler(IMTopic.DestroyUserTopic)
public class DestroyUserHandler extends IMHandler<WFCMessage.IDBuf> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.IDBuf request, Qos1PublishHandler.IMCallback callback) {
if (isAdmin) {
mServer.getImBusinessScheduler().execute(()-> {
m_sessionsStore.clearUserSession(fromUser);
m_messagesStore.clearUserMessages(fromUser);
m_messagesStore.clearUserSettings(fromUser);
m_messagesStore.clearUserFriend(fromUser);
m_messagesStore.clearUserGroups(fromUser);
m_messagesStore.clearUserChannels(fromUser);
m_messagesStore.destoryUser(fromUser);
});
return ErrorCode.ERROR_CODE_SUCCESS;
} else {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
}
@@ -22,11 +22,11 @@ import java.util.List;
public class FriendPullHandler extends IMHandler<WFCMessage.Version> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.Version request, Qos1PublishHandler.IMCallback callback) {
List<FriendData> friendDatas = m_messagesStore.getFriendList(fromUser, request.getVersion());
List<FriendData> friendDatas = m_messagesStore.getFriendList(fromUser, clientID, request.getVersion());
WFCMessage.GetFriendsResult.Builder builder = WFCMessage.GetFriendsResult.newBuilder();
for (FriendData data : friendDatas
) {
WFCMessage.Friend.Builder builder1 = WFCMessage.Friend.newBuilder().setState(data.getState()).setUid(data.getFriendUid()).setUpdateDt(data.getTimestamp());
WFCMessage.Friend.Builder builder1 = WFCMessage.Friend.newBuilder().setState(data.getState()).setBlacked(data.getBlacked()).setUid(data.getFriendUid()).setUpdateDt(data.getTimestamp());
if (!StringUtil.isNullOrEmpty(data.getAlias())) {
builder1.setAlias(data.getAlias());
}
@@ -0,0 +1,86 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.imhandler;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.WFCMessage;
import com.qiniu.util.Auth;
import com.xiaoleilu.loServer.action.UploadFileAction;
import io.moquette.server.config.MediaServerConfig;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
@Handler("GMUT")
public class GetMediaUploadTokenHandler extends IMHandler<WFCMessage.GetUploadTokenRequest> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.GetUploadTokenRequest request, Qos1PublishHandler.IMCallback callback) {
int type = request.getMediaType();
String token;
WFCMessage.GetUploadTokenResult.Builder resultBuilder = WFCMessage.GetUploadTokenResult.newBuilder();
if (MediaServerConfig.USER_QINIU) {
Auth auth = Auth.create(MediaServerConfig.QINIU_ACCESS_KEY, MediaServerConfig.QINIU_SECRET_KEY);
String bucketName;
String bucketDomain;
switch (type) {
case 0:
bucketName = MediaServerConfig.QINIU_BUCKET_GENERAL_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_GENERAL_DOMAIN;
break;
case 1:
bucketName = MediaServerConfig.QINIU_BUCKET_IMAGE_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_IMAGE_DOMAIN;
break;
case 2:
bucketName = MediaServerConfig.QINIU_BUCKET_VOICE_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_VOICE_DOMAIN;
break;
case 3:
bucketName = MediaServerConfig.QINIU_BUCKET_VIDEO_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_VIDEO_DOMAIN;
break;
case 4:
bucketName = MediaServerConfig.QINIU_BUCKET_FILE_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_FILE_DOMAIN;
break;
case 5:
bucketName = MediaServerConfig.QINIU_BUCKET_PORTRAIT_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_PORTRAIT_DOMAIN;
break;
case 6:
bucketName = MediaServerConfig.QINIU_BUCKET_FAVORITE_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_FAVORITE_DOMAIN;
break;
default:
bucketName = MediaServerConfig.QINIU_BUCKET_GENERAL_NAME;
bucketDomain = MediaServerConfig.QINIU_BUCKET_GENERAL_DOMAIN;
break;
}
token = auth.uploadToken(bucketName);
resultBuilder.setDomain(bucketDomain)
.setServer(MediaServerConfig.QINIU_SERVER_URL);
resultBuilder.setPort(80);
} else {
token = UploadFileAction.getToken(type);
resultBuilder.setDomain("http://" + MediaServerConfig.SERVER_IP + ":" + MediaServerConfig.HTTP_SERVER_PORT)
.setServer(MediaServerConfig.SERVER_IP);
resultBuilder.setPort(MediaServerConfig.HTTP_SERVER_PORT);
}
resultBuilder.setToken(token);
byte[] data = resultBuilder.buildPartial().toByteArray();
ackPayload.ensureWritable(data.length).writeBytes(data);
return ErrorCode.ERROR_CODE_SUCCESS;
}
}
@@ -20,7 +20,7 @@ import win.liyufan.im.IMTopic;
public class GetTokenHandler extends IMHandler<WFCMessage.GetTokenRequest> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.GetTokenRequest request, Qos1PublishHandler.IMCallback callback) {
MemorySessionStore.Session session = m_sessionsStore.createUserSession(fromUser, clientID);
MemorySessionStore.Session session = m_sessionsStore.updateOrCreateUserSession(fromUser, clientID, request.getPlatform());
TokenAuthenticator authenticator = new TokenAuthenticator();
String strToken = authenticator.generateToken(fromUser);
String result = strToken + "|" + session.getSecret() + "|" + session.getDbSecret();
@@ -32,7 +32,7 @@ abstract public class GroupHandler<T> extends IMHandler<T> {
builder.setFromUser(fromUser);
long messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId);
saveAndPublish(fromUser, null, builder.build());
saveAndPublish(fromUser, null, builder.build(), false);
}
}
@@ -32,7 +32,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
long timestamp = System.currentTimeMillis();
builder.setMessageId(messageId);
builder.setServerTimestamp(timestamp);
saveAndPublish(request.getTargetUid(), null, builder.build());
saveAndPublish(request.getTargetUid(), null, builder.build(), false);
WFCMessage.MessageContent.Builder contentBuilder = WFCMessage.MessageContent.newBuilder().setType(90).setContent("以上是打招呼信息");
builder = WFCMessage.Message.newBuilder();
@@ -44,7 +44,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
messageId = MessageShardingUtil.generateId();
builder.setMessageId(messageId);
saveAndPublish(request.getTargetUid(), null, builder.build());
saveAndPublish(request.getTargetUid(), null, builder.build(), false);
contentBuilder.setContent("你们已经成为好友了,现在可以开始聊天了");
builder.setContent(contentBuilder);
@@ -52,7 +52,7 @@ public class HandleFriendRequestHandler extends IMHandler<WFCMessage.HandleFrien
builder.setMessageId(messageId);
timestamp = System.currentTimeMillis();
builder.setServerTimestamp(timestamp);
saveAndPublish(request.getTargetUid(), null, builder.build());
saveAndPublish(request.getTargetUid(), null, builder.build(), false);
publisher.publishNotification(IMTopic.NotifyFriendTopic, request.getTargetUid(), heads[0]);
publisher.publishNotification(IMTopic.NotifyFriendTopic, fromUser, heads[1]);
@@ -32,6 +32,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -81,11 +83,10 @@ abstract public class IMHandler<T> {
}
}
for (Method method : getClass().getDeclaredMethods()
) {
if (method.getName() == actionName && method.getParameterCount() == 6) {
if (method.getName() == actionName && method.getParameterCount() == 6 && !method.getParameterTypes()[4].equals(Object.class)) {
dataCls = method.getParameterTypes()[4];
break;
}
@@ -233,14 +234,22 @@ abstract public class IMHandler<T> {
public void afterAction(String clientID, String fromUser, String topic, Qos1PublishHandler.IMCallback callback) {
}
protected long publish(String username, String clientID, WFCMessage.Message message) {
Set<String> notifyReceivers = new LinkedHashSet<>();
protected long saveAndPublish(String username, String clientID, WFCMessage.Message message) {
WFCMessage.Message.Builder messageBuilder = message.toBuilder();
int pullType = m_messagesStore.getNotifyReceivers(username, messageBuilder, notifyReceivers, false);
mServer.getImBusinessScheduler().execute(() -> this.publisher.publish2Receivers(messageBuilder.build(), notifyReceivers, clientID, pullType));
return notifyReceivers.size();
}
protected long saveAndPublish(String username, String clientID, WFCMessage.Message message, boolean ignoreMsg) {
Set<String> notifyReceivers = new LinkedHashSet<>();
message = m_messagesStore.storeMessage(username, clientID, message);
WFCMessage.Message.Builder messageBuilder = message.toBuilder();
int pullType = m_messagesStore.getNotifyReceivers(username, messageBuilder, notifyReceivers);
this.publisher.publish2Receivers(messageBuilder.build(), notifyReceivers, clientID, pullType);
int pullType = m_messagesStore.getNotifyReceivers(username, messageBuilder, notifyReceivers, ignoreMsg);
mServer.getImBusinessScheduler().execute(() -> this.publisher.publish2Receivers(messageBuilder.build(), notifyReceivers, clientID, pullType));
return notifyReceivers.size();
}
@@ -250,4 +259,12 @@ abstract public class IMHandler<T> {
mServer.getImBusinessScheduler().execute(() -> publisher.publish2Receivers(updatedMessage, notifyReceivers, clientID, ProtoConstants.PullType.Pull_Normal));
return notifyReceivers.size();
}
protected long saveAndMulticast(String username, String clientID, WFCMessage.Message message, Collection<String> targets) {
Set<String> notifyReceivers = new HashSet<>();
notifyReceivers.addAll(targets);
WFCMessage.Message updatedMessage = m_messagesStore.storeMessage(username, clientID, message);
mServer.getImBusinessScheduler().execute(() -> publisher.publish2Receivers(updatedMessage, notifyReceivers, clientID, ProtoConstants.PullType.Pull_Normal));
return notifyReceivers.size();
}
}
@@ -27,6 +27,12 @@ public class KickoffGroupMember extends GroupHandler<WFCMessage.RemoveGroupMembe
} else {
boolean isAllow = isAdmin;
if (!isAllow) {
if (groupInfo.getOwner() != null) {
if (request.getRemovedMemberList().contains(groupInfo.getOwner())) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
}
if (groupInfo.getOwner() != null && groupInfo.getOwner().equals(fromUser)) {
isAllow = true;
}
@@ -0,0 +1,57 @@
/*
* This file is part of the Wildfire Chat package.
* (c) Heavyrain2012 <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
package io.moquette.imhandler;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.hazelcast.util.StringUtil;
import io.moquette.BrokerConstants;
import io.moquette.server.Server;
import io.moquette.spi.impl.Qos1PublishHandler;
import io.netty.buffer.ByteBuf;
import win.liyufan.im.IMTopic;
import win.liyufan.im.MessageShardingUtil;
import java.util.Set;
import static cn.wildfirechat.proto.ProtoConstants.ContentType.Text;
import static cn.wildfirechat.proto.ProtoConstants.ConversationType.ConversationType_Private;
@Handler(value = IMTopic.MultiCastMessageTopic)
public class MultiCastMessageHandler extends IMHandler<WFCMessage.MultiCastMessage> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.MultiCastMessage multiCastMessage, Qos1PublishHandler.IMCallback callback) {
ErrorCode errorCode = ErrorCode.ERROR_CODE_SUCCESS;
if (!isAdmin) {
return ErrorCode.ERROR_CODE_NOT_RIGHT;
}
long timestamp = System.currentTimeMillis();
long messageId = MessageShardingUtil.generateId();
WFCMessage.Message message = WFCMessage.Message.newBuilder()
.setContent(multiCastMessage.getContent())
.setConversation(WFCMessage.Conversation.newBuilder().setTarget(fromUser).setType(ConversationType_Private).setLine(multiCastMessage.getLine()).build())
.setFromUser(fromUser)
.setMessageId(messageId)
.setServerTimestamp(timestamp)
.build();
saveAndMulticast(fromUser, clientID, message, multiCastMessage.getToList());
ackPayload = ackPayload.capacity(20);
ackPayload.writeLong(messageId);
ackPayload.writeLong(timestamp);
return errorCode;
}
}
@@ -21,7 +21,7 @@ import java.util.Set;
public class RecallMessageHandler extends IMHandler<WFCMessage.INT64Buf> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.INT64Buf int64Buf, Qos1PublishHandler.IMCallback callback) {
ErrorCode errorCode = m_messagesStore.recallMessage(int64Buf.getId(), fromUser);
ErrorCode errorCode = m_messagesStore.recallMessage(int64Buf.getId(), fromUser, isAdmin);
if(errorCode != ErrorCode.ERROR_CODE_SUCCESS) {
return errorCode;
@@ -33,7 +33,11 @@ public class RecallMessageHandler extends IMHandler<WFCMessage.INT64Buf> {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
m_messagesStore.getNotifyReceivers(fromUser, message.toBuilder(), notifyReceivers);
publish(fromUser, clientID, message);
//等待客户端实现根据撤回消息更新内容,之后可以删掉这段代码
m_messagesStore.getNotifyReceivers(fromUser, message.toBuilder(), notifyReceivers, false);
this.publisher.publishRecall2Receivers(int64Buf.getId(), fromUser, notifyReceivers, clientID);
return errorCode;
@@ -32,7 +32,7 @@ public class RouteHandler extends IMHandler<WFCMessage.RouteRequest> {
ClientSession clientSession = m_sessionsStore.sessionForClient(clientID);
boolean isSessionAlreadyStored = clientSession != null;
if (!isSessionAlreadyStored) {
m_sessionsStore.createNewSession(fromUser, clientID, true, true);
m_sessionsStore.loadActiveSession(fromUser, clientID);
} else {
m_sessionsStore.updateExistSession(fromUser, clientID, request, true);
}
@@ -26,6 +26,7 @@ import static cn.wildfirechat.proto.ProtoConstants.ContentType.Text;
public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
private int mSensitiveType = 0; //命中敏感词时,0 失败,1 吞掉, 2 敏感词替换成*。
private String mForwardUrl = null;
private int mBlacklistStrategy = 0; //黑名单中时,0失败,1吞掉。
public SendMessageHandler() {
super();
@@ -39,29 +40,45 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
} catch (Exception e) {
e.printStackTrace();
}
try {
mBlacklistStrategy = Integer.parseInt(mServer.getConfig().getProperty(BrokerConstants.MESSAGE_Blacklist_Strategy));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.Message message, Qos1PublishHandler.IMCallback callback) {
ErrorCode errorCode = ErrorCode.ERROR_CODE_SUCCESS;
if (message != null) {
boolean ignoreMsg = false;
if (!isAdmin) { //admin do not check the right
// 不能在端上直接发送撤回和群通知
if (message.getContent().getType() == 80 || (message.getContent().getType() >= 100 && message.getContent().getType() < 200)) {
return ErrorCode.INVALID_PARAMETER;
}
int userStatus = m_messagesStore.getUserStatus(fromUser);
if (userStatus == 1 || userStatus == 2) {
if (userStatus == ProtoConstants.UserStatus.Muted || userStatus == ProtoConstants.UserStatus.Forbidden) {
return ErrorCode.ERROR_CODE_FORBIDDEN_SEND_MSG;
}
if (message.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Private) {
if (m_messagesStore.isBlacked(message.getConversation().getTarget(), fromUser)) {
return ErrorCode.ERROR_CODE_IN_BLACK_LIST;
errorCode = m_messagesStore.isAllowUserMessage(message.getConversation().getTarget(), fromUser);
if (errorCode != ErrorCode.ERROR_CODE_SUCCESS) {
if (errorCode == ErrorCode.ERROR_CODE_IN_BLACK_LIST && mBlacklistStrategy != ProtoConstants.BlacklistStrategy.Message_Reject) {
ignoreMsg = true;
} else {
return errorCode;
}
}
userStatus = m_messagesStore.getUserStatus(message.getConversation().getTarget());
if (userStatus == ProtoConstants.UserStatus.Forbidden) {
return ErrorCode.ERROR_CODE_USER_FORBIDDEN;
}
}
if (message.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Group ) {
errorCode = m_messagesStore.canSendMessageInGroup(fromUser, message.getConversation().getTarget());
if (errorCode != ErrorCode.ERROR_CODE_SUCCESS) {
@@ -86,7 +103,7 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
publisher.forwardMessage(message, mForwardUrl);
}
boolean ignoreMsg = false;
if (!isAdmin && message.getContent().getType() == Text) {
Set<String> matched = m_messagesStore.handleSensitiveWord(message.getContent().getSearchableContent());
if (matched != null && !matched.isEmpty()) {
@@ -95,21 +112,21 @@ public class SendMessageHandler extends IMHandler<WFCMessage.Message> {
errorCode = ErrorCode.ERROR_CODE_SENSITIVE_MATCHED;
} else if(mSensitiveType == 1) {
ignoreMsg = true;
} else {
} else if(mSensitiveType == 2) {
String text = message.getContent().getSearchableContent();
for (String word : matched) {
text = text.replace(word, "***");
}
message = message.toBuilder().setContent(message.getContent().toBuilder().setSearchableContent(text).build()).build();
} else if(mSensitiveType == 3) {
}
}
}
if (errorCode == ErrorCode.ERROR_CODE_SUCCESS) {
if (!ignoreMsg) {
saveAndPublish(fromUser, clientID, message);
}
saveAndPublish(fromUser, clientID, message, ignoreMsg);
ackPayload = ackPayload.capacity(20);
ackPayload.writeLong(messageId);
ackPayload.writeLong(timestamp);
@@ -26,12 +26,12 @@ public class UploadDeviceTokenHandler extends IMHandler<WFCMessage.UploadDeviceT
session.setAppName(request.getAppName());
if (request.getPlatform() == ProtoConstants.Platform.Platform_iOS && request.getPushType() == 2) {
session.setVoipDeviceToken(request.getDeviceToken());
// session.setPushType(0);//如果有voip token,肯定是生产环境的推送
m_sessionsStore.updateSessionToken(session, true);
} else {
session.setDeviceToken(request.getDeviceToken());
session.setPushType(request.getPushType());
m_sessionsStore.updateSessionToken(session, false);
m_sessionsStore.cleanDuplatedToken(session.getClientID(), session.getPushType(), session.getDeviceToken(), false, session.getAppName());
}
return ErrorCode.ERROR_CODE_SUCCESS;
@@ -20,7 +20,7 @@ import java.util.List;
public class UserSearchHandler extends IMHandler<WFCMessage.SearchUserRequest> {
@Override
public ErrorCode action(ByteBuf ackPayload, String clientID, String fromUser, boolean isAdmin, WFCMessage.SearchUserRequest request, Qos1PublishHandler.IMCallback callback) {
List<WFCMessage.User> users = m_messagesStore.searchUser(request.getKeyword(), request.getFuzzy() > 0, request.getPage());
List<WFCMessage.User> users = m_messagesStore.searchUser(request.getKeyword(), request.getFuzzy(), request.getPage());
WFCMessage.SearchUserResult.Builder builder = WFCMessage.SearchUserResult.newBuilder();
builder.addAllEntry(users);
byte[] data = builder.build().toByteArray();
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -16,17 +16,19 @@
package io.moquette.persistence;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.hazelcast.util.StringUtil;
import io.moquette.BrokerConstants;
import io.moquette.server.Constants;
import io.moquette.server.Server;
import io.moquette.spi.ClientSession;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.moquette.spi.ISessionsStore;
import io.moquette.spi.IMessagesStore.StoredMessage;
import io.netty.handler.codec.mqtt.MqttVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.wildfirechat.common.ErrorCode;
import java.util.*;
import java.util.concurrent.*;
@@ -35,6 +37,8 @@ public class MemorySessionStore implements ISessionsStore {
private static int dumy = 1;
private static final Logger LOG = LoggerFactory.getLogger(MemorySessionStore.class);
private boolean supportMultiEndpoint = false;
public static class Session implements Comparable<Session>{
final String clientID;
String username;
@@ -50,6 +54,16 @@ public class MemorySessionStore implements ISessionsStore {
private volatile int unReceivedMsgs;
private int deleted;
public int getDeleted() {
return deleted;
}
public void setDeleted(int deleted) {
this.deleted = deleted;
}
private MqttVersion mqttVersion = MqttVersion.MQTT_3_1_1;
public long getLastActiveTime() {
@@ -130,6 +144,9 @@ public class MemorySessionStore implements ISessionsStore {
public void setUpdateDt(long updateDt) {
this.updateDt = updateDt;
if (this.lastActiveTime == 0) {
this.lastActiveTime = updateDt;
}
}
private int pushType;
@@ -248,8 +265,14 @@ public class MemorySessionStore implements ISessionsStore {
private final Server mServer;
private final DatabaseStore databaseStore;
public MemorySessionStore(Server server, DatabaseStore databaseStore) {
mServer = server;
this.databaseStore = databaseStore;
mServer = server;
this.databaseStore = databaseStore;
try {
supportMultiEndpoint = Boolean.parseBoolean(server.getConfig().getProperty(BrokerConstants.SERVER_MULTI_ENDPOINT));
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
@@ -261,6 +284,21 @@ public class MemorySessionStore implements ISessionsStore {
return session;
}
@Override
public void cleanDuplatedToken(String cid, int pushType, String token, boolean isVoip, String packageName) {
if (StringUtil.isNullOrEmpty(token) || isVoip) {
return;
}
Iterator<Map.Entry<String, Session>> it = sessions.entrySet().iterator();
while (it.hasNext()) {
Session session = it.next().getValue();
if (!session.getClientID().equals(cid) && (session.pushType == pushType && token.equals(session.deviceToken)) && (!StringUtil.isNullOrEmpty(packageName) && packageName.equals(session.getAppName()))) {
session.deviceToken = null;
}
}
}
@Override
public void initStore() {
}
@@ -272,18 +310,91 @@ public class MemorySessionStore implements ISessionsStore {
@Override
public void updateSessionToken(Session session, boolean voip) {
databaseStore.updateSessionToken(session.getUsername(), session.getClientID(), voip ? session.getVoipDeviceToken() : session.getDeviceToken(), voip);
databaseStore.updateSessionToken(session.getUsername(), session.getClientID(), voip ? session.getVoipDeviceToken() : session.getDeviceToken(), session.getPushType(), voip);
}
@Override
public Session createUserSession(String username, String clientID) {
public void clearUserSession(String username) {
LOG.info("Fooooooooo <{}>", username);
databaseStore.clearUserSessions(username);
ConcurrentSkipListSet<String> sessionSet = getUserSessionSet(username);
for (String clientID : sessionSet) {
Session s = sessions.remove(clientID);
mServer.getProcessor().kickoffSession(s);
}
userSessions.remove(username);
}
@Override
public Session updateOrCreateUserSession(String username, String clientID, int platform) {
LOG.debug("createUserSession for client <{}>, user <{}>", clientID, username);
Session session = sessions.get(clientID);
if (session != null && !session.username.equals(username)) {
if (userSessions.get(username) != null) {
userSessions.get(username).remove(clientID);
}
}
ClientSession clientSession = new ClientSession(clientID, this);
Session session = databaseStore.getSession(username, clientID, clientSession);
session = databaseStore.getSession(username, clientID, clientSession);
if (session == null) {
session = databaseStore.createSession(username, clientID, clientSession);
session = databaseStore.createSession(username, clientID, clientSession, platform);
}
sessions.put(clientID, session);
if (session.getDeleted() > 0) {
session.setDeleted(0);
databaseStore.updateSessionDeleted(username, clientID, 0);
}
if (session.getPlatform() != platform) {
session.setPlatform(platform);
databaseStore.updateSessionPlatform(username, clientID, platform);
}
if (!supportMultiEndpoint && platform > 0) {
databaseStore.clearMultiEndpoint(username, clientID, platform);
if (userSessions.get(username) != null) {
Iterator<String> it = userSessions.get(username).iterator();
while (it.hasNext()) {
String c = it.next();
if (!clientID.equals(c)) {
Session s = sessions.get(c);
if (s == null) {
it.remove();
continue;
}
boolean remove = false;
if (platform == ProtoConstants.Platform.Platform_Android || platform == ProtoConstants.Platform.Platform_iOS) {
if (s.getPlatform() == ProtoConstants.Platform.Platform_Android || s.getPlatform() == ProtoConstants.Platform.Platform_iOS) {
remove = true;
}
} else if(platform == ProtoConstants.Platform.Platform_OSX || platform == ProtoConstants.Platform.Platform_Windows) {
if (s.getPlatform() == ProtoConstants.Platform.Platform_OSX || s.getPlatform() == ProtoConstants.Platform.Platform_Windows) {
remove = true;
}
} else {
if (s.getPlatform() ==platform) {
remove = true;
}
}
if (remove) {
sessions.remove(c);
mServer.getProcessor().kickoffSession(s);
it.remove();
}
}
}
}
}
return session;
@@ -291,34 +402,29 @@ public class MemorySessionStore implements ISessionsStore {
@Override
public ErrorCode createNewSession(String username, String clientID, boolean cleanSession, boolean createWhenNoExist) {
public ErrorCode loadActiveSession(String username, String clientID) {
LOG.debug("createNewSession for client <{}>", clientID);
Session session = sessions.get(clientID);
if (session != null) {
if (session != null && session.getDeleted() == 0) {
LOG.error("already exists a session for client <{}>, bad condition", clientID);
throw new IllegalArgumentException("Can't create a session with the ID of an already existing" + clientID);
}
if (session != null && session.getDeleted() > 0) {
return ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH;
}
ClientSession clientSession = new ClientSession(clientID, this);
session = databaseStore.getSession(username, clientID, clientSession);
if (session == null) {
if (!createWhenNoExist) {
return ErrorCode.ERROR_CODE_NOT_EXIST;
}
session = databaseStore.createSession(username, clientID, clientSession);
if (session == null || session.getDeleted() > 0) {
return ErrorCode.ERROR_CODE_SECRECT_KEY_MISMATCH;
}
sessions.put(clientID, session);
ConcurrentSkipListSet<String> sessionSet = userSessions.get(username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<>();
userSessions.put(username, sessionSet);
}
sessionSet = userSessions.get(username);
ConcurrentSkipListSet<String> sessionSet = getUserSessionSet(username);
sessionSet.add(clientID);
return ErrorCode.ERROR_CODE_SUCCESS;
@@ -342,12 +448,7 @@ public class MemorySessionStore implements ISessionsStore {
session.setUsername(username);
sessions.put(clientID, session);
ConcurrentSkipListSet<String> sessionSet = userSessions.get(username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<>();
userSessions.put(username, sessionSet);
}
sessionSet = userSessions.get(username);
ConcurrentSkipListSet<String> sessionSet = getUserSessionSet(username);
sessionSet.add(clientID);
if (endpoint != null) {
@@ -393,14 +494,26 @@ public class MemorySessionStore implements ISessionsStore {
}
}
private ConcurrentSkipListSet<String> getUserSessionSet(String username) {
ConcurrentSkipListSet<String> sessionSet = userSessions.get(username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<String>();
List<Session> ss = databaseStore.getUserActivedSessions(username);
for (Session s : ss) {
sessionSet.add(s.getClientID());
sessions.put(s.getClientID(), s);
}
userSessions.put(username, sessionSet);
}
sessionSet = userSessions.get(username);
return sessionSet;
}
@Override
public Collection<Session> sessionForUser(String username) {
ConcurrentSkipListSet<String> sessionSet = userSessions.get(username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<String>();
userSessions.put(username, sessionSet);
}
sessionSet = userSessions.get(username);
ConcurrentSkipListSet<String> sessionSet = getUserSessionSet(username);
ArrayList<Session> out = new ArrayList<>();
for (String clientId : sessionSet
) {
@@ -466,7 +579,9 @@ public class MemorySessionStore implements ISessionsStore {
@Override
public void dropQueue(String clientID) {
sessions.get(clientID).queue.clear();
if (sessions.get(clientID) != null) {
sessions.get(clientID).queue.clear();
}
}
@Override
@@ -542,12 +657,7 @@ public class MemorySessionStore implements ISessionsStore {
LOG.error("Can't find the session for client <{}>", clientID);
return;
}
ConcurrentSkipListSet<String> sessionSet = userSessions.get(session.username);
if (sessionSet == null) {
sessionSet = new ConcurrentSkipListSet<>();
userSessions.put(session.username, sessionSet);
}
sessionSet = userSessions.get(session.username);
ConcurrentSkipListSet<String> sessionSet = getUserSessionSet(session.username);
sessionSet.remove(clientID);
// remove also the messages stored of type QoS1/2
@@ -24,8 +24,10 @@ public final class Constants {
public static final String ATTR_CLIENTID = "ClientID";
public static final String CLEAN_SESSION = "cleanSession";
public static final String KEEP_ALIVE = "keepAlive";
public static final int MAX_MESSAGE_QUEUE = 1024; // number of messages
public static final int MAX_CHATROOM_MESSAGE_QUEUE = 256; // number of chatroom messages
public static int MAX_MESSAGE_QUEUE = 1024; // number of messages
public static int MAX_CHATROOM_MESSAGE_QUEUE = 256; // number of chatroom messages
private Constants() {
}
@@ -406,6 +406,7 @@ public class Server {
}
public void stopServer() {
System.out.println("Server will flush data to db before shutting down, please wait 5 seconds!");
LOG.info("Unbinding server from the configured ports");
m_acceptor.close();
LOG.trace("Stopping MQTT protocol processor");
@@ -139,19 +139,11 @@ public class NettyAcceptor implements ServerAcceptor {
final NettyMQTTHandler mqttHandler = new NettyMQTTHandler(processor);
final boolean useFineMetrics = Boolean.parseBoolean(props.getProperty(METRICS_ENABLE_PROPERTY_NAME, "false"));
if (useFineMetrics) {
DropWizardMetricsHandler metricsHandler = new DropWizardMetricsHandler();
metricsHandler.init(props);
this.metrics = Optional.of(metricsHandler);
} else {
this.metrics = Optional.empty();
}
this.metrics = Optional.empty();
this.errorsCather = Optional.empty();
initializePlainTCPTransport(mqttHandler, props);
initializeWebSocketTransport(mqttHandler, props);
String sslTcpPortProp = props.getProperty(BrokerConstants.SSL_PORT_PROPERTY_NAME);
String wssPortProp = props.getProperty(BrokerConstants.WSS_PORT_PROPERTY_NAME);
if (sslTcpPortProp != null || wssPortProp != null) {
@@ -233,42 +225,6 @@ public class NettyAcceptor implements ServerAcceptor {
});
}
private void initializeWebSocketTransport(final NettyMQTTHandler handler, IConfig props) throws IOException {
LOG.info("Configuring Websocket MQTT transport");
String webSocketPortProp = props.getProperty(WEB_SOCKET_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
if (DISABLED_PORT_BIND.equals(webSocketPortProp)) {
// Do nothing no WebSocket configured
LOG.info("Property {} has been setted to {}. Websocket MQTT will be disabled",
BrokerConstants.WEB_SOCKET_PORT_PROPERTY_NAME, DISABLED_PORT_BIND);
return;
}
int port = Integer.parseInt(webSocketPortProp);
final MoquetteIdleTimeoutHandler timeoutHandler = new MoquetteIdleTimeoutHandler();
String host = props.getProperty(BrokerConstants.HOST_PROPERTY_NAME);
initFactory(host, port, "Websocket MQTT", new PipelineInitializer() {
@Override
void init(ChannelPipeline pipeline) {
pipeline.addLast(new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
pipeline.addLast("webSocketHandler",
new WebSocketServerProtocolHandler("/", MQTT_SUBPROTOCOL_CSV_LIST));
pipeline.addLast("ws2bytebufDecoder", new WebSocketFrameToByteBufDecoder());
pipeline.addLast("bytebuf2wsEncoder", new ByteBufToWebSocketFrameEncoder());
pipeline.addFirst("idleStateHandler", new IdleStateHandler(nettyChannelTimeoutSeconds, 0, 0));
pipeline.addAfter("idleStateHandler", "idleEventHandler", timeoutHandler);
pipeline.addFirst("bytemetrics", new BytesMetricsHandler(m_bytesMetricsCollector));
pipeline.addLast("decoder", new MqttDecoder());
pipeline.addLast("encoder", MqttEncoder.INSTANCE);
pipeline.addLast("metrics", new MessageMetricsHandler(m_metricsCollector));
pipeline.addLast("messageLogger", new MQTTMessageLogger());
pipeline.addLast("handler", handler);
}
});
}
private void initializeSSLTCPTransport(final NettyMQTTHandler handler, IConfig props, final SSLContext sslContext)
throws IOException {
LOG.info("Configuring SSL MQTT transport");
@@ -103,7 +103,7 @@ public class NettyMQTTHandler extends ChannelInboundHandlerAdapter {
String clientID = NettyUtils.clientID(ctx.channel());
if (clientID != null && !clientID.isEmpty()) {
LOG.info("Notifying connection lost event. MqttClientId = {}.", clientID);
m_processor.processConnectionLost(clientID, ctx.channel());
m_processor.processConnectionLost(clientID, ctx.channel(), false);
}
ctx.close();
}
@@ -16,6 +16,7 @@
package io.moquette.server.netty.metrics;
import io.moquette.server.netty.NettyUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
@@ -23,10 +24,15 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.moquette.server.netty.NettyUtils.ATTR_USERNAME;
public class BytesMetricsHandler extends ChannelDuplexHandler {
private static final Logger LOG = LoggerFactory.getLogger(BytesMetricsHandler.class);
private static final AttributeKey<BytesMetrics> ATTR_KEY_METRICS = AttributeKey.valueOf("BytesMetrics");
private static final AttributeKey<String> ATTR_KEY_USERNAME = AttributeKey.valueOf(ATTR_USERNAME);
private BytesMetricsCollector m_collector;
@@ -59,6 +65,12 @@ public class BytesMetricsHandler extends ChannelDuplexHandler {
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
BytesMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
String userId = ctx.channel().attr(ATTR_KEY_USERNAME).get();
if (userId == null) {
userId = "";
}
LOG.info("channel<{}> closing after read {} bytes and wrote {} bytes", userId, metrics.readBytes(), metrics.wroteBytes());
m_collector.sumReadBytes(metrics.readBytes());
m_collector.sumWroteBytes(metrics.wroteBytes());
super.close(ctx, promise);
@@ -1,95 +0,0 @@
/*
* Copyright (c) 2012-2017 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.moquette.server.netty.metrics;
import com.codahale.metrics.Counter;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.librato.metrics.reporter.Librato;
import io.moquette.server.config.IConfig;
import io.moquette.server.netty.NettyUtils;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.mqtt.MqttMessage;
import io.netty.handler.codec.mqtt.MqttMessageType;
import java.util.concurrent.TimeUnit;
import static io.moquette.BrokerConstants.*;
import static io.netty.channel.ChannelHandler.Sharable;
/**
* Pipeline handler use to track some MQTT metrics.
*/
@Sharable
public final class DropWizardMetricsHandler extends ChannelInboundHandlerAdapter {
private MetricRegistry metrics;
private Meter publishesMetrics;
private Meter subscribeMetrics;
private Counter connectedClientsMetrics;
public void init(IConfig props) {
this.metrics = new MetricRegistry();
this.publishesMetrics = metrics.meter("publish.requests");
this.subscribeMetrics = metrics.meter("subscribe.requests");
this.connectedClientsMetrics = metrics.counter("connect.num_clients");
// ConsoleReporter reporter = ConsoleReporter.forRegistry(metrics)
// .convertRatesTo(TimeUnit.SECONDS)
// .convertDurationsTo(TimeUnit.MILLISECONDS)
// .build();
// reporter.start(1, TimeUnit.MINUTES);
final String email = props.getProperty(METRICS_LIBRATO_EMAIL_PROPERTY_NAME);
final String token = props.getProperty(METRICS_LIBRATO_TOKEN_PROPERTY_NAME);
final String source = props.getProperty(METRICS_LIBRATO_SOURCE_PROPERTY_NAME);
Librato.reporter(this.metrics, email, token)
.setSource(source)
.start(10, TimeUnit.SECONDS);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object message) {
MqttMessage msg = (MqttMessage) message;
MqttMessageType messageType = msg.fixedHeader().messageType();
switch (messageType) {
case PUBLISH:
this.publishesMetrics.mark();
break;
case SUBSCRIBE:
this.subscribeMetrics.mark();
break;
case CONNECT:
this.connectedClientsMetrics.inc();
break;
case DISCONNECT:
this.connectedClientsMetrics.dec();
break;
default:
break;
}
ctx.fireChannelRead(message);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String clientID = NettyUtils.clientID(ctx.channel());
if (clientID != null && !clientID.isEmpty()) {
this.connectedClientsMetrics.dec();
}
ctx.fireChannelInactive();
}
}
@@ -22,10 +22,16 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static io.moquette.server.netty.NettyUtils.ATTR_USERNAME;
public class MessageMetricsHandler extends ChannelDuplexHandler {
private static final Logger LOG = LoggerFactory.getLogger(MessageMetricsHandler.class);
private static final AttributeKey<MessageMetrics> ATTR_KEY_METRICS = AttributeKey.valueOf("MessageMetrics");
private static final AttributeKey<String> ATTR_KEY_USERNAME = AttributeKey.valueOf(ATTR_USERNAME);
private MessageMetricsCollector m_collector;
@@ -58,6 +64,12 @@ public class MessageMetricsHandler extends ChannelDuplexHandler {
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
MessageMetrics metrics = ctx.channel().attr(ATTR_KEY_METRICS).get();
String userId = ctx.channel().attr(ATTR_KEY_USERNAME).get();
if (userId == null) {
userId = "";
}
LOG.info("channel<{}> closing after read {} messages and wrote {} messages", userId, metrics.messagesRead(), metrics.messagesWrote());
m_collector.sumReadMessages(metrics.messagesRead());
m_collector.sumWroteMessages(metrics.messagesWrote());
super.close(ctx, promise);
@@ -16,10 +16,12 @@
package io.moquette.spi;
import cn.wildfirechat.pojos.SystemSettingPojo;
import cn.wildfirechat.proto.WFCMessage;
import com.xiaoleilu.loServer.model.FriendData;
import cn.wildfirechat.pojos.InputOutputUserBlockStatus;
import io.moquette.persistence.DatabaseStore;
import io.moquette.persistence.MemorySessionStore;
import io.moquette.persistence.UserClientEntry;
import io.moquette.spi.impl.subscriptions.Topic;
import io.netty.buffer.ByteBuf;
@@ -97,7 +99,7 @@ public interface IMessagesStore {
DatabaseStore getDatabaseStore();
WFCMessage.Message storeMessage(String fromUser, String fromClientId, WFCMessage.Message message);
void storeSensitiveMessage(WFCMessage.Message message);
int getNotifyReceivers(String fromUser, WFCMessage.Message.Builder message, Set<String> notifyReceivers);
int getNotifyReceivers(String fromUser, WFCMessage.Message.Builder message, Set<String> notifyReceivers, boolean ignoreMsg);
Set<String> getAllEnds();
WFCMessage.PullMessageResult fetchMessage(String user, String exceptClientId, long fromMessageId, int pullType);
WFCMessage.PullMessageResult loadRemoteMessages(String user, WFCMessage.Conversation conversation, long beforeUid, int count);
@@ -106,11 +108,13 @@ public interface IMessagesStore {
ErrorCode addGroupMembers(String operator, boolean isAdmin, String groupId, List<WFCMessage.GroupMember> memberList);
ErrorCode kickoffGroupMembers(String operator, boolean isAdmin, String groupId, List<String> memberList);
ErrorCode quitGroup(String operator, String groupId);
void clearUserGroups(String userId);
ErrorCode dismissGroup(String operator, String groupId, boolean isAdmin);
ErrorCode modifyGroupInfo(String operator, String groupId, int modifyType, String value, boolean isAdmin);
ErrorCode modifyGroupAlias(String operator, String groupId, String alias);
List<WFCMessage.GroupInfo> getGroupInfos(List<WFCMessage.UserRequest> requests);
WFCMessage.GroupInfo getGroupInfo(String groupId);
Set<String> getUserGroupIds(String userId);
ErrorCode getGroupMembers(String groupId, long maxDt, List<WFCMessage.GroupMember> members);
WFCMessage.GroupMember getGroupMember(String groupId, String memberId);
ErrorCode transferGroup(String operator, String groupId, String newOwner, boolean isAdmin);
@@ -118,7 +122,9 @@ public interface IMessagesStore {
boolean isMemberInGroup(String member, String groupId);
ErrorCode canSendMessageInGroup(String member, String groupId);
ErrorCode recallMessage(long messageUid, String operatorId);
ErrorCode recallMessage(long messageUid, String operatorId, boolean isAdmin);
void clearUserMessages(String userId);
WFCMessage.Robot getRobot(String robotId);
void addRobot(WFCMessage.Robot robot);
@@ -130,11 +136,14 @@ public interface IMessagesStore {
List<InputOutputUserBlockStatus> getUserStatusList();
void addUserInfo(WFCMessage.User user, String password);
void destoryUser(String userId);
WFCMessage.User getUserInfo(String userId);
WFCMessage.User getUserInfoByName(String name);
WFCMessage.User getUserInfoByMobile(String mobile);
List<WFCMessage.User> searchUser(String keyword, boolean buzzy, int page);
List<WFCMessage.User> searchUser(String keyword, int searchType, int page);
boolean updateSystemSetting(int id, String value, String desc);
SystemSettingPojo getSystemSetting(int id);
void createChatroom(String chatroomId, WFCMessage.ChatroomInfo chatroomInfo);
void destoryChatroom(String chatroomId);
WFCMessage.ChatroomInfo getChatroomInfo(String chatroomId);
@@ -150,7 +159,8 @@ public interface IMessagesStore {
ErrorCode verifyToken(String userId, String token, List<String> serverIPs, List<Integer> ports);
ErrorCode login(String name, String password, List<String> userIdRet);
List<FriendData> getFriendList(String userId, long version);
List<FriendData> getFriendList(String userId, String clientId, long version);
void clearUserFriend(String userId);
List<WFCMessage.FriendRequest> getFriendRequestList(String userId, long version);
ErrorCode saveAddFriendRequest(String userId, WFCMessage.AddFriendRequest request, long[] head);
@@ -158,22 +168,26 @@ public interface IMessagesStore {
ErrorCode deleteFriend(String userId, String friendUid, long[] head);
ErrorCode blackUserRequest(String fromUser, String targetUserId, int status, long[] head);
ErrorCode SyncFriendRequestUnread(String userId, long unreadDt, long[] head);
boolean isBlacked(String fromUser, String userId);
ErrorCode isAllowUserMessage(String fromUser, String userId);
ErrorCode setFriendAliasRequest(String fromUser, String targetUserId, String alias, long[] head);
ErrorCode handleJoinChatroom(String userId, String clientId, String chatroomId);
ErrorCode handleQuitChatroom(String userId, String clientId, String chatroomId);
boolean checkChatroomParticipantIdelTime(MemorySessionStore.Session session);
ErrorCode getUserSettings(String userId, long version, WFCMessage.GetUserSettingResult.Builder builder);
WFCMessage.UserSettingEntry getUserSetting(String userId, int scope, String key);
List<WFCMessage.UserSettingEntry> getUserSetting(String userId, int scope);
long updateUserSettings(String userId, WFCMessage.ModifyUserSettingReq request);
void clearUserSettings(String userId);
boolean getUserGlobalSlient(String userId);
boolean getUserPushHiddenDetail(String userId);
boolean getUserConversationSlient(String userId, WFCMessage.Conversation conversation);
ErrorCode createChannel(String operator, WFCMessage.ChannelInfo channelInfo);
void clearUserChannels(String userId);
ErrorCode modifyChannelInfo(String operator, String channelId, int modifyType, String value);
ErrorCode transferChannel(String operator, String channelId, String newOwner);
ErrorCode distoryChannel(String operator, String channelId);
@@ -38,15 +38,19 @@ public interface ISessionsStore {
*/
boolean contains(String clientID);
Session createUserSession(String username, String clientID);
Session updateOrCreateUserSession(String username, String clientID, int platform);
ErrorCode createNewSession(String username, String clientID, boolean cleanSession, boolean createNoExist);
ErrorCode loadActiveSession(String username, String clientID);
ClientSession updateExistSession(String username, String clientID, WFCMessage.RouteRequest endpoint, boolean cleanSession);
Session getSession(String clientID);
void cleanDuplatedToken(String cid, int pushType, String token, boolean isVoip, String packageName);
void updateSessionToken(Session session, boolean voip);
void clearUserSession(String username);
/**
* @param clientID
* the client owning the session.
@@ -216,7 +216,7 @@ public class MessagesPublisher {
targetSession.refreshLastChatroomActiveTime();
}
if(System.currentTimeMillis() - targetSession.getLastChatroomActiveTime() > 5*60*1000) {
if (!m_messagesStore.checkChatroomParticipantIdelTime(targetSession)) {
m_messagesStore.handleQuitChatroom(user, targetSession.getClientID(), target);
continue;
}
@@ -16,7 +16,15 @@
package io.moquette.spi.impl;
import cn.wildfirechat.common.ErrorCode;
import cn.wildfirechat.pojos.SendMessageData;
import cn.wildfirechat.pojos.UserOnlineStatus;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.gson.Gson;
import com.hazelcast.core.Member;
import com.hazelcast.util.StringUtil;
import io.moquette.BrokerConstants;
import io.moquette.persistence.RPCCenter;
import io.moquette.interception.InterceptHandler;
import io.moquette.interception.messages.InterceptAcknowledgedMessage;
@@ -38,9 +46,12 @@ import io.netty.handler.codec.mqtt.*;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import win.liyufan.im.HttpUtils;
import win.liyufan.im.Utility;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static io.moquette.spi.impl.InternalRepublisher.createPublishForQos;
import static io.moquette.spi.impl.Utils.readBytesAndRewind;
@@ -55,7 +66,24 @@ import static io.moquette.server.ConnectionDescriptor.ConnectionState.*;
*
* Used by the front facing class ProtocolProcessorBootstrapper.
*/
public class ProtocolProcessor {
private static ExecutorService executorCallback = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public void kickoffSession(final MemorySessionStore.Session session) {
mServer.getImBusinessScheduler().execute(()->{
ConnectionDescriptor descriptor = connectionDescriptors.getConnection(session.getClientID());
try {
if (descriptor != null) {
processDisconnect(descriptor.getChannel(), true);
}
} catch (InterruptedException e) {
e.printStackTrace();
Utility.printExecption(LOG, e);
}
});
}
private void handleTargetRemovedFromCurrentNode(TargetEntry target) {
System.out.println("kickof user " + target);
if (target.type == TargetEntry.Type.TARGET_TYPE_USER) {
@@ -135,16 +163,22 @@ public class ProtocolProcessor {
this.connectionDescriptors, this.messagesPublisher, sessionsStore, server.getImBusinessScheduler(), server);
mServer = server;
String onlineStatusCallback = server.getConfig().getProperty(BrokerConstants.USER_ONLINE_STATUS_CALLBACK);
if (!com.hazelcast.util.StringUtil.isNullOrEmpty(onlineStatusCallback)) {
mUserOnlineStatusCallback = onlineStatusCallback;
}
}
private String mUserOnlineStatusCallback;
public void processConnect(Channel channel, MqttConnectMessage msg) {
MqttConnectPayload payload = msg.payload();
String clientId = payload.clientIdentifier();
LOG.info("Processing CONNECT message. CId={}, username={}", clientId, payload.userName());
if (msg.variableHeader().version() != MqttVersion.MQTT_3_1.protocolLevel()
&& msg.variableHeader().version() != MqttVersion.MQTT_3_1_1.protocolLevel()
&& msg.variableHeader().version() != MqttVersion.Wildfire_1.protocolLevel()) {
if (msg.variableHeader().version() < MqttVersion.MQTT_3_1_1.protocolLevel() ||
msg.variableHeader().version() >= MqttVersion.Wildfire_Max.protocolLevel()) {
MqttConnAckMessage badProto = connAck(CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION);
LOG.error("MQTT protocol version is not valid. CId={}", clientId);
@@ -165,7 +199,11 @@ public class ProtocolProcessor {
MqttVersion mqttVersion = MqttVersion.fromProtocolLevel(msg.variableHeader().version());
if (!login(channel, msg, clientId, mqttVersion)) {
MqttConnAckMessage badId = connAck(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
channel.writeAndFlush(badId);
channel.close();
LOG.error("The MQTT login failure. Username={}", payload.userName());
return;
}
if (!mServer.m_initialized) {
@@ -211,11 +249,20 @@ public class ProtocolProcessor {
MemorySessionStore.Session session = m_sessionsStore.getSession(clientId);
if(session != null) {
session.refreshLastActiveTime();
forwardOnlineStatusEvent(payload.userName(), clientId, session.getPlatform(), UserOnlineStatus.ONLINE);
} else {
forwardOnlineStatusEvent(payload.userName(), clientId, ProtoConstants.Platform.Platform_UNSET, UserOnlineStatus.ONLINE);
}
LOG.info("The CONNECT message has been processed. CId={}, username={}", clientId, payload.userName());
}
public void forwardOnlineStatusEvent(String userId, String clientId, int platform, int status) {
if (!StringUtil.isNullOrEmpty(mUserOnlineStatusCallback)) {
executorCallback.execute(() -> HttpUtils.httpJsonPost(mUserOnlineStatusCallback, new Gson().toJson(new UserOnlineStatus(userId, clientId, platform, status))));
}
}
private MqttConnAckMessage connAck(MqttConnectReturnCode returnCode) {
return connAck(returnCode, false);
}
@@ -244,7 +291,7 @@ public class ProtocolProcessor {
// handle user authentication
if (msg.variableHeader().hasUserName()) {
int status = m_messagesStore.getUserStatus(msg.payload().userName());
if (status == 2) {
if (status == ProtoConstants.UserStatus.Forbidden) {
failedBlocked(channel);
return false;
}
@@ -254,9 +301,17 @@ public class ProtocolProcessor {
MemorySessionStore.Session session = m_sessionsStore.getSession(clientId);
if (session == null) {
m_sessionsStore.createNewSession(msg.payload().userName(), clientId, true, false);
ErrorCode errorCode = m_sessionsStore.loadActiveSession(msg.payload().userName(), clientId);
if (errorCode != ErrorCode.ERROR_CODE_SUCCESS) {
return false;
}
session = m_sessionsStore.getSession(clientId);
}
if (session.getDeleted() != 0) {
LOG.error("user {} session {} is deleted. login failure", msg.payload().userName(), clientId);
return false;
}
if (session != null && session.getUsername().equals(msg.payload().userName())) {
pwd = AES.AESDecrypt(pwd, session.getSecret(), true);
@@ -465,7 +520,7 @@ public class ProtocolProcessor {
}
if (!clearSession) {
processConnectionLost(clientID, channel);
processConnectionLost(clientID, channel, clearSession);
return;
}
@@ -502,16 +557,20 @@ public class ProtocolProcessor {
return;
}
boolean stillPresent = this.connectionDescriptors.removeConnection(existingDescriptor);
if (!stillPresent) {
// another descriptor was inserted
LOG.warn("Another descriptor has been inserted. CId={}", clientID);
return;
}
this.connectionDescriptors.removeConnection(existingDescriptor);
LOG.info("The DISCONNECT message has been processed. CId={}", clientID);
String username = NettyUtils.userName(channel);
MemorySessionStore.Session session = m_sessionsStore.getSession(clientID);
if(session != null) {
forwardOnlineStatusEvent(username, clientID, session.getPlatform(), UserOnlineStatus.LOGOUT);
}
channel.closeFuture();
//disconnect the session
m_sessionsStore.sessionForClient(clientID).disconnect(clearSession);
}
@@ -542,17 +601,21 @@ public class ProtocolProcessor {
return true;
}
public void processConnectionLost(String clientID, Channel channel) {
public void processConnectionLost(String clientID, Channel channel, boolean clearSession) {
LOG.info("Processing connection lost event. CId={}", clientID);
String username = NettyUtils.userName(channel);
MemorySessionStore.Session session = m_sessionsStore.getSession(clientID);
if(session != null) {
session.refreshLastActiveTime();
forwardOnlineStatusEvent(username, clientID, session.getPlatform(), clearSession ? UserOnlineStatus.LOGOUT : UserOnlineStatus.OFFLINE);
}
ConnectionDescriptor oldConnDescr = new ConnectionDescriptor(clientID, channel);
if(connectionDescriptors.removeConnection(oldConnDescr)) {
MemorySessionStore.Session session = m_sessionsStore.getSession(clientID);
if(session != null) {
session.refreshLastActiveTime();
}
String username = NettyUtils.userName(channel);
m_interceptor.notifyClientConnectionLost(clientID, username);
}
}
/**
@@ -623,7 +686,8 @@ public class ProtocolProcessor {
public void onRpcMsg(String fromUser, String clientId, byte[] message, int messageId, String from, String request, boolean isAdmin) {
if(request.equals(RPCCenter.KICKOFF_USER_REQUEST)) {
mServer.getImBusinessScheduler().execute(()->handleTargetRemovedFromCurrentNode(new TargetEntry(TargetEntry.Type.TARGET_TYPE_USER, from)));
String userId = new String(message);
mServer.getImBusinessScheduler().execute(()->handleTargetRemovedFromCurrentNode(new TargetEntry(TargetEntry.Type.TARGET_TYPE_USER, userId)));
return;
}
qos1PublishHandler.onRpcMsg(fromUser, clientId, message, messageId, from, request, isAdmin);
@@ -178,7 +178,7 @@ public class Qos1PublishHandler extends QosPublishHandler {
} else {
MemorySessionStore.Session session = m_sessionStore.getSession(clientID);
if (session != null && session.getUsername().equals(fromUser)) {
if (data.length > 7*1024 && session.getMqttVersion() == MqttVersion.Wildfire_1) {
if (data.length > 7*1024 && session.getMqttVersion().protocolLevel() >= MqttVersion.Wildfire_1.protocolLevel()) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip;
try {
@@ -43,7 +43,7 @@ final class MqttCodecUtil {
return clientId != null && clientId.length() >= MIN_CLIENT_ID_LENGTH &&
clientId.length() <= MAX_CLIENT_ID_LENGTH;
}
if (mqttVersion == MqttVersion.MQTT_3_1_1 || mqttVersion == MqttVersion.Wildfire_1) {
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) {
// In 3.1.3.1 Client Identifier of MQTT 3.1.1 specification, The Server MAY allow ClientId’s
// that contain more than 23 encoded bytes. And, The Server MAY allow zero-length ClientId.
return clientId != null;
@@ -223,7 +223,7 @@ public final class MqttDecoder extends ReplayingDecoder<DecoderState> {
final int willQos = (b1 & 0x18) >> 3;
final boolean willFlag = (b1 & 0x04) == 0x04;
final boolean cleanSession = (b1 & 0x02) == 0x02;
if (mqttVersion == MqttVersion.MQTT_3_1_1 || mqttVersion == MqttVersion.Wildfire_1) {
if (mqttVersion.protocolLevel() >= MqttVersion.MQTT_3_1_1.protocolLevel()) {
final boolean zeroReservedFlag = (b1 & 0x01) == 0x0;
if (!zeroReservedFlag) {
// MQTT v3.1.1: The Server MUST validate that the reserved flag in the CONNECT Control Packet is
@@ -25,7 +25,9 @@ import io.netty.util.internal.ObjectUtil;
public enum MqttVersion {
MQTT_3_1("MQIsdp", (byte) 3),
MQTT_3_1_1("MQTT", (byte) 4),
Wildfire_1("MQTT", (byte) 5);
Wildfire_1("MQTT", (byte) 5),
Wildfire_2("MQTT", (byte) 6),
Wildfire_Max("MQTT", (byte) 7);
private final String name;
private final byte level;
@@ -52,7 +52,14 @@ public class HttpUtils {
@Override
public void onResponse(Call call, Response response) throws IOException {
LOG.info("POST to {} success with response", url, response.body().string());
LOG.info("POST to {} success with response: {}", url, response.body());
try {
if (response.body() != null) {
response.body().close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
@@ -33,7 +33,7 @@ public class IDUtils {
return ch - 'a' + 26;
} else if(ch >= '0' && ch <= '9') {
return ch - '0' + 52;
} else if(ch == '_') {
} else if(ch == '+') {
return 62;
}
return 63;
@@ -10,6 +10,7 @@ package win.liyufan.im;
public interface IMTopic {
String SendMessageTopic = "MS";
String MultiCastMessageTopic = "MMC";
String RecallMessageTopic = "MR";
String PullMessageTopic = "MP";
String NotifyMessageTopic = "MN";
@@ -70,6 +71,7 @@ public interface IMTopic {
String ChannelPullTopic = "CHP";
String GetTokenTopic = "GETTOKEN";
String DestroyUserTopic = "DESTROYUSER";
String LoadRemoteMessagesTopic = "LRM";
}
@@ -1,4 +1,7 @@
<?xml version ="1.0" encoding="UTF-8"?>
<!-- # 本配置文件为debug使用,修改这里不会在Release包中生效。Release中包含的配置文件在 ${Porject_Path}/distribution/src/main/resources目录下-->
<c3p0-config>
<!-- This is default config! -->
<default-config>
-85
View File
@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<relativePath>../</relativePath>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.24</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>client</artifactId>
<dependencies>
<dependency>
<groupId>cn.wildfirechat</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.fusesource.hawtbuf/hawtbuf -->
<dependency>
<groupId>org.fusesource.hawtbuf</groupId>
<artifactId>hawtbuf</artifactId>
<version>1.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.fusesource.hawtdispatch/hawtdispatch-transport -->
<dependency>
<groupId>org.fusesource.hawtdispatch</groupId>
<artifactId>hawtdispatch-transport</artifactId>
<version>1.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.10</version>
</dependency>
</dependencies>
</project>
@@ -1,424 +0,0 @@
package cn.wildfirechat.client;
import cn.wildfirechat.proto.ProtoConstants;
import cn.wildfirechat.proto.WFCMessage;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.xiaoleilu.loServer.pojos.InputRoute;
import io.moquette.spi.impl.security.AES;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.mqtt.client.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import static cn.wildfirechat.client.IMClient.ConnectionStatus.ConnectionStatus_Connected;
import static cn.wildfirechat.client.IMClient.ConnectionStatus.ConnectionStatus_Connecting;
import static cn.wildfirechat.client.IMClient.ConnectionStatus.ConnectionStatus_Unconnected;
public class IMClient implements Listener {
private final String userId;
private final String token;
private final String clientId;
private final String host;
private final int port;
private ConnectionStatusCallback connectionStatusCallback;
private ReceiveMessageCallback receiveMessageCallback;
private ConnectionStatus connectionStatus;
protected String mqttServerIp;
protected long mqttServerPort;
private static byte[] commonSecret= {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F};
protected String privateSecret;
private long messageHead;
private transient MQTT mqtt = new MQTT();
private transient CallbackConnection connection = null;
public interface ReceiveMessageCallback {
void onReceiveMessages(List<WFCMessage.Message> messageList, boolean hasMore);
void onRecallMessage(long messageUid);
}
public interface ConnectionStatusCallback {
void onConnectionStatusChanged(ConnectionStatus newStatus);
}
public interface SendMessageCallback {
void onSuccess(long messageUid, long timestamp);
void onFailure(int errorCode);
}
public enum ConnectionStatus {
ConnectionStatus_Unconnected,
ConnectionStatus_Connecting,
ConnectionStatus_Connected,
}
public IMClient(String userId, String token, String clientId, String host, int port) {
this.userId = userId;
byte[] data = Base64.getDecoder().decode(token);
data = AES.AESDecrypt(data, commonSecret, false);
String s = new String(data);
String[] ss = s.split("\\|");
this.token = ss[0];
this.privateSecret = ss[1];
this.clientId = clientId;
this.host = host;
this.port = port;
AES.init(commonSecret);
}
public void connect() {
if(route(userId, token)) {
try {
mqtt.setHost("tcp://" + mqttServerIp + ":" + mqttServerPort);
mqtt.setVersion("3.1.1");
mqtt.setKeepAlive((short)180);
mqtt.setClientId(clientId);
mqtt.setConnectAttemptsMax(100);
mqtt.setReconnectAttemptsMax(100);
mqtt.setUserName(userId);
byte[] password = AES.AESEncrypt(token, privateSecret);
mqtt.setPassword(new UTF8Buffer(password));
connection = mqtt.callbackConnection();
connection.listener(this);
//connecting
connectionStatus = ConnectionStatus_Connecting;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
connection.connect(new Callback<byte[]>() {
@Override
public void onSuccess(byte[] value) {
if (value != null) {
try {
WFCMessage.ConnectAckPayload ackPayload = WFCMessage.ConnectAckPayload.parseFrom(value);
System.out.println(ackPayload.getMsgHead());
messageHead = ackPayload.getMsgHead();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
System.out.println("on connect success");
//connected
connectionStatus = ConnectionStatus_Connected;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("on connect failure");
connectionStatus = ConnectionStatus_Unconnected;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void disconnect(boolean clearSession, final Callback<Void> onComplete) {
this.connection.disconnect(clearSession, onComplete);
}
public void sendMessage(WFCMessage.Conversation conversation, WFCMessage.MessageContent messageContent, final SendMessageCallback callback) {
WFCMessage.Message message = WFCMessage.Message.newBuilder().setConversation(conversation).setContent(messageContent).setFromUser(userId).build();
byte[] data = message.toByteArray();
data = AES.AESEncrypt(data, privateSecret);
connection.publish("MS", data, QoS.AT_LEAST_ONCE, false, new Callback<byte[]>() {
@Override
public void onSuccess(byte[] value) {
if (value[0] == 0) {
byte[] data = new byte[value.length-1];
for (int i = 0; i < data.length; i++) {
data[i] = value[i+1];
}
data = AES.AESDecrypt(data, privateSecret, true);
ByteBuffer buffer = ByteBuffer.wrap(data, 0,16);
long messageUid = buffer.getLong();
long timestamp = buffer.getLong();
callback.onSuccess(messageUid, timestamp);
} else {
callback.onFailure(value[0]);
}
}
@Override
public void onFailure(Throwable value) {
callback.onFailure(-1);
}
});
}
protected boolean route(String userId, String token) {
HttpPost httpPost;
try{
HttpClient httpClient = HttpClientBuilder.create().build();
httpPost = new HttpPost("http://" + host + ":" + port + "/route");
InputRoute inputRoute = new InputRoute();
inputRoute.setUserId(userId);
inputRoute.setClientId(clientId);
inputRoute.setToken(token);
WFCMessage.RouteRequest routeRequest = WFCMessage.RouteRequest.newBuilder().setPlatform(0).build();
WFCMessage.IMHttpWrapper request = WFCMessage.IMHttpWrapper.newBuilder().setClientId(clientId).setToken(token).setRequest("ROUTE").setData(ByteString.copyFrom(routeRequest.toByteArray())).build();
byte[] data = AES.AESEncrypt(request.toByteArray(), privateSecret);
data = Base64.getEncoder().encode(data);
StringEntity entity = new StringEntity(new String(data), Charset.forName("UTF-8"));
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
byte[] cidByte = AES.AESEncrypt(clientId.getBytes(), commonSecret);
cidByte = Base64.getEncoder().encode(cidByte);
String cid = new String(cidByte);
httpPost.setHeader("cid", cid);
byte[] uidByte = AES.AESEncrypt(userId.getBytes(), commonSecret);
uidByte = Base64.getEncoder().encode(uidByte);
String uid = new String(uidByte);
httpPost.setHeader("uid", uid);
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
if (response.getStatusLine().getStatusCode() != 200) {
System.out.println("Http response error {" + response.getStatusLine().getStatusCode() + "}");
return false;
}
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
try {
byte[] bytes = new byte[resEntity.getContent().available()];
resEntity.getContent().read(bytes);
if (bytes[0] == 0) {
byte[] bytes1 = new byte[bytes.length -1];
for (int i = 0; i < bytes1.length; i++) {
bytes1[i] = bytes[i+1];
}
byte[] rawData = AES.AESDecrypt(bytes1, privateSecret, true);
WFCMessage.RouteResponse routeResponse = WFCMessage.RouteResponse.parseFrom(rawData);
mqttServerIp = routeResponse.getHost();
mqttServerPort = routeResponse.getLongPort();
routeResponse.getShortPort();
return true;
} else {
System.out.println("the route failure:" + bytes[0]);
return false;
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedOperationException e) {
e.printStackTrace();
}
}
} else {
System.out.println("Http response nil");
}
}catch(Exception ex){
ex.printStackTrace();
}
return false;
}
@Override
public void onConnected() {
System.out.println("onConnected");
connectionStatus = ConnectionStatus_Connecting;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
@Override
public void onDisconnected() {
System.out.println("onDisconnected");
connectionStatus = ConnectionStatus_Unconnected;
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(connectionStatus);
}
}
@Override
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack) {
System.out.println("onPublish" + topic.toString());
ack.run();
if (topic.toString().equals("MN")) {
try {
WFCMessage.NotifyMessage notifyMessage = WFCMessage.NotifyMessage.parseFrom(body.toByteArray());
WFCMessage.PullMessageRequest request = WFCMessage.PullMessageRequest.newBuilder().setId(messageHead).setType(notifyMessage.getType()).build();
byte[] data = request.toByteArray();
data = AES.AESEncrypt(data, privateSecret);
connection.publish("MP", data, QoS.AT_LEAST_ONCE, false, new Callback<byte[]>(){
@Override
public void onSuccess(byte[] value) {
if (value == null || value.length == 0) {
System.out.println("Invalide data");
}
if (value[0] != 0) {
System.out.println("Pull message error with errorCode:" + value[0]);
}
byte[] data = new byte[value.length - 1];
for (int i = 0; i < data.length; i++) {
data[i] = value[i+1];
}
try {
data = AES.AESDecrypt(data, privateSecret, true);
try {
WFCMessage.PullMessageResult result = WFCMessage.PullMessageResult.parseFrom(data);
if (receiveMessageCallback != null && result.getMessageList().size() > 0) {
List<WFCMessage.Message> messages = result.getMessageList();
List<WFCMessage.Message> out = new ArrayList<>();
for (WFCMessage.Message msg : messages) {
if (msg.getConversation().getType() == ProtoConstants.ConversationType.ConversationType_Private && msg.getConversation().getTarget().equals(userId)) {
msg = msg.toBuilder().setConversation(msg.getConversation().toBuilder().setTarget(msg.getFromUser())).build();
}
out.add(msg);
}
receiveMessageCallback.onReceiveMessages(out, false);
}
messageHead = result.getHead();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
System.out.println("onSuccess");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("onFailure");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("onDisconnected" + value.toString());
if(connectionStatusCallback != null) {
connectionStatusCallback.onConnectionStatusChanged(ConnectionStatus_Unconnected);
}
}
public void setConnectionStatusCallback(ConnectionStatusCallback connectionStatusCallback) {
this.connectionStatusCallback = connectionStatusCallback;
}
public void setReceiveMessageCallback(ReceiveMessageCallback receiveMessageCallback) {
this.receiveMessageCallback = receiveMessageCallback;
}
public String getUserId() {
return userId;
}
public String getToken() {
return token;
}
public String getClientId() {
return clientId;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public ConnectionStatus getConnectionStatus() {
return connectionStatus;
}
public static void main(String[] args) {
//token与userid和clientid是绑定的,使用时一定要传入正确的userid和clientid,不然会认为token非法
//clientId唯一代表一个设备,只能有一个登录。如果使用同一个clientId登录多次,会出现不可预料问题。
IMClient client = new IMClient("KNK_K_00", "6I7UTfKSQT7VhsUJfU6UCvDeCyblLfcXPfajAFxgFGOcqvfFVQCvYy8A5vfAGUj2LSYgoM5W02MHh6jtG/otQs6ft8zLT4Efz315UlDLfrz+OSQYfwAuGFw9m0/x4ckrgiooprsKFxPMaNcCDact0qvp1dBhbeGUipdWVhI33b4=", "5B6DEB72-8A3D-45E2-A88A-47DC4E692B00", "192.168.0.193", 80);
client.setReceiveMessageCallback(new ReceiveMessageCallback() {
@Override
public void onReceiveMessages(List<WFCMessage.Message> messageList, boolean hasMore) {
System.out.println("recieved messages");
}
@Override
public void onRecallMessage(long messageUid) {
System.out.println("recalled messages");
}
});
client.setConnectionStatusCallback((ConnectionStatus newStatus) -> {
if (newStatus == ConnectionStatus_Connected) {
WFCMessage.Conversation conversation = WFCMessage.Conversation.newBuilder().setType(0).setTarget("IMININnn").setLine(0).build();
WFCMessage.MessageContent messageContent = WFCMessage.MessageContent.newBuilder().setSearchableContent("helloworld").setType(1).build();
client.sendMessage(conversation, messageContent, new SendMessageCallback() {
@Override
public void onSuccess(long messageUid, long timestamp) {
System.out.println("send success");
}
@Override
public void onFailure(int errorCode) {
System.out.println("send failure");
}
});
}
});
client.connect();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@@ -1,154 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import static org.fusesource.hawtbuf.Buffer.utf8;
/**
* <p>
* A blocking Connection interface to MQTT.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class BlockingConnection {
private final FutureConnection next;
public BlockingConnection(FutureConnection next) {
this.next = next;
}
public boolean isConnected() {
return next.isConnected();
}
public void connect() throws Exception {
this.next.connect().await();
}
public void disconnect() throws Exception {
this.next.disconnect().await();
}
public void kill() throws Exception {
this.next.kill().await();
}
public byte[] subscribe(final Topic[] topics) throws Exception {
return this.next.subscribe(topics).await();
}
public void unsubscribe(final String[] topics) throws Exception {
this.next.unsubscribe(topics).await();
}
public void unsubscribe(final UTF8Buffer[] topics) throws Exception {
this.next.unsubscribe(topics).await();
}
public void publish(final UTF8Buffer topic, final Buffer payload, final QoS qos, final boolean retain) throws Exception {
this.next.publish(topic, payload, qos, retain).await();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of overridden methods use File | Settings | File Templates.
}
public void publish(final String topic, final byte[] payload, final QoS qos, final boolean retain) throws Exception {
publish(utf8(topic), new Buffer(payload), qos, retain);
}
public Message receive() throws Exception {
return this.next.receive().await();
}
/**
* @return null if the receive times out.
*/
public Message receive(long amount, TimeUnit unit) throws Exception {
Future<Message> receive = this.next.receive();
try {
Message message = receive.await(amount, unit);
if( message!=null ) {
message.blocking = true;
}
return message;
} catch (TimeoutException e) {
// Put it back on the queue..
receive.then(new Callback<Message>() {
public void onSuccess(final Message value) {
next.putBackMessage(value);
}
public void onFailure(Throwable value) {
}
});
return null;
}
}
public void setReceiveBuffer(final long receiveBuffer) throws InterruptedException {
final CountDownLatch done = new CountDownLatch(1);
next.getDispatchQueue().execute(new Runnable() {
public void run() {
try {
next.setReceiveBuffer(receiveBuffer);
} finally {
done.countDown();
}
}
});
done.await();
}
public long getReceiveBuffer() throws InterruptedException {
final CountDownLatch done = new CountDownLatch(1);
final AtomicLong result = new AtomicLong();
next.getDispatchQueue().execute(new Runnable() {
public void run() {
try {
result.set(next.getReceiveBuffer());
} finally {
done.countDown();
}
}
});
done.await();
return result.get();
}
public void resume() {
next.resume();
}
public void suspend() {
next.suspend();
}
}
@@ -1,31 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* Function Result that carries one value.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public interface Callback<T> {
public void onSuccess(T value);
public void onFailure(Throwable value);
}
@@ -1,966 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.HexSupport;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.Dispatch;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.Task;
import org.fusesource.hawtdispatch.transport.DefaultTransportListener;
import org.fusesource.hawtdispatch.transport.HeartBeatMonitor;
import org.fusesource.hawtdispatch.transport.SslTransport;
import org.fusesource.hawtdispatch.transport.TcpTransport;
import org.fusesource.hawtdispatch.transport.Transport;
import org.fusesource.mqtt.codec.CONNACK;
import org.fusesource.mqtt.codec.DISCONNECT;
import org.fusesource.mqtt.codec.MQTTFrame;
import org.fusesource.mqtt.codec.MQTTProtocolCodec;
import org.fusesource.mqtt.codec.MessageSupport.Acked;
import org.fusesource.mqtt.codec.PINGREQ;
import org.fusesource.mqtt.codec.PINGRESP;
import org.fusesource.mqtt.codec.PUBACK;
import org.fusesource.mqtt.codec.PUBCOMP;
import org.fusesource.mqtt.codec.PUBLISH;
import org.fusesource.mqtt.codec.PUBREC;
import org.fusesource.mqtt.codec.PUBREL;
import org.fusesource.mqtt.codec.SUBACK;
import org.fusesource.mqtt.codec.SUBSCRIBE;
import org.fusesource.mqtt.codec.UNSUBACK;
import org.fusesource.mqtt.codec.UNSUBSCRIBE;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.fusesource.hawtbuf.Buffer.utf8;
import static org.fusesource.hawtdispatch.Dispatch.createQueue;
/**
* <p>
* A callback based non/blocking Connection interface to MQTT.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class CallbackConnection {
private static class Request {
private final MQTTFrame frame;
private final short id;
private final Callback cb;
Request(int id, MQTTFrame frame, Callback cb) {
this.id = (short) id;
this.cb = cb;
this.frame = frame;
}
}
private static final ExtendedListener DEFAULT_LISTENER = new ExtendedListener(){
public void onConnected() {
}
public void onDisconnected() {
}
public void onPublish(UTF8Buffer utf8Buffer, Buffer buffer, Runnable runnable) {
this.onFailure(createListenerNotSetError());
}
public void onPublish(UTF8Buffer topic, Buffer body, Callback<Callback<byte[]>> ack) {
this.onFailure(createListenerNotSetError());
}
public void onFailure(Throwable value) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), value);
}
};
private final DispatchQueue queue;
private final MQTT mqtt;
private Transport transport;
private ExtendedListener listener = DEFAULT_LISTENER;
private Runnable refiller;
private Map<Short, Request> requests = new ConcurrentHashMap<Short, Request>();
private LinkedList<Request> overflow = new LinkedList<Request>();
private final HashMap<Short, Callback<byte[]>> processed = new HashMap<Short, Callback<byte[]>>();
private Throwable failure;
private boolean disconnected = false;
private HeartBeatMonitor heartBeatMonitor;
private long pingedAt;
private long reconnects = 0;
private final AtomicInteger suspendCount = new AtomicInteger(0);
private final AtomicInteger suspendChanges = new AtomicInteger(0);
private final HashMap<UTF8Buffer, QoS> activeSubs = new HashMap<UTF8Buffer, QoS>();
public CallbackConnection(MQTT mqtt) {
this.mqtt = mqtt;
if(this.mqtt.dispatchQueue == null) {
this.queue = createQueue("mqtt client");
} else {
this.queue = this.mqtt.dispatchQueue;
}
}
public void connect(final Callback<byte[]> cb) {
assert cb !=null : "Callback should not be null.";
if( transport!=null ) {
cb.onFailure(new IllegalStateException("Already connected"));
return;
}
try {
createTransport(new LoginHandler(cb, true));
} catch (Throwable e) {
// This error happens when the MQTT config is invalid, reattempting
// wont fix this case.
cb.onFailure(e);
}
}
void reconnect() {
try {
// And reconnect.
createTransport(new LoginHandler(new Callback<byte[]>() {
public void onSuccess(byte[] value) {
mqtt.tracer.debug("Restoring MQTT connection state");
// Setup a new overflow so that the replay can be sent out before the original overflow list.
LinkedList<Request> originalOverflow = overflow;
Map<Short, Request> originalRequests = requests;
overflow = new LinkedList<Request>();
requests = new ConcurrentHashMap<Short, Request>();
// Restore any active subscriptions.
if (!activeSubs.isEmpty()) {
ArrayList<Topic> topics = new ArrayList<Topic>(activeSubs.size());
for (Map.Entry<UTF8Buffer, QoS> entry : activeSubs.entrySet()) {
topics.add(new Topic(entry.getKey(), entry.getValue()));
}
send(new SUBSCRIBE().topics(topics.toArray(new Topic[topics.size()])), null);
}
// Replay any un-acked requests..
for (Map.Entry<Short, Request> entry : originalRequests.entrySet()) {
MQTTFrame frame = entry.getValue().frame;
frame.dup(true); // set the dup flag as these frames were previously transmitted.
send(entry.getValue());
}
// Replay the original overflow
for (Request request : originalOverflow) {
// Stuff in the overflow never got sent out.. so no need to set the dup flag
send(request);
}
}
public void onFailure(Throwable value) {
handleFatalFailure(value);
}
}, false));
} catch (Throwable e) {
handleFatalFailure(e);
}
}
void handleSessionFailure(Throwable error) {
// Socket failure, should we try to reconnect?
if( !disconnected && (mqtt.reconnectAttemptsMax<0 || reconnects < mqtt.reconnectAttemptsMax ) ) {
mqtt.tracer.debug("Reconnecting transport");
// Cleanup the previous transport.
if(heartBeatMonitor!=null) {
heartBeatMonitor.stop();
heartBeatMonitor = null;
}
final Transport t = transport;
transport = null;
if(t!=null) {
t.stop(new Task() {
@Override
public void run() {
listener.onDisconnected();
reconnect();
}
});
} else {
reconnect();
}
} else {
// nope.
handleFatalFailure(error);
}
}
void reconnect(final Callback<Transport> onConnect) {
long reconnectDelay = mqtt.reconnectDelay;
if( reconnectDelay> 0 && mqtt.reconnectBackOffMultiplier > 1.0 ) {
reconnectDelay = (long) Math.pow(mqtt.reconnectDelay*reconnects, mqtt.reconnectBackOffMultiplier);
}
reconnectDelay = Math.min(reconnectDelay, mqtt.reconnectDelayMax);
reconnects += 1;
queue.executeAfter(reconnectDelay, TimeUnit.MILLISECONDS, new Task() {
@Override
public void run() {
if(disconnected) {
onConnect.onFailure(createDisconnectedError());
} else {
try {
createTransport(onConnect);
} catch (Exception e) {
onConnect.onFailure(e);
}
}
}
});
}
/**
* Creates and start a transport to the MQTT server. Passes it to the onConnect
* once the transport is connected.
*
* @param onConnect
* @throws Exception
*/
void createTransport(final Callback<Transport> onConnect) throws Exception {
mqtt.tracer.debug("Connecting");
String scheme = mqtt.host.getScheme();
final Transport transport;
if( "tcp".equals(scheme) ) {
transport = new TcpTransport();
} else if( SslTransport.protocol(scheme)!=null ) {
SslTransport ssl = new SslTransport();
if( mqtt.sslContext == null ) {
mqtt.sslContext = SSLContext.getDefault();
}
ssl.setSSLContext(mqtt.sslContext);
transport = ssl;
} else {
throw new Exception("Unsupported URI scheme '"+scheme+"'");
}
if( mqtt.blockingExecutor == null ) {
mqtt.blockingExecutor = MQTT.getBlockingThreadPool();
}
transport.setBlockingExecutor(mqtt.blockingExecutor);
transport.setDispatchQueue(queue);
transport.setProtocolCodec(new MQTTProtocolCodec());
if( transport instanceof TcpTransport ) {
TcpTransport tcp = (TcpTransport)transport;
tcp.setMaxReadRate(mqtt.maxReadRate);
tcp.setMaxWriteRate(mqtt.maxWriteRate);
tcp.setReceiveBufferSize(mqtt.receiveBufferSize);
tcp.setSendBufferSize(mqtt.sendBufferSize);
tcp.setTrafficClass(mqtt.trafficClass);
tcp.setUseLocalHost(mqtt.useLocalHost);
tcp.connecting(mqtt.host, mqtt.localAddress);
}
transport.setTransportListener(new DefaultTransportListener(){
@Override
public void onTransportConnected() {
mqtt.tracer.debug("Transport connected");
if(disconnected) {
onFailure(createDisconnectedError());
} else {
onConnect.onSuccess(transport);
}
}
@Override
public void onTransportFailure(final IOException error) {
mqtt.tracer.debug("Transport failure: %s", error);
onFailure(error);
}
private void onFailure(final Throwable error) {
if(!transport.isClosed()) {
transport.stop(new Task() {
@Override
public void run() {
onConnect.onFailure(error);
}
});
}
}
});
transport.start(NOOP);
}
class LoginHandler implements Callback<Transport> {
private final Callback<byte[]> cb;
private final boolean initialConnect;
LoginHandler(Callback<byte[]> cb, boolean initialConnect) {
this.cb = cb;
this.initialConnect = initialConnect;
}
public void onSuccess(final Transport transport) {
transport.setTransportListener(new DefaultTransportListener() {
@Override
public void onTransportFailure(IOException error) {
mqtt.tracer.debug("Transport failure: %s", error);
transport.stop(NOOP);
onFailure(error);
}
@Override
public void onTransportCommand(Object command) {
MQTTFrame response = (MQTTFrame) command;
mqtt.tracer.onReceive(response);
try {
switch (response.messageType()) {
case CONNACK.TYPE:
CONNACK connack = new CONNACK().decode(response);
switch (connack.code()) {
case CONNECTION_ACCEPTED:
mqtt.tracer.debug("MQTT login accepted");
onSessionEstablished(transport);
listener.onConnected();
cb.onSuccess(connack.payload);
queue.execute(new Task() {
@Override
public void run() {
drainOverflow();
}
});
break;
default:
mqtt.tracer.debug("MQTT login rejected");
// Bad creds or something. No point in reconnecting.
transport.stop(NOOP);
cb.onFailure(new MQTTException("Could not connect: " + connack.code(), connack));
}
break;
default:
mqtt.tracer.debug("Received unexpected MQTT frame: %d", response.messageType());
// Naughty MQTT server? No point in reconnecting.
transport.stop(NOOP);
cb.onFailure(new IOException("Could not connect. Received unexpected command: " + response.messageType()));
}
} catch (ProtocolException e) {
mqtt.tracer.debug("Protocol error: %s", e);
transport.stop(NOOP);
cb.onFailure(e);
}
}
});
transport.resumeRead();
if( mqtt.connect.clientId() == null ) {
String id = hex(transport.getLocalAddress())+Long.toHexString(System.currentTimeMillis()/1000);
if(id.length() > 23) {
id = id.substring(0,23);
}
mqtt.connect.clientId(utf8(id));
}
MQTTFrame encoded = mqtt.connect.encode();
boolean accepted = transport.offer(encoded);
mqtt.tracer.onSend(encoded);
mqtt.tracer.debug("Logging in");
assert accepted: "First frame should always be accepted by the transport";
}
private boolean tryReconnect() {
if(initialConnect) {
return mqtt.connectAttemptsMax<0 || reconnects < mqtt.connectAttemptsMax;
}
return mqtt.reconnectAttemptsMax<0 || reconnects < mqtt.reconnectAttemptsMax;
}
public void onFailure(Throwable value) {
// Socket failure, should we try to reconnect?
if( !disconnected && tryReconnect() ) {
reconnect(this);
} else {
// nope.
cb.onFailure(value);
}
}
}
private boolean onRefillCalled =false;
public void onSessionEstablished(Transport transport) {
this.transport = transport;
if( suspendCount.get() > 0 ) {
this.transport.suspendRead();
}
this.transport.setTransportListener(new DefaultTransportListener() {
@Override
public void onTransportCommand(Object command) {
MQTTFrame frame = (MQTTFrame) command;
mqtt.tracer.onReceive(frame);
processFrame(frame);
}
@Override
public void onRefill() {
onRefillCalled =true;
drainOverflow();
}
@Override
public void onTransportFailure(IOException error) {
handleSessionFailure(error);
}
});
pingedAt = 0;
if(mqtt.getKeepAlive()>0) {
heartBeatMonitor = new HeartBeatMonitor();
heartBeatMonitor.setWriteInterval((mqtt.getKeepAlive() * 1000) / 2);
heartBeatMonitor.setTransport(this.transport);
heartBeatMonitor.suspendRead(); // to match the suspended state of the transport.
heartBeatMonitor.setOnKeepAlive(new Task() {
@Override
public void run() {
// Don't care if the offer is rejected, just means we have data outbound.
if(!disconnected && pingedAt==0) {
MQTTFrame encoded = new PINGREQ().encode();
if(CallbackConnection.this.transport.offer(encoded)) {
mqtt.tracer.onSend(encoded);
final long now = System.currentTimeMillis();
final long suspends = suspendChanges.get();
pingedAt = now;
queue.executeAfter(CallbackConnection.this.mqtt.getKeepAlive(), TimeUnit.SECONDS, new Task() {
@Override
public void run() {
if (now == pingedAt) {
// if the connection remained suspend we will never get the ping response..
// Looks like the user has forgoton to resume the connection
if (suspends == suspendChanges.get() && suspendCount.get() > 0) {
// Since the connection has been suspended, we can't really
// check to see if we are getting the ping responses.
mqtt.tracer.debug("The connection has remained suspended for an extended period of time so it cannot do proper keep alive processing. Did you forget to resume the connection?");
} else {
mqtt.tracer.debug("Ping timeout");
handleSessionFailure(new ProtocolException("Ping timeout").fillInStackTrace());
}
}
}
});
}
}
}
});
heartBeatMonitor.start();
}
}
public Transport transport() {
return transport;
}
public DispatchQueue getDispatchQueue() {
return queue;
}
public void resume() {
suspendChanges.incrementAndGet();
if( suspendCount.decrementAndGet() == 0 && this.transport!=null ) {
this.transport.resumeRead();
if(this.heartBeatMonitor!=null){
this.heartBeatMonitor.resumeRead();
}
}
}
public void suspend() {
suspendChanges.incrementAndGet();
if( suspendCount.incrementAndGet() == 1 && this.transport!=null ) {
this.transport.suspendRead();
if(this.heartBeatMonitor!=null){
this.heartBeatMonitor.suspendRead();
}
}
}
public CallbackConnection refiller(Runnable refiller) {
queue.assertExecuting();
this.refiller = refiller;
return this;
}
public CallbackConnection listener(final Listener original) {
if( original instanceof ExtendedListener ) {
this.listener = (ExtendedListener) original;
} else {
this.listener = new ExtendedListener() {
public void onPublish(UTF8Buffer topic, Buffer body, final Callback<Callback<byte[]>> ack) {
original.onPublish(topic, body, new Runnable() {
public void run() {
ack.onSuccess(null);
}
});
}
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack) {
original.onPublish(topic, body, ack);
}
public void onConnected() {
original.onConnected();
}
public void onDisconnected() {
original.onDisconnected();
}
public void onFailure(Throwable value) {
original.onFailure(value);
}
};
}
return this;
}
public boolean full() {
queue.assertExecuting();
return this.transport.full();
}
public Throwable failure() {
queue.assertExecuting();
return failure;
}
public void disconnect(boolean clearSession, final Callback<Void> onComplete) {
if( disconnected ) {
if(onComplete!=null){
onComplete.onSuccess(null);
}
return;
}
disconnected = true;
final short requestId = getNextMessageId();
final Runnable stop = new Runnable() {
private boolean executed = false;
public void run() {
if(!executed) {
executed = true;
requests.remove(requestId);
if(heartBeatMonitor!=null) {
heartBeatMonitor.stop();
heartBeatMonitor = null;
}
transport.stop(new Task() {
@Override
public void run() {
listener.onDisconnected();
if (onComplete != null) {
onComplete.onSuccess(null);
}
}
});
}
}
};
Callback<Void> cb = new Callback<Void>() {
public void onSuccess(Void v) {
// To make sure DISCONNECT has been flushed out to the socket
onRefillCalled = false;
refiller = new Runnable() {
public void run() {
if(onRefillCalled) {
stop.run();
}
}
};
if(transport != null){
transport.flush();
}
}
public void onFailure(Throwable value) {
stop.run();
}
};
// Pop the frame into a request so it we get notified
// of any failures so we continue to stop the transport.
if(transport!=null) {
MQTTFrame frame = new DISCONNECT().encode().dup(clearSession);
send(new Request(getNextMessageId(), frame, cb));
} else {
cb.onSuccess(null);
}
}
/**
* Kills the connection without a graceful disconnect.
* @param onComplete
*/
public void kill(final Callback<Void> onComplete) {
if( disconnected ) {
if(onComplete!=null){
onComplete.onSuccess(null);
}
return;
}
disconnected = true;
if(heartBeatMonitor!=null) {
heartBeatMonitor.stop();
heartBeatMonitor = null;
}
transport.stop(new Task() {
@Override
public void run() {
listener.onDisconnected();
if (onComplete != null) {
onComplete.onSuccess(null);
}
}
});
}
public void publish(String topic, byte[] payload, QoS qos, boolean retain, Callback<byte[]> cb) {
publish(utf8(topic), new Buffer(payload), qos, retain, cb);
}
public void publish(UTF8Buffer topic, Buffer payload, QoS qos, boolean retain, Callback<byte[]> cb) {
queue.assertExecuting();
if( disconnected ) {
cb.onFailure(createDisconnectedError());
return;
}
PUBLISH command = new PUBLISH().qos(qos).retain(retain);
command.topicName(topic).payload(payload);
send(command, cb);
}
public void subscribe(final Topic[] topics, Callback<byte[]> cb) {
if(topics==null) {
throw new IllegalArgumentException("topics must not be null");
}
queue.assertExecuting();
if( disconnected ) {
cb.onFailure(createDisconnectedError());
return;
}
if( listener == DEFAULT_LISTENER ) {
cb.onFailure(createListenerNotSetError());
} else {
send(new SUBSCRIBE().topics(topics), new ProxyCallback<byte[]>(cb){
@Override
public void onSuccess(byte[] value) {
for (Topic topic : topics) {
activeSubs.put(topic.name(), topic.qos());
}
if(next!=null) {
next.onSuccess(value);
}
}
});
}
}
public void unsubscribe(final UTF8Buffer[] topics, Callback<Void> cb) {
queue.assertExecuting();
if( disconnected ) {
cb.onFailure(createDisconnectedError());
return;
}
send(new UNSUBSCRIBE().topics(topics), new ProxyCallback(cb){
@Override
public void onSuccess(Object value) {
for (UTF8Buffer topic : topics) {
activeSubs.remove(topic);
}
if(next!=null) {
next.onSuccess(value);
}
}
});
}
private void send(Acked command, Callback cb) {
short id = 0;
if(command.qos() != QoS.AT_MOST_ONCE) {
id = getNextMessageId();
command.messageId(id);
}
send(new Request(id, command.encode(), cb));
}
private void send(Request request) {
if( failure !=null ) {
if( request.cb!=null ) {
request.cb.onFailure(failure);
}
} else {
// Put the request in the map before sending it over the wire.
if(request.id!=0) {
this.requests.put(request.id, request);
}
if( overflow.isEmpty() && transport!=null && transport.offer(request.frame) ) {
mqtt.tracer.onSend(request.frame);
if(request.id==0) {
if( request.cb!=null ) {
((Callback<Void>)request.cb).onSuccess(null);
}
}
} else {
// Remove it from the request.
this.requests.remove(request.id);
overflow.addLast(request);
}
}
}
private short nextMessageId = 1;
private short getNextMessageId() {
short rc = nextMessageId;
nextMessageId++;
if(nextMessageId==0) {
nextMessageId=1;
}
return rc;
}
private void drainOverflow() {
queue.assertExecuting();
if( overflow.isEmpty() || transport==null ){
return;
}
Request request;
while((request=overflow.peek())!=null) {
if( this.transport.offer(request.frame) ) {
mqtt.tracer.onSend(request.frame);
overflow.removeFirst();
if(request.id==0) {
if( request.cb!=null ) {
((Callback<Void>)request.cb).onSuccess(null);
}
} else {
this.requests.put(request.id, request);
}
} else {
break;
}
}
if( overflow.isEmpty() ) {
if( refiller!=null ) {
try {
refiller.run();
} catch (Throwable e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
}
}
private void completeRequest(short id, byte originalType, Object arg) {
Request request = requests.remove(id);
if( request!=null ) {
assert originalType==request.frame.messageType();
if(request.cb!=null) {
if( arg==null ) {
((Callback<Void>)request.cb).onSuccess(null);
} else {
((Callback<Object>)request.cb).onSuccess(arg);
}
}
} else {
handleFatalFailure(new ProtocolException("Command from server contained an invalid message id: " + id));
}
}
private void processFrame(MQTTFrame frame) {
try {
switch(frame.messageType()) {
case PUBLISH.TYPE: {
PUBLISH publish = new PUBLISH().decode(frame);
toReceiver(publish);
break;
}
case PUBREL.TYPE:{
PUBREL ack = new PUBREL().decode(frame);
Callback<byte[]> onRel = processed.remove(ack.messageId());
PUBCOMP response = new PUBCOMP();
response.messageId(ack.messageId());
send(new Request(0, response.encode(), null));
if( onRel!=null ) {
onRel.onSuccess(null);
}
break;
}
case PUBACK.TYPE:{
PUBACK ack = new PUBACK().decode(frame);
completeRequest(ack.messageId(), PUBLISH.TYPE, ack.payload);
break;
}
case PUBREC.TYPE:{
PUBREC ack = new PUBREC().decode(frame);
PUBREL response = new PUBREL();
response.messageId(ack.messageId());
send(new Request(0, response.encode(), null));
break;
}
case PUBCOMP.TYPE:{
PUBCOMP ack = new PUBCOMP().decode(frame);
completeRequest(ack.messageId(), PUBLISH.TYPE, null);
break;
}
case SUBACK.TYPE: {
SUBACK ack = new SUBACK().decode(frame);
completeRequest(ack.messageId(), SUBSCRIBE.TYPE, ack.grantedQos());
break;
}
case UNSUBACK.TYPE: {
UNSUBACK ack = new UNSUBACK().decode(frame);
completeRequest(ack.messageId(), UNSUBSCRIBE.TYPE, null);
break;
}
case PINGRESP.TYPE: {
pingedAt = 0;
break;
}
default:
throw new ProtocolException("Unexpected MQTT command type: "+frame.messageType());
}
} catch (Throwable e) {
handleFatalFailure(e);
}
}
static public final Task NOOP = Dispatch.NOOP;
private void toReceiver(final PUBLISH publish) {
if( listener !=null ) {
try {
Callback<Callback<byte[]>> cb = null;
switch( publish.qos() ) {
case AT_LEAST_ONCE:
cb = new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
PUBACK response = new PUBACK();
response.messageId(publish.messageId());
send(new Request(0, response.encode(), null));
if( value !=null ) {
value.onSuccess(null);
}
}
public void onFailure(Throwable value) {
}
};
break;
case EXACTLY_ONCE:
cb = new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
PUBREC response = new PUBREC();
response.messageId(publish.messageId());
processed.put(publish.messageId(), value);
send(new Request(0, response.encode(), null));
}
public void onFailure(Throwable value) {
}
};
// Looks like a dup delivery.. filter it out.
if( processed.get(publish.messageId())!=null ) {
return;
}
break;
case AT_MOST_ONCE:
cb = new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
if (value != null) {
value.onSuccess(null);
}
}
public void onFailure(Throwable value) {
}
};
}
listener.onPublish(publish.topicName(), publish.payload(), cb);
} catch (Throwable e) {
handleFatalFailure(e);
}
}
}
private void handleFatalFailure(Throwable error) {
if( failure == null ) {
failure = error;
mqtt.tracer.debug("Fatal connection failure: %s", error);
// Fail incomplete requests.
ArrayList<Request> values = new ArrayList(requests.values());
requests.clear();
for (Request value : values) {
if( value.cb!= null ) {
value.cb.onFailure(failure);
}
}
ArrayList<Request> overflowEntries = new ArrayList<Request>(overflow);
overflow.clear();
for (Request entry : overflowEntries) {
if( entry.cb !=null ) {
entry.cb.onFailure(failure);
}
}
if( listener !=null && !disconnected ) {
try {
listener.onFailure(failure);
} catch (Exception e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
}
}
private static IllegalStateException createListenerNotSetError() {
return (IllegalStateException) new IllegalStateException("No connection listener set to handle message received from the server.").fillInStackTrace();
}
private static IllegalStateException createDisconnectedError() {
return (IllegalStateException) new IllegalStateException("Disconnected").fillInStackTrace();
}
static private String hex(SocketAddress address) {
if( address instanceof InetSocketAddress ) {
InetSocketAddress isa = (InetSocketAddress)address;
return HexSupport.toHexFromBuffer(new Buffer(isa.getAddress().getAddress()))+Integer.toHexString(isa.getPort());
}
return "";
}
}
@@ -1,32 +0,0 @@
/**
* Copyright (C) 2016, Red Hat Inc. All rights reserved.
*
* http://redhat.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public interface ExtendedListener extends Listener {
public void onPublish(UTF8Buffer topic, Buffer body, Callback<Callback<byte[]>> ack);
}
@@ -1,33 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import java.util.concurrent.TimeUnit;
/**
* <p>A simplified Future function results interface.</p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public interface Future<T> {
T await() throws Exception;
T await(long amount, TimeUnit unit) throws Exception;
void then(Callback<T> callback);
}
@@ -1,258 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.Task;
import java.util.ArrayList;
import java.util.LinkedList;
import static org.fusesource.hawtbuf.Buffer.utf8;
/**
* <p>
* A Future based optionally-blocking Connection interface to MQTT.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class FutureConnection {
private final CallbackConnection next;
private final LinkedList<Promise<Message>> receiveFutures = new LinkedList<Promise<Message>>();
private final LinkedList<Message> receivedFrames = new LinkedList<Message>();
private volatile boolean connected;
private long receiveBuffer = 1024*1024;
private long receiveBufferRemaining = receiveBuffer;
private boolean receiveBufferFull = false;
public FutureConnection(CallbackConnection next) {
this.next = next;
this.next.listener(new ExtendedListener() {
public void onConnected() {
connected = true;
}
public void onDisconnected() {
connected = false;
}
public void onPublish(UTF8Buffer topic, final Buffer payload, final Callback<Callback<byte[]>> onComplete) {
getDispatchQueue().assertExecuting();
receiveBufferRemaining -= payload.length();
if( !receiveBufferFull && receiveBufferRemaining <=0 ) {
receiveBufferFull = true;
suspend();
}
deliverMessage(new Message(getDispatchQueue(), topic, payload, new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
processed();
onComplete.onSuccess(value);
}
public void onFailure(Throwable value) {
processed();
onComplete.onFailure(value);
}
private void processed() {
getDispatchQueue().assertExecuting();
receiveBufferRemaining += payload.length();
if( receiveBufferFull && receiveBufferRemaining >0 ) {
receiveBufferFull = false;
resume();
}
}
}));
}
public void onPublish(UTF8Buffer topic, Buffer payload, final Runnable onComplete) {
onPublish(topic, payload, new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
onComplete.run();
}
public void onFailure(Throwable value) {
}
});
}
public void onFailure(Throwable value) {
getDispatchQueue().assertExecuting();
ArrayList<Promise<?>> tmp = new ArrayList<Promise<?>>(receiveFutures);
receiveFutures.clear();
for (Promise<?> future : tmp) {
future.onFailure(value);
}
connected = false;
}
});
}
void deliverMessage(Message msg) {
if( receiveFutures.isEmpty() ) {
receivedFrames.add(msg);
} else {
receiveFutures.removeFirst().onSuccess(msg);
}
}
void putBackMessage(Message msg) {
if( receiveFutures.isEmpty() ) {
receivedFrames.addFirst(msg);
} else {
receiveFutures.removeFirst().onSuccess(msg);
}
}
public boolean isConnected() {
return connected;
}
public DispatchQueue getDispatchQueue() {
return this.next.getDispatchQueue();
}
public Future<byte[]> connect() {
final Promise<byte[]> future = new Promise<byte[]>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.connect(future);
}
});
return future;
}
public Future<Void> disconnect() {
final Promise<Void> future = new Promise<Void>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.disconnect(false, future);
}
});
return future;
}
public Future<Void> kill() {
final Promise<Void> future = new Promise<Void>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.kill(future);
}
});
return future;
}
public Future<byte[]> subscribe(final Topic[] topics) {
final Promise<byte[]> future = new Promise<byte[]>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.subscribe(topics, future);
}
});
return future;
}
public Future<Void> unsubscribe(final String[] topics) {
UTF8Buffer[] buffers = new UTF8Buffer[topics.length];
for (int i = 0; i < buffers.length; i++) {
buffers[i] = new UTF8Buffer(topics[i]);
}
return unsubscribe(buffers);
}
public Future<Void> unsubscribe(final UTF8Buffer[] topics) {
final Promise<Void> future = new Promise<Void>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.unsubscribe(topics, future);
}
});
return future;
}
public Future<byte[]> publish(final String topic, final byte[] payload, final QoS qos, final boolean retain) {
return publish(utf8(topic), new Buffer(payload), qos, retain);
}
public Future<byte[]> publish(final UTF8Buffer topic, final Buffer payload, final QoS qos, final boolean retain) {
final Promise<byte[]> future = new Promise<byte[]>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.publish(topic, payload, qos, retain, future);
}
});
return future;
}
public Future<Message> receive() {
final Promise<Message> future = new Promise<Message>();
getDispatchQueue().execute(new Task(){
public void run() {
if( next.failure()!=null ) {
future.onFailure(next.failure());
} else {
if( receivedFrames.isEmpty() ) {
receiveFutures.add(future);
} else {
future.onSuccess(receivedFrames.removeFirst());
}
}
}
});
return future;
}
public long getReceiveBuffer() {
getDispatchQueue().assertExecuting();
return receiveBuffer;
}
public void setReceiveBuffer(long receiveBuffer) {
getDispatchQueue().assertExecuting();
long prev = this.receiveBuffer;
this.receiveBuffer = receiveBuffer;
long diff = prev-receiveBuffer;
receiveBufferRemaining -= diff;
if( !receiveBufferFull && receiveBufferRemaining <=0 ) {
receiveBufferFull = true;
suspend();
} else if( receiveBufferFull && receiveBufferRemaining >0 ) {
receiveBufferFull = false;
resume();
}
}
public void resume() {
next.resume();
}
public void suspend() {
next.suspend();
}
}
@@ -1,36 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
@Deprecated
public interface Listener {
public void onConnected();
public void onDisconnected();
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack);
public void onFailure(Throwable value);
}
@@ -1,392 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.transport.*;
import org.fusesource.mqtt.codec.CONNECT;
import javax.net.ssl.SSLContext;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import static org.fusesource.hawtbuf.Buffer.utf8;
import static org.fusesource.hawtdispatch.Dispatch.createQueue;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class MQTT {
private static final long KEEP_ALIVE = Long.parseLong(System.getProperty("mqtt.thread.keep_alive", Integer.toString(1000)));
private static final long STACK_SIZE = Long.parseLong(System.getProperty("mqtt.thread.stack_size", Integer.toString(1024*512)));
private static ThreadPoolExecutor blockingThreadPool;
public synchronized static ThreadPoolExecutor getBlockingThreadPool() {
if( blockingThreadPool == null ) {
blockingThreadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, KEEP_ALIVE, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread rc = new Thread(null, r, "MQTT Task", STACK_SIZE);
rc.setDaemon(true);
return rc;
}
}) {
@Override
public void shutdown() {
// we don't ever shutdown since we are shared..
}
@Override
public List<Runnable> shutdownNow() {
// we don't ever shutdown since we are shared..
return Collections.emptyList();
}
};
}
return blockingThreadPool;
}
public synchronized static void setBlockingThreadPool(ThreadPoolExecutor pool) {
blockingThreadPool = pool;
}
private static final URI DEFAULT_HOST = createDefaultHost();
private static URI createDefaultHost() {
try {
return new URI("tcp://127.0.0.1:1883");
} catch (URISyntaxException e) {
return null;
}
}
protected URI host = DEFAULT_HOST;
protected URI localAddress;
protected SSLContext sslContext;
protected DispatchQueue dispatchQueue;
protected Executor blockingExecutor;
protected int maxReadRate;
protected int maxWriteRate;
protected int trafficClass = TcpTransport.IPTOS_THROUGHPUT;
protected int receiveBufferSize = 1024*64;
protected int sendBufferSize = 1024*64;
protected boolean useLocalHost = true;
protected CONNECT connect = new CONNECT();
protected long reconnectDelay = 10;
protected long reconnectDelayMax = 30*1000;
protected double reconnectBackOffMultiplier = 2.0f;
protected long reconnectAttemptsMax = -1;
protected long connectAttemptsMax = -1;
protected Tracer tracer = new Tracer();
public MQTT() {
}
public MQTT(MQTT other) {
this.host = other.host;
this.localAddress = other.localAddress;
this.sslContext = other.sslContext;
this.dispatchQueue = other.dispatchQueue;
this.blockingExecutor = other.blockingExecutor;
this.maxReadRate = other.maxReadRate;
this.maxWriteRate = other.maxWriteRate;
this.trafficClass = other.trafficClass;
this.receiveBufferSize = other.receiveBufferSize;
this.sendBufferSize = other.sendBufferSize;
this.useLocalHost = other.useLocalHost;
this.connect = new CONNECT(other.connect);
this.reconnectDelay = other.reconnectDelay;
this.reconnectDelayMax = other.reconnectDelayMax;
this.reconnectBackOffMultiplier = other.reconnectBackOffMultiplier;
this.reconnectAttemptsMax = other.reconnectAttemptsMax;
this.connectAttemptsMax = other.connectAttemptsMax;
this.tracer = other.tracer;
}
public CallbackConnection callbackConnection() {
if( !isCleanSession() && ( getClientId()==null || getClientId().length==0 )) {
throw new IllegalArgumentException("The client id MUST be configured when clean session is set to false");
}
return new CallbackConnection(new MQTT(this));
}
public FutureConnection futureConnection() {
return new FutureConnection(callbackConnection());
}
public BlockingConnection blockingConnection() {
return new BlockingConnection(futureConnection());
}
public UTF8Buffer getClientId() {
return connect.clientId();
}
public short getKeepAlive() {
return connect.keepAlive();
}
public UTF8Buffer getPassword() {
return connect.password();
}
public byte getType() {
return connect.messageType();
}
public UTF8Buffer getUserName() {
return connect.userName();
}
public UTF8Buffer getWillMessage() {
return connect.willMessage();
}
public QoS getWillQos() {
return connect.willQos();
}
public UTF8Buffer getWillTopic() {
return connect.willTopic();
}
public boolean isCleanSession() {
return connect.cleanSession();
}
public boolean isWillRetain() {
return connect.willRetain();
}
public void setCleanSession(boolean cleanSession) {
connect.cleanSession(cleanSession);
}
public void setClientId(String clientId) {
this.setClientId(utf8(clientId));
}
public void setClientId(UTF8Buffer clientId) {
connect.clientId(clientId);
}
public void setKeepAlive(short keepAlive) {
connect.keepAlive(keepAlive);
}
public void setPassword(String password) {
this.setPassword(utf8(password));
}
public void setPassword(UTF8Buffer password) {
connect.password(password);
}
public void setUserName(String userName) {
this.setUserName(utf8(userName));
}
public void setUserName(UTF8Buffer userName) {
connect.userName(userName);
}
public void setWillMessage(String willMessage) {
connect.willMessage(utf8(willMessage));
}
public void setWillMessage(UTF8Buffer willMessage) {
connect.willMessage(willMessage);
}
public void setWillQos(QoS willQos) {
connect.willQos(willQos);
}
public void setVersion(String version) {
if( "3.1".equals(version) ) {
connect.version(3);
} else if( "3.1.1".equals(version) ) {
connect.version(4);
}
}
public String getVersion() {
switch(connect.version()) {
case 3: return "3.1";
case 4: return "3.1.1";
default: return "unknown";
}
}
public void setWillRetain(boolean willRetain) {
connect.willRetain(willRetain);
}
public void setWillTopic(String willTopic) {
this.setWillTopic(utf8(willTopic));
}
public void setWillTopic(UTF8Buffer willTopic) {
connect.willTopic(willTopic);
}
public Executor getBlockingExecutor() {
return blockingExecutor;
}
public void setBlockingExecutor(Executor blockingExecutor) {
this.blockingExecutor = blockingExecutor;
}
public DispatchQueue getDispatchQueue() {
return dispatchQueue;
}
public void setDispatchQueue(DispatchQueue dispatchQueue) {
this.dispatchQueue = dispatchQueue;
}
public URI getLocalAddress() {
return localAddress;
}
public void setLocalAddress(String localAddress) throws URISyntaxException {
this.setLocalAddress(new URI(localAddress));
}
public void setLocalAddress(URI localAddress) {
this.localAddress = localAddress;
}
public int getMaxReadRate() {
return maxReadRate;
}
public void setMaxReadRate(int maxReadRate) {
this.maxReadRate = maxReadRate;
}
public int getMaxWriteRate() {
return maxWriteRate;
}
public void setMaxWriteRate(int maxWriteRate) {
this.maxWriteRate = maxWriteRate;
}
public int getReceiveBufferSize() {
return receiveBufferSize;
}
public void setReceiveBufferSize(int receiveBufferSize) {
this.receiveBufferSize = receiveBufferSize;
}
public URI getHost() {
return host;
}
public void setHost(String host, int port) throws URISyntaxException {
this.setHost(new URI("tcp://"+host+":"+port));
}
public void setHost(String host) throws URISyntaxException {
this.setHost(new URI(host));
}
public void setHost(URI host) {
this.host = host;
}
public int getSendBufferSize() {
return sendBufferSize;
}
public void setSendBufferSize(int sendBufferSize) {
this.sendBufferSize = sendBufferSize;
}
public SSLContext getSslContext() {
return sslContext;
}
public void setSslContext(SSLContext sslContext) {
this.sslContext = sslContext;
}
public int getTrafficClass() {
return trafficClass;
}
public void setTrafficClass(int trafficClass) {
this.trafficClass = trafficClass;
}
public boolean isUseLocalHost() {
return useLocalHost;
}
public void setUseLocalHost(boolean useLocalHost) {
this.useLocalHost = useLocalHost;
}
public long getConnectAttemptsMax() {
return connectAttemptsMax;
}
public void setConnectAttemptsMax(long connectAttemptsMax) {
this.connectAttemptsMax = connectAttemptsMax;
}
public long getReconnectAttemptsMax() {
return reconnectAttemptsMax;
}
public void setReconnectAttemptsMax(long reconnectAttemptsMax) {
this.reconnectAttemptsMax = reconnectAttemptsMax;
}
public double getReconnectBackOffMultiplier() {
return reconnectBackOffMultiplier;
}
public void setReconnectBackOffMultiplier(double reconnectBackOffMultiplier) {
this.reconnectBackOffMultiplier = reconnectBackOffMultiplier;
}
public long getReconnectDelay() {
return reconnectDelay;
}
public void setReconnectDelay(long reconnectDelay) {
this.reconnectDelay = reconnectDelay;
}
public long getReconnectDelayMax() {
return reconnectDelayMax;
}
public void setReconnectDelayMax(long reconnectDelayMax) {
this.reconnectDelayMax = reconnectDelayMax;
}
public Tracer getTracer() {
return tracer;
}
public void setTracer(Tracer tracer) {
this.tracer = tracer;
}
}
@@ -1,14 +0,0 @@
package org.fusesource.mqtt.client;
import java.io.IOException;
import org.fusesource.mqtt.codec.CONNACK;
public class MQTTException extends IOException {
public final CONNACK connack;
public MQTTException(String msg, CONNACK connack) {
super(msg);
this.connack = connack;
}
}
@@ -1,104 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.Task;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Message {
private final UTF8Buffer topic;
private final Buffer payload;
private Callback<Callback<byte[]>> onComplete;
private final DispatchQueue queue;
boolean blocking = false;
public Message(DispatchQueue queue, UTF8Buffer topic, Buffer payload, Callback<Callback<byte[]>> onComplete) {
this.queue = queue;
this.payload = payload;
this.topic = topic;
this.onComplete = onComplete;
}
public byte[] getPayload() {
return payload.toByteArray();
}
/**
* Using getPayloadBuffer() is lower overhead version of getPayload()
* since it avoids a byte array copy.
* @return
*/
public Buffer getPayloadBuffer() {
return payload;
}
public String getTopic() {
return topic.toString();
}
/**
* Using getTopicBuffer is lower overhead version of getTopic()
* since it avoid doing UTF-8 decode.
* @return
*/
public UTF8Buffer getTopicBuffer() {
return topic;
}
public void ack() {
if( blocking ) {
final Promise<byte[]> future = new Promise<byte[]>();
ack(future);
try {
future.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
ack(null);
}
}
public void ack(final Callback<byte[]> onAcked) {
if(onComplete!=null) {
queue.execute(new Task() {
Callback<Callback<byte[]>> onCompleteCopy = onComplete;
@Override
public void run() {
onCompleteCopy.onSuccess(onAcked);
}
});
onComplete = null;
} else {
if( onAcked!=null ) {
onAcked.onSuccess(null);
}
}
}
}
@@ -1,47 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* Function Result that carries one value.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class NoopCallback<T> implements Callback<T> {
public final Callback<T> next;
public NoopCallback(Callback<T> next) {
this.next = next;
}
public void onSuccess(T value) {
if( next!=null ) {
next.onSuccess(value);
}
}
public void onFailure(Throwable value) {
if( next!=null ) {
next.onFailure(value);
}
}
}
@@ -1,109 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Promise<T> implements Callback<T>, Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private Callback<T> next;
private Throwable error;
private T value;
public void onFailure(Throwable value) {
Callback<T> callback = null;
synchronized(this) {
error = value;
latch.countDown();
callback = next;
}
if( callback!=null ) {
callback.onFailure(value);
}
}
public void onSuccess(T value) {
Callback<T> callback = null;
synchronized(this) {
this.value = value;
latch.countDown();
callback = next;
}
if( callback!=null ) {
callback.onSuccess(value);
}
}
public void then(Callback<T> callback) {
boolean fire = false;
synchronized(this) {
next = callback;
if( latch.getCount() == 0 ) {
fire = true;
}
}
if( fire ) {
if( error!=null ) {
callback.onFailure(error);
} else {
callback.onSuccess(value);
}
}
}
public T await(long amount, TimeUnit unit) throws Exception {
if( latch.await(amount, unit) ) {
return get();
} else {
throw new TimeoutException();
}
}
public T await() throws Exception {
latch.await();
return get();
}
private T get() throws Exception {
Throwable e = error;
if( e !=null ) {
if( e instanceof RuntimeException ) {
throw (RuntimeException) e;
} else if( e instanceof Exception) {
throw (Exception) e;
} else if( e instanceof Error) {
throw (Error) e;
} else {
// don't expect to hit this case.
throw new RuntimeException(e);
}
}
return value;
}
}
@@ -1,47 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* Function Result that carries one value.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class ProxyCallback<T> implements Callback<T> {
public final Callback<T> next;
public ProxyCallback(Callback<T> next) {
this.next = next;
}
public void onSuccess(T value) {
if( next!=null ) {
next.onSuccess(value);
}
}
public void onFailure(Throwable value) {
if( next!=null ) {
next.onFailure(value);
}
}
}
@@ -1,31 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public enum QoS {
AT_MOST_ONCE,
AT_LEAST_ONCE,
EXACTLY_ONCE
}
@@ -1,78 +0,0 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Topic {
private final UTF8Buffer name;
private final QoS qos;
public Topic(String name, QoS qos) {
this(new UTF8Buffer(name), qos);
}
public Topic(UTF8Buffer name, QoS qos) {
this.name = name;
this.qos = qos;
}
public UTF8Buffer name() {
return name;
}
public QoS qos() {
return qos;
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
if (this == o) return true;
Topic topic = (Topic) o;
if (name != null ? !name.equals(topic.name) : topic.name != null) return false;
if (qos != topic.qos) return false;
return true;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (qos != null ? qos.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "{ name=" + name +
", qos=" + qos +
" }";
}
}

Some files were not shown because too many files have changed in this diff Show More