├── .gitignore ├── LICENSE ├── README.md ├── com.qcloud.weapp.demo ├── .classpath ├── .project ├── .settings │ ├── .jsdtscope │ ├── org.eclipse.jdt.core.prefs │ ├── org.eclipse.wst.common.component │ ├── org.eclipse.wst.common.project.facet.core.xml │ ├── org.eclipse.wst.jsdt.ui.superType.container │ ├── org.eclipse.wst.jsdt.ui.superType.name │ └── org.eclipse.wst.ws.service.policy.prefs ├── WebContent │ ├── META-INF │ │ └── MANIFEST.MF │ └── WEB-INF │ │ └── web.xml ├── lib │ └── org.json.jar └── src │ └── com │ └── qcloud │ └── weapp │ └── demo │ ├── ChatTunnelHandler.java │ ├── QCloud.java │ ├── Startup.java │ └── servlet │ ├── LoginServlet.java │ ├── TunnelServlet.java │ └── UserServlet.java ├── com.qcloud.weapp.sdk ├── .classpath ├── .gitignore ├── .project ├── .settings │ ├── org.eclipse.jdt.core.prefs │ ├── org.eclipse.wst.common.component │ └── org.eclipse.wst.common.project.facet.core.xml ├── lib │ └── org.json.jar └── src │ ├── META-INF │ └── MANIFEST.MF │ └── com │ └── qcloud │ └── weapp │ ├── Configuration.java │ ├── ConfigurationException.java │ ├── ConfigurationManager.java │ ├── Hash.java │ ├── HttpRequest.java │ ├── authorization │ ├── AuthorizationAPI.java │ ├── AuthorizationAPIException.java │ ├── Constants.java │ ├── LoginService.java │ ├── LoginServiceException.java │ └── UserInfo.java │ └── tunnel │ ├── EmitError.java │ ├── EmitResult.java │ ├── Tunnel.java │ ├── TunnelAPI.java │ ├── TunnelClient.java │ ├── TunnelHandleOptions.java │ ├── TunnelHandler.java │ ├── TunnelInvalidInfo.java │ ├── TunnelInvalidType.java │ ├── TunnelMessage.java │ ├── TunnelRoom.java │ └── TunnelService.java └── com.qcloud.weapp.test ├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── lib ├── mockito-all-1.10.19.jar └── org.json.jar └── src └── com └── qcloud └── weapp └── test ├── HttpMock.java ├── URLConnectionMock.java ├── authorization ├── LoginServiceTest.java └── LoginServiceTestHelper.java └── tunnel ├── TunnelServiceTest.java └── TunnelServiceTestHelper.java /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 by Tencent Cloud 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wafer 服务端 SDK - Java 2 | ================================= 3 | 4 | [![license](https://img.shields.io/github/license/tencentyun/weapp-java-server-sdk.svg?style=flat-square)](LICENSE) 5 | 6 | 本项目是 [Wafer](https://github.com/tencentyun/wafer) 组成部分,以 SDK 的形式为业务服务器提供以下服务: 7 | 8 | + [会话服务](https://github.com/tencentyun/wafer/wiki/会话服务) 9 | + [信道服务](https://github.com/tencentyun/wafer/wiki/信道服务) 10 | 11 | ## SDK 获取 12 | 13 | 本项目遵守 [MIT](LICENSE) 协议,可以直接[下载 SDK 源码][sdk-download]进行修改、编译和发布。 14 | 15 | > 如果使用[自动部署](https://github.com/tencentyun/wafer/wiki/%E8%87%AA%E5%8A%A8%E9%83%A8%E7%BD%B2)并选择 Java 语言,则分配的业务服务器里已经部署了本 SDK 和 Demo 的发行版本。 16 | 17 | ## API 18 | 19 | 请参考[线上 API 文档][api-url]。 20 | 21 | ## 使用示例(Servlet) 22 | 23 | ### 配置 SDK 24 | 25 | SDK 必须经过初始化配置之后才能使用。可以选择使用代码初始化或者配置文件初始化。初始化配置建议在 `Servlet::init()` 里进行。 26 | 27 | 使用代码初始化: 28 | 29 | ```java 30 | import com.qcloud.weapp.*; 31 | 32 | Configuration configuration = new Configuration(); 33 | 34 | // 业务服务器访问域名 35 | configuration.setServerHost("199447.qcloud.la"); 36 | // 鉴权服务地址 37 | configuration.setAuthServerUrl("http://10.0.12.135/mina_auth/"); 38 | // 信道服务地址 39 | configuration.setTunnelServerUrl("https://ws.qcloud.com/"); 40 | // 信道服务签名 key 41 | configuration.setTunnelSignatureKey("my$ecretkey"); 42 | // 网络请求超时设置,单位为秒 43 | configuration.setNetworkTimeout(30); 44 | 45 | ConfigurationManager.setup(configuration); 46 | ``` 47 | 48 | 使用配置文件初始化: 49 | 50 | ```java 51 | import com.qcloud.weapp.*; 52 | 53 | var configFilePath = "/etc/qcloud/sdk.config"; 54 | ConfigurationManager.setupFromFile(configFilePath); 55 | ``` 56 | 57 | 关于 SDK 配置字段的含义以及配置文件格式的更多信息,请参考[服务端 SDK 配置][sdk-config-wiki]。 58 | 59 | ### 使用 SDK 提供登录服务 60 | 61 | #### 登录 62 | 63 | 业务服务器提供一个路由处理客户端的登录请求,直接把该请求交给 SDK 来处理即可完成登录。登录成功后,可以获取用户信息。 64 | 65 | ```java 66 | import com.qcloud.weapp.*; 67 | import com.qcloud.weapp.authorization.*; 68 | 69 | @WebServlet("/login") 70 | public class LoginServlet extends HttpServlet { 71 | /** 72 | * 处理登录请求 73 | * */ 74 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 75 | // 通过 ServletRequest 和 ServletResponse 初始化登录服务 76 | LoginService service = new LoginService(request, response); 77 | try { 78 | // 调用登录接口,如果登录成功可以获得登录信息 79 | UserInfo userInfo = service.login(); 80 | System.out.println("========= LoginSuccess, UserInfo: =========="); 81 | System.out.println(userInfo.toString()); 82 | } catch (LoginServiceException e) { 83 | // 登录失败会抛出登录失败异常 84 | e.printStackTrace(); 85 | } catch (ConfigurationException e) { 86 | // SDK 如果还没有配置会抛出配置异常 87 | e.printStackTrace(); 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | > 如果登录失败,[login()][login-api] 方法会抛出异常,需要使用 try-catch 来捕获异常。该异常可以不用处理,抛出来是为了方便业务服务器可以进行记录和监控。 94 | 95 | #### 获取会话状态 96 | 97 | 客户端交给业务服务器的请求,业务服务器可以通过 SDK 来检查该请求是否包含合法的微信小程序会话。如果包含,则会返回会话对应的用户信息。 98 | 99 | ```java 100 | import com.qcloud.weapp.*; 101 | import com.qcloud.weapp.authorization.*; 102 | 103 | @WebServlet("/user") 104 | public class UserServlet extends HttpServlet { 105 | /** 106 | * 从请求中获取会话中的用户信息 107 | */ 108 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 109 | LoginService service = new LoginService(request, response); 110 | try { 111 | // 调用检查登录接口,成功后可以获得用户信息,进行正常的业务请求 112 | UserInfo userInfo = service.check(); 113 | 114 | // 获取会话成功,输出获得的用户信息 115 | JSONObject result = new JSONObject(); 116 | JSONObject data = new JSONObject(); 117 | data.put("userInfo", new JSONObject(userInfo)); 118 | result.put("code", 0); 119 | result.put("message", "OK"); 120 | result.put("data", data); 121 | response.setContentType("application/json"); 122 | response.setCharacterEncoding("utf-8"); 123 | response.getWriter().write(result.toString()); 124 | 125 | } catch (LoginServiceException e) { 126 | e.printStackTrace(); 127 | } catch (JSONException e) { 128 | e.printStackTrace(); 129 | } catch (ConfigurationException e) { 130 | e.printStackTrace(); 131 | } 132 | } 133 | } 134 | 135 | ``` 136 | 137 | > 如果检查会话失败,或者会话无效,[check()][check-api] 方法会抛出异常,需要使用 try-catch 来捕获异常。该异常可以不用处理,抛出来是为了方便业务服务器可以进行记录和监控。 138 | 139 | 阅读解决方案文档中的[会话服务][session-service-wiki]了解更多解决方案中关于鉴权服务的技术资料。 140 | 141 | ### 使用 SDK 提供信道服务 142 | 143 | 业务在一个路由上提供信道服务,只需把该路由上的请求都交给 SDK 的信道服务处理即可。 144 | 145 | ```java 146 | import com.qcloud.weapp.*; 147 | import com.qcloud.weapp.tunnel.*; 148 | import com.qcloud.weapp.demo.ChatTunnelHandler; 149 | 150 | @WebServlet("/tunnel") 151 | public class TunnelServlet extends HttpServlet { 152 | /** 153 | * 把所有的请求交给 SDK 处理,提供 TunnelHandler 处理信道事件 154 | */ 155 | protected void service(HttpServletRequest request, HttpServletResponse response) 156 | throws ServletException, IOException { 157 | 158 | // 创建信道服务处理信道相关请求 159 | TunnelService tunnelService = new TunnelService(request, response); 160 | 161 | try { 162 | // 配置是可选的,配置 CheckLogin 为 true 的话,会在隧道建立之前获取用户信息,以便业务将隧道和用户关联起来 163 | TunnelHandleOptions options = new TunnelHandleOptions(); 164 | options.setCheckLogin(true); 165 | 166 | // 需要实现信道处理器,ChatTunnelHandler 是一个实现的范例 167 | tunnelService.handle(new ChatTunnelHandler(), options); 168 | } catch (ConfigurationException e) { 169 | e.printStackTrace(); 170 | } 171 | } 172 | } 173 | ``` 174 | 175 | 使用信道服务需要实现处理器,来获取处理信道的各种事件,具体可参考接口 [TunnelHandler][tunnel-handler-api] 的 API 文档以及配套 Demo 中的 [ChatTunnelHandler][chat-handler-source] 的实现。 176 | 177 | 阅读解决方案文档中的[信道服务][tunnel-service-wiki]了解更多解决方案中关于鉴权服务的技术资料。 178 | 179 | 180 | ## 在DEMO基础上进行开发 181 | 将编译好的文件放到 /var/lib/tomcat/webapps/ 目录下 182 | 183 | tomcat代码配置 /etc/tomcat 184 | 185 | 重启tomcat服务 service tomcat restart 186 | 187 | 188 | 189 | 190 | ## 反馈和贡献 191 | 192 | 如有问题,欢迎使用 [Issues][new-issue] 提出,也欢迎广大开发者给我们提 [Pull Request][pr]。 193 | 194 | [wafer]: https://github.com/tencentyun/wafer "查看 Wafer 根项目" 195 | [sdk-download]: https://github.com/tencentyun/wafer-java-server-sdk/archive/master.zip "下载 Java SDK 源码" 196 | [api-url]: https://tencentyun.github.io/wafer-java-server-sdk/api/ "查看 Java SDK API 文档" 197 | [sdk-config-wiki]: https://github.com/tencentyun/wafer/wiki/%E6%9C%8D%E5%8A%A1%E7%AB%AF-SDK-%E9%85%8D%E7%BD%AE "查看服务端 SDK 配置" 198 | [session-service-wiki]: https://github.com/tencentyun/wafer/wiki/会话服务 "查看关于会话服务的更多资料" 199 | [tunnel-service-wiki]: https://github.com/tencentyun/wafer/wiki/信道服务 "查看关于信道服务的更多资料" 200 | [login-api]: https://tencentyun.github.io/wafer-java-server-sdk/api/com/qcloud/weapp/authorization/LoginService.html#login-- "查看 LoginService::login() 方法 API" 201 | [check-api]: https://tencentyun.github.io/wafer-java-server-sdk/api/com/qcloud/weapp/authorization/LoginService.html#check-- "查看 LoginService::ckeck() 方法 API" 202 | [tunnel-handler-api]: https://tencentyun.github.io/wafer-java-server-sdk/api/com/qcloud/weapp/tunnel/TunnelHandler.html "查看 TunnelHandler 接口 API 文档" 203 | [chat-handler-source]: https://github.com/tencentyun/wafer-java-server-sdk/blob/master/com.qcloud.weapp.demo/src/com/qcloud/weapp/demo/ChatTunnelHandler.java "查看 ChatTunnelHandler 示例代码" 204 | 205 | [new-issue]: https://github.com/CFETeam/wafer-server-sdk-csharp/issues/new "反馈建议和问题" 206 | [pr]: https://github.com/CFETeam/wafer-server-sdk-csharp/pulls "创建 Pull Request" 207 | 208 | ## LICENSE 209 | 210 | [MIT](LICENSE) 211 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.qcloud.weapp.demo 4 | 5 | 6 | com.qcloud.weapp.sdk 7 | 8 | 9 | 10 | org.eclipse.jdt.core.javabuilder 11 | 12 | 13 | 14 | 15 | org.eclipse.wst.common.project.facet.core.builder 16 | 17 | 18 | 19 | 20 | org.eclipse.wst.validation.validationbuilder 21 | 22 | 23 | 24 | 25 | 26 | org.eclipse.jem.workbench.JavaEMFNature 27 | org.eclipse.wst.common.modulecore.ModuleCoreNature 28 | org.eclipse.wst.common.project.facet.core.nature 29 | org.eclipse.jdt.core.javanature 30 | org.eclipse.wst.jsdt.core.jsNature 31 | 32 | 33 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.8 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.source=1.8 13 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | uses 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/.settings/org.eclipse.wst.ws.service.policy.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.wst.ws.service.policy.projectEnabled=false 3 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/WebContent/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/WebContent/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.qcloud.weapp.demo 4 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/lib/org.json.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/wafer-java-server-sdk/ccb281e985cc725c6f516f1ba47b46ad375e7b64/com.qcloud.weapp.demo/lib/org.json.jar -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/src/com/qcloud/weapp/demo/ChatTunnelHandler.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.demo; 2 | 3 | import java.util.HashMap; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | import com.qcloud.weapp.authorization.UserInfo; 9 | import com.qcloud.weapp.tunnel.EmitError; 10 | import com.qcloud.weapp.tunnel.EmitResult; 11 | import com.qcloud.weapp.tunnel.Tunnel; 12 | import com.qcloud.weapp.tunnel.TunnelHandler; 13 | import com.qcloud.weapp.tunnel.TunnelInvalidInfo; 14 | import com.qcloud.weapp.tunnel.TunnelMessage; 15 | import com.qcloud.weapp.tunnel.TunnelRoom; 16 | 17 | /** 18 | *

实现 WebSocket 信道处理器

19 | *

本示例配合客户端 Demo 实现一个简单的聊天室功能

20 | * 21 | *

信道处理器需要处理信道的完整声明周期,包括:

22 | * 28 | * */ 29 | public class ChatTunnelHandler implements TunnelHandler { 30 | 31 | /** 32 | * 记录 WebSocket 信道对应的用户。在实际的业务中,应该使用数据库进行存储跟踪,这里作为示例只是演示其作用 33 | * */ 34 | private static HashMap userMap = new HashMap(); 35 | 36 | /** 37 | * 创建一个房间,包含当前已连接的 WebSocket 信道列表 38 | * */ 39 | private static TunnelRoom room = new TunnelRoom(); 40 | 41 | /** 42 | * 实现 OnTunnelRequest 方法
43 | * 在客户端请求 WebSocket 信道连接之后,会调用 OnTunnelRequest 方法,此时可以把信道 ID 和用户信息关联起来 44 | * */ 45 | @Override 46 | public void onTunnelRequest(Tunnel tunnel, UserInfo userInfo) { 47 | if (tunnel.getTunnelId() == "test") { 48 | userInfo = new UserInfo(); 49 | } 50 | if (userInfo != null) { 51 | userMap.put(tunnel.getTunnelId(), userInfo); 52 | } 53 | System.out.println(String.format("Tunnel Connected: %s", tunnel.getTunnelId())); 54 | } 55 | 56 | /** 57 | * 实现 OnTunnelConnect 方法
58 | * 在客户端成功连接 WebSocket 信道服务之后会调用该方法,此时通知所有其它在线的用户当前总人数以及刚加入的用户是谁 59 | * */ 60 | @Override 61 | public void onTunnelConnect(Tunnel tunnel) { 62 | if (userMap.containsKey(tunnel.getTunnelId())) { 63 | room.addTunnel(tunnel); 64 | JSONObject peopleMessage = new JSONObject(); 65 | try { 66 | peopleMessage.put("total", room.getTunnelCount()); 67 | peopleMessage.put("enter", new JSONObject(userMap.get(tunnel.getTunnelId()))); 68 | } catch (JSONException e) { 69 | e.printStackTrace(); 70 | } 71 | broadcast("people", peopleMessage); 72 | } else { 73 | closeTunnel(tunnel); 74 | } 75 | } 76 | 77 | /** 78 | * 实现 OnTunnelMessage 方法 79 | * 客户端推送消息到 WebSocket 信道服务器上后,会调用该方法,此时可以处理信道的消息。 80 | * 在本示例,我们处理 "speak" 类型的消息,该消息表示有用户发言。我们把这个发言的信息广播到所有在线的 WebSocket 信道上 81 | * */ 82 | @Override 83 | public void onTunnelMessage(Tunnel tunnel, TunnelMessage message) { 84 | if (message.getType().equals("speak") && userMap.containsKey(tunnel.getTunnelId())) { 85 | JSONObject speakMessage = new JSONObject(); 86 | try { 87 | JSONObject messageContent = (JSONObject) message.getContent(); 88 | speakMessage.put("word", messageContent.getString("word")); 89 | speakMessage.put("who", new JSONObject(userMap.get(tunnel.getTunnelId()))); 90 | } catch (JSONException e) { 91 | e.printStackTrace(); 92 | } 93 | broadcast("speak", speakMessage); 94 | } else { 95 | closeTunnel(tunnel); 96 | } 97 | 98 | } 99 | 100 | /** 101 | * 实现 OnTunnelClose 方法 102 | * 客户端关闭 WebSocket 信道或者被信道服务器判断为已断开后,会调用该方法,此时可以进行清理及通知操作 103 | * */ 104 | @Override 105 | public void onTunnelClose(Tunnel tunnel) { 106 | UserInfo leaveUser = null; 107 | if (userMap.containsKey(tunnel.getTunnelId())) { 108 | leaveUser = userMap.get(tunnel.getTunnelId()); 109 | userMap.remove(tunnel.getTunnelId()); 110 | } 111 | room.removeTunnel(tunnel); 112 | JSONObject peopleMessage = new JSONObject(); 113 | try { 114 | peopleMessage.put("total", room.getTunnelCount()); 115 | peopleMessage.put("leave", new JSONObject(leaveUser)); 116 | } catch (JSONException e) { 117 | e.printStackTrace(); 118 | } 119 | broadcast("people", peopleMessage); 120 | } 121 | 122 | /** 123 | * 关闭指定的信道 124 | * */ 125 | private void closeTunnel(Tunnel tunnel) { 126 | try { 127 | tunnel.close(); 128 | } catch (EmitError e) { 129 | e.printStackTrace(); 130 | } 131 | } 132 | 133 | /** 134 | * 广播消息到房间里所有的信道 135 | * */ 136 | private void broadcast(String messageType, JSONObject messageContent) { 137 | try { 138 | EmitResult result = room.broadcast(messageType, messageContent); 139 | // 广播后发现的无效信道进行清理 140 | for (TunnelInvalidInfo invalidInfo : result.getTunnelInvalidInfos()) { 141 | onTunnelClose(Tunnel.getById(invalidInfo.getTunnelId())); 142 | } 143 | } catch (EmitError e) { 144 | // 如果消息发送发生异常,这里可以进行错误处理或者重试的逻辑 145 | e.printStackTrace(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/src/com/qcloud/weapp/demo/QCloud.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.demo; 2 | 3 | import org.json.JSONException; 4 | 5 | import com.qcloud.weapp.ConfigurationManager; 6 | import com.qcloud.weapp.ConfigurationException; 7 | 8 | public class QCloud { 9 | 10 | public static void setupSDK() { 11 | try { 12 | String configFilePath = getConfigFilePath(); 13 | System.out.println("QCloud SDK 配置文件路径:" + configFilePath); 14 | 15 | ConfigurationManager.setupFromFile(configFilePath); 16 | System.out.println("QCloud SDK 已成功配置!"); 17 | } catch (JSONException e) { 18 | e.printStackTrace(); 19 | } catch (ConfigurationException e) { 20 | e.printStackTrace(); 21 | } 22 | } 23 | 24 | private static String getConfigFilePath() { 25 | String osName = System.getProperty("os.name").toLowerCase(); 26 | String defaultConfigFilePath = null; 27 | boolean isWindows = osName.indexOf("windows") > -1; 28 | boolean isLinux = osName.indexOf("linux") > -1; 29 | 30 | if (isWindows) { 31 | defaultConfigFilePath = "C:\\qcloud\\sdk.config"; 32 | } 33 | else if (isLinux) { 34 | defaultConfigFilePath = "/etc/qcloud/sdk.config"; 35 | } 36 | return defaultConfigFilePath; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/src/com/qcloud/weapp/demo/Startup.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.demo; 2 | 3 | import javax.servlet.ServletConfig; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.annotation.WebServlet; 6 | import javax.servlet.http.HttpServlet; 7 | 8 | /** 9 | * Servlet implementation class Startup 10 | */ 11 | @WebServlet(name="Startup", urlPatterns = {}, loadOnStartup = 1) 12 | public class Startup extends HttpServlet { 13 | private static final long serialVersionUID = 1L; 14 | 15 | /** 16 | * @see Servlet#init(ServletConfig) 17 | */ 18 | @Override 19 | public void init(ServletConfig config) throws ServletException { 20 | QCloud.setupSDK(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/src/com/qcloud/weapp/demo/servlet/LoginServlet.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.demo.servlet; 2 | 3 | import java.io.IOException; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.annotation.WebServlet; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import com.qcloud.weapp.ConfigurationException; 11 | import com.qcloud.weapp.authorization.LoginService; 12 | import com.qcloud.weapp.authorization.LoginServiceException; 13 | import com.qcloud.weapp.authorization.UserInfo; 14 | 15 | @WebServlet("/login") 16 | public class LoginServlet extends HttpServlet { 17 | 18 | private static final long serialVersionUID = 6585319986631669934L; 19 | 20 | /** 21 | * 处理登录请求 22 | * */ 23 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 24 | // 通过 ServletRequest 和 ServletResponse 初始化登录服务 25 | LoginService service = new LoginService(request, response); 26 | try { 27 | // 调用登录接口,如果登录成功可以获得登录信息 28 | UserInfo userInfo = service.login(); 29 | System.out.println("========= LoginSuccess, UserInfo: =========="); 30 | System.out.println(userInfo.toString()); 31 | } catch (LoginServiceException e) { 32 | // 登录失败会抛出登录失败异常 33 | e.printStackTrace(); 34 | } catch (ConfigurationException e) { 35 | // SDK 如果还没有配置会抛出配置异常 36 | e.printStackTrace(); 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/src/com/qcloud/weapp/demo/servlet/TunnelServlet.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.demo.servlet; 2 | 3 | import java.io.IOException; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.annotation.WebServlet; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import com.qcloud.weapp.ConfigurationException; 11 | import com.qcloud.weapp.tunnel.TunnelHandleOptions; 12 | import com.qcloud.weapp.tunnel.TunnelService; 13 | import com.qcloud.weapp.demo.ChatTunnelHandler; 14 | 15 | /** 16 | * 使用 SDK 提供信道服务 17 | */ 18 | @WebServlet("/tunnel") 19 | public class TunnelServlet extends HttpServlet { 20 | private static final long serialVersionUID = -6490955903032763981L; 21 | 22 | /** 23 | * 把所有的请求交给 SDK 处理,提供 TunnelHandler 处理信道事件 24 | */ 25 | protected void service(HttpServletRequest request, HttpServletResponse response) 26 | throws ServletException, IOException { 27 | 28 | // 创建信道服务处理信道相关请求 29 | TunnelService tunnelService = new TunnelService(request, response); 30 | 31 | try { 32 | // 配置是可选的,配置 CheckLogin 为 true 的话,会在隧道建立之前获取用户信息,以便业务将隧道和用户关联起来 33 | TunnelHandleOptions options = new TunnelHandleOptions(); 34 | options.setCheckLogin(true); 35 | 36 | // 需要实现信道处理器,ChatTunnelHandler 是一个实现的范例 37 | tunnelService.handle(new ChatTunnelHandler(), options); 38 | } catch (ConfigurationException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /com.qcloud.weapp.demo/src/com/qcloud/weapp/demo/servlet/UserServlet.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.demo.servlet; 2 | 3 | import java.io.IOException; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.annotation.WebServlet; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | 10 | import org.json.JSONException; 11 | import org.json.JSONObject; 12 | 13 | import com.qcloud.weapp.ConfigurationException; 14 | import com.qcloud.weapp.authorization.LoginService; 15 | import com.qcloud.weapp.authorization.LoginServiceException; 16 | import com.qcloud.weapp.authorization.UserInfo; 17 | 18 | /** 19 | * Servlet implementation class UserServlet 20 | */ 21 | @WebServlet("/user") 22 | public class UserServlet extends HttpServlet { 23 | 24 | private static final long serialVersionUID = 6579706670441711811L; 25 | 26 | /** 27 | * 从请求中获取会话中的用户信息 28 | */ 29 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 30 | LoginService service = new LoginService(request, response); 31 | try { 32 | // 调用检查登录接口,成功后可以获得用户信息,进行正常的业务请求 33 | UserInfo userInfo = service.check(); 34 | 35 | // 获取会话成功,输出获得的用户信息 36 | JSONObject result = new JSONObject(); 37 | JSONObject data = new JSONObject(); 38 | data.put("userInfo", new JSONObject(userInfo)); 39 | result.put("code", 0); 40 | result.put("message", "OK"); 41 | result.put("data", data); 42 | response.setContentType("application/json"); 43 | response.setCharacterEncoding("utf-8"); 44 | response.getWriter().write(result.toString()); 45 | 46 | } catch (LoginServiceException e) { 47 | e.printStackTrace(); 48 | } catch (JSONException e) { 49 | e.printStackTrace(); 50 | } catch (ConfigurationException e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.qcloud.weapp.sdk 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.common.project.facet.core.builder 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.validation.validationbuilder 20 | 21 | 22 | 23 | 24 | 25 | org.eclipse.jem.workbench.JavaEMFNature 26 | org.eclipse.wst.common.modulecore.ModuleCoreNature 27 | org.eclipse.jdt.core.javanature 28 | org.eclipse.wst.common.project.facet.core.nature 29 | 30 | 31 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.6 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.6 12 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/lib/org.json.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/wafer-java-server-sdk/ccb281e985cc725c6f516f1ba47b46ad375e7b64/com.qcloud.weapp.sdk/lib/org.json.jar -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/Configuration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | */ 4 | package com.qcloud.weapp; 5 | 6 | /** 7 | * 表示 SDK 配置 8 | * @see com.qcloud.weapp.ConfigurationManager 9 | * @see 服务端 SDK 配置 10 | */ 11 | public class Configuration { 12 | 13 | /** The server host. */ 14 | private String serverHost; 15 | 16 | /** The auth server url. */ 17 | private String authServerUrl; 18 | 19 | /** The tunnel server url. */ 20 | private String tunnelServerUrl; 21 | 22 | /** The tunnel signature key. */ 23 | private String tunnelSignatureKey; 24 | 25 | /** The network proxy. */ 26 | private String networkProxy; 27 | 28 | /** The network timeout. */ 29 | private int networkTimeout; 30 | 31 | /** 32 | * Gets the server host. 33 | * 34 | * @return the server host 35 | */ 36 | public String getServerHost() { 37 | return serverHost; 38 | } 39 | 40 | /** 41 | * Sets the server host. 42 | * 43 | * @param serverHost the new server host 44 | */ 45 | public void setServerHost(String serverHost) { 46 | this.serverHost = serverHost; 47 | } 48 | 49 | /** 50 | * Gets the auth server url. 51 | * 52 | * @return the auth server url 53 | */ 54 | public String getAuthServerUrl() { 55 | return authServerUrl; 56 | } 57 | 58 | /** 59 | * Sets the auth server url. 60 | * 61 | * @param authServerUrl the new auth server url 62 | */ 63 | public void setAuthServerUrl(String authServerUrl) { 64 | this.authServerUrl = authServerUrl; 65 | } 66 | 67 | /** 68 | * Gets the tunnel server url. 69 | * 70 | * @return the tunnel server url 71 | */ 72 | public String getTunnelServerUrl() { 73 | return tunnelServerUrl; 74 | } 75 | 76 | /** 77 | * Sets the tunnel server url. 78 | * 79 | * @param tunnelServerUrl the new tunnel server url 80 | */ 81 | public void setTunnelServerUrl(String tunnelServerUrl) { 82 | this.tunnelServerUrl = tunnelServerUrl; 83 | } 84 | 85 | /** 86 | * Gets the tunnel signature key. 87 | * 88 | * @return the tunnel signature key 89 | */ 90 | public String getTunnelSignatureKey() { 91 | return tunnelSignatureKey; 92 | } 93 | 94 | /** 95 | * Sets the tunnel signature key. 96 | * 97 | * @param tunnelSignatureKey the new tunnel signature key 98 | */ 99 | public void setTunnelSignatureKey(String tunnelSignatureKey) { 100 | this.tunnelSignatureKey = tunnelSignatureKey; 101 | } 102 | 103 | /** 104 | * Gets the network proxy. 105 | * 106 | * @return the network proxy 107 | */ 108 | public String getNetworkProxy() { 109 | return networkProxy; 110 | } 111 | 112 | /** 113 | * Sets the network proxy. 114 | * 115 | * @param networkProxy the new network proxy 116 | */ 117 | public void setNetworkProxy(String networkProxy) { 118 | this.networkProxy = networkProxy; 119 | } 120 | 121 | /** 122 | * Gets the network timeout. 123 | * 124 | * @return the network timeout 125 | */ 126 | public int getNetworkTimeout() { 127 | return networkTimeout; 128 | } 129 | 130 | /** 131 | * Sets the network timeout. 132 | * 133 | * @param networkTimeout the new network timeout 134 | */ 135 | public void setNetworkTimeout(Integer networkTimeout) { 136 | this.networkTimeout = networkTimeout; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/ConfigurationException.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp; 2 | 3 | /** 4 | * 表示配置时产生的异常 5 | * */ 6 | public class ConfigurationException extends Exception { 7 | private static final long serialVersionUID = 570042088042301018L; 8 | 9 | ConfigurationException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/ConfigurationManager.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | /** 11 | * 配置管理,使用该类进行 SDK 配置 12 | * */ 13 | public class ConfigurationManager { 14 | 15 | private static Configuration currentConfiguration; 16 | 17 | /** 18 | * 获取当前的 SDK 配置 19 | * */ 20 | public static Configuration getCurrentConfiguration() throws ConfigurationException { 21 | if (currentConfiguration == null) { 22 | throw new ConfigurationException("SDK 还没有进行配置,请调用 ConfigurationManager.setup() 方法配置 SDK"); 23 | } 24 | return currentConfiguration; 25 | } 26 | 27 | /** 28 | * 使用指定的配置初始化 SDK 29 | * 30 | * @param configuration 配置 31 | * @see 服务端 SDK 配置 32 | * */ 33 | public static void setup(Configuration configuration) throws ConfigurationException { 34 | if (configuration == null) { 35 | throw new ConfigurationException("配置不能为空"); 36 | } 37 | if (configuration.getServerHost() == null) throw new ConfigurationException("服务器主机配置不能为空"); 38 | if (configuration.getAuthServerUrl() == null) throw new ConfigurationException("鉴权服务器配置不能为空"); 39 | if (configuration.getTunnelServerUrl() == null) throw new ConfigurationException("信道服务器配置不能为空"); 40 | if (configuration.getTunnelSignatureKey() == null) throw new ConfigurationException("SDK 密钥配置不能为空"); 41 | currentConfiguration = configuration; 42 | } 43 | 44 | /** 45 | * 从配置文件初始化 SDK 46 | * 47 | * @param configFilePath 配置文件的路径 48 | * @see 服务端 SDK 配置 49 | * */ 50 | public static void setupFromFile(String configFilePath) throws JSONException, ConfigurationException { 51 | JSONObject configs = new JSONObject(getConfigJson(configFilePath)); 52 | Configuration configuration = new Configuration(); 53 | configuration.setServerHost(configs.getString("serverHost")); 54 | configuration.setAuthServerUrl(configs.getString("authServerUrl")); 55 | configuration.setTunnelServerUrl(configs.getString("tunnelServerUrl")); 56 | configuration.setTunnelSignatureKey(configs.getString("tunnelSignatureKey")); 57 | if (configs.has("networkProxy")) { 58 | configuration.setNetworkProxy(configs.getString("networkProxy")); 59 | } 60 | if (configs.has("networkTimeout")) { 61 | configuration.setNetworkTimeout(configs.getInt("networkTimeout")); 62 | } 63 | ConfigurationManager.setup(configuration); 64 | } 65 | 66 | private static String getConfigJson(String configFilePath) { 67 | 68 | String configJsonText = null; 69 | 70 | try { 71 | BufferedReader br = new BufferedReader(new FileReader(configFilePath)); 72 | StringBuilder sb = new StringBuilder(); 73 | String line; 74 | while((line = br.readLine()) != null) { 75 | sb.append(line); 76 | sb.append(System.lineSeparator()); 77 | } 78 | configJsonText = sb.toString(); 79 | br.close(); 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | 84 | return configJsonText; 85 | } 86 | 87 | } 88 | 89 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/Hash.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp; 2 | 3 | import java.security.MessageDigest; 4 | 5 | /** 6 | * 工具类,用于计算哈希值(SDK 内部使用) 7 | * */ 8 | public class Hash { 9 | 10 | /** 11 | * 计算字符串的 sha1 哈希值 12 | * */ 13 | public static String sha1(String str) { 14 | return compute(str, "SHA-1"); 15 | } 16 | 17 | /** 18 | * 计算字符串的 md5 哈希值 19 | * */ 20 | public static String md5(String str) { 21 | return compute(str, "MD5"); 22 | } 23 | 24 | public static String compute(String str, String algorithm) { 25 | if (str == null) { 26 | return null; 27 | } 28 | try { 29 | MessageDigest messageDigest = MessageDigest.getInstance(algorithm); 30 | messageDigest.update(str.getBytes("utf-8")); 31 | return byteArrayToHexString(messageDigest.digest()); 32 | } catch (Exception e) { 33 | throw new RuntimeException(e); 34 | } 35 | } 36 | 37 | private static String byteArrayToHexString(byte[] b) { 38 | String result = ""; 39 | for (int i = 0; i < b.length; i++) { 40 | result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1); 41 | } 42 | return result; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.OutputStreamWriter; 7 | import java.net.HttpURLConnection; 8 | import java.net.InetSocketAddress; 9 | import java.net.Proxy; 10 | import java.net.URL; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | /** 15 | * 用于创建网络请求,SDK 内部使用 16 | * */ 17 | public class HttpRequest { 18 | 19 | public interface ConnectionProvider { 20 | HttpURLConnection getConnection(String url, Proxy proxy) throws IOException; 21 | } 22 | 23 | private static ConnectionProvider connectionProvider = new ConnectionProvider() { 24 | 25 | @Override 26 | public HttpURLConnection getConnection(String url, Proxy proxy) throws IOException { 27 | if (proxy == null) { 28 | return (HttpURLConnection) new URL(url).openConnection(); 29 | } else { 30 | return (HttpURLConnection) new URL(url).openConnection(proxy); 31 | } 32 | } 33 | }; 34 | 35 | public static void setUrlProvider(ConnectionProvider provider) { 36 | connectionProvider = provider; 37 | } 38 | 39 | public static ConnectionProvider getUrlProvider() { 40 | return connectionProvider; 41 | } 42 | 43 | private String url; 44 | 45 | public HttpRequest(String url) { 46 | this.url = url; 47 | } 48 | 49 | public String post(String body) throws IOException { 50 | HttpURLConnection connection; 51 | Proxy proxy = null; 52 | try { 53 | String proxyString = ConfigurationManager.getCurrentConfiguration().getNetworkProxy(); 54 | if (proxyString != null) { 55 | Pattern proxyPattern = Pattern.compile("^(.+)\\:(\\d+)$"); 56 | Matcher proxyMatch = proxyPattern.matcher(proxyString); 57 | if (proxyMatch.find()) { 58 | String host = proxyMatch.group(1); 59 | String port = proxyMatch.group(2); 60 | proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, Integer.parseInt(port))); 61 | } 62 | } 63 | } catch (ConfigurationException e) { 64 | e.printStackTrace(); 65 | } 66 | 67 | connection = connectionProvider.getConnection(url, proxy); 68 | 69 | int networkTimeout = 30000; 70 | 71 | try { 72 | networkTimeout = ConfigurationManager.getCurrentConfiguration().getNetworkTimeout(); 73 | if (networkTimeout == 0) { 74 | networkTimeout = 30000; 75 | } 76 | } catch (ConfigurationException e) { 77 | e.printStackTrace(); 78 | } 79 | 80 | connection.setConnectTimeout(networkTimeout); 81 | connection.setReadTimeout(networkTimeout); 82 | connection.setDoOutput(true); 83 | connection.setRequestMethod("POST"); 84 | connection.setRequestProperty("Content-Type", "application/json;charset=utf-8"); 85 | 86 | // send the request 87 | OutputStreamWriter requestWriter = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); 88 | requestWriter.write(body); 89 | requestWriter.flush(); 90 | 91 | // read the response 92 | BufferedReader responseReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 93 | StringBuffer responseBuffer = new StringBuffer(); 94 | for (String line; (line = responseReader.readLine()) != null;) { 95 | responseBuffer.append(line); 96 | } 97 | return responseBuffer.toString(); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/authorization/AuthorizationAPI.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.authorization; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import com.qcloud.weapp.ConfigurationException; 11 | import com.qcloud.weapp.ConfigurationManager; 12 | import com.qcloud.weapp.HttpRequest; 13 | 14 | class AuthorizationAPI { 15 | 16 | private String getAPIUrl() throws ConfigurationException { 17 | return ConfigurationManager.getCurrentConfiguration().getAuthServerUrl(); 18 | } 19 | 20 | public JSONObject login(String code, String encryptedData, String iv) throws AuthorizationAPIException, ConfigurationException { 21 | Map params = new HashMap(); 22 | params.put("code", code); 23 | params.put("encrypt_data", encryptedData); 24 | params.put("iv", iv); 25 | return request("qcloud.cam.id_skey", params); 26 | } 27 | 28 | public JSONObject checkLogin(String id, String skey) throws AuthorizationAPIException, ConfigurationException { 29 | Map params = new HashMap(); 30 | params.put("id", id); 31 | params.put("skey", skey); 32 | return request("qcloud.cam.auth", params); 33 | } 34 | 35 | public JSONObject request(String apiName, Map apiParams) throws AuthorizationAPIException, ConfigurationException { 36 | String requestBody = null; 37 | String responseBody = null; 38 | 39 | try { 40 | HttpRequest request = new HttpRequest(getAPIUrl()); 41 | 42 | requestBody = buildRequestBody(apiName, apiParams); 43 | System.out.println("==============Auth Request============="); 44 | System.out.println(requestBody); 45 | 46 | responseBody = request.post(requestBody); 47 | System.out.println("==============Auth Response============="); 48 | System.out.println(requestBody); 49 | } catch (IOException e) { 50 | throw new AuthorizationAPIException("连接鉴权服务错误,请检查网络状态" + getAPIUrl() + e.getMessage()); 51 | } 52 | 53 | JSONObject body = null; 54 | int returnCode = 0; 55 | String returnMessage = null; 56 | 57 | try { 58 | body = new JSONObject(responseBody); 59 | returnCode = body.getInt("returnCode"); 60 | returnMessage = body.getString("returnMessage"); 61 | } catch (JSONException e) { 62 | throw new AuthorizationAPIException("调用鉴权服务失败:返回了非法的 JSON 字符串", e); 63 | } 64 | 65 | if (returnCode != 0) { 66 | AuthorizationAPIException error = new AuthorizationAPIException(String.format("调用鉴权服务失败:#%d - %s", returnCode, returnMessage)); 67 | error.setCode(returnCode); 68 | throw error; 69 | } 70 | JSONObject returnData = null; 71 | try { 72 | returnData = body.getJSONObject("returnData"); 73 | } catch (JSONException e) {} 74 | 75 | return returnData; 76 | } 77 | 78 | private String buildRequestBody(String apiName, Map apiParams) { 79 | JSONObject jsonObject = new JSONObject(); 80 | try { 81 | JSONObject interfaceJson = new JSONObject(); 82 | interfaceJson.put("interfaceName", apiName); 83 | interfaceJson.put("para", apiParams); 84 | 85 | jsonObject.put("version", 1); 86 | jsonObject.put("componentName", "MA"); 87 | jsonObject.put("interface", interfaceJson); 88 | } catch (JSONException e) { 89 | e.printStackTrace(); 90 | } 91 | 92 | return jsonObject.toString(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/authorization/AuthorizationAPIException.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.authorization; 2 | 3 | class AuthorizationAPIException extends Exception { 4 | private static final long serialVersionUID = -3088657611850871775L; 5 | private int code; 6 | 7 | public int getCode() { 8 | return code; 9 | } 10 | 11 | public void setCode(int code) { 12 | this.code = code; 13 | } 14 | 15 | public AuthorizationAPIException(String message, Exception inner) { 16 | super(message, inner); 17 | } 18 | 19 | public AuthorizationAPIException(String message) { 20 | this(message, null); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/authorization/Constants.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.authorization; 2 | 3 | /** 4 | * 登录服务常量,包括登录错误类型 5 | * */ 6 | public final class Constants { 7 | static final String WX_SESSION_MAGIC_ID = "F2C224D4-2BCE-4C64-AF9F-A6D872000D1A"; 8 | static final String WX_HEADER_CODE = "X-WX-Code"; 9 | static final String WX_HEADER_ID = "X-WX-Id"; 10 | static final String WX_HEADER_SKEY = "X-WX-Skey"; 11 | static final String WX_HEADER_ENCRYPTED_DATA = "X-WX-Encrypted-Data"; 12 | static final String WX_HEADER_IV = "X-WX-IV"; 13 | 14 | /** 15 | * 表示登录失败 16 | * */ 17 | public static final String ERR_LOGIN_FAILED = "ERR_LOGIN_FAILED"; 18 | 19 | /** 20 | * 表示会话过期的错误 21 | * */ 22 | public static final String ERR_INVALID_SESSION = "ERR_INVALID_SESSION"; 23 | 24 | /** 25 | * 表示检查登录态失败 26 | * */ 27 | public static final String ERR_CHECK_LOGIN_FAILED = "ERR_CHECK_LOGIN_FAILED"; 28 | } 29 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/authorization/LoginService.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.authorization; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | import com.qcloud.weapp.ConfigurationException; 12 | 13 | /** 14 | * 提供登录服务 15 | * */ 16 | public class LoginService { 17 | private HttpServletRequest request; 18 | private HttpServletResponse response; 19 | 20 | /** 21 | * 从 Servlet Request 和 Servlet Response 创建登录服务 22 | * @param request Servlet Request 23 | * @param response Servlet Response 24 | * */ 25 | public LoginService(HttpServletRequest request, HttpServletResponse response) { 26 | this.request = request; 27 | this.response = response; 28 | } 29 | 30 | private void writeJson(JSONObject json) { 31 | try { 32 | this.response.setContentType("application/json"); 33 | this.response.setCharacterEncoding("utf-8"); 34 | this.response.getWriter().print(json.toString()); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | 40 | private JSONObject prepareResponseJson() { 41 | JSONObject json = new JSONObject(); 42 | try { 43 | json.put(Constants.WX_SESSION_MAGIC_ID, 1); 44 | } catch (JSONException e) { 45 | e.printStackTrace(); 46 | } 47 | return json; 48 | } 49 | 50 | private JSONObject getJsonForError(Exception error, int errorCode) { 51 | JSONObject json = prepareResponseJson(); 52 | try { 53 | json.put("code", errorCode); 54 | if (error instanceof LoginServiceException) { 55 | json.put("error", ((LoginServiceException) error).getType()); 56 | } 57 | json.put("message", error.getMessage()); 58 | } catch (JSONException e) { 59 | e.printStackTrace(); 60 | } 61 | return json; 62 | } 63 | 64 | private JSONObject getJsonForError(Exception error) { 65 | return getJsonForError(error, -1); 66 | } 67 | 68 | /** 69 | * 处理登录请求 70 | * @return 登录成功将返回用户信息 71 | * */ 72 | public UserInfo login() throws IllegalArgumentException, LoginServiceException, ConfigurationException { 73 | String code = getHeader(Constants.WX_HEADER_CODE); 74 | String encryptedData = getHeader(Constants.WX_HEADER_ENCRYPTED_DATA); 75 | String iv = getHeader(Constants.WX_HEADER_IV); 76 | 77 | AuthorizationAPI api = new AuthorizationAPI(); 78 | JSONObject loginResult; 79 | 80 | try { 81 | loginResult = api.login(code, encryptedData, iv); 82 | } catch (AuthorizationAPIException apiError) { 83 | LoginServiceException error = new LoginServiceException(Constants.ERR_LOGIN_FAILED, apiError.getMessage(), apiError); 84 | writeJson(getJsonForError(error)); 85 | throw error; 86 | } 87 | 88 | JSONObject json = prepareResponseJson(); 89 | JSONObject session = new JSONObject(); 90 | JSONObject userInfo = null; 91 | try { 92 | session.put("id", loginResult.get("id")); 93 | session.put("skey", loginResult.get("skey")); 94 | json.put("session", session); 95 | writeJson(json); 96 | } catch (JSONException e) { 97 | e.printStackTrace(); 98 | } 99 | 100 | try { 101 | userInfo = loginResult.getJSONObject("user_info"); 102 | } catch (JSONException e) { 103 | e.printStackTrace(); 104 | } 105 | 106 | return UserInfo.BuildFromJson(userInfo); 107 | } 108 | 109 | /** 110 | * 检查当前请求的会话状态 111 | * @return 如果包含可用会话,将会返回会话对应的用户信息 112 | * */ 113 | public UserInfo check() throws LoginServiceException, ConfigurationException { 114 | String id = getHeader(Constants.WX_HEADER_ID); 115 | String skey = getHeader(Constants.WX_HEADER_SKEY); 116 | 117 | AuthorizationAPI api = new AuthorizationAPI(); 118 | JSONObject checkLoginResult = null; 119 | try { 120 | checkLoginResult = api.checkLogin(id, skey); 121 | } catch (AuthorizationAPIException apiError) { 122 | String errorType = Constants.ERR_CHECK_LOGIN_FAILED; 123 | if (apiError.getCode() == 60011 || apiError.getCode() == 60012) { 124 | errorType = Constants.ERR_INVALID_SESSION; 125 | } 126 | LoginServiceException error = new LoginServiceException(errorType, apiError.getMessage(), apiError); 127 | writeJson(getJsonForError(error)); 128 | throw error; 129 | } 130 | JSONObject userInfo = null; 131 | try { 132 | userInfo = checkLoginResult.getJSONObject("user_info"); 133 | } catch (JSONException e) { 134 | e.printStackTrace(); 135 | } 136 | return UserInfo.BuildFromJson(userInfo); 137 | } 138 | 139 | private String getHeader(String key) throws LoginServiceException { 140 | String value = request.getHeader(key); 141 | if (value == null || value.isEmpty()) { 142 | LoginServiceException error = new LoginServiceException("INVALID_REQUEST", String.format("请求头不包含 %s,请配合客户端 SDK 使用", key)); 143 | writeJson(getJsonForError(error)); 144 | throw error; 145 | } 146 | return value; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/authorization/LoginServiceException.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.authorization; 2 | 3 | /** 4 | * 表示登录异常 5 | * */ 6 | public class LoginServiceException extends Exception { 7 | 8 | private static final long serialVersionUID = 7179434716738339025L; 9 | private String type; 10 | 11 | LoginServiceException(String type, String message, Exception innerException) { 12 | super(message, innerException); 13 | this.type = type; 14 | } 15 | 16 | LoginServiceException(String type, String message) { 17 | this(type, message, null); 18 | } 19 | 20 | /** 21 | * 获取登录异常的类型,具体的取值可参考 Constans 里面的常量 22 | * @see com.qcloud.weapp.authorization.Constants 23 | * */ 24 | public String getType() { 25 | return this.type; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/authorization/UserInfo.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.authorization; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | /** 7 | * 表示微信用户信息. 8 | */ 9 | public class UserInfo { 10 | 11 | /** The open id. */ 12 | private String openId; 13 | 14 | /** The nick name. */ 15 | private String nickName; 16 | 17 | /** The avatar url. */ 18 | private String avatarUrl; 19 | 20 | /** The gender. */ 21 | private Integer gender; 22 | 23 | /** The language. */ 24 | private String language; 25 | 26 | /** The city. */ 27 | private String city; 28 | 29 | /** The province. */ 30 | private String province; 31 | 32 | /** The country. */ 33 | private String country; 34 | 35 | /** 36 | * Builds the from json. 37 | * 38 | * @param json the json 39 | * @return the user info 40 | */ 41 | static UserInfo BuildFromJson(JSONObject json) { 42 | if (json == null) return null; 43 | 44 | UserInfo userInfo = new UserInfo(); 45 | try { 46 | if (json.has("openId")) userInfo.openId = json.getString("openId"); 47 | if (json.has("nickName")) userInfo.nickName = json.getString("nickName"); 48 | if (json.has("avatarUrl")) userInfo.avatarUrl = json.getString("avatarUrl"); 49 | if (json.has("gender")) userInfo.gender = json.getInt("gender"); 50 | if (json.has("language")) userInfo.language = json.getString("language"); 51 | if (json.has("city")) userInfo.city = json.getString("city"); 52 | if (json.has("province")) userInfo.province = json.getString("province"); 53 | if (json.has("country")) userInfo.country = json.getString("country"); 54 | } catch (JSONException e) { 55 | e.printStackTrace(); 56 | } 57 | return userInfo; 58 | } 59 | 60 | /** 61 | * Gets the open id. 62 | * 63 | * @return the open id 64 | */ 65 | public String getOpenId() { 66 | return openId; 67 | } 68 | 69 | /** 70 | * Gets the nick name. 71 | * 72 | * @return the nick name 73 | */ 74 | public String getNickName() { 75 | return nickName; 76 | } 77 | 78 | /** 79 | * Gets the avatar url. 80 | * 81 | * @return the avatar url 82 | */ 83 | public String getAvatarUrl() { 84 | return avatarUrl; 85 | } 86 | 87 | /** 88 | * Gets the gender. 89 | * 90 | * @return the gender 91 | */ 92 | public Integer getGender() { 93 | return gender; 94 | } 95 | 96 | /** 97 | * Gets the language. 98 | * 99 | * @return the language 100 | */ 101 | public String getLanguage() { 102 | return language; 103 | } 104 | 105 | /** 106 | * Gets the city. 107 | * 108 | * @return the city 109 | */ 110 | public String getCity() { 111 | return city; 112 | } 113 | 114 | /** 115 | * Gets the province. 116 | * 117 | * @return the province 118 | */ 119 | public String getProvince() { 120 | return province; 121 | } 122 | 123 | /** 124 | * Gets the country. 125 | * 126 | * @return the country 127 | */ 128 | public String getCountry() { 129 | return country; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/EmitError.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | /** 4 | * 表示信道消息发送时发生的异常 5 | * */ 6 | public class EmitError extends Exception { 7 | private static final long serialVersionUID = 4722717669710824633L; 8 | 9 | EmitError(String message, Exception inner) { 10 | super(message, inner); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/EmitResult.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * 表示向信道发送消息后反馈的结果,可能会包含无效信道的列表 7 | * @see com.qcloud.weapp.tunnel.Tunnel 8 | * @see com.qcloud.weapp.tunnel.TunnelRoom 9 | * */ 10 | public class EmitResult { 11 | private ArrayList tunnelInvalidInfos; 12 | 13 | EmitResult(ArrayList tunnelInvalidInfos) { 14 | this.tunnelInvalidInfos = tunnelInvalidInfos; 15 | } 16 | 17 | /** 18 | * 获取无效信道列表 19 | * */ 20 | public ArrayList getTunnelInvalidInfos() { 21 | return tunnelInvalidInfos; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/Tunnel.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | /** 4 | * 表示一个信道,不可以被实例化。可以通过 Tunnel.getById() 获取。 5 | * */ 6 | public class Tunnel { 7 | 8 | private String tunnelId; 9 | private String connectUrl; 10 | 11 | Tunnel(String tunnelId) { 12 | this.tunnelId = tunnelId; 13 | } 14 | 15 | String getConnectUrl() { 16 | return connectUrl; 17 | } 18 | 19 | void setConnectUrl(String connectUrl) { 20 | this.connectUrl = connectUrl; 21 | } 22 | 23 | public String getTunnelId() { 24 | return tunnelId; 25 | } 26 | 27 | void setTunnelId(String tunnelId) { 28 | this.tunnelId = tunnelId; 29 | } 30 | 31 | /** 32 | * 获取具有指定信道 ID 的信道 33 | * */ 34 | public static Tunnel getById(String tunnelId) { 35 | return new Tunnel(tunnelId); 36 | } 37 | 38 | /** 39 | * 发送消息到信道中 40 | * @param messageType 消息类型 41 | * @param messageContent 消息内容,如果需要发送对象或者数组,需要使用 JSONObject 或 JSONArray 类型 42 | * */ 43 | public EmitResult emit(String messageType, Object messageContent) throws EmitError { 44 | TunnelAPI api = new TunnelAPI(); 45 | return api.emitMessage(new String[]{ tunnelId }, messageType, messageContent); 46 | } 47 | 48 | /** 49 | * 关闭当前信道 50 | * */ 51 | public EmitResult close() throws EmitError { 52 | TunnelAPI api = new TunnelAPI(); 53 | return api.emitPacket(new String[]{ tunnelId }, "close", null); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelAPI.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | import java.util.ArrayList; 4 | 5 | import org.json.JSONArray; 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import com.qcloud.weapp.ConfigurationException; 10 | import com.qcloud.weapp.ConfigurationManager; 11 | import com.qcloud.weapp.Hash; 12 | import com.qcloud.weapp.HttpRequest; 13 | 14 | class TunnelAPI { 15 | private String getTunnelServerUrl() throws ConfigurationException { 16 | return ConfigurationManager.getCurrentConfiguration().getTunnelServerUrl(); 17 | } 18 | 19 | public Tunnel requestConnect(String receiveUrl) throws Exception { 20 | JSONObject data = null; 21 | 22 | try { 23 | data = new JSONObject(); 24 | data.put("receiveUrl", receiveUrl); 25 | data.put("protocolType", "wss"); 26 | } catch (JSONException e) { 27 | // impossible 28 | } 29 | 30 | JSONObject result = request("/get/wsurl", data, true); 31 | Tunnel tunnel = new Tunnel(result.getString("tunnelId")); 32 | tunnel.setConnectUrl(result.getString("connectUrl")); 33 | 34 | return tunnel; 35 | } 36 | 37 | public EmitResult emitMessage(Tunnel[] tunnels, String messageType, Object messageContent) throws EmitError { 38 | String[] tunnelIds = new String[tunnels.length]; 39 | Integer i = 0; 40 | for (Tunnel tunnel : tunnels) { 41 | tunnelIds[i++] = tunnel.getTunnelId(); 42 | } 43 | return emitMessage(tunnelIds, messageType, messageContent); 44 | } 45 | 46 | public EmitResult emitMessage(String[] tunnelIds, String messageType, Object messageContent) throws EmitError { 47 | JSONObject packet = new JSONObject(); 48 | try { 49 | packet.put("type", messageType); 50 | packet.put("content", messageContent); 51 | } catch (JSONException e) { 52 | e.printStackTrace(); 53 | } 54 | return emitPacket(tunnelIds, "message", packet); 55 | } 56 | 57 | public EmitResult emitPacket(String[] tunnelIds, String packetType, JSONObject packetContent) throws EmitError { 58 | if (tunnelIds.length == 0) { 59 | return new EmitResult(new ArrayList()); 60 | } 61 | JSONArray data = new JSONArray(); 62 | JSONObject packet = new JSONObject(); 63 | try { 64 | packet.put("type", packetType); 65 | packet.put("tunnelIds", tunnelIds); 66 | packet.put("content", packetContent == null ? null : packetContent.toString()); 67 | data.put(packet); 68 | } catch (JSONException e) { 69 | e.printStackTrace(); 70 | } 71 | try { 72 | JSONObject emitReturn = request("/ws/push", data, false); 73 | JSONArray invalidTunnelIds = emitReturn != null && emitReturn.has("invalidTunnelIds") ? emitReturn.getJSONArray("invalidTunnelIds") : new JSONArray(); 74 | ArrayList infos = new ArrayList(); 75 | for(int i = 0; i < invalidTunnelIds.length(); i++) { 76 | TunnelInvalidInfo info = new TunnelInvalidInfo(); 77 | info.setTunnelId(invalidTunnelIds.getString(i)); 78 | infos.add(info); 79 | } 80 | EmitResult emitResult = new EmitResult(infos); 81 | return emitResult; 82 | } catch (Exception e) { 83 | e.printStackTrace(); 84 | throw new EmitError("网络不可用或者信道服务器不可用", e); 85 | } 86 | } 87 | 88 | public JSONObject request(String path, Object data, Boolean isSendTcKey) throws Exception { 89 | boolean isValidData = data instanceof JSONObject || data instanceof JSONArray; 90 | if (!isValidData) { 91 | throw new Exception("数据只能是 JSONObject 或者 JSONArray 类型"); 92 | } 93 | 94 | String url = getTunnelServerUrl() + path; 95 | String responseContent; 96 | 97 | try { 98 | String requestContent = buildRequestContent(data, isSendTcKey); 99 | responseContent = new HttpRequest(url).post(requestContent); 100 | } catch (Exception e) { 101 | throw new Exception("请求信道 API 失败,网络异常或鉴权服务器错误", e); 102 | } 103 | 104 | try { 105 | JSONObject body = new JSONObject(responseContent); 106 | if (body.getInt("code") != 0) { 107 | throw new Exception(String.format("信道服务调用失败:#%d - %s", body.get("code"), body.get("message"))); 108 | } 109 | return body.has("data") ? new JSONObject(body.getString("data")) : null; 110 | } catch (JSONException e) { 111 | throw new Exception("信道服务器响应格式错误,无法解析 JSON 字符串", e); 112 | } 113 | 114 | } 115 | 116 | private String buildRequestContent(Object data, boolean includeTckey) throws ConfigurationException { 117 | // data must be JsonObject or JsonArray 118 | String encodeData = data.toString(); 119 | JSONObject requestPayload = new JSONObject(); 120 | try { 121 | requestPayload.put("data", encodeData); 122 | requestPayload.put("dataEncode", "json"); 123 | requestPayload.put("tcId", TunnelClient.getId()); 124 | if (includeTckey) { 125 | requestPayload.put("tcKey", TunnelClient.getKey()); 126 | } 127 | requestPayload.put("signature", signature(encodeData)); 128 | } catch (JSONException e) { 129 | e.printStackTrace(); 130 | } 131 | return requestPayload.toString(); 132 | } 133 | 134 | private String signature(String data) throws ConfigurationException { 135 | return Hash.sha1(data + TunnelClient.getKey()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelClient.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | import com.qcloud.weapp.ConfigurationException; 4 | import com.qcloud.weapp.ConfigurationManager; 5 | import com.qcloud.weapp.Hash; 6 | 7 | class TunnelClient { 8 | private static String _id = null; 9 | public static String getId() throws ConfigurationException { 10 | if (_id == null) { 11 | _id = Hash.md5(ConfigurationManager.getCurrentConfiguration().getServerHost()); 12 | } 13 | return _id; 14 | } 15 | 16 | public static String getKey() throws ConfigurationException { 17 | return ConfigurationManager.getCurrentConfiguration().getTunnelSignatureKey(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelHandleOptions.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | /** 4 | * 表示信道服务选项 5 | * */ 6 | public class TunnelHandleOptions { 7 | private boolean checkLogin; 8 | 9 | /** 10 | * 是否配置为检查登录态 11 | * */ 12 | public boolean isCheckLogin() { 13 | return checkLogin; 14 | } 15 | 16 | /** 17 | * 设置是否检查登录态,如果检查登录态,则在连接请求时可以获取到用户信息 18 | * */ 19 | public void setCheckLogin(boolean checkLogin) { 20 | this.checkLogin = checkLogin; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelHandler.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | import com.qcloud.weapp.authorization.UserInfo; 4 | 5 | /** 6 | *

信道事件处理接口,实现该接口处理信道事件。

7 | *

信道处理器需要处理信道的完整声明周期,包括:

8 | *
    9 | *
  • onTunnelRequest() - 当用户发起信道请求的时候,会得到用户信息,此时可以关联信道 ID 和用户信息
  • 10 | *
  • onTunnelConnect() - 当用户建立了信道连接之后,可以记录下已经连接的信道
  • 11 | *
  • onTunnelMessage() - 当用户消息发送到信道上时,使用该函数处理信道的消息
  • 12 | *
  • onTunnelClose() - 当信道关闭时,清理关于该信道的信息,以及回收相关资源
  • 13 | *
14 | * */ 15 | public interface TunnelHandler { 16 | /** 17 | * 当用户发起信道请求的时候调用,会得到用户信息,此时可以关联信道 ID 和用户信息 18 | * @param tunnel 发起连接请求的信道 19 | * @param userInfo 发起连接对应的用户(需要信道服务配置 checkLogin 为 true) 20 | * */ 21 | void onTunnelRequest(Tunnel tunnel, UserInfo userInfo); 22 | 23 | /** 24 | * 当用户建立了信道连接之后调用,此时可以记录下已经连接的信道 25 | * @param tunnel 已经建立连接的信道,此时可以向信道发送消息 26 | * */ 27 | void onTunnelConnect(Tunnel tunnel); 28 | 29 | /** 30 | * 当信道收到消息时调用,此时可以处理消息,也可以向信道发送消息 31 | * @param tunnel 收到消息的信道 32 | * @param message 收到的消息 33 | * */ 34 | void onTunnelMessage(Tunnel tunnel, TunnelMessage message); 35 | 36 | /** 37 | * 当信道关闭的时候调用,此时可以清理信道使用的资源 38 | * @param tunnel 已经关闭的信道 39 | * */ 40 | void onTunnelClose(Tunnel tunnel); 41 | } 42 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelInvalidInfo.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | /** 4 | * 表示一个信道不可用的信息 5 | * */ 6 | public class TunnelInvalidInfo { 7 | private String tunnelId; 8 | private TunnelInvalidType type; 9 | 10 | /** 11 | * 获取不可用信道的 ID 12 | * */ 13 | public String getTunnelId() { 14 | return tunnelId; 15 | } 16 | void setTunnelId(String tunnelId) { 17 | this.tunnelId = tunnelId; 18 | } 19 | /** 20 | * 获取信道不可用的类型 21 | * */ 22 | public TunnelInvalidType getType() { 23 | return type; 24 | } 25 | void setType(TunnelInvalidType type) { 26 | this.type = type; 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelInvalidType.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | /** 4 | * 表示信道不可用的类型 5 | * */ 6 | public enum TunnelInvalidType { 7 | /** 8 | * 表示信道已经关闭,所以不可用 9 | * */ 10 | TunnelHasClosed 11 | } 12 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelMessage.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | /** 7 | * 表示一个信道消息 8 | * */ 9 | public class TunnelMessage { 10 | private String type; 11 | private Object content; 12 | 13 | TunnelMessage(String messageRaw) { 14 | try { 15 | JSONObject resolved = new JSONObject(messageRaw); 16 | this.type = resolved.getString("type"); 17 | this.content = resolved.get("content"); 18 | } catch (JSONException e) { 19 | this.type = "UnknownRaw"; 20 | this.content = messageRaw; 21 | } 22 | } 23 | /** 24 | * 获取信道消息的类型 25 | * */ 26 | public String getType() { 27 | return type; 28 | } 29 | /** 30 | * 获取信道消息的内容 31 | * */ 32 | public Object getContent() { 33 | return content; 34 | } 35 | void setType(String type) { 36 | this.type = type; 37 | } 38 | void setContent(JSONObject content) { 39 | this.content = content; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelRoom.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | import java.util.ArrayList; 4 | import java.util.function.Predicate; 5 | 6 | /** 7 | * 房间维护一批信道的集合,可以通过广播方法向房间里的所有信道推送消息 8 | * */ 9 | public class TunnelRoom { 10 | private ArrayList tunnels; 11 | 12 | /** 13 | * 实例化一个信道房间,初始包含指定的信道集合 14 | * */ 15 | public TunnelRoom(ArrayList tunnels) { 16 | if (tunnels == null) { 17 | tunnels = new ArrayList(); 18 | } 19 | this.tunnels = tunnels; 20 | } 21 | 22 | /** 23 | * 实例化一个信道房间,初始不包含任何信道 24 | * */ 25 | public TunnelRoom() { 26 | this(new ArrayList()); 27 | } 28 | 29 | /** 30 | * 向房间里添加信道 31 | * @param tunnel 要添加的信道 32 | * */ 33 | public void addTunnel(Tunnel tunnel) { 34 | tunnels.add(tunnel); 35 | } 36 | 37 | /** 38 | * 从房间移除指定的信道 39 | * @param tunnel 要移除的信道 40 | * */ 41 | public void removeTunnel(Tunnel tunnel) { 42 | removeTunnelById(tunnel.getTunnelId()); 43 | } 44 | 45 | /** 46 | * 从房间里移除具有指定 ID 的信道 47 | * @param tunnelId 要移除的信道的 ID 48 | * */ 49 | public void removeTunnelById (final String tunnelId) { 50 | tunnels.removeIf(new Predicate() { 51 | @Override 52 | public boolean test(Tunnel t) { 53 | return t.getTunnelId().equals(tunnelId); 54 | } 55 | }); 56 | } 57 | 58 | /** 59 | * 获取房间里信道的数量 60 | * */ 61 | public int getTunnelCount() { 62 | return tunnels.size(); 63 | } 64 | 65 | /** 66 | * 向房间里的每一个信道广播消息 67 | * @param messageType 要广播的消息的类型 68 | * @param messageContent 要广播的消息的内容 69 | * */ 70 | public EmitResult broadcast(String messageType, Object messageContent) throws EmitError { 71 | TunnelAPI api = new TunnelAPI(); 72 | return api.emitMessage(tunnels.toArray(new Tunnel[] {}), messageType, messageContent); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /com.qcloud.weapp.sdk/src/com/qcloud/weapp/tunnel/TunnelService.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.tunnel; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.net.URI; 7 | import java.nio.charset.StandardCharsets; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import org.json.JSONException; 13 | import org.json.JSONObject; 14 | 15 | import com.qcloud.weapp.ConfigurationException; 16 | import com.qcloud.weapp.ConfigurationManager; 17 | import com.qcloud.weapp.Hash; 18 | import com.qcloud.weapp.authorization.LoginService; 19 | import com.qcloud.weapp.authorization.LoginServiceException; 20 | import com.qcloud.weapp.authorization.UserInfo; 21 | 22 | 23 | /** 24 | * 提供信道服务 25 | * */ 26 | public class TunnelService { 27 | private HttpServletRequest request; 28 | private HttpServletResponse response; 29 | 30 | /** 31 | * 从 Servlet Request 和 Servlet Response 实例化一个信道服务 32 | * */ 33 | public TunnelService(HttpServletRequest request, HttpServletResponse response) { 34 | this.request = request; 35 | this.response = response; 36 | } 37 | 38 | private void writeJson(JSONObject json) { 39 | try { 40 | this.response.setContentType("application/json"); 41 | this.response.setCharacterEncoding("utf-8"); 42 | this.response.getWriter().print(json.toString()); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | 48 | private JSONObject getJsonForError(Exception error, int errorCode) { 49 | JSONObject json = new JSONObject(); 50 | try { 51 | json.put("code", errorCode); 52 | if (error instanceof LoginServiceException) { 53 | json.put("error", ((LoginServiceException) error).getType()); 54 | } 55 | json.put("message", error.getMessage()); 56 | } catch (JSONException e) { 57 | e.printStackTrace(); 58 | } 59 | return json; 60 | } 61 | 62 | private JSONObject getJsonForError(Exception error) { 63 | return getJsonForError(error, -1); 64 | } 65 | 66 | 67 | /** 68 | * 处理 WebSocket 信道请求 69 | * @param handler 指定信道处理器处理信道事件 70 | * @param options 指定信道服务的配置 71 | */ 72 | public void handle(TunnelHandler handler, TunnelHandleOptions options) throws ConfigurationException { 73 | if (request.getMethod().toUpperCase() == "GET") { 74 | handleGet(handler, options); 75 | } 76 | if (request.getMethod().toUpperCase() == "POST") { 77 | handlePost(handler, options); 78 | } 79 | } 80 | 81 | /** 82 | * 处理 GET 请求 83 | * 84 | * GET 请求表示客户端请求进行信道连接,此时会向 SDK 申请信道连接地址,并且返回给客户端 85 | * 如果配置指定了要求登陆,还会调用登陆服务来校验登陆态并获得用户信息 86 | */ 87 | private void handleGet(TunnelHandler handler, TunnelHandleOptions options) throws ConfigurationException { 88 | Tunnel tunnel = null; 89 | UserInfo user = null; 90 | 91 | if (options != null && options.isCheckLogin()) { 92 | try { 93 | LoginService loginService = new LoginService(request, response); 94 | user = loginService.check(); 95 | } catch (Exception e) { 96 | return; 97 | } 98 | } 99 | 100 | TunnelAPI api = new TunnelAPI(); 101 | try { 102 | String receiveUrl = buildReceiveUrl(); 103 | tunnel = api.requestConnect(receiveUrl); 104 | } catch (Exception e) { 105 | writeJson(getJsonForError(e)); 106 | return; 107 | } 108 | 109 | JSONObject result = new JSONObject(); 110 | try { 111 | result.put("url", tunnel.getConnectUrl()); 112 | } catch (JSONException e) { 113 | e.printStackTrace(); 114 | } 115 | writeJson(result); 116 | 117 | handler.onTunnelRequest(tunnel, user); 118 | } 119 | 120 | private String buildReceiveUrl() throws ConfigurationException { 121 | URI tunnelServerUri = URI.create(ConfigurationManager.getCurrentConfiguration().getTunnelServerUrl()); 122 | String schema = tunnelServerUri.getScheme(); 123 | String host = ConfigurationManager.getCurrentConfiguration().getServerHost(); 124 | String path = request.getRequestURI(); 125 | return schema + "://" + host + path; 126 | } 127 | 128 | private void handlePost(TunnelHandler handler, TunnelHandleOptions options) throws ConfigurationException { 129 | String requestContent = null; 130 | 131 | // 1. 读取报文内容 132 | try { 133 | BufferedReader requestReader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8)); 134 | requestContent = ""; 135 | for (String line; (line = requestReader.readLine()) != null;) { 136 | requestContent += line; 137 | } 138 | } catch (IOException e) { 139 | e.printStackTrace(); 140 | writeJson(getJsonForError(e)); 141 | return; 142 | } 143 | 144 | // 2. 读取报文内容成 JSON 并保存在 body 变量中 145 | JSONObject body = null; 146 | String data = null, signature = null; 147 | try { 148 | body = new JSONObject(requestContent); 149 | data = body.getString("data"); 150 | signature = body.getString("signature"); 151 | // String signature = body.getString("signature"); 152 | } catch (JSONException e) { 153 | JSONObject errJson = new JSONObject(); 154 | try { 155 | errJson.put("code", 9001); 156 | errJson.put("message", "Cant not parse the request body: invalid json"); 157 | } catch (JSONException e1) { 158 | e1.printStackTrace(); 159 | } 160 | writeJson(errJson); 161 | } 162 | 163 | // 3. 检查报文签名 164 | String computedSignature = Hash.sha1(data + TunnelClient.getKey()); 165 | if (!computedSignature.equals(signature)) { 166 | JSONObject json = new JSONObject(); 167 | try { 168 | json.put("code", 9003); 169 | json.put("message", "Bad Request - 签名错误"); 170 | } catch (JSONException e) { 171 | e.printStackTrace(); 172 | } 173 | writeJson(json); 174 | return; 175 | } 176 | 177 | // 4. 解析报文中携带的数据 178 | JSONObject packet; 179 | String tunnelId = null; 180 | String packetType = null; 181 | String packetContent = null; 182 | try { 183 | packet = new JSONObject(data); 184 | tunnelId = packet.getString("tunnelId"); 185 | packetType = packet.getString("type"); 186 | if (packet.has("content")) { 187 | packetContent = packet.getString("content"); 188 | } 189 | 190 | JSONObject response = new JSONObject(); 191 | response.put("code", 0); 192 | response.put("message", "OK"); 193 | writeJson(response); 194 | } catch (JSONException e) { 195 | JSONObject response = new JSONObject(); 196 | try { 197 | response.put("code", 9004); 198 | response.put("message", "Bad Request - 无法解析的数据包"); 199 | } catch (JSONException e1) { 200 | e1.printStackTrace(); 201 | } 202 | writeJson(response); 203 | e.printStackTrace(); 204 | } 205 | 206 | // 5. 交给客户处理实例处理报文 207 | Tunnel tunnel = Tunnel.getById(tunnelId); 208 | if (packetType.equals("connect")) { 209 | handler.onTunnelConnect(tunnel); 210 | } 211 | else if (packetType.equals("message")) { 212 | handler.onTunnelMessage(tunnel, new TunnelMessage(packetContent)); 213 | } else if (packetType.equals("close")) { 214 | handler.onTunnelClose(tunnel); 215 | } 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.qcloud.weapp.test 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.8 12 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/lib/mockito-all-1.10.19.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/wafer-java-server-sdk/ccb281e985cc725c6f516f1ba47b46ad375e7b64/com.qcloud.weapp.test/lib/mockito-all-1.10.19.jar -------------------------------------------------------------------------------- /com.qcloud.weapp.test/lib/org.json.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/wafer-java-server-sdk/ccb281e985cc725c6f516f1ba47b46ad375e7b64/com.qcloud.weapp.test/lib/org.json.jar -------------------------------------------------------------------------------- /com.qcloud.weapp.test/src/com/qcloud/weapp/test/HttpMock.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.test; 2 | 3 | import static org.mockito.Mockito.*; 4 | 5 | import java.io.ByteArrayInputStream; 6 | import java.io.IOException; 7 | import java.io.PrintWriter; 8 | import java.io.StringWriter; 9 | 10 | import javax.servlet.ServletInputStream; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | 14 | import org.mockito.Matchers; 15 | import org.mockito.invocation.InvocationOnMock; 16 | import org.mockito.stubbing.Answer; 17 | 18 | public class HttpMock { 19 | public HttpServletRequest request; 20 | public HttpServletResponse response; 21 | 22 | private StringWriter sw; 23 | private PrintWriter pw; 24 | public void setupResponseWriter() { 25 | try { 26 | sw = new StringWriter(); 27 | pw = new PrintWriter(sw); 28 | when(response.getWriter()).thenReturn(pw); 29 | } catch (IOException e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | private String responseText = null; 35 | public String getResponseText() { 36 | if (responseText == null) { 37 | pw.flush(); 38 | responseText = sw.toString(); 39 | } 40 | return responseText; 41 | } 42 | 43 | public void setRequestBody(String requestBody) { 44 | try { 45 | // @see http://blog.timmattison.com/archives/2014/12/16/mockito-and-servletinputstreams/ 46 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody.getBytes("utf-8")); 47 | ServletInputStream mockServletInputStream = mock(ServletInputStream.class); 48 | when(mockServletInputStream.read(Matchers.any(), anyInt(), anyInt())).thenAnswer(new Answer() { 49 | @Override 50 | public Integer answer(InvocationOnMock invocationOnMock) throws Throwable { 51 | Object[] args = invocationOnMock.getArguments(); 52 | byte[] output = (byte[]) args[0]; 53 | int offset = (int) args[1]; 54 | int length = (int) args[2]; 55 | return byteArrayInputStream.read(output, offset, length); 56 | } 57 | }); 58 | when(request.getInputStream()).thenReturn(mockServletInputStream); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /com.qcloud.weapp.test/src/com/qcloud/weapp/test/URLConnectionMock.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.test; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.UnsupportedEncodingException; 7 | import java.net.HttpURLConnection; 8 | import java.net.Proxy; 9 | 10 | import static org.mockito.Mockito.*; 11 | import com.qcloud.weapp.*; 12 | 13 | public class URLConnectionMock implements HttpRequest.ConnectionProvider { 14 | 15 | private ByteArrayInputStream responseStream; 16 | private ByteArrayOutputStream requestStream; 17 | 18 | public URLConnectionMock() { 19 | requestStream = new ByteArrayOutputStream(); 20 | } 21 | 22 | public void setResponseBody(String body) { 23 | try { 24 | responseStream = new ByteArrayInputStream(body.getBytes("utf-8")); 25 | } catch (UnsupportedEncodingException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | public String getRequestBody() { 31 | try { 32 | return new String(requestStream.toByteArray(), "utf-8"); 33 | } catch (UnsupportedEncodingException e) { 34 | e.printStackTrace(); 35 | return null; 36 | } 37 | } 38 | 39 | @Override 40 | public HttpURLConnection getConnection(String url, Proxy proxy) throws IOException { 41 | HttpURLConnection connMock = mock(HttpURLConnection.class); 42 | when(connMock.getInputStream()).thenReturn(responseStream); 43 | when(connMock.getOutputStream()).thenReturn(requestStream); 44 | return connMock; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/src/com/qcloud/weapp/test/authorization/LoginServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.test.authorization; 2 | 3 | import static org.junit.Assert.*; 4 | import static org.mockito.Mockito.*; 5 | 6 | import java.io.IOException; 7 | 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | import org.junit.*; 11 | 12 | import com.qcloud.weapp.Configuration; 13 | import com.qcloud.weapp.ConfigurationException; 14 | import com.qcloud.weapp.ConfigurationManager; 15 | import com.qcloud.weapp.authorization.*; 16 | import com.qcloud.weapp.test.HttpMock; 17 | 18 | @SuppressWarnings("unused") 19 | public class LoginServiceTest { 20 | 21 | private LoginServiceTestHelper helper = new LoginServiceTestHelper(); 22 | 23 | @Before 24 | public void setup() { 25 | Configuration config = new Configuration(); 26 | config.setServerHost("test.qcloud.la"); 27 | config.setAuthServerUrl("http://127.0.0.1:10086/auth"); 28 | config.setTunnelServerUrl("http://127.0.0.1:10086/tunnel"); 29 | config.setTunnelSignatureKey("test key"); 30 | config.setNetworkTimeout(1000); 31 | try { 32 | ConfigurationManager.setup(config); 33 | } catch (ConfigurationException e) { 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | @Test 39 | public void testLoginProcess() { 40 | HttpMock mock = helper.createLoginHttpMock("valid-code", "valid-data", "valid-iv"); 41 | LoginService service = new LoginService(mock.request, mock.response); 42 | 43 | try { 44 | UserInfo userInfo = service.login(); 45 | assertNotNull(userInfo); 46 | } catch (IllegalArgumentException | LoginServiceException | ConfigurationException e) { 47 | e.printStackTrace(); 48 | fail(e.getMessage()); 49 | } 50 | 51 | try { 52 | JSONObject body = new JSONObject(mock.getResponseText()); 53 | assertTrue(helper.checkBodyHasMagicId(body)); 54 | assertTrue(helper.checkBodyHasSession(body)); 55 | } catch (JSONException e) { 56 | fail("invalid response body"); 57 | } 58 | } 59 | 60 | @Test 61 | public void testLoginProcessWithoutCodeOrData() { 62 | testLoginProcessExpectError(null, "valid-data", "valid-iv"); 63 | testLoginProcessExpectError("valid-code", null, "valid-iv"); 64 | testLoginProcessExpectError("valid-code", "valid-data", null); 65 | } 66 | 67 | @Test 68 | public void testLoginProcessWithInvalidCodeOrData() { 69 | testLoginProcessExpectError("invalid-code", "valid-data", "valid-iv"); 70 | testLoginProcessExpectError("valid-code", "invalid-data", "valid-iv"); 71 | testLoginProcessExpectError("valid-code", "valid-data", "invalid-iv"); 72 | } 73 | 74 | @Test 75 | public void testLoginProcessWithServerResponseError() { 76 | testLoginProcessExpectError("expect-valid-json", "valid-data", "valid-iv"); 77 | } 78 | 79 | @Test 80 | public void testLoginProcessWithServer500() { 81 | testLoginProcessExpectError("expect-500", "valid-data", "valid-iv"); 82 | } 83 | 84 | @Test 85 | public void testLoginProcessWithServerTimeout() { 86 | testLoginProcessExpectError("expect-timeout", "valid-data", "valid-iv"); 87 | } 88 | 89 | private LoginServiceException testLoginProcessExpectError(String code, String encrypteData, String iv) { 90 | HttpMock mock = helper.createLoginHttpMock(code, encrypteData, iv); 91 | LoginService service = new LoginService(mock.request, mock.response); 92 | 93 | LoginServiceException errorShouldThrow = null; 94 | try { 95 | UserInfo userInfo = service.login(); 96 | } catch (LoginServiceException e) { 97 | errorShouldThrow = e; 98 | } catch (ConfigurationException e) { 99 | e.printStackTrace(); 100 | } 101 | 102 | assertNotNull(errorShouldThrow); 103 | try { 104 | JSONObject body = new JSONObject(mock.getResponseText()); 105 | assertTrue(helper.checkBodyHasMagicId(body)); 106 | assertNotNull(body.get("error")); 107 | } catch (JSONException e) { 108 | fail("invalid response body"); 109 | } 110 | return errorShouldThrow; 111 | } 112 | 113 | @Test 114 | public void testCheck() { 115 | HttpMock mock = helper.createCheckHttpMock("valid-id", "valid-key"); 116 | LoginService service = new LoginService(mock.request, mock.response); 117 | try { 118 | UserInfo userInfo = service.check(); 119 | assertNotNull(userInfo); 120 | } catch (IllegalArgumentException | LoginServiceException | ConfigurationException e) { 121 | fail(e.getMessage()); 122 | e.printStackTrace(); 123 | } 124 | try { 125 | verify(mock.response, never()).getWriter(); 126 | } catch (IOException e) { 127 | e.printStackTrace(); 128 | } 129 | } 130 | 131 | @Test 132 | public void testCheckWithoutIdOrSkey() { 133 | testCheckExpectError(null, "valid-key", null); 134 | testCheckExpectError("valid-id", null, null); 135 | } 136 | 137 | @Test 138 | public void testCheckWithInvalidIdOrSkey() { 139 | testCheckExpectError("invalid-id", "valid-key", false); 140 | testCheckExpectError("valid-id", "invalid-key", false); 141 | } 142 | 143 | @Test 144 | public void testCheckWithInvalidSession() { 145 | testCheckExpectError("expect-60011", "valid-key", true); 146 | testCheckExpectError("expect-60012", "valid-key", true); 147 | } 148 | 149 | @Test 150 | public void testCheckWithServerInvalidResponse() { 151 | testCheckExpectError("expect-invalid-json", "valid-key", false); 152 | } 153 | 154 | @Test 155 | public void testCheckExpectError() { 156 | testCheckExpectError("expect-500", "valid-key", false); 157 | } 158 | 159 | @Test 160 | public void testCheckWithServerTimeout() { 161 | testCheckExpectError("expect-timeout", "valid-key", false); 162 | } 163 | 164 | private void testCheckExpectError(String id, String skey, Boolean expectInvalidSession) { 165 | HttpMock mock = helper.createCheckHttpMock(id, skey); 166 | LoginService service = new LoginService(mock.request, mock.response); 167 | LoginServiceException errorShouldThrow = null; 168 | 169 | try { 170 | UserInfo userInfo = service.check(); 171 | } catch (LoginServiceException e) { 172 | errorShouldThrow = e; 173 | } catch (ConfigurationException e) { 174 | e.printStackTrace(); 175 | } 176 | 177 | JSONObject body = null; 178 | try { 179 | body = new JSONObject(mock.getResponseText()); 180 | assertTrue(helper.checkBodyHasMagicId(body)); 181 | String error = body.getString("error"); 182 | if (expectInvalidSession != null) { 183 | assertEquals(error.equals("ERR_INVALID_SESSION"), expectInvalidSession); 184 | } 185 | } catch (JSONException e) { 186 | fail("响应格式错误::" + e.getMessage()); 187 | } 188 | try { 189 | verify(mock.response, times(1)).getWriter(); 190 | } catch (IOException e) { 191 | e.printStackTrace(); 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/src/com/qcloud/weapp/test/authorization/LoginServiceTestHelper.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.test.authorization; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import com.qcloud.weapp.test.HttpMock; 10 | 11 | import static org.mockito.Mockito.*; 12 | 13 | public class LoginServiceTestHelper { 14 | 15 | public HttpMock createLoginHttpMock(String code, String encryptedData, String iv) { 16 | HttpServletRequest request = mock(HttpServletRequest.class); 17 | HttpServletResponse response = mock(HttpServletResponse.class); 18 | 19 | when(request.getHeader("X-WX-Code")).thenReturn(code); 20 | when(request.getHeader("X-WX-Encrypted-Data")).thenReturn(encryptedData); 21 | when(request.getHeader("X-WX-IV")).thenReturn(iv); 22 | 23 | HttpMock mock = new HttpMock(); 24 | mock.request = request; 25 | mock.response = response; 26 | mock.setupResponseWriter(); 27 | 28 | return mock; 29 | } 30 | 31 | public HttpMock createCheckHttpMock(String id, String skey) { 32 | HttpServletRequest request = mock(HttpServletRequest.class); 33 | HttpServletResponse response = mock(HttpServletResponse.class); 34 | 35 | when(request.getHeader("X-WX-Id")).thenReturn(id); 36 | when(request.getHeader("X-WX-Skey")).thenReturn(skey); 37 | 38 | HttpMock mock = new HttpMock(); 39 | mock.request = request; 40 | mock.response = response; 41 | mock.setupResponseWriter(); 42 | 43 | return mock; 44 | } 45 | 46 | public boolean checkBodyHasMagicId(JSONObject body) { 47 | return body.has("F2C224D4-2BCE-4C64-AF9F-A6D872000D1A"); 48 | } 49 | 50 | public boolean checkBodyHasSession(JSONObject body) { 51 | if (!body.has("session")) return false; 52 | try { 53 | JSONObject session = body.getJSONObject("session"); 54 | return session.has("id") && session.has("skey"); 55 | } catch (JSONException e) { 56 | return false; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/src/com/qcloud/weapp/test/tunnel/TunnelServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.test.tunnel; 2 | 3 | import org.junit.*; 4 | import static org.junit.Assert.*; 5 | import org.mockito.ArgumentCaptor; 6 | import org.mockito.Mockito; 7 | 8 | import static org.mockito.Mockito.*; 9 | 10 | import org.json.JSONArray; 11 | import org.json.JSONException; 12 | import org.json.JSONObject; 13 | 14 | import com.qcloud.weapp.Configuration; 15 | import com.qcloud.weapp.ConfigurationException; 16 | import com.qcloud.weapp.ConfigurationManager; 17 | import com.qcloud.weapp.authorization.UserInfo; 18 | import com.qcloud.weapp.test.HttpMock; 19 | import com.qcloud.weapp.test.URLConnectionMock; 20 | import com.qcloud.weapp.tunnel.EmitError; 21 | import com.qcloud.weapp.tunnel.EmitResult; 22 | import com.qcloud.weapp.tunnel.Tunnel; 23 | import com.qcloud.weapp.tunnel.TunnelHandleOptions; 24 | import com.qcloud.weapp.tunnel.TunnelHandler; 25 | import com.qcloud.weapp.tunnel.TunnelMessage; 26 | import com.qcloud.weapp.tunnel.TunnelRoom; 27 | import com.qcloud.weapp.tunnel.TunnelService; 28 | 29 | public class TunnelServiceTest { 30 | private TunnelServiceTestHelper helper = new TunnelServiceTestHelper(); 31 | 32 | @Before 33 | public void setup() { 34 | Configuration config = new Configuration(); 35 | config.setServerHost("test.qcloud.la"); 36 | config.setAuthServerUrl("http://127.0.0.1:10086/auth"); 37 | config.setTunnelServerUrl("http://127.0.0.1:10086/tunnel"); 38 | config.setTunnelSignatureKey("test key"); 39 | config.setNetworkTimeout(1000); 40 | try { 41 | ConfigurationManager.setup(config); 42 | } catch (ConfigurationException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | 47 | @Test 48 | public void testGetConnectionWithSession() { 49 | HttpMock httpMock = helper.createTunnelHttpMock("GET", "valid"); 50 | TunnelHandler handlerMock = mock(TunnelHandler.class); 51 | 52 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 53 | TunnelHandleOptions options = new TunnelHandleOptions(); 54 | options.setCheckLogin(true); 55 | try { 56 | service.handle(handlerMock, options); 57 | } catch (ConfigurationException e) { 58 | e.printStackTrace(); 59 | } 60 | 61 | try { 62 | JSONObject body = new JSONObject(httpMock.getResponseText()); 63 | assertNotNull(body.getString("url")); 64 | } catch (JSONException e) { 65 | fail(); 66 | } 67 | 68 | ArgumentCaptor tunnel = ArgumentCaptor.forClass(Tunnel.class); 69 | ArgumentCaptor userInfo = ArgumentCaptor.forClass(UserInfo.class); 70 | verify(handlerMock, only()).onTunnelRequest(tunnel.capture(), userInfo.capture()); 71 | assertNotNull(tunnel.getValue()); 72 | assertNotNull(userInfo.getValue()); 73 | } 74 | 75 | 76 | @Test 77 | public void testGetConnectionWithoutSession() { 78 | HttpMock httpMock = helper.createTunnelHttpMock("GET"); 79 | TunnelHandler handlerMock = mock(TunnelHandler.class); 80 | 81 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 82 | TunnelHandleOptions options = new TunnelHandleOptions(); 83 | options.setCheckLogin(false); 84 | try { 85 | service.handle(handlerMock, options); 86 | } catch (ConfigurationException e) { 87 | e.printStackTrace(); 88 | } 89 | 90 | try { 91 | JSONObject body = new JSONObject(httpMock.getResponseText()); 92 | assertNotNull(body.getString("url")); 93 | } catch (JSONException e) { 94 | fail(e.getMessage()); 95 | } 96 | 97 | ArgumentCaptor tunnel = ArgumentCaptor.forClass(Tunnel.class); 98 | ArgumentCaptor userInfo = ArgumentCaptor.forClass(UserInfo.class); 99 | verify(handlerMock, only()).onTunnelRequest(tunnel.capture(), userInfo.capture()); 100 | assertNotNull(tunnel.getValue()); 101 | assertNull(userInfo.getValue()); 102 | } 103 | 104 | 105 | @Test 106 | public void testGetConnectionWithInvalidSession() { 107 | HttpMock httpMock = helper.createTunnelHttpMock("GET", "invalid"); 108 | TunnelHandler handlerMock = mock(TunnelHandler.class); 109 | 110 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 111 | TunnelHandleOptions options = new TunnelHandleOptions(); 112 | options.setCheckLogin(true); 113 | try { 114 | service.handle(handlerMock, options); 115 | } catch (ConfigurationException e) { 116 | e.printStackTrace(); 117 | } 118 | 119 | try { 120 | JSONObject body = new JSONObject(httpMock.getResponseText()); 121 | assertTrue(helper.checkBodyHasMagicId(body)); 122 | assertNotNull(body.get("error")); 123 | } catch (JSONException e) { 124 | fail(); 125 | } 126 | verify(handlerMock, never()).onTunnelRequest(any(Tunnel.class), any(UserInfo.class)); 127 | } 128 | 129 | @Test 130 | public void testPostConnectPacket() throws JSONException, ConfigurationException { 131 | HttpMock httpMock = helper.createTunnelHttpMock("POST"); 132 | httpMock.setRequestBody(helper.buildPacket(new JSONObject() 133 | .put("type", "connect") 134 | .put("tunnelId", "tunnel1") 135 | .toString() 136 | )); 137 | 138 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 139 | TunnelHandler handlerMock = mock(TunnelHandler.class); 140 | 141 | service.handle(handlerMock, null); 142 | 143 | ArgumentCaptor tunnel = ArgumentCaptor.forClass(Tunnel.class); 144 | verify(handlerMock, times(1)).onTunnelConnect(tunnel.capture()); 145 | verify(handlerMock, only()).onTunnelConnect(tunnel.capture()); 146 | assertEquals(tunnel.getValue().getTunnelId(), "tunnel1"); 147 | assertTrue(helper.checkPostResponseSuccess(httpMock.getResponseText())); 148 | } 149 | 150 | 151 | @Test 152 | public void testPostClosePacket() throws JSONException, ConfigurationException { 153 | HttpMock httpMock = helper.createTunnelHttpMock("POST"); 154 | httpMock.setRequestBody(helper.buildPacket(new JSONObject() 155 | .put("type", "close") 156 | .put("tunnelId", "tunnel1") 157 | .toString() 158 | )); 159 | 160 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 161 | TunnelHandler handlerMock = mock(TunnelHandler.class); 162 | 163 | service.handle(handlerMock, null); 164 | 165 | ArgumentCaptor tunnel = ArgumentCaptor.forClass(Tunnel.class); 166 | verify(handlerMock, times(1)).onTunnelClose(tunnel.capture()); 167 | verify(handlerMock, only()).onTunnelClose(tunnel.capture()); 168 | assertEquals(tunnel.getValue().getTunnelId(), "tunnel1"); 169 | assertTrue(helper.checkPostResponseSuccess(httpMock.getResponseText())); 170 | } 171 | 172 | @Test 173 | public void testPostMessagePacket() throws JSONException, ConfigurationException { 174 | HttpMock httpMock = helper.createTunnelHttpMock("POST"); 175 | httpMock.setRequestBody(helper.buildPacket(new JSONObject() 176 | .put("type", "message") 177 | .put("tunnelId", "tunnel1") 178 | .put("content", new JSONObject() 179 | .put("type", "test-type") 180 | .put("content", "test-content") 181 | .toString() 182 | ) 183 | .toString() 184 | )); 185 | 186 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 187 | TunnelHandler handlerMock = mock(TunnelHandler.class); 188 | 189 | service.handle(handlerMock, null); 190 | 191 | ArgumentCaptor tunnel = ArgumentCaptor.forClass(Tunnel.class); 192 | ArgumentCaptor message = ArgumentCaptor.forClass(TunnelMessage.class); 193 | verify(handlerMock, times(1)).onTunnelMessage(tunnel.capture(), message.capture()); 194 | verify(handlerMock, only()).onTunnelMessage(tunnel.capture(), message.capture()); 195 | assertEquals("tunnel1", tunnel.getValue().getTunnelId()); 196 | assertEquals("test-type", message.getValue().getType()); 197 | assertEquals("test-content", message.getValue().getContent()); 198 | assertTrue(helper.checkPostResponseSuccess(httpMock.getResponseText())); 199 | } 200 | 201 | @Test 202 | public void testPostUnknownMessagePacket() throws JSONException, ConfigurationException { 203 | HttpMock httpMock = helper.createTunnelHttpMock("POST"); 204 | httpMock.setRequestBody(helper.buildPacket(new JSONObject() 205 | .put("type", "message") 206 | .put("tunnelId", "tunnel1") 207 | .put("content", "unknown-raw") 208 | .toString() 209 | )); 210 | 211 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 212 | TunnelHandler handlerMock = mock(TunnelHandler.class); 213 | 214 | service.handle(handlerMock, null); 215 | 216 | ArgumentCaptor tunnel = ArgumentCaptor.forClass(Tunnel.class); 217 | ArgumentCaptor message = ArgumentCaptor.forClass(TunnelMessage.class); 218 | verify(handlerMock, times(1)).onTunnelMessage(tunnel.capture(), message.capture()); 219 | assertEquals("tunnel1", tunnel.getValue().getTunnelId()); 220 | assertEquals("UnknownRaw", message.getValue().getType()); 221 | assertEquals("unknown-raw", message.getValue().getContent()); 222 | verifyNoMoreInteractions(handlerMock); 223 | assertTrue(helper.checkPostResponseSuccess(httpMock.getResponseText())); 224 | } 225 | 226 | @Test 227 | public void testPostUnknownPacket() throws JSONException, ConfigurationException { 228 | HttpMock httpMock = helper.createTunnelHttpMock("POST"); 229 | httpMock.setRequestBody(helper.buildPacket(new JSONObject() 230 | .put("type", "unknown") 231 | .put("tunnelId", "tunnel1") 232 | .toString() 233 | )); 234 | 235 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 236 | TunnelHandler handlerMock = mock(TunnelHandler.class); 237 | 238 | service.handle(handlerMock, null); 239 | 240 | Mockito.verifyZeroInteractions(handlerMock); 241 | assertTrue(helper.checkPostResponseSuccess(httpMock.getResponseText())); 242 | } 243 | 244 | @Test 245 | public void testPostPacketWithErrorSignature() throws JSONException, ConfigurationException { 246 | HttpMock httpMock = helper.createTunnelHttpMock("POST"); 247 | httpMock.setRequestBody(helper.buildPacket( 248 | new JSONObject() 249 | .put("type", "connect") 250 | .put("tunnelId", "tunnel1") 251 | .toString(), 252 | true 253 | )); 254 | 255 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 256 | TunnelHandler handlerMock = mock(TunnelHandler.class); 257 | 258 | service.handle(handlerMock, null); 259 | 260 | Mockito.verifyZeroInteractions(handlerMock); 261 | assertFalse(helper.checkPostResponseSuccess(httpMock.getResponseText())); 262 | } 263 | 264 | 265 | @Test 266 | public void testPostBadRequest() throws JSONException, ConfigurationException { 267 | HttpMock httpMock = helper.createTunnelHttpMock("POST"); 268 | httpMock.setRequestBody("illegal request"); 269 | 270 | TunnelService service = new TunnelService(httpMock.request, httpMock.response); 271 | TunnelHandler handlerMock = mock(TunnelHandler.class); 272 | 273 | service.handle(handlerMock, null); 274 | 275 | Mockito.verifyZeroInteractions(handlerMock); 276 | assertFalse(helper.checkPostResponseSuccess(httpMock.getResponseText())); 277 | } 278 | 279 | @Test 280 | public void testTunnelEmit() throws JSONException, EmitError { 281 | URLConnectionMock mock = helper.useURLConnectionMock(); 282 | try { 283 | mock.setResponseBody(new JSONObject().put("code", 0).toString()); 284 | 285 | Tunnel tunnel = Tunnel.getById("tunnel1"); 286 | tunnel.emit("test-type", "test-content"); 287 | 288 | JSONArray packets = helper.resolvePackets(mock.getRequestBody()); 289 | 290 | assertEquals(1, packets.length()); 291 | 292 | JSONObject firstPacket = packets.getJSONObject(0); 293 | assertEquals("tunnel1", firstPacket.getJSONArray("tunnelIds").getString(0)); 294 | assertEquals("message", firstPacket.getString("type")); 295 | 296 | JSONObject message = new JSONObject(firstPacket.getString("content")); 297 | assertEquals("test-type", message.getString("type")); 298 | assertEquals("test-content", message.getString("content")); 299 | } finally { 300 | helper.restoreURLConnectionMock(); 301 | } 302 | } 303 | 304 | @Test 305 | public void testEmitWithInvalidTunnels() throws JSONException, EmitError { 306 | URLConnectionMock mock = helper.useURLConnectionMock(); 307 | try { 308 | mock.setResponseBody(new JSONObject() 309 | .put("code", 0) 310 | .put("data", new JSONObject() 311 | .put("invalidTunnelIds", new JSONArray().put("tunnel1")) 312 | .toString() 313 | ) 314 | .toString() 315 | ); 316 | Tunnel tunnel = Tunnel.getById("tunnel1"); 317 | EmitResult result = tunnel.emit("test-type", new JSONObject().put("message", "test-content")); 318 | assertEquals(1, result.getTunnelInvalidInfos().size()); 319 | assertEquals("tunnel1", result.getTunnelInvalidInfos().get(0).getTunnelId()); 320 | } finally { 321 | helper.restoreURLConnectionMock(); 322 | } 323 | } 324 | 325 | @Test 326 | public void testTunnelClose() throws JSONException, EmitError { 327 | URLConnectionMock mock = helper.useURLConnectionMock(); 328 | try { 329 | mock.setResponseBody(new JSONObject().put("code", 0).toString()); 330 | Tunnel tunnel = Tunnel.getById("tunnel1"); 331 | tunnel.close(); 332 | 333 | JSONArray packets = helper.resolvePackets(mock.getRequestBody()); 334 | 335 | assertEquals(1, packets.length()); 336 | assertEquals("tunnel1", packets.getJSONObject(0).getJSONArray("tunnelIds").getString(0)); 337 | assertEquals("close", packets.getJSONObject(0).getString("type")); 338 | } finally { 339 | helper.restoreURLConnectionMock(); 340 | } 341 | } 342 | 343 | @Test 344 | public void testRoomBroadcast() throws JSONException, EmitError { 345 | URLConnectionMock mock = helper.useURLConnectionMock(); 346 | try { 347 | mock.setResponseBody(new JSONObject().put("code", 0).toString()); 348 | 349 | TunnelRoom room = new TunnelRoom(); 350 | 351 | room.addTunnel(Tunnel.getById("tunnel1")); 352 | room.addTunnel(Tunnel.getById("tunnel2")); 353 | assertEquals(2, room.getTunnelCount()); 354 | 355 | room.removeTunnelById("tunnel1"); 356 | assertEquals(1, room.getTunnelCount()); 357 | 358 | room.addTunnel(Tunnel.getById("tunnel3")); 359 | room.broadcast("test-type", "test-message"); 360 | 361 | JSONArray packets = helper.resolvePackets(mock.getRequestBody()); 362 | 363 | assertEquals(1, packets.length()); 364 | 365 | JSONObject firstPacket = packets.getJSONObject(0); 366 | assertEquals(2, firstPacket.getJSONArray("tunnelIds").length()); 367 | assertEquals("tunnel2", firstPacket.getJSONArray("tunnelIds").get(0)); 368 | assertEquals("tunnel3", firstPacket.getJSONArray("tunnelIds").get(1)); 369 | assertEquals("message", firstPacket.getString("type")); 370 | 371 | JSONObject message = new JSONObject(firstPacket.getString("content")); 372 | assertEquals("test-type", message.getString("type")); 373 | assertEquals("test-message", message.getString("content")); 374 | } finally { 375 | helper.restoreURLConnectionMock(); 376 | } 377 | } 378 | 379 | 380 | @Test 381 | public void testRoomBroadcastWithInvalidTunnels() throws JSONException, EmitError { 382 | URLConnectionMock mock = helper.useURLConnectionMock(); 383 | try { 384 | mock.setResponseBody(new JSONObject() 385 | .put("code", 0) 386 | .put("data", new JSONObject() 387 | .put("invalidTunnelIds", new JSONArray().put("tunnel1").put("tunnel2")) 388 | ) 389 | .toString() 390 | ); 391 | 392 | TunnelRoom room = new TunnelRoom(); 393 | 394 | room.addTunnel(Tunnel.getById("tunnel1")); 395 | room.addTunnel(Tunnel.getById("tunnel2")); 396 | room.addTunnel(Tunnel.getById("tunnel3")); 397 | EmitResult result = room.broadcast("test-type", "test-message"); 398 | 399 | assertEquals(2, result.getTunnelInvalidInfos().size()); 400 | assertEquals("tunnel1", result.getTunnelInvalidInfos().get(0).getTunnelId()); 401 | assertEquals("tunnel2", result.getTunnelInvalidInfos().get(1).getTunnelId()); 402 | } finally { 403 | helper.restoreURLConnectionMock(); 404 | } 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /com.qcloud.weapp.test/src/com/qcloud/weapp/test/tunnel/TunnelServiceTestHelper.java: -------------------------------------------------------------------------------- 1 | package com.qcloud.weapp.test.tunnel; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletResponse; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import com.qcloud.weapp.ConfigurationException; 11 | import com.qcloud.weapp.ConfigurationManager; 12 | import com.qcloud.weapp.Hash; 13 | import com.qcloud.weapp.HttpRequest; 14 | import com.qcloud.weapp.test.HttpMock; 15 | import com.qcloud.weapp.test.URLConnectionMock; 16 | 17 | import static org.mockito.Mockito.*; 18 | 19 | public class TunnelServiceTestHelper { 20 | 21 | public HttpMock createTunnelHttpMock(String method, String sessionType) { 22 | HttpServletRequest request = mock(HttpServletRequest.class); 23 | HttpServletResponse response = mock(HttpServletResponse.class); 24 | 25 | when(request.getMethod()).thenReturn(method); 26 | 27 | if (sessionType != null) { 28 | if (sessionType.equals("valid")) { 29 | when(request.getHeader("X-WX-Id")).thenReturn("valid-id"); 30 | when(request.getHeader("X-WX-Skey")).thenReturn("valid-key"); 31 | } 32 | else if (sessionType.equals("invalid")) { 33 | when(request.getHeader("X-WX-Id")).thenReturn("invalid-id"); 34 | when(request.getHeader("X-WX-Skey")).thenReturn("invalid-key"); 35 | } 36 | } 37 | 38 | HttpMock mock = new HttpMock(); 39 | mock.request = request; 40 | mock.response = response; 41 | mock.setupResponseWriter(); 42 | 43 | return mock; 44 | } 45 | 46 | public HttpMock createTunnelHttpMock(String method) { 47 | return createTunnelHttpMock(method, null); 48 | } 49 | 50 | public boolean checkBodyHasMagicId(JSONObject body) { 51 | return body.has("F2C224D4-2BCE-4C64-AF9F-A6D872000D1A"); 52 | } 53 | 54 | public String buildPacket(String data, boolean fakeSignature) { 55 | JSONObject json = new JSONObject(); 56 | try { 57 | json.put("data", data); 58 | json.put("dataEncode", "json"); 59 | json.put("signature", fakeSignature ? "fake-signature" : Hash.sha1(data + ConfigurationManager.getCurrentConfiguration().getTunnelSignatureKey())); 60 | } catch (JSONException e) { 61 | e.printStackTrace(); 62 | } catch (ConfigurationException e) { 63 | e.printStackTrace(); 64 | } 65 | return json.toString(); 66 | } 67 | 68 | public String buildPacket(String data) { 69 | return buildPacket(data, false); 70 | } 71 | 72 | public JSONArray resolvePackets(String body) throws JSONException { 73 | return new JSONArray(new JSONObject(body).getString(("data"))); 74 | } 75 | 76 | public boolean checkPostResponseSuccess(String responseText) { 77 | try { 78 | JSONObject response = new JSONObject(responseText); 79 | return response.getInt("code") == 0; 80 | } catch (JSONException e) { 81 | return false; 82 | } 83 | } 84 | 85 | private HttpRequest.ConnectionProvider originProvider; 86 | public URLConnectionMock useURLConnectionMock() { 87 | originProvider = HttpRequest.getUrlProvider(); 88 | URLConnectionMock mock = new URLConnectionMock(); 89 | HttpRequest.setUrlProvider(mock); 90 | return mock; 91 | } 92 | 93 | public void restoreURLConnectionMock() { 94 | HttpRequest.setUrlProvider(originProvider); 95 | } 96 | } 97 | --------------------------------------------------------------------------------