├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── README_CN.md ├── examples ├── Cargo.toml ├── client-config.yaml ├── server-config.yaml └── src │ ├── client.rs │ ├── client_pt.rs │ ├── lib.rs │ └── server.rs ├── fusen-common ├── Cargo.toml └── src │ ├── codec.rs │ ├── config │ ├── mod.rs │ ├── toml.rs │ └── yaml.rs │ ├── date_util.rs │ ├── error.rs │ ├── lib.rs │ ├── logs.rs │ ├── macro.rs │ ├── net.rs │ ├── register.rs │ ├── server.rs │ ├── trie.rs │ └── url.rs ├── fusen-macro ├── derive-macro │ ├── Cargo.toml │ └── src │ │ ├── attr_macro.rs │ │ └── lib.rs └── procedural-macro │ ├── Cargo.toml │ └── src │ ├── data.rs │ ├── handler_macro.rs │ ├── lib.rs │ ├── server_macro.rs │ └── trait_macro.rs └── fusen ├── Cargo.toml └── src ├── client.rs ├── codec ├── grpc_codec.rs ├── http_codec.rs ├── json_codec.rs ├── mod.rs ├── request_codec.rs └── response_codec.rs ├── config.rs ├── filter ├── mod.rs └── server.rs ├── handler ├── aspect │ └── mod.rs ├── loadbalance │ └── mod.rs └── mod.rs ├── lib.rs ├── protocol ├── compression.rs ├── http_handler.rs ├── mod.rs ├── server.rs └── socket.rs ├── register ├── mod.rs └── nacos.rs ├── route ├── client.rs ├── mod.rs └── server.rs ├── server.rs └── support ├── compression.rs ├── dubbo.rs ├── grpc.rs ├── mod.rs ├── shutdown.rs └── triple.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | .vscode/ 4 | .DS_Store 5 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "fusen-common", 4 | "fusen-macro/derive-macro", 5 | "fusen-macro/procedural-macro", 6 | "fusen", 7 | "examples", 8 | ] 9 | resolver = "2" 10 | 11 | [workspace.package] 12 | authors = ["kwsc98 "] 13 | edition = "2021" 14 | license = "Apache-2.0" 15 | description = "RPC framework for service registration and discovery through API exposure, compatible with Dubbo3 protocol, intertunable with Java projects" 16 | 17 | 18 | [workspace.dependencies] 19 | fusen-common = { path = "fusen-common", version = "0.6.20" } 20 | fusen-rs = { path = "fusen", version = "0.6.24" } 21 | fusen-derive-macro = { path = "fusen-macro/derive-macro", version = "0.6.13" } 22 | fusen-procedural-macro = { path = "fusen-macro/procedural-macro", version = "0.6.14" } 23 | 24 | #网络协议处理 25 | tokio = { version = "1.43.0", features = ["full"] } 26 | hyper = { version = "1.5.2", features = ["full"] } 27 | hyper-tls = { version = "0.6.0" } 28 | hyper-util = { version = "0.1.9", features = ["full"] } 29 | http = "1.2.0" 30 | http-body = "1.0.1" 31 | http-body-util = "0.1.2" 32 | bytes = "1.9.0" 33 | futures = "0.3.31" 34 | async-trait = "0.1.85" 35 | async-recursion = "1.1.1" 36 | h2 = "0.4.7" 37 | futures-util = "0.3.31" 38 | urlencoding = "2.1.3" 39 | 40 | 41 | #日志处理 42 | tracing = "0.1.41" 43 | tracing-futures = { version = "0.2.5" } 44 | tracing-subscriber = { version = "0.3.19", features = ["json","env-filter"] } 45 | pretty_env_logger = "0.5.0" 46 | tracing-opentelemetry = "0.28.0" 47 | opentelemetry = "0.27.1" 48 | opentelemetry_sdk = { version = "0.27.1", features = ["rt-tokio"] } 49 | opentelemetry-otlp = "0.27.0" 50 | tracing-appender = "0.2.3" 51 | 52 | 53 | #json序列化 54 | serde = { version = "1.0.217", features = ["derive"] } 55 | serde_json = "1.0.135" 56 | uuid = { version = "1.12.0", features = ["v4"] } 57 | 58 | percent-encoding = "2.3.1" 59 | pin-project-lite = "0.2.16" 60 | lazy_static = "1.5.0" 61 | proc-macro2 = "1.0.93" 62 | rand = "0.8.5" 63 | toml = "0.8.15" 64 | serde_yaml = "0.9.34" 65 | local-ip-address = "0.6.3" 66 | chrono = "0.4.39" 67 | 68 | #注册中心 69 | nacos-sdk = "0.4.3" 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # `fusen-rs` A most like an RPC framework 3 | 4 | fusen-rs is a high-performance, lightweight microservice framework that uses Rust macros to solve the current problems of complex use and low performance of mainstream rpc frameworks. It does not need to generate RPC calling code through scripts and scaffolding, and compiles it through macros. It uses "reflection" to achieve high-performance calls and meet the simplicity of RPC calls. It also supports Dubbo3. The SpringCloud microservice ecosystem can perform service registration discovery and mutual calls with Java projects, and supports user-defined components and other functions. 5 | 6 | [ [中文](./README_CN.md) ] 7 | 8 | ## Function List 9 | 10 | - :white_check_mark: RPC call abstraction layer (Rust macro) 11 | - :white_check_mark: Multi-protocol support (HTTP1, HTTP2) 12 | - :white_check_mark: Service registration and discovery (Nacos) 13 | - :white_check_mark: Microservice ecological compatibility (Dubbo3, SpringCloud) 14 | - :white_check_mark: Custom components (custom load balancer, Aspect surround notification component) 15 | - :white_check_mark: Configuration center (local file configuration, Nacos) 16 | - :white_check_mark: Graceful shutdown 17 | - :white_check_mark: Microservice link tracking (opentelemetry) 18 | - :construction: HTTP3 protocol support 19 | 20 | ## Quick Start 21 | 22 | ### Common Interface 23 | 24 | ```rust 25 | #[derive(Serialize, Deserialize, Default, Debug)] 26 | pub struct ReqDto { 27 | pub str: String, 28 | } 29 | 30 | #[derive(Serialize, Deserialize, Default, Debug)] 31 | pub struct ResDto { 32 | pub str: String, 33 | } 34 | 35 | #[fusen_trait(id = "org.apache.dubbo.springboot.demo.DemoService")] 36 | pub trait DemoService { 37 | async fn sayHello(&self, name: String) -> String; 38 | 39 | #[asset(path = "/sayHelloV2-http", method = POST)] 40 | async fn sayHelloV2(&self, name: ReqDto) -> ResDto; 41 | 42 | #[asset(path = "/divide", method = GET)] 43 | async fn divideV2(&self, a: i32, b: i32) -> String; 44 | } 45 | ``` 46 | 47 | ### Server 48 | 49 | ```rust 50 | #[derive(Debug)] 51 | struct DemoServiceImpl { 52 | _db: String, 53 | } 54 | 55 | #[fusen_server(id = "org.apache.dubbo.springboot.demo.DemoService")] 56 | impl DemoService for DemoServiceImpl { 57 | async fn sayHello(&self, req: String) -> FusenResult { 58 | info!("res : {:?}", req); 59 | Ok("Hello ".to_owned() + &req) 60 | } 61 | #[asset(path="/sayHelloV2-http",method = POST)] 62 | async fn sayHelloV2(&self, req: ReqDto) -> FusenResult { 63 | info!("res : {:?}", req); 64 | Ok(ResDto::default().str("Hello ".to_owned() + req.get_str() + " V2")) 65 | } 66 | #[asset(path="/divide",method = GET)] 67 | async fn divideV2(&self, a: i32, b: i32) -> FusenResult { 68 | info!("res : a={:?},b={:?}", a, b); 69 | Ok((a + b).to_string()) 70 | } 71 | } 72 | 73 | #[tokio::main(flavor = "multi_thread", worker_threads = 16)] 74 | async fn main() { 75 | fusen_common::logs::init_log(); 76 | let server = DemoServiceImpl { 77 | _db: "我是一个DB数据库".to_string(), 78 | }; 79 | FusenApplicationContext::builder() 80 | //使用配置文件进行初始化 81 | .init(get_config_by_file("examples/server-config.yaml").unwrap()) 82 | .add_fusen_server(Box::new(server)) 83 | .add_handler(ServerLogAspect.load()) 84 | .build() 85 | .run() 86 | .await; 87 | } 88 | ``` 89 | 90 | ### Client 91 | 92 | ```rust 93 | #[tokio::main(flavor = "multi_thread", worker_threads = 16)] 94 | async fn main() { 95 | fusen_common::logs::init_log(); 96 | let context = FusenApplicationContext::builder() 97 | //使用配置文件进行初始化 98 | .init(get_config_by_file("examples/client-config.yaml").unwrap()) 99 | .add_handler(CustomLoadBalance.load()) 100 | .add_handler(ClientLogAspect.load()) 101 | .build(); 102 | //直接当HttpClient调用HTTP1 + JSON 103 | let client = DemoServiceClient::new(Arc::new( 104 | context.client(Type::Host("127.0.0.1:8081".to_string())), 105 | )); 106 | let res = client 107 | .sayHelloV2(ReqDto::default().str("world".to_string())) 108 | .await; 109 | info!("rev host msg : {:?}", res); 110 | //通过Fusen进行服务注册与发现,并且进行HTTP2+JSON进行调用 111 | let client = DemoServiceClient::new(Arc::new(context.client(Type::Fusen))); 112 | let res = client 113 | .sayHelloV2(ReqDto::default().str("world".to_string())) 114 | .await; 115 | info!("rev fusen msg : {:?}", res); 116 | // //通过Dubbo进行服务注册与发现,并且进行HTTP2+Grpc进行调用 117 | let client = DemoServiceClient::new(Arc::new(context.client(Type::Dubbo))); 118 | let res = client 119 | .sayHelloV2(ReqDto::default().str("world".to_string())) 120 | .await; 121 | info!("rev dubbo msg : {:?}", res); 122 | //通过SpringCloud进行服务注册与发现,并且进行HTTP1+JSON进行调用 123 | let client = DemoServiceClient::new(Arc::new(context.client(Type::SpringCloud))); 124 | let res = client 125 | .sayHelloV2(ReqDto::default().str("world".to_string())) 126 | .await; 127 | info!("rev springcloud msg : {:?}", res); 128 | } 129 | ``` 130 | 131 | ## Custom component 132 | 133 | Microservice custom components include load balancers, service breaker/current limiting components, pre- and post-request processors, service link tracking and other components. Since the components are highly customized, this project is provided with reference to the concept of AOP Two custom components are provided to provide flexible request processing. 134 | 135 | ### LoadBalance 136 | 137 | Load balancing component, LoadBalance provides a select interface to implement user-defined service balancing configuration. 138 | 139 | ```rust 140 | #[handler(id = "CustomLoadBalance")] 141 | impl LoadBalance for CustomLoadBalance { 142 | async fn select( 143 | &self, 144 | invokers: Arc, 145 | ) -> Result, fusen_rs::Error> { 146 | invokers 147 | .select() 148 | .ok_or("not find server : CustomLoadBalance".into()) 149 | } 150 | } 151 | ``` 152 | 153 | ### Aspect 154 | 155 | I believe everyone is familiar with the concept of dynamic proxy. This is a technology used by Java to enhance classes, and the Spring framework uses this feature to encapsulate a more advanced model, which is the AOP aspect-first programming model. This component is a reference This model implements the wraparound notification model. Users can implement various component requirements based on this component, such as service circuit breaker/current limiting, request pre- and post-processing, link tracking, request response time monitoring and other requirements, and Aspect Components support multi-level nested calls and provide flexible definition methods to meet users' complex needs. 156 | 157 | ```rust 158 | #[handler(id = "ServerLogAspect")] 159 | impl Aspect for ServerLogAspect { 160 | async fn aroud( 161 | &self, 162 | join_point: ProceedingJoinPoint, 163 | ) -> Result { 164 | let start_time = get_now_date_time_as_millis(); 165 | info!("server receive request : {:?}", join_point.get_context()); 166 | let context = join_point.proceed().await; 167 | info!( 168 | "server dispose done RT : {:?}ms : {:?}", 169 | get_now_date_time_as_millis() - start_time, 170 | context 171 | ); 172 | context 173 | } 174 | } 175 | ``` 176 | 177 | ## Dubbo3 178 | 179 | This project is also compatible with the dubbo3 protocol, and can easily perform service registration discovery and intermodulation with the Java version of the Dubbo3 project through interface exposure. 180 | 181 | Rust's Server and Client don't need to be modified at all, just like the above example. 182 | 183 | The Java version of the Dubbo3 project does not need to be modified at the code level. It only needs to add some dependencies and configurations (because the way Dubbo3 uses interface exposure does not support the json serialization protocol by default, it uses the binary serialization format of fastjson2, so here we need to manually Add support for fastjson1) 184 | 185 | Here we use duboo3’s official sample dubbo-samples-spring-boot project for demonstration. 186 | 187 | 188 | First, we need to add the maven dependencies of fastjson and nacos to the pom.xml of the Server and Client services. 189 | 190 | ```java 191 | 192 | org.apache.dubbo 193 | dubbo-serialization-fastjson 194 | 2.7.23 195 | 196 | 197 | 198 | com.alibaba.nacos 199 | nacos-client 200 | 2.2.0 201 | 202 | ``` 203 | 204 | ### Java-Server 205 | 206 | ```java 207 | @DubboService 208 | public class DemoServiceImpl implements DemoService { 209 | 210 | @Override 211 | public String sayHello(String name) { 212 | return "Hello " + name; 213 | } 214 | } 215 | ``` 216 | 217 | ### Server-application.yml 218 | 219 | ```java 220 | dubbo: 221 | application: 222 | name: dubbo-springboot-demo-provider 223 | protocol: 224 | name: tri 225 | port: 50052 226 | //添加fastjson的支持 227 | prefer-serialization: fastjson 228 | registry: 229 | address: nacos://${nacos.address:127.0.0.1}:8848 230 | ``` 231 | 232 | ### Java-Client 233 | 234 | ```java 235 | @Component 236 | public class Task implements CommandLineRunner { 237 | @DubboReference 238 | private DemoService demoService; 239 | 240 | @Override 241 | public void run(String... args) throws Exception { 242 | String result = demoService.sayHello("world"); 243 | System.out.println("Receive result ======> " + result); 244 | 245 | new Thread(()-> { 246 | while (true) { 247 | try { 248 | Thread.sleep(1000); 249 | System.out.println(new Date() + " Receive result ======> " + demoService.sayHello("world")); 250 | } catch (InterruptedException e) { 251 | e.printStackTrace(); 252 | Thread.currentThread().interrupt(); 253 | } 254 | } 255 | }).start(); 256 | } 257 | } 258 | ``` 259 | 260 | ### Client-application.yml 261 | 262 | ```java 263 | dubbo: 264 | application: 265 | name: dubbo-springboot-demo-consumer 266 | registry: 267 | address: nacos://${nacos.address:127.0.0.1}:8848 268 | ``` 269 | 270 | ## SpringCloud 271 | 272 | At the same time, this project has also expanded the HTTP interface to be used as a WebServer framework, and also supports Spring Cloud service registration and discovery. Users can flexibly select and switch the protocols that need to be exposed, and support simultaneous exposure. 273 | 274 | Here we use the spring-cloud-alibaba project for demonstration 275 | 276 | 277 | When calling SpringCloud on RustClient, you need to change fusen_trait_id to the target service id (application_name) 278 | 279 | ```rust 280 | #[fusen_trait(id = "service-provider")] 281 | ``` 282 | 283 | There is no need to modify the Java server and client code. Just start it. 284 | 285 | ### SpringCloud-Server 286 | 287 | Provider startup class 288 | package com.alibaba.cloud.examples.ProviderApplication 289 | 290 | ```java 291 | //EchoController 292 | @RestController 293 | public class EchoController { 294 | ... 295 | @GetMapping("/divide") 296 | public String divide(@RequestParam Integer a, @RequestParam Integer b) { 297 | if (b == 0) { 298 | return String.valueOf(0); 299 | } else { 300 | return String.valueOf(a / b); 301 | } 302 | } 303 | ... 304 | } 305 | ``` 306 | 307 | ### SpringCloud-Client 308 | 309 | Consumer startup class 310 | package com.alibaba.cloud.examples.ConsumerApplication 311 | 312 | ```java 313 | //TestController 314 | @RestController 315 | public class TestController { 316 | ... 317 | @GetMapping("/divide-feign") 318 | public String divide(@RequestParam Integer a, @RequestParam Integer b) { 319 | return echoClient.divide(a, b); 320 | } 321 | ... 322 | } 323 | 324 | ``` 325 | 326 | Test curl (curl => SpringCloud => fusen-rust) 327 | 328 | 329 | ```rust 330 | 2024-04-10T06:52:32.737307Z INFO ThreadId(07) server: 33: res : a=1,b=2 331 | ``` 332 | 333 | Test curl ( curl => fusen-rust ) 334 | 335 | 336 | 337 | ```rust 338 | 2024-04-10T06:54:26.436416Z INFO ThreadId(512) server: 33: res : a=2,b=3 339 | ``` 340 | 341 | Test curl ( curl => fusen-rust ) 342 | 343 | curl --location --request POST '' \ 344 | --header 'Content-Type: application/json' \ 345 | --header 'Connection: keep-alive' \ 346 | --data-raw '{ 347 | "str" ​​: "World" 348 | }' 349 | 350 | ```rust 351 | 2024-04-10T07:02:50.138057Z INFO ThreadId(03) server: 26: res : ReqDto { str: "World" } 352 | ``` 353 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | 2 | # `fusen-rs` 一个最像RPC框架的Rust-RPC框架 3 | 4 | fusen-rs是一个高性能,轻量级的微服务框架,通过使用Rust宏来解决目前主流rpc框架使用复杂,性能低等问题,不需要通过脚本和脚手架生成RPC调用代码,通过宏来进行编译期"反射"来实现高性能的调用,满足RPC调用的简易性,同时支持Dubbo3,SpringCloud微服务生态可以与Java项目进行服务注册发现与互相调用,并且支持用户自定义组件等功能. 5 | 6 | ## 功能列表 7 | 8 | - :white_check_mark: RPC调用抽象层(Rust宏) 9 | - :white_check_mark: 多协议支持(HTTP1, HTTP2) 10 | - :white_check_mark: 服务注册与发现(Nacos) 11 | - :white_check_mark: 微服务生态兼容(Dubbo3, SpringCloud) 12 | - :white_check_mark: 自定义组件(自定义负载均衡器,Aspect环绕通知组件) 13 | - :white_check_mark: 配置中心(本地文件配置, Nacos) 14 | - :white_check_mark: 优雅停机 15 | - :white_check_mark: 微服务链路追踪(opentelemetry) 16 | - :construction: HTTP3协议支持 17 | 18 | ## 快速开始 19 | 20 | ### Common Interface 21 | 22 | ```rust 23 | #[derive(Serialize, Deserialize, Default, Debug)] 24 | pub struct ReqDto { 25 | pub str: String, 26 | } 27 | 28 | #[derive(Serialize, Deserialize, Default, Debug)] 29 | pub struct ResDto { 30 | pub str: String, 31 | } 32 | 33 | #[fusen_trait(id = "org.apache.dubbo.springboot.demo.DemoService")] 34 | pub trait DemoService { 35 | async fn sayHello(&self, name: String) -> String; 36 | 37 | #[asset(path = "/sayHelloV2-http", method = POST)] 38 | async fn sayHelloV2(&self, name: ReqDto) -> ResDto; 39 | 40 | #[asset(path = "/divide", method = GET)] 41 | async fn divideV2(&self, a: i32, b: i32) -> String; 42 | } 43 | ``` 44 | 45 | ### Server 46 | 47 | ```rust 48 | #[derive(Debug)] 49 | struct DemoServiceImpl { 50 | _db: String, 51 | } 52 | 53 | #[fusen_server(id = "org.apache.dubbo.springboot.demo.DemoService")] 54 | impl DemoService for DemoServiceImpl { 55 | async fn sayHello(&self, req: String) -> FusenResult { 56 | info!("res : {:?}", req); 57 | Ok("Hello ".to_owned() + &req) 58 | } 59 | #[asset(path="/sayHelloV2-http",method = POST)] 60 | async fn sayHelloV2(&self, req: ReqDto) -> FusenResult { 61 | info!("res : {:?}", req); 62 | Ok(ResDto::default().str("Hello ".to_owned() + req.get_str() + " V2")) 63 | } 64 | #[asset(path="/divide",method = GET)] 65 | async fn divideV2(&self, a: i32, b: i32) -> FusenResult { 66 | info!("res : a={:?},b={:?}", a, b); 67 | Ok((a + b).to_string()) 68 | } 69 | } 70 | 71 | #[tokio::main(flavor = "multi_thread", worker_threads = 16)] 72 | async fn main() { 73 | fusen_common::logs::init_log(); 74 | let server = DemoServiceImpl { 75 | _db: "我是一个DB数据库".to_string(), 76 | }; 77 | FusenApplicationContext::builder() 78 | //使用配置文件进行初始化 79 | .init(get_config_by_file("examples/server-config.yaml").unwrap()) 80 | .add_fusen_server(Box::new(server)) 81 | .add_handler(ServerLogAspect.load()) 82 | .build() 83 | .run() 84 | .await; 85 | } 86 | ``` 87 | 88 | ### Client 89 | 90 | ```rust 91 | #[tokio::main(flavor = "multi_thread", worker_threads = 16)] 92 | async fn main() { 93 | fusen_common::logs::init_log(); 94 | let context = FusenApplicationContext::builder() 95 | //使用配置文件进行初始化 96 | .init(get_config_by_file("examples/client-config.yaml").unwrap()) 97 | .add_handler(CustomLoadBalance.load()) 98 | .add_handler(ClientLogAspect.load()) 99 | .build(); 100 | //直接当HttpClient调用HTTP1 + JSON 101 | let client = DemoServiceClient::new(Arc::new( 102 | context.client(Type::Host("127.0.0.1:8081".to_string())), 103 | )); 104 | let res = client 105 | .sayHelloV2(ReqDto::default().str("world".to_string())) 106 | .await; 107 | info!("rev host msg : {:?}", res); 108 | //通过Fusen进行服务注册与发现,并且进行HTTP2+JSON进行调用 109 | let client = DemoServiceClient::new(Arc::new(context.client(Type::Fusen))); 110 | let res = client 111 | .sayHelloV2(ReqDto::default().str("world".to_string())) 112 | .await; 113 | info!("rev fusen msg : {:?}", res); 114 | // //通过Dubbo进行服务注册与发现,并且进行HTTP2+Grpc进行调用 115 | let client = DemoServiceClient::new(Arc::new(context.client(Type::Dubbo))); 116 | let res = client 117 | .sayHelloV2(ReqDto::default().str("world".to_string())) 118 | .await; 119 | info!("rev dubbo msg : {:?}", res); 120 | //通过SpringCloud进行服务注册与发现,并且进行HTTP1+JSON进行调用 121 | let client = DemoServiceClient::new(Arc::new(context.client(Type::SpringCloud))); 122 | let res = client 123 | .sayHelloV2(ReqDto::default().str("world".to_string())) 124 | .await; 125 | info!("rev springcloud msg : {:?}", res); 126 | } 127 | ``` 128 | 129 | ## 自定义组件 130 | 131 | 微服务自定义组件包括, 负载均衡器, 服务熔断/限流组件, 前置后置请求处理器, 服务链路追踪等组件. 由于组件的定制化程度较高, 所以本项目参考AOP的概念提供了两种自定义组件,来提供灵活的请求处理。 132 | 133 | ### LoadBalance 134 | 135 | 负载均衡组件, LoadBalance提供一个select接口来实现用户自定义服务均衡配置。 136 | 137 | ```rust 138 | #[handler(id = "CustomLoadBalance")] 139 | impl LoadBalance for CustomLoadBalance { 140 | async fn select( 141 | &self, 142 | invokers: Arc, 143 | ) -> Result, fusen_rs::Error> { 144 | invokers 145 | .select() 146 | .ok_or("not find server : CustomLoadBalance".into()) 147 | } 148 | } 149 | ``` 150 | 151 | ### Aspect 152 | 153 | 动态代理的概念相信大家都不陌生,这是Java对类进行增强的一种技术,而Spring框架利用此特性封装出了更高级的模型, 那就是AOP面先切面编程模型. 本组件就是参考了此模型,实现了环绕式通知模型, 用户可以基于此组件实现各种组件需求,比如说服务熔断/限流,请求的前置后置处理,链路追踪,请求响应时间监控等需求,并且Aspect组件支持多层嵌套调用,提供灵活的定义方式满足用户复杂需求. 154 | 155 | ```rust 156 | #[handler(id = "ServerLogAspect")] 157 | impl Aspect for ServerLogAspect { 158 | async fn aroud( 159 | &self, 160 | join_point: ProceedingJoinPoint, 161 | ) -> Result { 162 | let start_time = get_now_date_time_as_millis(); 163 | info!("server receive request : {:?}", join_point.get_context()); 164 | let context = join_point.proceed().await; 165 | info!( 166 | "server dispose done RT : {:?}ms : {:?}", 167 | get_now_date_time_as_millis() - start_time, 168 | context 169 | ); 170 | context 171 | } 172 | } 173 | ``` 174 | 175 | ## Dubbo3 176 | 177 | 本项目同时兼容dubbo3协议,可以很方便的与Java版本的Dubbo3项目通过接口暴露的方式进行服务注册发现和互调。 178 | 179 | Rust的Server和Client完全不用改造就如上示例即可。 180 | 181 | Java版本的Dubbo3项目,代码层面不需要改造,只需要添加一些依赖和配置(因Dubbo3使用接口暴露的方式默认不支持json序列化协议,而是采用fastjson2的二进制序列化格式,所以这里我们需手动添加fastjson1的支持) 182 | 183 | 这里我们使用duboo3的官方示例dubbo-samples-spring-boot项目进行演示 184 | 185 | 186 | 首先我们需要把Server和Client的服务的pom.xml都添加fastjson和nacos的maven依赖 187 | 188 | ```java 189 | 190 | org.apache.dubbo 191 | dubbo-serialization-fastjson 192 | 2.7.23 193 | 194 | 195 | 196 | com.alibaba.nacos 197 | nacos-client 198 | 2.2.0 199 | 200 | ``` 201 | 202 | ### Java-Server 203 | 204 | ```java 205 | @DubboService 206 | public class DemoServiceImpl implements DemoService { 207 | 208 | @Override 209 | public String sayHello(String name) { 210 | return "Hello " + name; 211 | } 212 | } 213 | ``` 214 | 215 | ### Server-application.yml 216 | 217 | ```java 218 | dubbo: 219 | application: 220 | name: dubbo-springboot-demo-provider 221 | protocol: 222 | name: tri 223 | port: 50052 224 | //添加fastjson的支持 225 | prefer-serialization: fastjson 226 | registry: 227 | address: nacos://${nacos.address:127.0.0.1}:8848 228 | ``` 229 | 230 | ### Java-Client 231 | 232 | ```java 233 | @Component 234 | public class Task implements CommandLineRunner { 235 | @DubboReference 236 | private DemoService demoService; 237 | 238 | @Override 239 | public void run(String... args) throws Exception { 240 | String result = demoService.sayHello("world"); 241 | System.out.println("Receive result ======> " + result); 242 | 243 | new Thread(()-> { 244 | while (true) { 245 | try { 246 | Thread.sleep(1000); 247 | System.out.println(new Date() + " Receive result ======> " + demoService.sayHello("world")); 248 | } catch (InterruptedException e) { 249 | e.printStackTrace(); 250 | Thread.currentThread().interrupt(); 251 | } 252 | } 253 | }).start(); 254 | } 255 | } 256 | ``` 257 | 258 | ### Client-application.yml 259 | 260 | ```java 261 | dubbo: 262 | application: 263 | name: dubbo-springboot-demo-consumer 264 | registry: 265 | address: nacos://${nacos.address:127.0.0.1}:8848 266 | ``` 267 | 268 | ## SpringCloud 269 | 270 | 同时本项目还拓展了HTTP接口可以当做一个WebServer框架,并且还支持了SpringCloud服务注册与发现,用户可以灵活的选择和切换需要暴露的协议,并且支持同时暴露。 271 | 272 | 这里我们使用spring-cloud-alibaba项目进行演示 273 | 274 | 275 | RustClient端调用SpringCloud需要将fusen_trait_id修改为目标服务id(application_name) 276 | 277 | ```rust 278 | #[fusen_trait(id = "service-provider")] 279 | ``` 280 | 281 | Java的Server和Client端的代码也无需改造。直接启动即可。 282 | 283 | ### SpringCloud-Server 284 | 285 | Provider启动类 286 | package com.alibaba.cloud.examples.ProviderApplication 287 | 288 | ```java 289 | //EchoController 290 | @RestController 291 | public class EchoController { 292 | ... 293 | @GetMapping("/divide") 294 | public String divide(@RequestParam Integer a, @RequestParam Integer b) { 295 | if (b == 0) { 296 | return String.valueOf(0); 297 | } else { 298 | return String.valueOf(a / b); 299 | } 300 | } 301 | ... 302 | } 303 | ``` 304 | 305 | ### SpringCloud-Client 306 | 307 | Consumer启动类 308 | package com.alibaba.cloud.examples.ConsumerApplication 309 | 310 | ```java 311 | //TestController 312 | @RestController 313 | public class TestController { 314 | ... 315 | @GetMapping("/divide-feign") 316 | public String divide(@RequestParam Integer a, @RequestParam Integer b) { 317 | return echoClient.divide(a, b); 318 | } 319 | ... 320 | } 321 | 322 | ``` 323 | 324 | 测试curl ( curl => SpringCloud => fusen-rust ) 325 | 326 | 327 | ```rust 328 | 2024-04-10T06:52:32.737307Z INFO ThreadId(07) server: 33: res : a=1,b=2 329 | ``` 330 | 331 | 测试curl ( curl => fusen-rust ) 332 | 333 | 334 | 335 | ```rust 336 | 2024-04-10T06:54:26.436416Z INFO ThreadId(512) server: 33: res : a=2,b=3 337 | ``` 338 | 339 | 测试curl ( curl => fusen-rust ) 340 | 341 | curl --location --request POST '' \ 342 | --header 'Content-Type: application/json' \ 343 | --header 'Connection: keep-alive' \ 344 | --data-raw '{ 345 | "str" : "World" 346 | }' 347 | 348 | ```rust 349 | 2024-04-10T07:02:50.138057Z INFO ThreadId(03) server: 26: res : ReqDto { str: "World" } 350 | ``` 351 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | authors.workspace = true 4 | edition.workspace = true 5 | license.workspace = true 6 | description.workspace = true 7 | 8 | 9 | [[bin]] 10 | name = "client" 11 | path = "src/client.rs" 12 | [[bin]] 13 | name = "client-pt" 14 | path = "src/client_pt.rs" 15 | [[bin]] 16 | name = "server" 17 | path = "src/server.rs" 18 | 19 | 20 | 21 | [dependencies] 22 | fusen-rs.workspace = true 23 | 24 | #网络协议处理 25 | tokio.workspace = true 26 | hyper.workspace = true 27 | http.workspace = true 28 | http-body.workspace = true 29 | http-body-util.workspace = true 30 | bytes.workspace = true 31 | futures.workspace = true 32 | async-trait.workspace = true 33 | 34 | #日志处理 35 | tracing.workspace = true 36 | tracing-futures.workspace = true 37 | tracing-subscriber.workspace = true 38 | pretty_env_logger.workspace = true 39 | tracing-opentelemetry.workspace = true 40 | opentelemetry.workspace = true 41 | opentelemetry_sdk.workspace = true 42 | opentelemetry-otlp.workspace = true 43 | tracing-appender.workspace = true 44 | 45 | #json序列化 46 | serde.workspace = true 47 | serde_json.workspace = true 48 | uuid.workspace = true 49 | 50 | prost = "0.13.2" 51 | prost-build = "0.13.2" 52 | 53 | pin-project-lite.workspace = true 54 | lazy_static.workspace = true 55 | rand.workspace = true 56 | async-recursion.workspace = true 57 | 58 | -------------------------------------------------------------------------------- /examples/client-config.yaml: -------------------------------------------------------------------------------- 1 | application_name: fusen-client 2 | register: register://NacosConfig?namespace=&password=&server_addr=127.0.0.1:8848&username= 3 | handler_infos: 4 | - id: org.apache.dubbo.springboot.demo.DemoService 5 | handlers_id: 6 | - CustomLoadBalance 7 | - LogAspect 8 | 9 | -------------------------------------------------------------------------------- /examples/server-config.yaml: -------------------------------------------------------------------------------- 1 | application_name: fusen-server 2 | port: 8082 3 | register: register://NacosConfig?namespace=&password=&server_addr=127.0.0.1:8848&username= 4 | handler_infos: 5 | - id: org.apache.dubbo.springboot.demo.DemoService 6 | handlers_id: 7 | - LogAspect 8 | - ServerLogAspect 9 | -------------------------------------------------------------------------------- /examples/src/client.rs: -------------------------------------------------------------------------------- 1 | use examples::{DemoServiceClient, LogAspect, ReqDto}; 2 | use fusen_rs::fusen_common::config::get_config_by_file; 3 | use fusen_rs::fusen_common::logs::LogConfig; 4 | use fusen_rs::fusen_common::register::Type; 5 | use fusen_rs::fusen_procedural_macro::handler; 6 | use fusen_rs::handler::loadbalance::LoadBalance; 7 | use fusen_rs::handler::HandlerLoad; 8 | use fusen_rs::protocol::socket::InvokerAssets; 9 | use fusen_rs::register::ResourceInfo; 10 | use fusen_rs::{fusen_common, FusenApplicationContext}; 11 | use std::sync::Arc; 12 | use tracing::{info, info_span}; 13 | 14 | struct CustomLoadBalance; 15 | 16 | #[handler(id = "CustomLoadBalance")] 17 | impl LoadBalance for CustomLoadBalance { 18 | async fn select( 19 | &self, 20 | invokers: Arc, 21 | ) -> Result, fusen_rs::Error> { 22 | let _span = info_span!("CustomLoadBalance").or_current(); 23 | invokers 24 | .select() 25 | .ok_or("not find server : CustomLoadBalance".into()) 26 | } 27 | } 28 | 29 | #[tokio::main] 30 | async fn main() { 31 | let log_config = LogConfig::default() 32 | .devmode(Some(true)) 33 | .env_filter(Some( 34 | "fusen-rs=debug,client=debug,examples=debug".to_owned(), 35 | )) 36 | .endpoint(Some("http://127.0.0.1:4317".to_owned())); 37 | let _log_work = fusen_common::logs::init_log(&log_config, "fusen-client"); 38 | let context = FusenApplicationContext::builder() 39 | //使用配置文件进行初始化 40 | .init(get_config_by_file("examples/client-config.yaml").unwrap()) 41 | // .application_name("fusen-client") 42 | // .register( 43 | // NacosConfig::default() 44 | // .server_addr("127.0.0.1:8848".to_owned()) 45 | // .to_url() 46 | // .unwrap() 47 | // .as_str(), 48 | // ) 49 | // .add_handler_info(HandlerInfo::new( 50 | // "org.apache.dubbo.springboot.demo.DemoService".to_owned(), 51 | // vec!["CustomLoadBalance".to_owned(), "ClientLogAspect".to_owned()], 52 | // )) 53 | .add_handler(CustomLoadBalance.load()) 54 | .add_handler(LogAspect::new("debug").load()) 55 | .build(); 56 | //直接当HttpClient调用HTTP1 + JSON 57 | let client = DemoServiceClient::new(Arc::new( 58 | context.client(Type::Host("127.0.0.1:8082".to_string())), 59 | )); 60 | let res = client 61 | .sayHelloV2(ReqDto::default().str("world".to_string())) 62 | .await; 63 | info!("rev host msg : {:?}", res); 64 | //通过Fusen进行服务注册与发现,并且进行HTTP2+JSON进行调用 65 | let client = DemoServiceClient::new(Arc::new(context.client(Type::Fusen))); 66 | let res = client 67 | .sayHelloV2(ReqDto::default().str("world".to_string())) 68 | .await; 69 | info!("rev fusen msg : {:?}", res); 70 | // //通过Dubbo进行服务注册与发现,并且进行HTTP2+Grpc进行调用 71 | let client = DemoServiceClient::new(Arc::new(context.client(Type::Dubbo))); 72 | let res = client 73 | .sayHelloV2(ReqDto::default().str("world".to_string())) 74 | .await; 75 | info!("rev dubbo msg : {:?}", res); 76 | //通过SpringCloud进行服务注册与发现,并且进行HTTP1+JSON进行调用 77 | let client = DemoServiceClient::new(Arc::new(context.client(Type::SpringCloud))); 78 | let res = client 79 | .sayHelloV2(ReqDto::default().str("world".to_string())) 80 | .await; 81 | info!("rev springcloud msg : {:?}", res); 82 | } -------------------------------------------------------------------------------- /examples/src/client_pt.rs: -------------------------------------------------------------------------------- 1 | use examples::{DemoServiceClient, ReqDto}; 2 | use fusen_rs::fusen_common::config::get_config_by_file; 3 | use fusen_rs::fusen_common::date_util::get_now_date_time_as_millis; 4 | use fusen_rs::fusen_common::logs::LogConfig; 5 | use fusen_rs::fusen_common::register::Type; 6 | use fusen_rs::{fusen_common, FusenApplicationContext}; 7 | use std::sync::Arc; 8 | use std::time::Duration; 9 | use tokio::sync::mpsc; 10 | use tracing::info; 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | let log_config = LogConfig::default() 15 | .devmode(Some(true)) 16 | .env_filter(Some( 17 | "fusen-rs=debug,client=debug,examples=debug".to_owned(), 18 | )) 19 | .endpoint(Some("http://127.0.0.1:4317".to_owned())); 20 | let _log_work = fusen_common::logs::init_log(&log_config, "fusen-client-pt"); 21 | let context = FusenApplicationContext::builder() 22 | .init(get_config_by_file("examples/client-config.yaml").unwrap()) 23 | .build(); 24 | let client = Box::leak(Box::new(DemoServiceClient::new(Arc::new( 25 | context.client(Type::Fusen), 26 | )))); 27 | let _ = client 28 | .sayHelloV2(ReqDto::default().str("world".to_string())) 29 | .await; 30 | tokio::time::sleep(Duration::from_secs(1)).await; 31 | let start_time = get_now_date_time_as_millis(); 32 | let mut m: (mpsc::Sender, mpsc::Receiver) = mpsc::channel(1); 33 | for _ in 0..1 { 34 | tokio::spawn(do_run(m.0.clone(), client)); 35 | } 36 | drop(m.0); 37 | m.1.recv().await; 38 | info!("{:?}", get_now_date_time_as_millis() - start_time); 39 | } 40 | 41 | async fn do_run(send: mpsc::Sender, client: &'static DemoServiceClient) { 42 | for _ in 0..1000000 { 43 | let res = client 44 | .sayHelloV2(ReqDto::default().str("world".to_string())) 45 | .await; 46 | if let Err(err) = res { 47 | info!("{:?}", err); 48 | } 49 | } 50 | drop(send); 51 | } 52 | -------------------------------------------------------------------------------- /examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use bytes::Bytes; 4 | use fusen_rs::{ 5 | filter::ProceedingJoinPoint, 6 | fusen_common::{self, date_util::get_now_date_time_as_millis, FusenContext, FusenRequest}, 7 | fusen_procedural_macro::{asset, fusen_trait, handler, Data}, 8 | handler::aspect::Aspect, 9 | }; 10 | use opentelemetry::propagation::text_map_propagator::TextMapPropagator; 11 | use opentelemetry::{trace::TraceContextExt, Context}; 12 | use opentelemetry_sdk::propagation::TraceContextPropagator; 13 | use serde::{Deserialize, Serialize}; 14 | use tracing::{debug_span, error, error_span, info, info_span, warn_span, Instrument, Span}; 15 | use tracing_opentelemetry::OpenTelemetrySpanExt; 16 | 17 | #[derive(Serialize, Deserialize, Default, Debug, Data)] 18 | pub struct ReqDto { 19 | str: String, 20 | } 21 | 22 | #[derive(Serialize, Deserialize, Default, Debug, Data)] 23 | pub struct ResDto { 24 | str: String, 25 | } 26 | 27 | #[fusen_trait(id = "org.apache.dubbo.springboot.demo.DemoService")] 28 | pub trait DemoService { 29 | async fn sayHello(&self, name: String) -> String; 30 | 31 | #[asset(path = "/sayHelloV2-http", method = POST)] 32 | async fn sayHelloV2(&self, name: ReqDto) -> ResDto; 33 | 34 | #[asset(path = "/divide", method = GET)] 35 | async fn divideV2(&self, a: i32, b : Option) -> String; 36 | } 37 | 38 | #[allow(dead_code)] 39 | #[derive(Data)] 40 | pub struct LogAspect { 41 | level: String, 42 | trace_context_propagator: TraceContextPropagator, 43 | } 44 | 45 | impl LogAspect { 46 | pub fn new(level: &str) -> Self { 47 | Self { 48 | level: level.to_owned(), 49 | trace_context_propagator: TraceContextPropagator::new(), 50 | } 51 | } 52 | } 53 | 54 | impl LogAspect { 55 | fn get_parent_span(&self, path: &str) -> Span { 56 | match self.get_level().as_str() { 57 | "info" => info_span!("begin_span", path = path), 58 | "debug" => debug_span!("begin_span", path = path), 59 | "warn" => warn_span!("begin_span", path = path), 60 | "error" => error_span!("begin_span", path = path), 61 | _ => tracing::trace_span!("begin_span", path = path), 62 | } 63 | } 64 | fn get_new_span(&self, context: Context, path: &str) -> Span { 65 | let span = match self.get_level().as_str() { 66 | "info" => info_span!( 67 | "trace_span", 68 | trace_id = context.span().span_context().trace_id().to_string(), 69 | path = path 70 | ), 71 | "debug" => debug_span!( 72 | "trace_span", 73 | trace_id = context.span().span_context().trace_id().to_string(), 74 | path = path 75 | ), 76 | "warn" => warn_span!( 77 | "trace_span", 78 | trace_id = context.span().span_context().trace_id().to_string(), 79 | path = path 80 | ), 81 | "error" => error_span!( 82 | "trace_span", 83 | trace_id = context.span().span_context().trace_id().to_string(), 84 | path = path 85 | ), 86 | _ => tracing::trace_span!( 87 | "trace_span", 88 | trace_id = context.span().span_context().trace_id().to_string(), 89 | path = path 90 | ), 91 | }; 92 | span.set_parent(context); 93 | span 94 | } 95 | } 96 | 97 | #[handler(id = "LogAspect")] 98 | impl Aspect for LogAspect { 99 | async fn aroud( 100 | &self, 101 | mut join_point: ProceedingJoinPoint, 102 | ) -> Result { 103 | let mut span_context = self.get_trace_context_propagator().extract_with_context( 104 | &Span::current().context(), 105 | join_point.get_context().get_meta_data().get_inner(), 106 | ); 107 | let mut first_span = None; 108 | if !span_context.has_active_span() { 109 | let span = self.get_parent_span( 110 | &join_point 111 | .get_context() 112 | .get_context_info() 113 | .get_path() 114 | .get_key(), 115 | ); 116 | span_context = span.context(); 117 | let _ = first_span.insert(span); 118 | } 119 | let span = self.get_new_span( 120 | span_context, 121 | &join_point 122 | .get_context() 123 | .get_context_info() 124 | .get_path() 125 | .get_key(), 126 | ); 127 | let trace_id = span.context().span().span_context().trace_id().to_string(); 128 | span.set_attribute("trace_id", trace_id.to_owned()); 129 | if join_point 130 | .get_context() 131 | .get_meta_data() 132 | .get_value("traceparent") 133 | .is_none() 134 | { 135 | self.get_trace_context_propagator().inject_context( 136 | &span.context(), 137 | join_point 138 | .get_mut_context() 139 | .get_mut_request() 140 | .get_mut_headers(), 141 | ); 142 | }; 143 | let future = async move { 144 | let start_time = get_now_date_time_as_millis(); 145 | info!(message = "start handler"); 146 | let context = join_point.proceed().await; 147 | info!( 148 | message = "end handler", 149 | elapsed = get_now_date_time_as_millis() - start_time, 150 | ); 151 | context 152 | }; 153 | let result = tokio::spawn(future.instrument(span)).await; 154 | let context = match result { 155 | Ok(context) => context, 156 | Err(error) => { 157 | error!(message = format!("{:?}", error)); 158 | let mut context = FusenContext::new( 159 | "unique_identifier".to_owned(), 160 | Default::default(), 161 | FusenRequest::new("", HashMap::new(), Bytes::new()), 162 | Default::default(), 163 | ); 164 | *context.get_mut_response().get_mut_response() = Ok(Bytes::copy_from_slice( 165 | b"{\"code\":\"9999\",\"message\":\"service error\"}", 166 | )); 167 | Ok(context) 168 | } 169 | }; 170 | context 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /examples/src/server.rs: -------------------------------------------------------------------------------- 1 | use examples::{DemoService, LogAspect, ReqDto, ResDto}; 2 | use fusen_rs::filter::ProceedingJoinPoint; 3 | use fusen_rs::fusen_common::config::get_config_by_file; 4 | use fusen_rs::fusen_common::date_util::get_now_date_time_as_millis; 5 | use fusen_rs::fusen_common::logs::LogConfig; 6 | use fusen_rs::fusen_procedural_macro::{asset, handler}; 7 | use fusen_rs::handler::aspect::Aspect; 8 | use fusen_rs::handler::HandlerLoad; 9 | use fusen_rs::{fusen_common, FusenApplicationContext}; 10 | use fusen_rs::{fusen_common::FusenResult, fusen_procedural_macro::fusen_server}; 11 | use tracing::{info, info_span}; 12 | 13 | struct ServerLogAspect; 14 | 15 | #[handler(id = "ServerLogAspect")] 16 | impl Aspect for ServerLogAspect { 17 | async fn aroud( 18 | &self, 19 | join_point: ProceedingJoinPoint, 20 | ) -> Result { 21 | let start_time = get_now_date_time_as_millis(); 22 | info!("server receive request : {:?}", join_point.get_context()); 23 | let context = join_point.proceed().await; 24 | info!( 25 | "server dispose done RT : {:?}ms : {:?}", 26 | get_now_date_time_as_millis() - start_time, 27 | context 28 | ); 29 | context 30 | } 31 | } 32 | 33 | #[derive(Debug)] 34 | struct DemoServiceImpl { 35 | _db: String, 36 | } 37 | 38 | #[fusen_server(id = "org.apache.dubbo.springboot.demo.DemoService")] 39 | impl DemoService for DemoServiceImpl { 40 | async fn sayHello(&self, req: String) -> FusenResult { 41 | info!("res : {:?}", req); 42 | Ok("Hello ".to_owned() + &req) 43 | } 44 | #[asset(path="/sayHelloV2-http",method = POST)] 45 | async fn sayHelloV2(&self, req: ReqDto) -> FusenResult { 46 | let _span = info_span!("sayHelloV2-http").entered(); 47 | info!("开始处理 sayHelloV2-http"); 48 | info!("接收消息 : {:?}", req); 49 | let _span2 = info_span!("get_user_info_by_db").entered(); 50 | info!("get_user_info_by_db : selet * from user where id = $1"); 51 | drop(_span2); 52 | Ok(ResDto::default().str("Hello ".to_owned() + req.get_str() + " V2")) 53 | } 54 | #[asset(path="/divide",method = GET)] 55 | async fn divideV2(&self, a: i32, b: Option) -> FusenResult { 56 | info!("res : a={:?},b={:?}", a, b); 57 | Ok((a).to_string()) 58 | } 59 | } 60 | 61 | #[tokio::main] 62 | async fn main() { 63 | let log_config = LogConfig::default() 64 | .devmode(Some(true)) 65 | .env_filter(Some( 66 | "fusen-rs=debug,server=debug,examples=debug".to_owned(), 67 | )) 68 | .endpoint(Some("http://127.0.0.1:4317".to_owned())); 69 | let _log_work = fusen_common::logs::init_log(&log_config, "fusen-server"); 70 | let server = DemoServiceImpl { 71 | _db: "我是一个DB数据库".to_string(), 72 | }; 73 | let _ = FusenApplicationContext::builder() 74 | //使用配置文件进行初始化 75 | .init(get_config_by_file("examples/server-config.yaml").unwrap()) 76 | // .application_name("fusen-server") 77 | // //初始化Fusen注册中心,同时支持Dubbo3协议与Fusen协议 78 | // .register( 79 | // NacosConfig::default() 80 | // .server_addr("127.0.0.1:8848".to_owned()) 81 | // .to_url() 82 | // .unwrap() 83 | // .as_str(), 84 | // ) 85 | // //同时兼容RPC协议与HTTP协议 86 | // .port(Some(8081)) 87 | // .add_handler_info(HandlerInfo::new( 88 | // "org.apache.dubbo.springboot.demo.DemoService".to_owned(), 89 | // vec!["ServerLogAspect".to_owned()], 90 | // )) 91 | .add_fusen_server(Box::new(server)) 92 | .add_handler(ServerLogAspect.load()) 93 | .add_handler(LogAspect::new("debug").load()) 94 | .build() 95 | .run() 96 | .await; 97 | } 98 | -------------------------------------------------------------------------------- /fusen-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fusen-common" 3 | version = "0.6.20" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | description.workspace = true 8 | 9 | 10 | [dependencies] 11 | fusen-procedural-macro.workspace = true 12 | #网络协议处理 13 | tokio.workspace = true 14 | http-body-util.workspace = true 15 | hyper.workspace = true 16 | http.workspace = true 17 | async-recursion.workspace = true 18 | 19 | 20 | #json序列化 21 | serde.workspace = true 22 | serde_json.workspace = true 23 | uuid.workspace = true 24 | bytes.workspace = true 25 | percent-encoding.workspace = true 26 | toml.workspace = true 27 | serde_yaml.workspace = true 28 | 29 | #日志处理 30 | tracing.workspace = true 31 | tracing-futures.workspace = true 32 | tracing-subscriber.workspace = true 33 | pretty_env_logger.workspace = true 34 | chrono.workspace = true 35 | tracing-opentelemetry.workspace = true 36 | opentelemetry.workspace = true 37 | opentelemetry_sdk.workspace = true 38 | opentelemetry-otlp.workspace = true 39 | tracing-appender.workspace = true 40 | 41 | #注册中心 42 | nacos-sdk.workspace = true 43 | rand.workspace = true 44 | local-ip-address.workspace = true 45 | -------------------------------------------------------------------------------- /fusen-common/src/codec.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, Bytes, BytesMut}; 2 | use serde::Serialize; 3 | 4 | use crate::error::{BoxError, FusenError}; 5 | 6 | pub enum CodecType { 7 | JSON, 8 | GRPC, 9 | } 10 | 11 | impl From<&str> for CodecType { 12 | fn from(value: &str) -> Self { 13 | if value.to_lowercase().contains("grpc") { 14 | Self::GRPC 15 | } else { 16 | Self::JSON 17 | } 18 | } 19 | } 20 | 21 | pub fn json_field_compatible(ty: &str, field: Bytes) -> Result { 22 | let mut field_str = 23 | String::from_utf8(field.to_vec()).map_err(|e| FusenError::Info(e.to_string()))?; 24 | if field_str.to_lowercase().starts_with("null") { 25 | return Err(FusenError::Null); 26 | } 27 | if ty == "String" && !field_str.starts_with('"') { 28 | field_str.insert(0, '"'); 29 | field_str.insert(field_str.len(), '"'); 30 | } 31 | Ok(field_str) 32 | } 33 | 34 | pub fn byte_to_vec(bytes: Bytes) -> Bytes { 35 | let body = bytes.chunk(); 36 | if !body.starts_with(b"[") { 37 | let mut mut_bytes = BytesMut::from("[\""); 38 | mut_bytes.extend(bytes); 39 | mut_bytes.extend_from_slice(b"\"]"); 40 | return mut_bytes.into(); 41 | } 42 | bytes 43 | } 44 | 45 | pub fn object_to_bytes(obj: &T) -> Result { 46 | let bytes = serde_json::to_vec(obj)?; 47 | Ok(Bytes::copy_from_slice(&bytes)) 48 | } 49 | -------------------------------------------------------------------------------- /fusen-common/src/config/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, sync::Arc}; 2 | 3 | use toml::get_toml_by_context; 4 | use yaml::get_yaml_by_context; 5 | 6 | use crate::error::BoxError; 7 | 8 | pub mod toml; 9 | pub mod yaml; 10 | 11 | pub trait HotConfig { 12 | fn build_hot_config( 13 | &mut self, 14 | ident: Self, 15 | listener: tokio::sync::mpsc::Receiver, 16 | ) -> Result<(), BoxError>; 17 | 18 | #[allow(async_fn_in_trait)] 19 | async fn get_hot_config(&self) -> Option>; 20 | } 21 | 22 | pub fn get_config_by_file(path: &str) -> Result { 23 | let contents = 24 | fs::read_to_string(path).unwrap_or_else(|_| panic!("read path erro : {:?}", path)); 25 | let file_type: Vec<&str> = path.split('.').collect(); 26 | match file_type[file_type.len() - 1].as_bytes() { 27 | b"toml" => get_toml_by_context(&contents), 28 | b"yaml" => get_yaml_by_context(&contents), 29 | file_type => Err(format!("not support {:?}", file_type).into()), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /fusen-common/src/config/toml.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use toml::Value; 3 | 4 | use crate::error::BoxError; 5 | 6 | pub fn get_toml_by_context< T: serde::de::DeserializeOwned>(toml_context: &str) -> Result { 7 | // 解析 TOML 文件内容 8 | let parsed_toml: Value = toml_context.parse()?; 9 | let json = json!(parsed_toml); 10 | Ok(T::deserialize(json).map_err(|e| format!("toml to json error {:?}", e))?) 11 | } 12 | -------------------------------------------------------------------------------- /fusen-common/src/config/yaml.rs: -------------------------------------------------------------------------------- 1 | use crate::error::BoxError; 2 | use serde_yaml::Value; 3 | 4 | pub fn get_yaml_by_context< T: serde::de::DeserializeOwned>( 5 | yaml_context: &str, 6 | ) -> Result { 7 | // 解析 yaml 文件内容 8 | let parsed_toml: Value = serde_yaml::from_str(yaml_context)?; 9 | Ok(T::deserialize(parsed_toml).map_err(|e| format!("json to json error {:?}", e))?) 10 | } 11 | -------------------------------------------------------------------------------- /fusen-common/src/date_util.rs: -------------------------------------------------------------------------------- 1 | use std::time::{SystemTime, UNIX_EPOCH}; 2 | 3 | pub fn get_now_date_time_as_millis() -> i128 { 4 | let start = SystemTime::now(); 5 | let since_the_epoch = start 6 | .duration_since(UNIX_EPOCH) 7 | .expect("Time went backwards"); 8 | since_the_epoch.as_millis() as i128 9 | } 10 | -------------------------------------------------------------------------------- /fusen-common/src/error.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::fmt::{self, Display, Formatter}; 3 | pub type BoxError = Box; 4 | pub type BoxFusenError = Box; 5 | 6 | #[derive(Serialize, Deserialize, Debug)] 7 | pub enum FusenError { 8 | Null, 9 | NotFind, 10 | Info(String), 11 | } 12 | 13 | impl FusenError { 14 | pub fn boxed(self) -> BoxFusenError { 15 | Box::new(self) 16 | } 17 | } 18 | 19 | impl From<&str> for FusenError { 20 | fn from(value: &str) -> Self { 21 | FusenError::Info(value.to_string()) 22 | } 23 | } 24 | 25 | impl From for FusenError { 26 | fn from(value: String) -> Self { 27 | FusenError::Info(value) 28 | } 29 | } 30 | 31 | impl From for FusenError { 32 | fn from(value: crate::Error) -> Self { 33 | let msg = value.to_string(); 34 | match msg.as_str() { 35 | "404" => FusenError::NotFind, 36 | _ => FusenError::Info(msg), 37 | } 38 | } 39 | } 40 | impl From for FusenError { 41 | fn from(value: http::Error) -> Self { 42 | FusenError::Info(value.to_string()) 43 | } 44 | } 45 | 46 | unsafe impl Send for FusenError {} 47 | 48 | unsafe impl Sync for FusenError {} 49 | 50 | impl Display for FusenError { 51 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 52 | match self { 53 | FusenError::Null => write!(f, "null value"), 54 | FusenError::Info(msg) => write!(f, "{}", msg), 55 | FusenError::NotFind => write!(f, "404",), 56 | } 57 | } 58 | } 59 | 60 | impl std::error::Error for FusenError {} 61 | -------------------------------------------------------------------------------- /fusen-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Bytes, BytesMut}; 2 | use codec::CodecType; 3 | use error::FusenError; 4 | use fusen_procedural_macro::Data; 5 | use http::{HeaderMap, HeaderValue}; 6 | use register::Type; 7 | use serde::{Deserialize, Serialize}; 8 | use std::collections::{hash_map::Iter, HashMap}; 9 | pub type Error = Box; 10 | pub type Result = std::result::Result; 11 | pub type Response = std::result::Result; 12 | pub type FusenFuture = std::pin::Pin + Send>>; 13 | pub type FusenResult = std::result::Result; 14 | pub mod codec; 15 | pub mod config; 16 | pub mod date_util; 17 | pub mod error; 18 | pub mod logs; 19 | pub mod r#macro; 20 | pub mod net; 21 | pub mod register; 22 | pub mod server; 23 | pub mod trie; 24 | pub mod url; 25 | 26 | #[derive(Debug, Data)] 27 | pub struct MetaData { 28 | inner: HashMap, 29 | } 30 | 31 | impl MetaData { 32 | pub fn get_codec(&self) -> CodecType { 33 | let content_type = self.get_value("content-type"); 34 | if let Some(str) = content_type { 35 | if str.to_lowercase().contains("grpc") { 36 | return CodecType::GRPC; 37 | } 38 | } 39 | CodecType::JSON 40 | } 41 | pub fn into_inner(self) -> HashMap { 42 | self.inner 43 | } 44 | pub fn get_value(&self, key: &str) -> Option<&String> { 45 | self.inner.get(key) 46 | } 47 | 48 | pub fn get_iter(&self) -> Iter { 49 | self.inner.iter() 50 | } 51 | pub fn clone_map(&self) -> HashMap { 52 | self.inner.clone() 53 | } 54 | 55 | pub fn insert(&mut self, key: String, value: String) -> Option { 56 | self.inner.insert(key, value) 57 | } 58 | pub fn remove(&mut self, key: &str) -> Option { 59 | self.inner.remove(key) 60 | } 61 | } 62 | 63 | impl From<&HeaderMap> for MetaData { 64 | fn from(value: &HeaderMap) -> MetaData { 65 | value.iter().fold(MetaData::new(), |mut meta, e| { 66 | meta.inner 67 | .insert(e.0.to_string(), e.1.to_str().unwrap().to_string()); 68 | meta 69 | }) 70 | } 71 | } 72 | 73 | impl MetaData { 74 | pub fn new() -> Self { 75 | MetaData::default() 76 | } 77 | } 78 | 79 | impl Default for MetaData { 80 | fn default() -> Self { 81 | let mut inner = HashMap::new(); 82 | inner.insert("prefer.serialization".to_owned(), "fastjson".to_owned()); 83 | inner.insert( 84 | "preserved.register.source".to_owned(), 85 | "SPRING_CLOUD".to_owned(), 86 | ); 87 | inner.insert("protocol".to_owned(), "tri".to_owned()); 88 | Self { inner } 89 | } 90 | } 91 | 92 | #[derive(Debug, Default, Data)] 93 | pub struct ContextInfo { 94 | path: Path, 95 | class_name: String, 96 | method_name: String, 97 | version: Option, 98 | group: Option, 99 | } 100 | 101 | impl ContextInfo { 102 | pub fn new( 103 | path: Path, 104 | class_name: String, 105 | method_name: String, 106 | version: Option, 107 | group: Option, 108 | ) -> Self { 109 | ContextInfo { 110 | path, 111 | class_name, 112 | method_name, 113 | version, 114 | group, 115 | } 116 | } 117 | pub fn get_handler_key(&self) -> String { 118 | let mut key = self.class_name.clone(); 119 | if let Some(version) = &self.version { 120 | key.push(':'); 121 | key.push_str(version); 122 | } 123 | key 124 | } 125 | } 126 | 127 | #[derive(Debug, Data)] 128 | pub struct FusenRequest { 129 | method: String, 130 | headers: HashMap, 131 | query_fields: HashMap, 132 | body: Bytes, 133 | } 134 | 135 | impl FusenRequest { 136 | pub fn new_for_client(method: &str, fields_ty: Vec, bodys: Vec) -> Self { 137 | let mut query_fields = HashMap::new(); 138 | let mut bytes = BytesMut::new(); 139 | let method_str = method.to_lowercase(); 140 | if method_str == "get" || method_str == "delete" { 141 | for (idx, body) in bodys.into_iter().enumerate() { 142 | let mut pre = 0; 143 | if fields_ty[idx].starts_with("r#") { 144 | pre = 2; 145 | } 146 | query_fields.insert(fields_ty[idx][pre..].to_owned(), body.replace('\"', "")); 147 | } 148 | } else if bodys.len() == 1 { 149 | bytes.extend_from_slice(bodys[0].as_bytes()); 150 | } else { 151 | bytes.extend_from_slice(serde_json::to_string(&bodys).unwrap().as_bytes()); 152 | } 153 | FusenRequest { 154 | method: method.to_ascii_lowercase(), 155 | headers: Default::default(), 156 | query_fields, 157 | body: bytes.into(), 158 | } 159 | } 160 | pub fn new(method: &str, query_fields: HashMap, body: Bytes) -> Self { 161 | FusenRequest { 162 | method: method.to_ascii_lowercase(), 163 | headers: Default::default(), 164 | query_fields, 165 | body, 166 | } 167 | } 168 | pub fn get_fields( 169 | &mut self, 170 | temp_fields_name: Vec<&str>, 171 | temp_fields_ty: Vec<&str>, 172 | ) -> Result> { 173 | let mut new_fields = vec![]; 174 | if self.method == "get" || self.method == "delete" { 175 | let hash_map = &self.query_fields; 176 | for item in temp_fields_name.iter().enumerate() { 177 | let mut pre = 0; 178 | if item.1.starts_with("r#") { 179 | pre = 2; 180 | }; 181 | match hash_map.get(&item.1[pre..]) { 182 | Some(fields) => { 183 | let mut temp = String::new(); 184 | if "String" == temp_fields_ty[item.0] 185 | || "Option < String >" == temp_fields_ty[item.0] 186 | { 187 | temp.push('\"'); 188 | temp.push_str(fields); 189 | temp.push('\"'); 190 | } else { 191 | temp.push_str(fields); 192 | } 193 | new_fields.push(temp); 194 | } 195 | None => { 196 | new_fields.push("null".to_owned()); 197 | } 198 | } 199 | } 200 | } else if self.body.starts_with(b"[") { 201 | new_fields = serde_json::from_slice(&self.body)?; 202 | } else { 203 | new_fields.push(String::from_utf8(self.body.to_vec())?); 204 | } 205 | Ok(new_fields) 206 | } 207 | } 208 | 209 | #[derive(Debug, Data)] 210 | pub struct FusenResponse { 211 | headers: HashMap, 212 | response: std::result::Result, 213 | response_ty: Option<&'static str>, 214 | } 215 | 216 | impl Default for FusenResponse { 217 | fn default() -> Self { 218 | Self { 219 | headers: Default::default(), 220 | response: Err(FusenError::Null), 221 | response_ty: Default::default(), 222 | } 223 | } 224 | } 225 | 226 | impl FusenResponse { 227 | pub fn insert_return_ty(&mut self, ty: &'static str) { 228 | let _ = self.response_ty.insert(ty); 229 | } 230 | pub fn into_response(self) -> std::result::Result { 231 | self.response 232 | } 233 | } 234 | 235 | #[derive(Debug, Data)] 236 | pub struct FusenContext { 237 | unique_identifier: String, 238 | server_type: Type, 239 | meta_data: MetaData, 240 | context_info: ContextInfo, 241 | request: FusenRequest, 242 | response: FusenResponse, 243 | } 244 | 245 | impl FusenContext { 246 | pub fn new( 247 | unique_identifier: String, 248 | context_info: ContextInfo, 249 | request: FusenRequest, 250 | meta_data: MetaData, 251 | ) -> FusenContext { 252 | FusenContext { 253 | unique_identifier, 254 | context_info, 255 | server_type: Type::Fusen, 256 | meta_data, 257 | request, 258 | response: Default::default(), 259 | } 260 | } 261 | pub fn insert_server_type(&mut self, server_tyep: Type) { 262 | self.server_type = server_tyep 263 | } 264 | pub fn into_response(self) -> FusenResponse { 265 | self.response 266 | } 267 | pub fn get_return_ty(&self) -> Option<&'static str> { 268 | self.response.response_ty 269 | } 270 | } 271 | 272 | #[derive(Clone, Debug, Serialize, Deserialize)] 273 | pub struct MethodResource { 274 | name: String, 275 | path: String, 276 | method: String, 277 | } 278 | 279 | #[derive(Debug, Clone)] 280 | pub enum Path { 281 | GET(String), 282 | PUT(String), 283 | DELETE(String), 284 | POST(String), 285 | } 286 | 287 | impl Default for Path { 288 | fn default() -> Self { 289 | Path::GET(Default::default()) 290 | } 291 | } 292 | 293 | impl Path { 294 | pub fn to_str(&self) -> &'static str { 295 | match self { 296 | Path::GET(_) => "GET", 297 | Path::PUT(_) => "PUT", 298 | Path::DELETE(_) => "DELETE", 299 | Path::POST(_) => "POST", 300 | } 301 | } 302 | 303 | pub fn get_key(&self) -> String { 304 | let mut key = String::new(); 305 | match self { 306 | Path::GET(path) => { 307 | key.push_str("get:"); 308 | key.push_str(path); 309 | } 310 | Path::POST(path) => { 311 | key.push_str("post:"); 312 | key.push_str(path); 313 | } 314 | Path::PUT(path) => { 315 | key.push_str("put:"); 316 | key.push_str(path); 317 | } 318 | Path::DELETE(path) => { 319 | key.push_str("delete:"); 320 | key.push_str(path); 321 | } 322 | }; 323 | key 324 | } 325 | 326 | pub fn update_path(&mut self, new_path: String) { 327 | match self { 328 | Path::GET(path) => *path = new_path, 329 | Path::POST(path) => *path = new_path, 330 | Path::PUT(path) => *path = new_path, 331 | Path::DELETE(path) => *path = new_path, 332 | } 333 | } 334 | 335 | pub fn get_path(&self) -> String { 336 | match self { 337 | Path::GET(path) => path, 338 | Path::POST(path) => path, 339 | Path::PUT(path) => path, 340 | Path::DELETE(path) => path, 341 | } 342 | .clone() 343 | } 344 | 345 | pub fn new(method: &str, path: String) -> Self { 346 | match method.to_lowercase().as_str() { 347 | "get" => Self::GET(path), 348 | "put" => Self::PUT(path), 349 | "delete" => Self::DELETE(path), 350 | _ => Self::POST(path), 351 | } 352 | } 353 | } 354 | 355 | impl MethodResource { 356 | pub fn get_name(&self) -> String { 357 | self.name.to_string() 358 | } 359 | pub fn get_path(&self) -> String { 360 | self.path.to_string() 361 | } 362 | pub fn get_method(&self) -> String { 363 | self.method.to_string() 364 | } 365 | pub fn new(name: String, path: String, method: String) -> Self { 366 | Self { name, path, method } 367 | } 368 | pub fn new_macro(method_str: &str) -> Self { 369 | let method: Vec = serde_json::from_str(method_str).unwrap(); 370 | Self { 371 | name: method[0].to_string(), 372 | path: method[1].to_string(), 373 | method: method[2].to_string(), 374 | } 375 | } 376 | pub fn form_json_str(str: &str) -> Self { 377 | serde_json::from_str(str).unwrap() 378 | } 379 | pub fn to_json_str(&self) -> String { 380 | serde_json::to_string(self).unwrap() 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /fusen-common/src/logs.rs: -------------------------------------------------------------------------------- 1 | use chrono::Local; 2 | use fusen_procedural_macro::Data; 3 | use opentelemetry::{ 4 | trace::{TraceError, TracerProvider}, 5 | StringValue, Value, 6 | }; 7 | use opentelemetry_otlp::{SpanExporter, WithExportConfig}; 8 | use opentelemetry_sdk::{runtime, trace::TracerProvider as Tracer, Resource}; 9 | use serde::{Deserialize, Serialize}; 10 | use std::str::FromStr; 11 | use tracing_appender::non_blocking::WorkerGuard; 12 | use tracing_appender::rolling::{RollingFileAppender, Rotation}; 13 | use tracing_opentelemetry::OpenTelemetryLayer; 14 | use tracing_subscriber::util::SubscriberInitExt; 15 | use tracing_subscriber::Layer; 16 | use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; 17 | 18 | #[derive(Clone, Data, Debug, Default, Serialize, Deserialize)] 19 | pub struct LogConfig { 20 | pub path: Option, 21 | pub endpoint: Option, 22 | pub env_filter: Option, 23 | pub devmode: Option, 24 | } 25 | 26 | #[derive(Default, Data)] 27 | pub struct LogWorkGroup { 28 | work_guard: Option, 29 | tracer_provider: Option, 30 | } 31 | 32 | impl Drop for LogWorkGroup { 33 | fn drop(&mut self) { 34 | if let Some(tracer_provider) = &self.tracer_provider { 35 | let _ = tracer_provider.shutdown(); 36 | } 37 | } 38 | } 39 | 40 | fn init_opentelemetry_trace(otlp_url: &str, app_name: &str) -> Result { 41 | let exporter = SpanExporter::builder() 42 | .with_tonic() 43 | .with_endpoint(otlp_url) 44 | .build()?; 45 | Ok(Tracer::builder() 46 | .with_resource(Resource::new(vec![opentelemetry::KeyValue::new( 47 | "service.name", 48 | Value::String(StringValue::from(app_name.to_owned())), 49 | )])) 50 | .with_batch_exporter(exporter, runtime::Tokio) 51 | .build()) 52 | } 53 | 54 | pub fn init_log(log_config: &LogConfig, app_name: &str) -> Option { 55 | let mut worker_guard = None; 56 | let mut tracer_guard = None; 57 | let mut layter_list = vec![]; 58 | let env_filter = || { 59 | if let Some(env_filter) = &log_config.env_filter { 60 | EnvFilter::from_str(env_filter).unwrap() 61 | } else { 62 | EnvFilter::from_default_env() 63 | } 64 | }; 65 | if let Some(path) = &log_config.path { 66 | let file_appender = RollingFileAppender::new(Rotation::DAILY, path, app_name); 67 | let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); 68 | let _ = worker_guard.insert(guard); 69 | let tracing = tracing_subscriber::fmt::layer() 70 | .with_line_number(true) 71 | .with_thread_ids(true); 72 | let json_tracing = tracing.json().with_writer(non_blocking); 73 | layter_list.push(json_tracing.boxed()); 74 | }; 75 | if log_config.devmode.is_some_and(|e| e) { 76 | let tracing = tracing_subscriber::fmt::layer() 77 | .with_line_number(true) 78 | .with_thread_ids(true); 79 | layter_list.push(tracing.boxed()); 80 | } 81 | if let Some(endpoint) = &log_config.endpoint { 82 | let provider = init_opentelemetry_trace(endpoint, app_name).unwrap(); 83 | let _ = tracer_guard.insert(provider.clone()); 84 | let opentelemetry = OpenTelemetryLayer::new(provider.tracer(app_name.to_owned())); 85 | layter_list.push(opentelemetry.boxed()); 86 | } 87 | if layter_list.is_empty() { 88 | return None; 89 | } 90 | let mut layer = layter_list.remove(0); 91 | for item in layter_list { 92 | layer = Box::new(layer.and_then(item)); 93 | } 94 | tracing_subscriber::registry() 95 | .with(env_filter()) 96 | .with(layer) 97 | .init(); 98 | Some( 99 | LogWorkGroup::default() 100 | .tracer_provider(tracer_guard) 101 | .work_guard(worker_guard), 102 | ) 103 | } 104 | 105 | pub fn get_uuid() -> String { 106 | uuid::Uuid::new_v4().to_string() 107 | } 108 | 109 | pub fn get_trade_id() -> String { 110 | format!( 111 | "{}-{}", 112 | uuid::Uuid::new_v4(), 113 | Local::now().format("%Y%m%d%H%M%S") 114 | ) 115 | } 116 | -------------------------------------------------------------------------------- /fusen-common/src/macro.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! fusen_attr { 3 | ( 4 | $name:ident, 5 | $($req:ident),* 6 | ) => { 7 | #[derive(Default)] 8 | struct $name { 9 | $( 10 | $req : Option 11 | ),* 12 | } 13 | impl $name { 14 | fn from_attr(args: TokenStream) -> Result<$name, syn::Error> { 15 | syn::punctuated::Punctuated::::parse_terminated 16 | .parse2(args.into()) 17 | .and_then(|args| Self::build_attr(args)) 18 | } 19 | 20 | fn build_attr(args: syn::punctuated::Punctuated::) -> Result<$name, syn::Error> { 21 | let mut methods_name = String::new(); 22 | $( 23 | let mut $req = None; 24 | methods_name.push_str("`"); 25 | methods_name.push_str(stringify!($req)); 26 | methods_name.push_str("`"); 27 | methods_name.push_str(", "); 28 | 29 | )* 30 | 31 | for arg in args { 32 | match arg { 33 | syn::Meta::NameValue(namevalue) => { 34 | let ident = namevalue 35 | .path 36 | .get_ident() 37 | .ok_or_else(|| { 38 | syn::Error::new_spanned(&namevalue, "Must have specified ident") 39 | })? 40 | .to_string() 41 | .to_lowercase(); 42 | let lit = match &namevalue.value { 43 | syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit.to_token_stream().to_string(), 44 | expr => expr.to_token_stream().to_string(), 45 | } 46 | .replace("\"", ""); 47 | match ident.as_str() { 48 | $( 49 | stringify!($req) => { 50 | let _ = $req.insert(lit); 51 | } 52 | )* 53 | name => { 54 | let msg = format!( 55 | "Unknown attribute {} is specified; expected one of: {} ", 56 | name,&methods_name[..(methods_name.len()-2)], 57 | ); 58 | return Err(syn::Error::new_spanned(namevalue, msg)); 59 | } 60 | } 61 | } 62 | other => { 63 | return Err(syn::Error::new_spanned( 64 | other, 65 | "Unknown attribute inside the macro", 66 | )); 67 | } 68 | } 69 | } 70 | Ok($name{ 71 | $( 72 | $req, 73 | )* 74 | }) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /fusen-common/src/net.rs: -------------------------------------------------------------------------------- 1 | pub fn get_network_ip() -> std::result::Result> { 2 | Ok(local_ip_address::local_ip()?.to_string()) 3 | } 4 | 5 | pub fn get_ip() -> String { 6 | match get_network_ip() { 7 | Ok(ok) => ok, 8 | Err(_err) => "127.0.0.1".to_string(), 9 | } 10 | } 11 | 12 | pub fn get_path(mut ip: String, port: Option<&str>) -> String { 13 | if let Some(port) = port { 14 | ip.push(':'); 15 | ip.push_str(port); 16 | } 17 | ip 18 | } 19 | -------------------------------------------------------------------------------- /fusen-common/src/register.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | pub enum RegisterType { 4 | Nacos(String), 5 | } 6 | 7 | #[derive(Serialize, Deserialize, Debug, Clone, Default)] 8 | pub enum Type { 9 | Dubbo, 10 | SpringCloud, 11 | #[default] 12 | Fusen, 13 | Host(String), 14 | } 15 | -------------------------------------------------------------------------------- /fusen-common/src/server.rs: -------------------------------------------------------------------------------- 1 | use fusen_procedural_macro::Data; 2 | 3 | use crate::{FusenContext, FusenFuture, MethodResource}; 4 | 5 | pub trait RpcServer: Send + Sync { 6 | fn invoke(&'static self, msg: FusenContext) -> FusenFuture; 7 | fn get_info(&self) -> ServerInfo; 8 | } 9 | 10 | #[derive(Debug, Data)] 11 | pub struct ServerInfo { 12 | pub id: String, 13 | pub version: Option, 14 | pub group: Option, 15 | pub methods: Vec, 16 | } 17 | 18 | impl ServerInfo { 19 | pub fn new( 20 | id: &str, 21 | version: Option<&str>, 22 | group: Option<&str>, 23 | methods: Vec, 24 | ) -> ServerInfo { 25 | Self { 26 | id: id.to_owned(), 27 | version: version.map(|e| e.to_owned()), 28 | group: group.map(|e| e.to_owned()), 29 | methods, 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fusen-common/src/trie.rs: -------------------------------------------------------------------------------- 1 | use async_recursion::async_recursion; 2 | use fusen_procedural_macro::Data; 3 | use std::{collections::HashMap, sync::Arc}; 4 | use tokio::sync::RwLock; 5 | 6 | #[derive(Debug, Default)] 7 | pub struct Trie { 8 | root: Arc>, 9 | } 10 | 11 | unsafe impl Sync for Trie {} 12 | unsafe impl Send for Trie {} 13 | 14 | #[derive(Debug, Default)] 15 | struct TreeNode { 16 | nodes: HashMap>>, 17 | value: Option, 18 | } 19 | 20 | #[derive(Debug, Data)] 21 | pub struct QueryResult { 22 | pub path: String, 23 | pub query_fields: Option>, 24 | } 25 | 26 | impl Trie { 27 | pub async fn insert(&mut self, path: String) { 28 | let paths: Vec<&str> = path.split('/').collect(); 29 | let mut temp = self.root.clone(); 30 | for item in paths { 31 | let res_node = temp.read().await.nodes.get(item).cloned(); 32 | match res_node { 33 | Some(node) => { 34 | temp = node; 35 | } 36 | None => { 37 | let new_node = Arc::new(RwLock::new(Default::default())); 38 | temp.clone() 39 | .write() 40 | .await 41 | .nodes 42 | .insert(item.to_owned(), new_node.clone()); 43 | temp = new_node; 44 | } 45 | } 46 | } 47 | let _ = temp.write().await.value.insert(path); 48 | } 49 | 50 | pub async fn search(&self, path: &str) -> Option { 51 | Self::search_by_nodes(path, self.root.clone()).await 52 | } 53 | 54 | #[async_recursion] 55 | async fn search_by_nodes(path: &str, mut temp: Arc>) -> Option { 56 | let mut query_fields: Vec<(String, String)> = vec![]; 57 | let paths: Vec<&str> = path.split('/').collect(); 58 | for (idx, item) in paths.iter().enumerate() { 59 | let res_node = temp.read().await.nodes.get(*item).cloned(); 60 | match res_node { 61 | Some(node) => { 62 | temp = node; 63 | } 64 | None => { 65 | let temp_path = paths[idx + 1..].join("/").to_string(); 66 | for entry in temp.read().await.nodes.iter() { 67 | if entry.0.starts_with('{') { 68 | if temp_path.is_empty() && entry.1.read().await.value.is_some() { 69 | query_fields.push(( 70 | entry.0[1..entry.0.len() - 1].to_string(), 71 | item.to_string(), 72 | )); 73 | return Some(QueryResult { 74 | path: entry.1.read().await.value.as_ref().unwrap().clone(), 75 | query_fields: if query_fields.is_empty() { 76 | None 77 | } else { 78 | Some(query_fields) 79 | }, 80 | }); 81 | } 82 | if let Some(query_result) = 83 | Self::search_by_nodes(&temp_path, entry.1.clone()).await 84 | { 85 | query_fields.push(( 86 | entry.0[1..entry.0.len() - 1].to_string(), 87 | item.to_string(), 88 | )); 89 | if let Some(mut temp_query_fields) = query_result.query_fields { 90 | query_fields.append(&mut temp_query_fields); 91 | } 92 | return Some(QueryResult { 93 | path: query_result.path, 94 | query_fields: if query_fields.is_empty() { 95 | None 96 | } else { 97 | Some(query_fields) 98 | }, 99 | }); 100 | } 101 | } 102 | } 103 | return None; 104 | } 105 | } 106 | } 107 | temp.read().await.value.as_ref().map(|path| QueryResult { 108 | path: path.clone(), 109 | query_fields: if query_fields.is_empty() { 110 | None 111 | } else { 112 | Some(query_fields) 113 | }, 114 | }) 115 | } 116 | } 117 | 118 | #[tokio::test] 119 | async fn test() { 120 | let mut pre_trie = Trie::default(); 121 | pre_trie.insert("/tasks/{tasks_id}/point".to_owned()).await; 122 | pre_trie 123 | .insert("/tasks/{tasks_id}/point/{user_id}".to_owned()) 124 | .await; 125 | pre_trie 126 | .insert("/tasks/{tasks_id}/point/{user_id}/{merchant_id}".to_owned()) 127 | .await; 128 | println!("{:?}", pre_trie.search("/tasks/iu321/point").await); 129 | println!("{:?}", pre_trie.search("/tasks/iu322/point/user9090").await); 130 | println!( 131 | "{:?}", 132 | pre_trie.search("/tasks/iu322/point/user9090/dsdsdsd").await 133 | ); 134 | } 135 | -------------------------------------------------------------------------------- /fusen-common/src/url.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use bytes::Buf; 4 | use percent_encoding::{self, percent_decode_str, percent_encode, AsciiSet, CONTROLS}; 5 | use serde::{Deserialize, Serialize}; 6 | const FRAGMENT: &AsciiSet = &CONTROLS 7 | .add(b':') 8 | .add(b'/') 9 | .add(b'&') 10 | .add(b'?') 11 | .add(b'=') 12 | .add(b','); 13 | 14 | pub fn decode_url(url: &str) -> Result { 15 | Ok(percent_decode_str(url) 16 | .decode_utf8() 17 | .map_err(|e| e.to_string())? 18 | .to_string()) 19 | } 20 | pub fn encode_url(url: &str) -> String { 21 | percent_encode(url.as_bytes(), FRAGMENT).to_string() 22 | } 23 | 24 | pub fn from_url<'a, T: Deserialize<'a>>(url: &str) -> Result { 25 | let info: Vec<&str> = url.split('&').collect(); 26 | let mut map = HashMap::new(); 27 | for item in info { 28 | let item: Vec<&str> = item.split('=').collect(); 29 | map.insert(item[0], item[1]); 30 | } 31 | let json_str = serde_json::to_vec(&map)?; 32 | let mut deserializer = serde_json::Deserializer::from_reader(json_str.reader()); 33 | T::deserialize(&mut deserializer).map_err(|e| e.to_string().into()) 34 | } 35 | 36 | pub fn to_url(t: &T) -> Result { 37 | let value = serde_json::to_value(t)?; 38 | let mut str = String::new(); 39 | for item in value.as_object().ok_or("err serialize")? { 40 | if let Some(value) = item.1.as_str() { 41 | str.push('&'); 42 | str.push_str(item.0); 43 | str.push('='); 44 | str.push_str(value); 45 | } 46 | } 47 | if !str.is_empty() { 48 | str.remove(0); 49 | } 50 | Ok(str) 51 | } -------------------------------------------------------------------------------- /fusen-macro/derive-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fusen-derive-macro" 3 | version = "0.6.13" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | description.workspace = true 8 | -------------------------------------------------------------------------------- /fusen-macro/derive-macro/src/attr_macro.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! fusen_attr { 3 | ( 4 | $name:ident, 5 | $($req:ident),* 6 | ) => { 7 | #[derive(Default)] 8 | struct $name { 9 | $( 10 | $req : Option 11 | ),* 12 | } 13 | impl $name { 14 | fn from_attr(args: TokenStream) -> Result<$name, syn::Error> { 15 | syn::punctuated::Punctuated::::parse_terminated 16 | .parse2(args.into()) 17 | .and_then(|args| Self::build_attr(args)) 18 | } 19 | 20 | fn build_attr(args: syn::punctuated::Punctuated::) -> Result<$name, syn::Error> { 21 | let mut methods_name = String::new(); 22 | $( 23 | let mut $req = None; 24 | methods_name.push_str("`"); 25 | methods_name.push_str(stringify!($req)); 26 | methods_name.push_str("`"); 27 | methods_name.push_str(", "); 28 | 29 | )* 30 | 31 | for arg in args { 32 | match arg { 33 | syn::Meta::NameValue(namevalue) => { 34 | let ident = namevalue 35 | .path 36 | .get_ident() 37 | .ok_or_else(|| { 38 | syn::Error::new_spanned(&namevalue, "Must have specified ident") 39 | })? 40 | .to_string() 41 | .to_lowercase(); 42 | let lit = match &namevalue.value { 43 | syn::Expr::Lit(syn::ExprLit { lit, .. }) => lit.to_token_stream().to_string(), 44 | expr => expr.to_token_stream().to_string(), 45 | } 46 | .replace("\"", ""); 47 | match ident.as_str() { 48 | $( 49 | stringify!($req) => { 50 | let _ = $req.insert(lit); 51 | } 52 | )* 53 | name => { 54 | let msg = format!( 55 | "Unknown attribute {} is specified; expected one of: {} ", 56 | name,&methods_name[..(methods_name.len()-2)], 57 | ); 58 | return Err(syn::Error::new_spanned(namevalue, msg)); 59 | } 60 | } 61 | } 62 | other => { 63 | return Err(syn::Error::new_spanned( 64 | other, 65 | "Unknown attribute inside the macro", 66 | )); 67 | } 68 | } 69 | } 70 | Ok($name{ 71 | $( 72 | $req, 73 | )* 74 | }) 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /fusen-macro/derive-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod attr_macro; 2 | 3 | -------------------------------------------------------------------------------- /fusen-macro/procedural-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fusen-procedural-macro" 3 | version = "0.6.14" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | description.workspace = true 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | [dependencies] 13 | fusen-derive-macro.workspace = true 14 | #json序列化 15 | serde.workspace = true 16 | serde_json.workspace = true 17 | quote = "1.0.36" 18 | syn = { version = "2.0.71", features = ["full"] } 19 | proc-macro2.workspace = true 20 | -------------------------------------------------------------------------------- /fusen-macro/procedural-macro/src/data.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{format_ident, quote, ToTokens}; 3 | use syn::{parse_macro_input, Data, DeriveInput}; 4 | 5 | pub fn data(item: TokenStream) -> TokenStream { 6 | let org_item = parse_macro_input!(item as DeriveInput); 7 | let ident = &org_item.ident; 8 | let (generics_0, generics_1, generics_2) = org_item.generics.split_for_impl(); 9 | let Data::Struct(data_struct) = &org_item.data else { 10 | return syn::Error::new_spanned(org_item.to_token_stream(), "builder must label to struct") 11 | .into_compile_error() 12 | .into(); 13 | }; 14 | let fields_builder = data_struct.fields.iter().fold(vec![], |mut vec, e| { 15 | let ident = e.ident.as_ref().unwrap(); 16 | let _type = e.ty.to_token_stream(); 17 | let mut ident_name = ident.to_string(); 18 | if ident_name.starts_with("r#") { 19 | ident_name = ident_name[2..ident_name.len()].to_string(); 20 | } 21 | let get_name = format_ident!("get_{}", ident_name); 22 | let get_mut_name = format_ident!("get_mut_{}", ident_name); 23 | let set_name = format_ident!("set_{}", ident_name); 24 | 25 | vec.push(quote!( 26 | pub fn #ident(mut self,#ident : #_type) -> Self { 27 | self.#ident = #ident; 28 | self 29 | } 30 | pub fn #get_name(&self) -> &#_type { 31 | &self.#ident 32 | } 33 | pub fn #get_mut_name(&mut self) -> &mut #_type { 34 | &mut self.#ident 35 | } 36 | pub fn #set_name(&mut self,#ident : #_type) -> &mut #_type { 37 | self.#ident = #ident; 38 | &mut self.#ident 39 | } 40 | )); 41 | vec 42 | }); 43 | let token = quote! { 44 | impl #generics_0 #ident #generics_1 45 | #generics_2 46 | { 47 | 48 | #(#fields_builder)* 49 | 50 | } 51 | }; 52 | token.into() 53 | } 54 | -------------------------------------------------------------------------------- /fusen-macro/procedural-macro/src/handler_macro.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, ItemImpl, Type}; 4 | 5 | use crate::HandlerAttr; 6 | 7 | pub fn fusen_handler(attr: HandlerAttr, item: TokenStream) -> TokenStream { 8 | let org_item = parse_macro_input!(item as ItemImpl); 9 | let item_self = &org_item.self_ty; 10 | let id = match attr.id { 11 | Some(id) => id, 12 | None => { 13 | if let Type::Path(path) = item_self.as_ref() { 14 | path.path.segments[0].ident.to_string() 15 | } else { 16 | return syn::Error::new_spanned(org_item, "handler must exist impl") 17 | .into_compile_error() 18 | .into(); 19 | } 20 | } 21 | }; 22 | let item = org_item.clone(); 23 | let trait_ident = item.trait_.unwrap().1; 24 | let (handler_invoker, handler_trait) = match trait_ident.segments[0].ident.to_string().as_str() 25 | { 26 | "LoadBalance" => ( 27 | quote!(fusen_rs::handler::HandlerInvoker::LoadBalance(Box::leak( 28 | Box::new(self) 29 | )),), 30 | quote! { 31 | impl fusen_rs::handler::loadbalance::LoadBalance_ for #item_self { 32 | fn select_( 33 | &'static self, 34 | invokers: std::sync::Arc, 35 | ) -> fusen_rs::fusen_common::FusenFuture, fusen_rs::Error>> { 36 | Box::pin(async move { 37 | self.select(invokers).await 38 | }) 39 | } 40 | } 41 | }, 42 | ), 43 | "Aspect" => ( 44 | quote!(fusen_rs::handler::HandlerInvoker::Aspect(Box::leak( 45 | Box::new(self) 46 | )),), 47 | quote! { 48 | impl fusen_rs::filter::FusenFilter for #item_self { 49 | fn call( 50 | &'static self, 51 | join_point: fusen_rs::filter::ProceedingJoinPoint, 52 | ) -> fusen_rs::fusen_common::FusenFuture> { 53 | Box::pin(async move { 54 | self.aroud(join_point).await 55 | }) 56 | } 57 | } 58 | }, 59 | ), 60 | _ => { 61 | return syn::Error::new_spanned( 62 | trait_ident, 63 | "handler must impl 'LoadBalance', 'Aspect'", 64 | ) 65 | .into_compile_error() 66 | .into() 67 | } 68 | }; 69 | quote!( 70 | #org_item 71 | 72 | #handler_trait 73 | 74 | impl fusen_rs::handler::HandlerLoad for #item_self { 75 | fn load(self) -> fusen_rs::handler::Handler { 76 | fusen_rs::handler::Handler::new(#id.to_string(),#handler_invoker) 77 | } 78 | } 79 | ) 80 | .into() 81 | } 82 | -------------------------------------------------------------------------------- /fusen-macro/procedural-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use fusen_derive_macro::fusen_attr; 2 | use proc_macro::TokenStream; 3 | use quote::{quote, ToTokens}; 4 | use syn::{parse::Parser, parse_macro_input, Attribute, DeriveInput, Meta}; 5 | 6 | mod data; 7 | mod handler_macro; 8 | mod server_macro; 9 | mod trait_macro; 10 | 11 | #[proc_macro_attribute] 12 | pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream { 13 | let attr = HandlerAttr::from_attr(attr); 14 | match attr { 15 | Ok(attr) => handler_macro::fusen_handler(attr, item), 16 | Err(err) => err.into_compile_error().into(), 17 | } 18 | } 19 | 20 | #[proc_macro_attribute] 21 | pub fn fusen_trait(attr: TokenStream, item: TokenStream) -> TokenStream { 22 | let attr = FusenAttr::from_attr(attr); 23 | match attr { 24 | Ok(attr) => trait_macro::fusen_trait(attr, item), 25 | Err(err) => err.into_compile_error().into(), 26 | } 27 | } 28 | 29 | #[proc_macro_derive(Data)] 30 | pub fn data(item: TokenStream) -> TokenStream { 31 | data::data(item) 32 | } 33 | 34 | #[proc_macro_attribute] 35 | pub fn fusen_server(attr: TokenStream, item: TokenStream) -> TokenStream { 36 | let attr = FusenAttr::from_attr(attr); 37 | match attr { 38 | Ok(attr) => server_macro::fusen_server(attr, item), 39 | Err(err) => err.into_compile_error().into(), 40 | } 41 | } 42 | 43 | #[proc_macro_attribute] 44 | pub fn asset(_attr: TokenStream, item: TokenStream) -> TokenStream { 45 | item 46 | } 47 | 48 | #[proc_macro_attribute] 49 | pub fn url_config(attr: TokenStream, item: TokenStream) -> TokenStream { 50 | let attr = UrlConfigAttr::from_attr(attr); 51 | if let Err(err) = attr { 52 | return err.into_compile_error().into(); 53 | } 54 | let attr = attr.unwrap(); 55 | let attr = attr.attr; 56 | let org_item = parse_macro_input!(item as DeriveInput); 57 | let Some(attr) = attr else { 58 | return syn::Error::new_spanned( 59 | org_item.to_token_stream(), 60 | "url_config must label to attr", 61 | ) 62 | .into_compile_error() 63 | .into(); 64 | }; 65 | let id = &org_item.ident; 66 | let token = quote! { 67 | 68 | #[derive(serde::Serialize, serde::Deserialize, Default, fusen_procedural_macro::Data)] 69 | #org_item 70 | 71 | impl #id { 72 | pub fn from_url(url : &str) -> Result { 73 | let info : Vec<&str> = url.split("://").collect(); 74 | if info[0] != #attr { 75 | return Err(format!("err1 url {}",url).into()); 76 | } 77 | let info : Vec<&str> = info[1].split("?").collect(); 78 | if info[0] != stringify!(#id) { 79 | return Err(format!("err2 url {}",url).into()); 80 | } 81 | fusen_common::url::from_url(info[1]) 82 | } 83 | pub fn to_url(&self) -> Result { 84 | let mut res = String::new(); 85 | res.push_str(&(#attr.to_owned() + "://" + stringify!(#id) + "?" )); 86 | res.push_str(&(fusen_common::url::to_url(self)?)); 87 | Ok(res) 88 | } 89 | } 90 | }; 91 | token.into() 92 | } 93 | 94 | fn get_asset_by_attrs(attrs: &Vec) -> Result { 95 | for attr in attrs { 96 | if let Meta::List(list) = &attr.meta { 97 | if let Some(segment) = list.path.segments.first() { 98 | if segment.ident == "asset" { 99 | return ResourceAttr::from_attr(list.tokens.clone().into()); 100 | } 101 | } 102 | } 103 | } 104 | Ok(ResourceAttr::default()) 105 | } 106 | 107 | fusen_attr! { 108 | ResourceAttr, 109 | path, 110 | method 111 | } 112 | 113 | fusen_attr! { 114 | UrlConfigAttr, 115 | attr 116 | } 117 | 118 | fusen_attr! { 119 | FusenAttr, 120 | id, 121 | version, 122 | group 123 | } 124 | 125 | fusen_attr! { 126 | HandlerAttr, 127 | id 128 | } 129 | -------------------------------------------------------------------------------- /fusen-macro/procedural-macro/src/server_macro.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse_macro_input, FnArg, ImplItem, ItemImpl}; 4 | 5 | use crate::{get_asset_by_attrs, FusenAttr}; 6 | 7 | pub fn fusen_server(attr: FusenAttr, item: TokenStream) -> TokenStream { 8 | let version = match attr.version { 9 | Some(version) => quote!(Some(&#version)), 10 | None => quote!(None), 11 | }; 12 | let group = match attr.group { 13 | Some(group) => quote!(Some(&#group)), 14 | None => quote!(None), 15 | }; 16 | let org_item = parse_macro_input!(item as ItemImpl); 17 | let methods_info = match get_resource_by_server(org_item.clone()) { 18 | Ok(methods_info) => methods_info.into_iter().fold(vec![], |mut vec, e| { 19 | vec.push(serde_json::to_string(&e).unwrap()); 20 | vec 21 | }), 22 | Err(err) => return err.into_compile_error().into(), 23 | }; 24 | let id = match attr.id { 25 | Some(id) => { 26 | quote!(#id) 27 | } 28 | None => { 29 | let id = org_item.trait_.as_ref().unwrap().1.segments[0] 30 | .ident 31 | .to_string(); 32 | quote!(#id) 33 | } 34 | }; 35 | let item = org_item.clone(); 36 | let org_item = get_server_item(org_item); 37 | let item_self = item.self_ty; 38 | let items_fn = item.items.iter().fold(vec![], |mut vec, e| { 39 | if let ImplItem::Fn(fn_item) = e { 40 | let method = &fn_item.sig.ident; 41 | let mut req_pat = vec![]; 42 | let mut req_type = vec![]; 43 | let request = fn_item.sig.inputs.iter().fold(vec![], |mut vec, e| { 44 | if let FnArg::Typed(input) = e { 45 | let request = &input.pat; 46 | let request_type = &input.ty; 47 | let token = quote! { 48 | let result : Result<#request_type,_> = serde_json::from_slice(req_poi_param[idx].as_bytes()); 49 | if let Err(err) = result { 50 | param.get_mut_response().set_response(Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string()))); 51 | return param; 52 | } 53 | let #request : #request_type = result.unwrap(); 54 | idx += 1; 55 | }; 56 | req_pat.push(request); 57 | req_type.push(request_type); 58 | vec.push(token); 59 | } 60 | vec 61 | }, 62 | ); 63 | vec.push(quote! { 64 | if ¶m.get_context_info().get_method_name()[..] == stringify!(#method) { 65 | let fields_name = vec![#( 66 | stringify!(#req_pat), 67 | )*]; 68 | let fields_ty = vec![#( 69 | stringify!(#req_type), 70 | )*]; 71 | let req_poi_param = match param.get_mut_request().get_fields(fields_name,fields_ty) { 72 | Ok(res) => res, 73 | Err(err) => { 74 | param.get_mut_response().set_response(Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string()))); 75 | return param; 76 | } 77 | }; 78 | let mut idx = 0; 79 | #( 80 | #request 81 | )* 82 | let res = self.#method( 83 | #( 84 | #req_pat, 85 | )* 86 | ).await; 87 | param.get_mut_response().set_response( match res { 88 | Ok(res) => { 89 | let res = fusen_rs::fusen_common::codec::object_to_bytes(&res); 90 | match res { 91 | Ok(res) => Ok(res), 92 | Err(err) => Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string())) 93 | } 94 | }, 95 | Err(info) => Err(info) 96 | }); 97 | return param; 98 | } 99 | } 100 | ) 101 | } 102 | vec 103 | }); 104 | let expanded = quote! { 105 | 106 | #org_item 107 | 108 | impl fusen_rs::fusen_common::server::RpcServer for #item_self { 109 | fn invoke (&'static self, param : fusen_rs::fusen_common::FusenContext) -> fusen_rs::fusen_common::FusenFuture { 110 | let rpc = self; 111 | Box::pin(async move {rpc.prv_invoke(param).await}) 112 | } 113 | fn get_info(&self) -> fusen_rs::fusen_common::server::ServerInfo { 114 | 115 | let mut methods : Vec = vec![]; 116 | #( 117 | methods.push(fusen_rs::fusen_common::MethodResource::new_macro(#methods_info)); 118 | )* 119 | fusen_rs::fusen_common::server::ServerInfo::new(#id,#version,#group,methods) 120 | } 121 | } 122 | 123 | impl #item_self { 124 | async fn prv_invoke (&self, mut param : fusen_rs::fusen_common::FusenContext) -> fusen_rs::fusen_common::FusenContext { 125 | #(#items_fn)* 126 | let error_info = format!( 127 | "not find method by {}", 128 | param.get_context_info().get_method_name() 129 | ); 130 | param.get_mut_response().set_response(Err(fusen_rs::fusen_common::error::FusenError::from(error_info))); 131 | return param; 132 | } 133 | } 134 | }; 135 | expanded.into() 136 | } 137 | 138 | fn get_server_item(item: ItemImpl) -> proc_macro2::TokenStream { 139 | let impl_item = item.impl_token; 140 | let trait_ident = item.trait_.unwrap().1; 141 | let ident = item.self_ty.to_token_stream(); 142 | let fn_items = item.items.iter().fold(vec![], |mut vec, e| { 143 | if let ImplItem::Fn(fn_item) = e { 144 | vec.push(fn_item); 145 | } 146 | vec 147 | }); 148 | quote! { 149 | #impl_item #trait_ident for #ident { 150 | #( 151 | #[allow(non_snake_case)] 152 | #fn_items 153 | )* 154 | } 155 | } 156 | } 157 | 158 | fn get_resource_by_server(item: ItemImpl) -> Result, syn::Error> { 159 | let mut res = vec![]; 160 | let attrs = &item.attrs; 161 | let resource = get_asset_by_attrs(attrs)?; 162 | let parent_path = match resource.path { 163 | Some(id) => id, 164 | None => "/".to_owned() + &item.trait_.unwrap().1.segments[0].ident.to_string(), 165 | }; 166 | let parent_method = match resource.method { 167 | Some(method) => method, 168 | None => "POST".to_string(), 169 | }; 170 | for fn_item in item.items.iter() { 171 | if let ImplItem::Fn(item_fn) = fn_item { 172 | let resource = get_asset_by_attrs(&item_fn.attrs)?; 173 | let path = match resource.path { 174 | Some(path) => path, 175 | None => "/".to_owned() + &item_fn.sig.ident.to_string(), 176 | }; 177 | let method = match resource.method { 178 | Some(method) => method, 179 | None => parent_method.clone(), 180 | }; 181 | let mut parent_path = parent_path.clone(); 182 | parent_path.push_str(&path); 183 | res.push((item_fn.sig.ident.to_string(), parent_path, method)); 184 | } 185 | } 186 | Ok(res) 187 | } 188 | -------------------------------------------------------------------------------- /fusen-macro/procedural-macro/src/trait_macro.rs: -------------------------------------------------------------------------------- 1 | use crate::{get_asset_by_attrs, FusenAttr}; 2 | use proc_macro::TokenStream; 3 | use quote::{quote, ToTokens}; 4 | use std::collections::HashMap; 5 | use syn::{parse_macro_input, FnArg, ItemTrait, ReturnType, TraitItem}; 6 | 7 | pub fn fusen_trait(attr: FusenAttr, item: TokenStream) -> TokenStream { 8 | let group = match attr.group { 9 | Some(group) => quote!(Some(&#group)), 10 | None => quote!(None), 11 | }; 12 | let version = match attr.version { 13 | Some(version) => quote!(Some(&#version)), 14 | None => quote!(None), 15 | }; 16 | let input = parse_macro_input!(item as ItemTrait); 17 | let mut methods_cache = HashMap::new(); 18 | let methods_info = match get_resource_by_trait(input.clone()) { 19 | Ok(methods_info) => methods_info.into_iter().fold(vec![], |mut vec, e| { 20 | vec.push(serde_json::to_string(&e).unwrap()); 21 | methods_cache.insert(e.0.to_owned(), (e.1.to_owned(), e.2.to_owned())); 22 | vec 23 | }), 24 | Err(err) => return err.into_compile_error().into(), 25 | }; 26 | let id = match attr.id { 27 | Some(trait_id) => { 28 | quote!(#trait_id) 29 | } 30 | None => { 31 | let id = input.ident.to_string(); 32 | quote!(#id) 33 | } 34 | }; 35 | let item_trait = get_item_trait(input.clone()); 36 | let trait_ident = &input.ident; 37 | let vis = &input.vis; 38 | let items = &input.items; 39 | let mut sig_item = vec![]; 40 | for item in items { 41 | if let TraitItem::Fn(item) = item { 42 | sig_item.push(item.sig.clone()); 43 | } 44 | } 45 | let mut fn_quote = vec![]; 46 | for item in sig_item { 47 | let asyncable = item.asyncness; 48 | let ident = item.ident; 49 | let inputs = item.inputs; 50 | let mut fields_ty = vec![]; 51 | let req = inputs.iter().fold(vec![], |mut vec, e| { 52 | if let FnArg::Typed(req) = e { 53 | vec.push(req.pat.clone()); 54 | fields_ty.push(req.pat.to_token_stream().to_string()); 55 | } 56 | vec 57 | }); 58 | let output = item.output; 59 | let output_type = match &output { 60 | ReturnType::Default => { 61 | quote! {()} 62 | } 63 | ReturnType::Type(_, res_type) => res_type.to_token_stream(), 64 | }; 65 | let (methos_path, methos_type) = methods_cache.get(&ident.to_string()).unwrap(); 66 | fn_quote.push( 67 | quote! { 68 | #[allow(non_snake_case)] 69 | #[allow(clippy::too_many_arguments)] 70 | pub #asyncable fn #ident (#inputs) -> Result<#output_type,fusen_rs::fusen_common::error::FusenError> { 71 | let mut req_vec : Vec = vec![]; 72 | let fields_ty = vec![ 73 | #( 74 | #fields_ty.to_string(), 75 | )*]; 76 | #( 77 | let mut res_poi_str = serde_json::to_string(&#req); 78 | if let Err(err) = res_poi_str { 79 | return Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string())); 80 | } 81 | req_vec.push(res_poi_str.unwrap()); 82 | )* 83 | let version : Option<&str> = #version; 84 | let group : Option<&str> = #group; 85 | let mut mate_data = fusen_rs::fusen_common::MetaData::new(); 86 | let mut request = fusen_rs::fusen_common::FusenRequest::new_for_client(#methos_type,fields_ty,req_vec); 87 | let mut context = fusen_rs::fusen_common::FusenContext::new( 88 | fusen_rs::fusen_common::logs::get_uuid(), 89 | fusen_rs::fusen_common::ContextInfo::default() 90 | .path(fusen_rs::fusen_common::Path::new(#methos_type,#methos_path.to_string())) 91 | .version(version.map(|e|e.to_string())) 92 | .class_name(#id.to_owned()) 93 | .method_name(stringify!(#ident).to_string()) 94 | .group(group.map(|e|e.to_string())), 95 | request, 96 | mate_data, 97 | ); 98 | context.get_mut_response().insert_return_ty(stringify!(#output_type)); 99 | let res : Result<#output_type,fusen_rs::fusen_common::error::FusenError> = self.client.invoke::<#output_type>(context).await; 100 | return res; 101 | } 102 | } 103 | ); 104 | } 105 | let rpc_client = syn::Ident::new(&format!("{}Client", trait_ident), trait_ident.span()); 106 | 107 | let expanded = quote! { 108 | #item_trait 109 | 110 | #[derive(Clone)] 111 | #vis struct #rpc_client { 112 | client : std::sync::Arc 113 | } 114 | impl #rpc_client { 115 | #( 116 | #fn_quote 117 | )* 118 | pub fn new(client : std::sync::Arc) -> #rpc_client { 119 | #rpc_client {client} 120 | } 121 | 122 | pub fn get_info(&self) -> fusen_rs::fusen_common::server::ServerInfo { 123 | let mut methods : Vec = vec![]; 124 | #( 125 | methods.push(fusen_rs::fusen_common::MethodResource::new_macro(#methods_info)); 126 | )* 127 | fusen_rs::fusen_common::server::ServerInfo::new(#id,#version,#group,methods) 128 | } 129 | 130 | } 131 | 132 | }; 133 | TokenStream::from(expanded) 134 | } 135 | 136 | fn get_item_trait(item: ItemTrait) -> proc_macro2::TokenStream { 137 | let trait_ident = &item.ident; 138 | let item_fn = item.items.iter().fold(vec![], |mut vec, e| { 139 | if let TraitItem::Fn(item_fn) = e { 140 | let asyncable = &item_fn.sig.asyncness; 141 | let ident = &item_fn.sig.ident; 142 | let inputs = &item_fn.sig.inputs; 143 | let attrs = &item_fn.attrs; 144 | let output_type = match &item_fn.sig.output { 145 | ReturnType::Default => { 146 | quote! {()} 147 | } 148 | ReturnType::Type(_, res_type) => res_type.to_token_stream(), 149 | }; 150 | vec.push(quote! { 151 | #(#attrs)* 152 | #asyncable fn #ident (#inputs) -> fusen_rs::fusen_common::FusenResult<#output_type>; 153 | }); 154 | } 155 | vec 156 | }); 157 | quote! { 158 | pub trait #trait_ident { 159 | #( 160 | #[allow(async_fn_in_trait)] 161 | #[allow(non_snake_case)] 162 | #item_fn 163 | )* 164 | } 165 | } 166 | } 167 | 168 | fn get_resource_by_trait(item: ItemTrait) -> Result, syn::Error> { 169 | let mut res = vec![]; 170 | let attrs = &item.attrs; 171 | let resource = get_asset_by_attrs(attrs)?; 172 | let parent_path = match resource.path { 173 | Some(path) => path, 174 | None => "/".to_owned() + &item.ident.to_string(), 175 | }; 176 | let parent_method = match resource.method { 177 | Some(method) => method, 178 | None => "POST".to_string(), 179 | }; 180 | for fn_item in item.items.iter() { 181 | if let TraitItem::Fn(item_fn) = fn_item { 182 | let resource = get_asset_by_attrs(&item_fn.attrs)?; 183 | let path = match resource.path { 184 | Some(path) => path, 185 | None => "/".to_owned() + &item_fn.sig.ident.to_string(), 186 | }; 187 | let method = match resource.method { 188 | Some(method) => method, 189 | None => parent_method.clone(), 190 | }; 191 | let mut parent_path = parent_path.clone(); 192 | parent_path.push_str(&path); 193 | res.push((item_fn.sig.ident.to_string(), parent_path, method)); 194 | } 195 | } 196 | Ok(res) 197 | } 198 | -------------------------------------------------------------------------------- /fusen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fusen-rs" 3 | version = "0.6.24" 4 | authors.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | description.workspace = true 8 | 9 | 10 | [dependencies] 11 | fusen-common.workspace = true 12 | fusen-procedural-macro.workspace = true 13 | 14 | 15 | #网络协议处理 16 | tokio.workspace = true 17 | hyper.workspace = true 18 | hyper-tls.workspace = true 19 | hyper-util.workspace = true 20 | http.workspace = true 21 | http-body.workspace = true 22 | http-body-util.workspace = true 23 | bytes.workspace = true 24 | futures.workspace = true 25 | async-trait.workspace = true 26 | async-recursion.workspace = true 27 | h2.workspace = true 28 | futures-util.workspace = true 29 | urlencoding.workspace = true 30 | 31 | #日志处理 32 | tracing.workspace = true 33 | tracing-futures.workspace = true 34 | tracing-subscriber.workspace = true 35 | pretty_env_logger.workspace = true 36 | opentelemetry_sdk.workspace = true 37 | opentelemetry.workspace = true 38 | tracing-opentelemetry.workspace = true 39 | 40 | 41 | #json序列化 42 | serde.workspace = true 43 | serde_json.workspace = true 44 | uuid.workspace = true 45 | 46 | prost = "0.13.1" 47 | prost-types = "0.13.1" 48 | 49 | pin-project-lite.workspace = true 50 | lazy_static.workspace = true 51 | 52 | #注册中心 53 | nacos-sdk.workspace = true 54 | rand.workspace = true 55 | -------------------------------------------------------------------------------- /fusen/src/client.rs: -------------------------------------------------------------------------------- 1 | use crate::filter::{FusenFilter, ProceedingJoinPoint}; 2 | use crate::handler::HandlerContext; 3 | use fusen_common::codec::json_field_compatible; 4 | use fusen_common::error::FusenError; 5 | use fusen_common::register::Type; 6 | use fusen_common::FusenContext; 7 | use serde::{Deserialize, Serialize}; 8 | use std::sync::Arc; 9 | 10 | pub struct FusenClient { 11 | server_type: Type, 12 | client_filter: &'static dyn FusenFilter, 13 | handle_context: Arc, 14 | } 15 | 16 | impl FusenClient { 17 | pub fn build( 18 | server_type: Type, 19 | client_filter: &'static dyn FusenFilter, 20 | handle_context: Arc, 21 | ) -> FusenClient { 22 | FusenClient { 23 | server_type, 24 | client_filter, 25 | handle_context, 26 | } 27 | } 28 | 29 | pub async fn invoke(&self, mut context: FusenContext) -> Result 30 | where 31 | Res: Send + Sync + Serialize + for<'a> Deserialize<'a> + Default, 32 | { 33 | let mut aspect_handler = self 34 | .handle_context 35 | .get_controller(&context.get_context_info().get_handler_key()) 36 | .get_aspect(); 37 | context.insert_server_type(self.server_type.clone()); 38 | aspect_handler.push_back(self.client_filter); 39 | let join_point = ProceedingJoinPoint::new(aspect_handler, context); 40 | let context = join_point.proceed().await?; 41 | let return_ty = context.get_response().get_response_ty().unwrap(); 42 | match context.into_response().into_response() { 43 | Ok(res) => { 44 | let response = json_field_compatible(return_ty, res)?; 45 | let response: Res = 46 | serde_json::from_str(&response).map_err(|e| FusenError::from(e.to_string()))?; 47 | Ok(response) 48 | } 49 | Err(err) => Err(err), 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /fusen/src/codec/grpc_codec.rs: -------------------------------------------------------------------------------- 1 | use prost::Message; 2 | use std::marker::PhantomData; 3 | 4 | use crate::support::triple::get_buf; 5 | 6 | use super::BodyCodec; 7 | 8 | pub struct GrpcBodyCodec { 9 | _d: PhantomData, 10 | _u: PhantomData, 11 | _t: PhantomData, 12 | } 13 | 14 | impl GrpcBodyCodec { 15 | pub fn new() -> Self { 16 | GrpcBodyCodec { 17 | _d: PhantomData, 18 | _u: PhantomData, 19 | _t: PhantomData, 20 | } 21 | } 22 | } 23 | 24 | impl Default for GrpcBodyCodec { 25 | fn default() -> Self { 26 | Self::new() 27 | } 28 | } 29 | 30 | impl BodyCodec for GrpcBodyCodec 31 | where 32 | D: bytes::Buf, 33 | U: Message + Send + 'static + Default, 34 | T: Message + Default + Send + 'static, 35 | { 36 | type DecodeType = U; 37 | 38 | type EncodeType = T; 39 | 40 | fn decode(&self, body: &D) -> Result { 41 | let data = body.chunk(); 42 | let wrapper = Self::DecodeType::decode(&data[5..])?; 43 | Ok(wrapper) 44 | } 45 | 46 | fn encode(&self, res: &Self::EncodeType) -> Result { 47 | let buf = res.encode_to_vec(); 48 | let buf = get_buf(buf); 49 | Ok(bytes::Bytes::from(buf)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /fusen/src/codec/http_codec.rs: -------------------------------------------------------------------------------- 1 | use std::{convert::Infallible, sync::Arc}; 2 | 3 | use super::{ 4 | request_codec::RequestHandler, 5 | response_codec::{ResponseCodec, ResponseHandler}, 6 | HttpCodec, 7 | }; 8 | use crate::{codec::request_codec::RequestCodec, filter::server::PathCache, BoxBody}; 9 | use bytes::Bytes; 10 | use fusen_common::FusenContext; 11 | use http_body_util::BodyExt; 12 | 13 | pub struct FusenHttpCodec { 14 | request_handle: RequestHandler, 15 | response_handle: ResponseHandler, 16 | } 17 | 18 | impl FusenHttpCodec { 19 | pub fn new(path_cahce: Arc) -> Self { 20 | FusenHttpCodec { 21 | request_handle: RequestHandler::new(path_cahce), 22 | response_handle: ResponseHandler::new(), 23 | } 24 | } 25 | } 26 | 27 | impl HttpCodec for FusenHttpCodec { 28 | async fn decode( 29 | &self, 30 | request: http::Request>, 31 | ) -> Result { 32 | self.request_handle.decode(request.map(|e| e.boxed())).await 33 | } 34 | 35 | async fn encode( 36 | &self, 37 | context: fusen_common::FusenContext, 38 | ) -> Result>, crate::Error> { 39 | self.response_handle.encode(context) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /fusen/src/codec/json_codec.rs: -------------------------------------------------------------------------------- 1 | use bytes::BufMut; 2 | use serde::{de::DeserializeOwned, Serialize}; 3 | use std::marker::PhantomData; 4 | 5 | use super::BodyCodec; 6 | 7 | pub struct JsonBodyCodec { 8 | _d: PhantomData, 9 | _u: PhantomData, 10 | _t: PhantomData, 11 | } 12 | 13 | impl JsonBodyCodec { 14 | pub fn new() -> Self { 15 | Self { 16 | _d: PhantomData, 17 | _u: PhantomData, 18 | _t: PhantomData, 19 | } 20 | } 21 | } 22 | 23 | impl Default for JsonBodyCodec { 24 | fn default() -> Self { 25 | Self::new() 26 | } 27 | } 28 | 29 | impl BodyCodec for JsonBodyCodec 30 | where 31 | D: bytes::Buf, 32 | U: DeserializeOwned, 33 | T: Serialize, 34 | { 35 | type DecodeType = U; 36 | 37 | type EncodeType = T; 38 | 39 | fn decode(&self, body: &D) -> Result { 40 | Ok(serde_json::from_slice(body.chunk())?) 41 | } 42 | 43 | fn encode(&self, res: &Self::EncodeType) -> Result { 44 | let mut byte = bytes::BytesMut::new(); 45 | let mut_bytes = &mut byte; 46 | serde_json::to_writer(mut_bytes.writer(), &res).map_err(Box::new)?; 47 | Ok(byte.into()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fusen/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use crate::BoxBody; 4 | use fusen_common::FusenContext; 5 | use http::Request; 6 | use http::Response; 7 | pub mod grpc_codec; 8 | pub mod http_codec; 9 | pub mod json_codec; 10 | pub mod request_codec; 11 | pub mod response_codec; 12 | 13 | #[allow(async_fn_in_trait)] 14 | pub trait HttpCodec 15 | where 16 | D: bytes::Buf, 17 | { 18 | async fn decode(&self, req: Request>) -> Result; 19 | 20 | async fn encode( 21 | &self, 22 | context: FusenContext, 23 | ) -> Result>, crate::Error>; 24 | } 25 | 26 | pub trait BodyCodec 27 | where 28 | D: bytes::Buf, 29 | { 30 | type DecodeType; 31 | 32 | type EncodeType; 33 | 34 | fn decode(&self, body: &D) -> Result; 35 | 36 | fn encode(&self, res: &Self::EncodeType) -> Result; 37 | } 38 | -------------------------------------------------------------------------------- /fusen/src/codec/request_codec.rs: -------------------------------------------------------------------------------- 1 | use super::{grpc_codec::GrpcBodyCodec, BodyCodec}; 2 | use crate::{ 3 | filter::server::{PathCache, PathCacheResult}, 4 | support::triple::TripleRequestWrapper, 5 | BoxBody, 6 | }; 7 | use bytes::{Bytes, BytesMut}; 8 | use fusen_common::{ 9 | error::FusenError, logs::get_trade_id, register::Type, ContextInfo, FusenContext, FusenRequest, 10 | MetaData, Path, 11 | }; 12 | use http::Request; 13 | use http_body_util::{BodyExt, Full}; 14 | use std::{collections::HashMap, convert::Infallible, sync::Arc}; 15 | 16 | pub(crate) trait RequestCodec { 17 | fn encode(&self, msg: &FusenContext) -> Result>, crate::Error>; 18 | 19 | async fn decode(&self, request: Request>) -> Result; 20 | } 21 | 22 | pub struct RequestHandler { 23 | grpc_codec: Box< 24 | (dyn BodyCodec< 25 | bytes::Bytes, 26 | DecodeType = TripleRequestWrapper, 27 | EncodeType = TripleRequestWrapper, 28 | > + Sync 29 | + Send), 30 | >, 31 | path_cache: Arc, 32 | } 33 | 34 | impl RequestHandler { 35 | pub fn new(path_cache: Arc) -> Self { 36 | let grpc_codec = 37 | GrpcBodyCodec::::new(); 38 | RequestHandler { 39 | grpc_codec: Box::new(grpc_codec), 40 | path_cache, 41 | } 42 | } 43 | } 44 | 45 | impl RequestCodec for RequestHandler { 46 | fn encode( 47 | &self, 48 | context: &FusenContext, 49 | ) -> Result>, crate::Error> { 50 | let content_type = match context.get_server_type() { 51 | &Type::Dubbo => ("application/grpc", "tri-service-version"), 52 | _ => ("application/json", "version"), 53 | }; 54 | let mut builder = Request::builder().header("connection", "keep-alive"); 55 | for (key, value) in context.get_request().get_headers() { 56 | builder = builder.header(key, value); 57 | } 58 | if let Some(version) = context.get_context_info().get_version() { 59 | builder = builder.header(content_type.1, version); 60 | } 61 | let request = match context.get_context_info().get_path().clone() { 62 | fusen_common::Path::GET(path) | fusen_common::Path::DELETE(path) => builder 63 | .method(context.get_context_info().get_path().to_str()) 64 | .uri(get_path(path, context.get_request().get_query_fields())) 65 | .body(Full::new(Bytes::new()).boxed()), 66 | fusen_common::Path::POST(mut path) | fusen_common::Path::PUT(mut path) => { 67 | let body: Bytes = match context.get_server_type() { 68 | &Type::Dubbo => { 69 | path = format!( 70 | "/{}/{}", 71 | context.get_context_info().get_class_name(), 72 | context.get_context_info().get_method_name() 73 | ); 74 | let body = context.get_request().get_body(); 75 | let fields: Vec = if body.starts_with(b"[") { 76 | serde_json::from_slice(body)? 77 | } else { 78 | vec![String::from_utf8(body.to_vec())?] 79 | }; 80 | let triple_request_wrapper = TripleRequestWrapper::from(fields); 81 | self.grpc_codec.encode(&triple_request_wrapper)? 82 | } 83 | _ => Bytes::copy_from_slice(context.get_request().get_body()), 84 | }; 85 | let builder = builder.header("content-length", body.len()); 86 | builder 87 | .header("content-type", content_type.0) 88 | .method(context.get_context_info().get_path().to_str()) 89 | .uri(path) 90 | .body(Full::new(body).boxed()) 91 | } 92 | }?; 93 | Ok(request) 94 | } 95 | 96 | async fn decode( 97 | &self, 98 | mut request: Request>, 99 | ) -> Result { 100 | let meta_data = MetaData::from(request.headers()); 101 | let path = request.uri().path().to_string(); 102 | let request_method = request.method().to_string().to_lowercase(); 103 | let mut temp_query_fields_ty: HashMap = HashMap::new(); 104 | let mut body = BytesMut::new(); 105 | let url = request.uri().to_string(); 106 | let url: Vec<&str> = url.split('?').collect(); 107 | if url.len() > 1 { 108 | let params: Vec<&str> = url[1].split('&').collect(); 109 | for item in params { 110 | let item: Vec<&str> = item.split('=').collect(); 111 | if let Ok(param) = urlencoding::decode(item[1]) { 112 | temp_query_fields_ty.insert(item[0].to_owned(), param.to_string()); 113 | } 114 | } 115 | } 116 | let mut bytes = BytesMut::new(); 117 | while let Some(Ok(frame)) = request.body_mut().frame().await { 118 | if frame.is_data() { 119 | bytes.extend(frame.into_data().unwrap()); 120 | } 121 | } 122 | let bytes: Bytes = bytes.into(); 123 | match meta_data.get_codec() { 124 | fusen_common::codec::CodecType::JSON => { 125 | body.extend_from_slice(&bytes); 126 | } 127 | fusen_common::codec::CodecType::GRPC => { 128 | let bytes = self 129 | .grpc_codec 130 | .decode(&bytes) 131 | .map_err(FusenError::from)? 132 | .get_body(); 133 | body.extend_from_slice(&bytes); 134 | } 135 | } 136 | let unique_identifier = meta_data 137 | .get_value("unique_identifier") 138 | .map_or(get_trade_id(), |e| e.clone()); 139 | let version = meta_data 140 | .get_value("tri-service-version") 141 | .map_or(meta_data.get_value("version"), Some) 142 | .cloned(); 143 | let mut path = Path::new(&request_method, path); 144 | let PathCacheResult { 145 | class, 146 | method, 147 | fields, 148 | } = self 149 | .path_cache 150 | .seach(&mut path).await 151 | .ok_or(FusenError::NotFind)?; 152 | if let Some(fields) = fields { 153 | for (key, value) in fields { 154 | temp_query_fields_ty.insert(key, value); 155 | } 156 | } 157 | let context = FusenContext::new( 158 | unique_identifier, 159 | ContextInfo::default() 160 | .class_name(class) 161 | .method_name(method) 162 | .path(path) 163 | .version(version), 164 | FusenRequest::new(&request_method, temp_query_fields_ty, body.into()), 165 | meta_data, 166 | ); 167 | Ok(context) 168 | } 169 | } 170 | 171 | fn get_path(mut path: String, query_fields: &HashMap) -> String { 172 | if path.contains('{') { 173 | return get_rest_path(path, query_fields); 174 | } 175 | if !query_fields.is_empty() { 176 | path.push('?'); 177 | for item in query_fields { 178 | path.push_str(item.0); 179 | path.push('='); 180 | path.push_str(&urlencoding::encode(item.1)); 181 | path.push('&'); 182 | } 183 | path.remove(path.len() - 1); 184 | } 185 | path 186 | } 187 | 188 | fn get_rest_path(mut path: String, query_fields: &HashMap) -> String { 189 | if !query_fields.is_empty() { 190 | for item in query_fields { 191 | let temp = format!("{{{}}}", item.0); 192 | path = path.replace(&temp, item.1); 193 | } 194 | } 195 | path 196 | } 197 | -------------------------------------------------------------------------------- /fusen/src/codec/response_codec.rs: -------------------------------------------------------------------------------- 1 | use super::{grpc_codec::GrpcBodyCodec, BodyCodec}; 2 | use crate::support::triple::TripleResponseWrapper; 3 | use bytes::{Bytes, BytesMut}; 4 | use fusen_common::{codec::CodecType, error::FusenError, FusenContext}; 5 | use http::{HeaderMap, HeaderValue, Response}; 6 | use http_body::Frame; 7 | use http_body_util::{combinators::BoxBody, BodyExt}; 8 | use std::convert::Infallible; 9 | 10 | pub(crate) trait ResponseCodec { 11 | fn encode(&self, msg: FusenContext) -> Result>, crate::Error>; 12 | 13 | async fn decode(&self, request: Response>) -> Result; 14 | } 15 | 16 | pub struct ResponseHandler { 17 | grpc_codec: Box< 18 | (dyn BodyCodec< 19 | bytes::Bytes, 20 | DecodeType = TripleResponseWrapper, 21 | EncodeType = TripleResponseWrapper, 22 | > + Sync 23 | + Send), 24 | >, 25 | } 26 | 27 | impl ResponseHandler { 28 | pub fn new() -> Self { 29 | let grpc_codec = 30 | GrpcBodyCodec::::new(); 31 | ResponseHandler { 32 | grpc_codec: Box::new(grpc_codec), 33 | } 34 | } 35 | } 36 | 37 | impl Default for ResponseHandler { 38 | fn default() -> Self { 39 | Self::new() 40 | } 41 | } 42 | 43 | impl ResponseCodec for ResponseHandler { 44 | fn encode( 45 | &self, 46 | context: FusenContext, 47 | ) -> Result>, crate::Error> { 48 | let meta_data = context.get_meta_data(); 49 | let content_type = match meta_data.get_codec() { 50 | fusen_common::codec::CodecType::JSON => "application/json", 51 | fusen_common::codec::CodecType::GRPC => "application/grpc", 52 | }; 53 | let mut builder = Response::builder().header("content-type", content_type); 54 | for (key, value) in context.get_response().get_headers() { 55 | builder = builder.header(key, value); 56 | } 57 | let body = match meta_data.get_codec() { 58 | fusen_common::codec::CodecType::JSON => { 59 | vec![match context.into_response().into_response() { 60 | Ok(res) => Frame::data(res), 61 | Err(err) => { 62 | if let FusenError::Null = err { 63 | Frame::data(bytes::Bytes::from("null")) 64 | } else { 65 | return Err(crate::Error::from(err.to_string())); 66 | } 67 | } 68 | }] 69 | } 70 | fusen_common::codec::CodecType::GRPC => { 71 | let mut status = "0"; 72 | let mut message = String::from("success"); 73 | let mut trailers = HeaderMap::new(); 74 | let mut vec = vec![]; 75 | match context.into_response().into_response() { 76 | Ok(data) => { 77 | let res_wrapper = TripleResponseWrapper::form(data.into()); 78 | let buf = self 79 | .grpc_codec 80 | .encode(&res_wrapper) 81 | .map_err(FusenError::from)?; 82 | vec.push(Frame::data(buf)); 83 | } 84 | Err(err) => { 85 | message = match err { 86 | FusenError::Null => { 87 | let res_wrapper = TripleResponseWrapper::form(b"null".to_vec()); 88 | let buf = self 89 | .grpc_codec 90 | .encode(&res_wrapper) 91 | .map_err(FusenError::from)?; 92 | vec.push(Frame::data(buf)); 93 | "null value".to_owned() 94 | } 95 | FusenError::NotFind => { 96 | status = "91"; 97 | "not find".to_owned() 98 | } 99 | FusenError::Info(msg) => { 100 | status = "92"; 101 | msg.clone() 102 | } 103 | } 104 | } 105 | } 106 | trailers.insert("grpc-status", HeaderValue::from_str(status).unwrap()); 107 | trailers.insert("grpc-message", HeaderValue::from_str(&message).unwrap()); 108 | vec.push(Frame::trailers(trailers)); 109 | vec 110 | } 111 | }; 112 | 113 | let chunks = body.into_iter().fold(vec![], |mut vec, e| { 114 | vec.push(Ok(e)); 115 | vec 116 | }); 117 | let stream = futures_util::stream::iter(chunks); 118 | let stream_body = http_body_util::StreamBody::new(stream); 119 | let response = builder 120 | .body(stream_body.boxed()) 121 | .map_err(FusenError::from)?; 122 | Ok(response) 123 | } 124 | 125 | async fn decode( 126 | &self, 127 | mut response: Response>, 128 | ) -> Result { 129 | let mut bytes = BytesMut::new(); 130 | while let Some(Ok(frame)) = response.frame().await { 131 | if frame.is_trailers() { 132 | let trailers = frame 133 | .trailers_ref() 134 | .ok_or(FusenError::from("error trailers N1"))?; 135 | match trailers.get("grpc-status") { 136 | Some(status) => match status.as_bytes() { 137 | b"0" => { 138 | break; 139 | } 140 | else_status => { 141 | let msg = match trailers.get("grpc-message") { 142 | Some(value) => { 143 | String::from_utf8(value.as_bytes().to_vec()).unwrap() 144 | } 145 | None => { 146 | "grpc-status=".to_owned() 147 | + core::str::from_utf8(else_status).unwrap() 148 | } 149 | }; 150 | match else_status { 151 | b"90" => return Err(FusenError::Null), 152 | b"91" => return Err(FusenError::NotFind), 153 | _ => return Err(FusenError::from(msg)), 154 | }; 155 | } 156 | }, 157 | None => return Err(FusenError::from("error trailers N2")), 158 | } 159 | } 160 | bytes.extend(frame.into_data().unwrap()); 161 | } 162 | if !response.status().is_success() { 163 | let mut err_info = format!("errcode : {}", response.status().as_str()); 164 | if !bytes.is_empty() { 165 | err_info.push_str(&format!(" ,message : {:?}", bytes)); 166 | } 167 | return Err(FusenError::from(err_info)); 168 | } 169 | let codec_type = response 170 | .headers() 171 | .iter() 172 | .find(|e| e.0.as_str().to_lowercase() == "content-type") 173 | .map(|e| e.1) 174 | .map_or(CodecType::JSON, |e| match e.to_str() { 175 | Ok(coder) => CodecType::from(coder), 176 | Err(_) => CodecType::JSON, 177 | }); 178 | let bytes: Bytes = bytes.into(); 179 | let res = match codec_type { 180 | CodecType::JSON => bytes, 181 | CodecType::GRPC => { 182 | let response = self.grpc_codec.decode(&bytes)?; 183 | Bytes::copy_from_slice(&response.data) 184 | } 185 | }; 186 | Ok(res) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /fusen/src/config.rs: -------------------------------------------------------------------------------- 1 | use fusen_procedural_macro::Data; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | use crate::handler::HandlerInfo; 5 | 6 | #[derive(Serialize, Deserialize, Default, Data)] 7 | pub struct FusenApplicationConfig { 8 | application_name: String, 9 | port: Option, 10 | register: Option, 11 | handler_infos: Option>, 12 | } 13 | -------------------------------------------------------------------------------- /fusen/src/filter/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::LinkedList; 2 | 3 | use fusen_common::{error::BoxError, FusenContext}; 4 | use fusen_procedural_macro::Data; 5 | 6 | use crate::FusenFuture; 7 | pub mod server; 8 | 9 | pub trait FusenFilter: Send + Sync + 'static { 10 | fn call( 11 | &'static self, 12 | join_point: ProceedingJoinPoint, 13 | ) -> FusenFuture>; 14 | } 15 | 16 | #[derive(Data)] 17 | pub struct ProceedingJoinPoint { 18 | link: LinkedList<&'static dyn FusenFilter>, 19 | context: FusenContext, 20 | } 21 | 22 | impl ProceedingJoinPoint { 23 | pub fn new(link: LinkedList<&'static dyn FusenFilter>, context: FusenContext) -> Self { 24 | Self { link, context } 25 | } 26 | pub fn into_data(self) -> FusenContext { 27 | self.context 28 | } 29 | pub async fn proceed(mut self) -> Result { 30 | match self.link.pop_front() { 31 | Some(filter) => filter.call(self).await, 32 | None => Ok(self.into_data()), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fusen/src/filter/server.rs: -------------------------------------------------------------------------------- 1 | use super::{FusenFilter, ProceedingJoinPoint}; 2 | use fusen_common::{ 3 | error::FusenError, 4 | server::RpcServer, 5 | trie::{QueryResult, Trie}, 6 | FusenContext, FusenFuture, Path, 7 | }; 8 | use std::{collections::HashMap, sync::Arc}; 9 | 10 | #[derive(Clone, Default)] 11 | pub struct RpcServerFilter { 12 | cache: HashMap, 13 | path_cache: Arc, 14 | } 15 | 16 | impl RpcServerFilter { 17 | pub async fn new(cache: HashMap) -> Self { 18 | let mut hash_cache = HashMap::new(); 19 | let mut rest_trie = Trie::default(); 20 | for item in &cache { 21 | let info = item.1.get_info(); 22 | for method in info.get_methods() { 23 | let path = method.get_path().clone(); 24 | let name = method.get_name().clone(); 25 | let method = method.get_method().clone(); 26 | if path.contains('{') { 27 | rest_trie.insert(path.clone()).await; 28 | } 29 | hash_cache.insert( 30 | Path::new(&method, path).get_key(), 31 | (info.get_id().to_string(), name.clone()), 32 | ); 33 | hash_cache.insert( 34 | Path::new(&method, format!("/{}/{}", info.get_id(), name.clone())).get_key(), 35 | (info.get_id().to_string(), name), 36 | ); 37 | } 38 | } 39 | RpcServerFilter { 40 | cache, 41 | path_cache: Arc::new(PathCache { 42 | path_cache: hash_cache, 43 | rest_trie, 44 | }), 45 | } 46 | } 47 | pub fn get_path_cache(&self) -> Arc { 48 | self.path_cache.clone() 49 | } 50 | 51 | pub fn get_server(&self, context: &mut FusenContext) -> Option<&'static dyn RpcServer> { 52 | let context_info = context.get_context_info(); 53 | let mut class_name = context_info.get_class_name().clone(); 54 | if let Some(version) = context_info.get_version() { 55 | class_name.push(':'); 56 | class_name.push_str(version); 57 | } 58 | self.cache.get(&class_name).copied() 59 | } 60 | } 61 | 62 | impl FusenFilter for RpcServerFilter { 63 | fn call( 64 | &self, 65 | mut join_point: ProceedingJoinPoint, 66 | ) -> FusenFuture> { 67 | let server = self.get_server(join_point.get_mut_context()); 68 | match server { 69 | Some(server) => { 70 | Box::pin(async move { Ok(server.invoke(join_point.into_data()).await) }) 71 | } 72 | None => Box::pin(async move { 73 | join_point 74 | .get_mut_context() 75 | .get_mut_response() 76 | .set_response(Err(FusenError::NotFind)); 77 | Ok(join_point.into_data()) 78 | }), 79 | } 80 | } 81 | } 82 | 83 | #[derive(Debug, Default)] 84 | pub struct PathCache { 85 | path_cache: HashMap, 86 | rest_trie: Trie, 87 | } 88 | 89 | pub struct PathCacheResult { 90 | pub class: String, 91 | pub method: String, 92 | pub fields: Option>, 93 | } 94 | 95 | impl PathCache { 96 | pub async fn seach(&self, mut_path: &mut Path) -> Option { 97 | if let Some(data) = self.path_cache.get(&mut_path.get_key()) { 98 | Some(PathCacheResult { 99 | class: data.0.clone(), 100 | method: data.1.clone(), 101 | fields: None, 102 | }) 103 | } else if let Some(rest_data) = self.rest_trie.search(&mut_path.get_path()).await { 104 | let QueryResult { path, query_fields } = rest_data; 105 | mut_path.update_path(path); 106 | self.path_cache 107 | .get(&mut_path.get_key()) 108 | .map(|data| PathCacheResult { 109 | class: data.0.clone(), 110 | method: data.1.clone(), 111 | fields: query_fields, 112 | }) 113 | } else { 114 | None 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /fusen/src/handler/aspect/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use super::HandlerContext; 4 | use crate::codec::request_codec::RequestCodec; 5 | use crate::codec::response_codec::ResponseCodec; 6 | use crate::filter::ProceedingJoinPoint; 7 | use crate::register::ResourceInfo; 8 | use crate::route::client::Route; 9 | use crate::{ 10 | codec::{request_codec::RequestHandler, response_codec::ResponseHandler}, 11 | filter::FusenFilter, 12 | FusenFuture, 13 | }; 14 | use fusen_common::error::FusenError; 15 | use fusen_common::FusenContext; 16 | use http_body_util::BodyExt; 17 | use opentelemetry::propagation::TextMapPropagator; 18 | use opentelemetry::trace::TraceContextExt; 19 | use opentelemetry_sdk::propagation::TraceContextPropagator; 20 | use tracing::Span; 21 | use tracing_opentelemetry::OpenTelemetrySpanExt; 22 | 23 | #[allow(async_fn_in_trait)] 24 | pub trait Aspect { 25 | async fn aroud(&self, join_point: ProceedingJoinPoint) -> Result; 26 | } 27 | 28 | pub struct DefaultAspect; 29 | 30 | impl FusenFilter for DefaultAspect { 31 | fn call( 32 | &'static self, 33 | join_point: ProceedingJoinPoint, 34 | ) -> FusenFuture> { 35 | Box::pin(async move { join_point.proceed().await }) 36 | } 37 | } 38 | 39 | pub struct AspectClientFilter { 40 | request_handle: RequestHandler, 41 | response_handle: ResponseHandler, 42 | handle_context: Arc, 43 | route: Route, 44 | trace_context_propagator: TraceContextPropagator, 45 | } 46 | 47 | impl AspectClientFilter { 48 | pub fn new( 49 | request_handle: RequestHandler, 50 | response_handle: ResponseHandler, 51 | handle_context: Arc, 52 | route: Route, 53 | ) -> Self { 54 | AspectClientFilter { 55 | request_handle, 56 | response_handle, 57 | handle_context, 58 | route, 59 | trace_context_propagator: TraceContextPropagator::new(), 60 | } 61 | } 62 | } 63 | 64 | impl FusenFilter for AspectClientFilter { 65 | fn call( 66 | &'static self, 67 | mut join_point: ProceedingJoinPoint, 68 | ) -> FusenFuture> { 69 | Box::pin(async move { 70 | let handler_controller = self.handle_context.get_controller( 71 | &join_point 72 | .get_context() 73 | .get_context_info() 74 | .get_handler_key(), 75 | ); 76 | let resource_info: Arc = self 77 | .route 78 | .get_server_resource(join_point.get_context()) 79 | .await 80 | .map_err(|e| FusenError::Info(e.to_string()))?; 81 | let socket = handler_controller 82 | .as_ref() 83 | .get_load_balance() 84 | .select_(resource_info) 85 | .await?; 86 | let span_context = Span::current().context(); 87 | if span_context.has_active_span() { 88 | self.trace_context_propagator.inject_context( 89 | &span_context, 90 | join_point 91 | .get_mut_context() 92 | .get_mut_request() 93 | .get_mut_headers(), 94 | ); 95 | } 96 | let request = self.request_handle.encode(join_point.get_context())?; 97 | let response: http::Response = 98 | socket.send_request(request).await?; 99 | let res = self 100 | .response_handle 101 | .decode(response.map(|e| e.boxed())) 102 | .await; 103 | join_point 104 | .get_mut_context() 105 | .get_mut_response() 106 | .set_response(res); 107 | Ok(join_point.into_data()) 108 | }) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /fusen/src/handler/loadbalance/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{protocol::socket::InvokerAssets, register::ResourceInfo}; 2 | use fusen_common::FusenFuture; 3 | use std::sync::Arc; 4 | 5 | #[allow(async_fn_in_trait)] 6 | pub trait LoadBalance { 7 | async fn select(&self, invokers: Arc) 8 | -> Result, crate::Error>; 9 | } 10 | 11 | pub trait LoadBalance_: Send + Sync { 12 | fn select_( 13 | &'static self, 14 | invokers: Arc, 15 | ) -> FusenFuture, crate::Error>>; 16 | } 17 | 18 | pub struct DefaultLoadBalance; 19 | 20 | impl LoadBalance_ for DefaultLoadBalance { 21 | fn select_( 22 | &self, 23 | invokers: Arc, 24 | ) -> FusenFuture, crate::Error>> { 25 | Box::pin(async move { invokers.select().ok_or("not find server".into()) }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /fusen/src/handler/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::filter::FusenFilter; 2 | 3 | use self::loadbalance::{DefaultLoadBalance, LoadBalance_}; 4 | use aspect::DefaultAspect; 5 | use serde::{Deserialize, Serialize}; 6 | use std::{ 7 | collections::{HashMap, LinkedList}, 8 | sync::Arc, 9 | }; 10 | pub mod aspect; 11 | pub mod loadbalance; 12 | 13 | #[derive(Clone)] 14 | pub struct HandlerContext { 15 | context: HashMap>, 16 | cache: HashMap>, 17 | } 18 | 19 | impl Default for HandlerContext { 20 | fn default() -> Self { 21 | let mut context = Self { 22 | context: Default::default(), 23 | cache: Default::default(), 24 | }; 25 | let handler = Handler::new( 26 | "DefaultLoadBalance".to_string(), 27 | HandlerInvoker::LoadBalance(Box::leak(Box::new(DefaultLoadBalance))), 28 | ); 29 | let aspect = Handler::new( 30 | "DefaultAspect".to_string(), 31 | HandlerInvoker::Aspect(Box::leak(Box::new(DefaultAspect))), 32 | ); 33 | context.insert(handler); 34 | context.insert(aspect); 35 | context 36 | .load_controller(HandlerInfo::new( 37 | "DefaultFusenClientHandlerInfo".to_string(), 38 | vec![], 39 | )) 40 | .unwrap(); 41 | context 42 | } 43 | } 44 | 45 | impl HandlerContext { 46 | pub fn insert(&mut self, handler: Handler) { 47 | self.context.insert(handler.id.clone(), Arc::new(handler)); 48 | } 49 | 50 | fn get_handler(&self, key: &str) -> Option> { 51 | self.context.get(key).cloned() 52 | } 53 | 54 | pub fn get_controller(&self, class_name: &str) -> &Arc { 55 | self.cache.get(class_name).map_or( 56 | self.cache.get("DefaultFusenClientHandlerInfo").unwrap(), 57 | |e| e, 58 | ) 59 | } 60 | 61 | pub fn insert_controller(&mut self, key: String, controller: Arc) { 62 | self.cache.insert(key, controller); 63 | } 64 | 65 | pub fn load_controller(&mut self, handler_info: HandlerInfo) -> Result<(), crate::Error> { 66 | let mut load_balance: Option<&'static dyn LoadBalance_> = None; 67 | let mut aspect: LinkedList<&'static dyn FusenFilter> = LinkedList::new(); 68 | 69 | for item in &handler_info.handlers_id { 70 | if let Some(handler) = self.get_handler(item) { 71 | match handler.handler_invoker { 72 | HandlerInvoker::LoadBalance(handler) => { 73 | let _ = load_balance.insert(handler); 74 | } 75 | HandlerInvoker::Aspect(handler) => { 76 | aspect.push_back(handler); 77 | } 78 | }; 79 | } 80 | } 81 | if load_balance.is_none() { 82 | if let Some(handler) = self.get_handler("DefaultLoadBalance") { 83 | match handler.handler_invoker { 84 | HandlerInvoker::LoadBalance(handler) => load_balance.insert(handler), 85 | _ => return Err(crate::Error::from("DefaultLoadBalance get Error")), 86 | }; 87 | } 88 | } 89 | let handler_controller = HandlerController { 90 | load_balance: load_balance 91 | .ok_or_else(|| crate::Error::from("not find load_balance"))?, 92 | aspect, 93 | }; 94 | self.cache 95 | .insert(handler_info.id, Arc::new(handler_controller)); 96 | Ok(()) 97 | } 98 | } 99 | 100 | pub struct HandlerController { 101 | load_balance: &'static dyn LoadBalance_, 102 | aspect: LinkedList<&'static dyn FusenFilter>, 103 | } 104 | 105 | impl HandlerController { 106 | pub fn get_load_balance(&self) -> &'static dyn LoadBalance_ { 107 | self.load_balance 108 | } 109 | pub fn get_aspect(&self) -> LinkedList<&'static dyn FusenFilter> { 110 | self.aspect.clone() 111 | } 112 | } 113 | 114 | pub enum HandlerInvoker { 115 | LoadBalance(&'static dyn LoadBalance_), 116 | Aspect(&'static dyn FusenFilter), 117 | } 118 | 119 | #[derive(Serialize, Deserialize, Default, Clone)] 120 | pub struct HandlerInfo { 121 | id: String, 122 | handlers_id: Vec, 123 | } 124 | 125 | impl HandlerInfo { 126 | pub fn new(id: String, handlers_id: Vec) -> Self { 127 | HandlerInfo { id, handlers_id } 128 | } 129 | } 130 | 131 | pub struct Handler { 132 | id: String, 133 | handler_invoker: HandlerInvoker, 134 | } 135 | 136 | impl Handler { 137 | pub fn new(id: String, handler_invoker: HandlerInvoker) -> Self { 138 | Self { 139 | id, 140 | handler_invoker, 141 | } 142 | } 143 | } 144 | 145 | pub trait HandlerLoad { 146 | fn load(self) -> Handler; 147 | } 148 | -------------------------------------------------------------------------------- /fusen/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod codec; 3 | pub mod config; 4 | pub mod filter; 5 | pub mod handler; 6 | pub mod protocol; 7 | pub mod register; 8 | pub mod route; 9 | pub mod server; 10 | pub mod support; 11 | use crate::{ 12 | handler::HandlerInfo, 13 | register::{Category, RegisterBuilder, Resource}, 14 | }; 15 | use client::FusenClient; 16 | use codec::{request_codec::RequestHandler, response_codec::ResponseHandler}; 17 | use config::FusenApplicationConfig; 18 | use filter::FusenFilter; 19 | pub use fusen_common; 20 | use fusen_common::{ 21 | register::Type, 22 | server::{RpcServer, ServerInfo}, 23 | MetaData, 24 | }; 25 | pub use fusen_procedural_macro; 26 | use handler::{aspect::AspectClientFilter, Handler, HandlerContext}; 27 | use register::Register; 28 | use route::client::Route; 29 | use server::FusenServer; 30 | use std::{collections::HashMap, convert::Infallible, sync::Arc, time::Duration}; 31 | use support::shutdown::Shutdown; 32 | use tokio::{ 33 | signal::{self}, 34 | sync::broadcast, 35 | }; 36 | pub type Error = fusen_common::Error; 37 | pub type Result = fusen_common::Result; 38 | pub type FusenFuture = fusen_common::FusenFuture; 39 | pub type HttpBody = futures_util::stream::Iter< 40 | std::vec::IntoIter, Infallible>>, 41 | >; 42 | pub type BoxBody = http_body_util::combinators::BoxBody; 43 | 44 | pub type StreamBody = http_body_util::StreamBody< 45 | futures_util::stream::Iter, E>>>, 46 | >; 47 | 48 | #[derive(Default)] 49 | pub struct FusenApplicationBuilder { 50 | port: Option, 51 | application_name: String, 52 | register_config: Option, 53 | handlers: Vec, 54 | handler_infos: Vec, 55 | servers: HashMap>, 56 | } 57 | 58 | impl FusenApplicationBuilder { 59 | pub fn application_name(mut self, application_name: &str) -> Self { 60 | application_name.clone_into(&mut self.application_name); 61 | self 62 | } 63 | 64 | pub fn port(mut self, port: Option) -> Self { 65 | self.port = port.map(|e| e.to_string()); 66 | self 67 | } 68 | 69 | pub fn register(mut self, register_config: Option<&str>) -> Self { 70 | self.register_config = register_config.map(|e| e.to_owned()); 71 | self 72 | } 73 | 74 | pub fn add_fusen_server(mut self, server: Box) -> Self { 75 | let info = server.get_info(); 76 | let server_name = info.get_id().to_string(); 77 | let mut key = server_name.clone(); 78 | if let Some(version) = info.get_version() { 79 | key.push(':'); 80 | key.push_str(version); 81 | } 82 | self.servers.insert(key, server); 83 | self 84 | } 85 | 86 | pub fn add_handler(mut self, handler: Handler) -> Self { 87 | self.handlers.push(handler); 88 | self 89 | } 90 | pub fn add_handler_info(mut self, info: HandlerInfo) -> Self { 91 | self.handler_infos.push(info); 92 | self 93 | } 94 | 95 | pub fn init(self, config: FusenApplicationConfig) -> Self { 96 | let mut builder = self 97 | .application_name(config.get_application_name()) 98 | .port(*config.get_port()) 99 | .register(config.get_register().as_deref()); 100 | if let Some(handler_infos) = config.get_handler_infos() { 101 | for handler_info in handler_infos { 102 | builder = builder.add_handler_info(handler_info.clone()); 103 | } 104 | } 105 | builder 106 | } 107 | 108 | pub fn build(self) -> FusenApplicationContext { 109 | let FusenApplicationBuilder { 110 | application_name, 111 | port, 112 | register_config, 113 | handlers, 114 | handler_infos, 115 | servers, 116 | } = self; 117 | let mut handler_context = HandlerContext::default(); 118 | for handler in handlers { 119 | handler_context.insert(handler); 120 | } 121 | for info in handler_infos { 122 | let _ = handler_context.load_controller(info); 123 | } 124 | let mut register = None; 125 | if let Some(register_config) = register_config { 126 | let _ = register.insert(Arc::new( 127 | RegisterBuilder::new(register_config) 128 | .unwrap() 129 | .init(application_name.clone()), 130 | )); 131 | } 132 | let handler_context = Arc::new(handler_context); 133 | FusenApplicationContext { 134 | register: register.clone(), 135 | handler_context: handler_context.clone(), 136 | client_filter: Box::leak(Box::new(AspectClientFilter::new( 137 | RequestHandler::new(Arc::new(Default::default())), 138 | ResponseHandler::new(), 139 | handler_context.clone(), 140 | Route::new(register), 141 | ))), 142 | server: FusenServer::new(port, servers, handler_context), 143 | } 144 | } 145 | } 146 | 147 | pub struct FusenApplicationContext { 148 | register: Option>>, 149 | handler_context: Arc, 150 | client_filter: &'static dyn FusenFilter, 151 | server: FusenServer, 152 | } 153 | impl FusenApplicationContext { 154 | pub fn builder() -> FusenApplicationBuilder { 155 | FusenApplicationBuilder::default() 156 | } 157 | 158 | pub fn client(&self, server_type: Type) -> FusenClient { 159 | FusenClient::build( 160 | server_type, 161 | self.client_filter, 162 | self.handler_context.clone(), 163 | ) 164 | } 165 | 166 | pub async fn run(mut self) -> Result<()> { 167 | let port = self.server.get_port().clone(); 168 | let (sender, receiver) = broadcast::channel::<()>(1); 169 | let shutdown = Shutdown::new(receiver); 170 | let mut shutdown_complete_rx = self.server.run(shutdown).await; 171 | let mut resources = vec![]; 172 | if let Some(register) = self.register.clone() { 173 | //首先注册server 174 | let resource = Resource::default() 175 | .category(Category::Server) 176 | .host(fusen_common::net::get_ip()) 177 | .port(port.clone()) 178 | .params(MetaData::default().into_inner()); 179 | resources.push(resource.clone()); 180 | register.register(resource).await?; 181 | //再注册service 182 | for server in self.server.get_fusen_servers().values() { 183 | let ServerInfo { 184 | id, 185 | version, 186 | group, 187 | methods, 188 | } = server.get_info(); 189 | let server_name = id; 190 | let resource = Resource::default() 191 | .server_name(server_name) 192 | .category(Category::Service) 193 | .group(group) 194 | .version(version) 195 | .methods(methods) 196 | .host(fusen_common::net::get_ip()) 197 | .port(port.clone()) 198 | .params(MetaData::default().into_inner()); 199 | resources.push(resource.clone()); 200 | register.register(resource).await?; 201 | } 202 | } 203 | let register = self.register.clone(); 204 | //如果检测到关机,先注销服务延迟5s后停机 205 | tokio::spawn(async move { 206 | tokio::select! { 207 | _ = signal::ctrl_c() => { 208 | if let Some(register) = register { 209 | for resource in resources { 210 | let _ = register.deregister(resource).await; 211 | } 212 | tokio::time::sleep(Duration::from_secs(5)).await; 213 | } 214 | drop(sender); 215 | } 216 | } 217 | }); 218 | let _ = shutdown_complete_rx.recv().await; 219 | tracing::info!("fusen server shut"); 220 | Ok(()) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /fusen/src/protocol/compression.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::collections::HashMap; 19 | 20 | use bytes::{Buf, BufMut, BytesMut}; 21 | use flate2::{ 22 | read::{GzDecoder, GzEncoder}, 23 | Compression, 24 | }; 25 | use lazy_static::lazy_static; 26 | 27 | pub const GRPC_ACCEPT_ENCODING: &str = "grpc-accept-encoding"; 28 | pub const GRPC_ENCODING: &str = "grpc-encoding"; 29 | 30 | #[derive(Debug, Clone, Copy)] 31 | pub enum CompressionEncoding { 32 | Gzip, 33 | } 34 | 35 | lazy_static! { 36 | pub static ref COMPRESSIONS: HashMap> = { 37 | let mut v = HashMap::new(); 38 | v.insert("gzip".to_string(), Some(CompressionEncoding::Gzip)); 39 | v 40 | }; 41 | } 42 | 43 | impl CompressionEncoding { 44 | pub fn from_accept_encoding(header: &http::HeaderMap) -> Option { 45 | let accept_encoding = header.get(GRPC_ACCEPT_ENCODING)?; 46 | let encodings = accept_encoding.to_str().ok()?; 47 | 48 | encodings 49 | .trim() 50 | .split(',') 51 | .map(|s| s.trim()) 52 | .into_iter() 53 | .find_map(|s| match s { 54 | "gzip" => Some(CompressionEncoding::Gzip), 55 | _ => None, 56 | }) 57 | } 58 | 59 | pub fn into_header_value(self) -> http::HeaderValue { 60 | match self { 61 | CompressionEncoding::Gzip => http::HeaderValue::from_static("gzip"), 62 | } 63 | } 64 | } 65 | 66 | pub fn compress( 67 | encoding: CompressionEncoding, 68 | src: &mut BytesMut, 69 | dst: &mut BytesMut, 70 | len: usize, 71 | ) -> Result<(), std::io::Error> { 72 | dst.reserve(len); 73 | 74 | match encoding { 75 | CompressionEncoding::Gzip => { 76 | let mut en = GzEncoder::new(src.reader(), Compression::default()); 77 | 78 | let mut dst_writer = dst.writer(); 79 | 80 | std::io::copy(&mut en, &mut dst_writer)?; 81 | } 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | pub fn _decompress( 88 | encoding: CompressionEncoding, 89 | src: &mut BytesMut, 90 | dst: &mut BytesMut, 91 | len: usize, 92 | ) -> Result<(), std::io::Error> { 93 | let capacity = len * 2; 94 | dst.reserve(capacity); 95 | 96 | match encoding { 97 | CompressionEncoding::Gzip => { 98 | let mut de = GzDecoder::new(src.reader()); 99 | 100 | let mut dst_writer = dst.writer(); 101 | 102 | std::io::copy(&mut de, &mut dst_writer)?; 103 | } 104 | } 105 | Ok(()) 106 | } 107 | -------------------------------------------------------------------------------- /fusen/src/protocol/http_handler.rs: -------------------------------------------------------------------------------- 1 | use super::StreamHandler; 2 | use crate::route::server::FusenRouter; 3 | use hyper_util::rt::TokioIo; 4 | use tracing::debug; 5 | impl StreamHandler { 6 | pub async fn run_http(mut self) { 7 | let hyper_io = TokioIo::new(self.tcp_stream); 8 | let route = FusenRouter::new(self.route, self.http_codec, self.handler_context); 9 | let conn = self.builder.serve_connection(hyper_io, route); 10 | let err_info = tokio::select! { 11 | res = conn => 12 | match res { 13 | Ok(_) => "client close".to_string(), 14 | Err(err) => err.to_string(), 15 | } 16 | , 17 | res2 = self.shutdown.recv() => match res2 { 18 | Ok(_) => "http2 shutdown error".to_string(), 19 | Err(_) => "http2 server shutdown".to_string(), 20 | } 21 | }; 22 | debug!("connect close by {}", err_info); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /fusen/src/protocol/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use hyper_util::{rt::TokioExecutor, server::conn::auto::Builder}; 4 | use tokio::{ 5 | net::TcpStream, 6 | sync::{broadcast, mpsc}, 7 | }; 8 | 9 | use crate::{ 10 | codec::http_codec::FusenHttpCodec, filter::server::RpcServerFilter, handler::HandlerContext, 11 | }; 12 | 13 | mod http_handler; 14 | pub mod server; 15 | pub mod socket; 16 | 17 | pub struct StreamHandler { 18 | builder: Arc>, 19 | tcp_stream: TcpStream, 20 | route: &'static RpcServerFilter, 21 | http_codec: Arc, 22 | handler_context: Arc, 23 | shutdown: broadcast::Receiver<()>, 24 | _shutdown_complete: mpsc::Sender<()>, 25 | } 26 | -------------------------------------------------------------------------------- /fusen/src/protocol/server.rs: -------------------------------------------------------------------------------- 1 | use crate::codec::http_codec::FusenHttpCodec; 2 | use crate::filter::server::RpcServerFilter; 3 | use crate::handler::HandlerContext; 4 | use crate::protocol::StreamHandler; 5 | use crate::support::shutdown::Shutdown; 6 | use fusen_common::server::RpcServer; 7 | use hyper_util::rt::TokioExecutor; 8 | use std::collections::HashMap; 9 | use std::sync::Arc; 10 | use tokio::net::TcpListener; 11 | use tokio::sync::mpsc::Receiver; 12 | use tokio::sync::{broadcast, mpsc}; 13 | use tracing::{debug, error}; 14 | 15 | #[derive(Clone)] 16 | pub struct TcpServer { 17 | port: String, 18 | fusen_servers: HashMap, 19 | } 20 | 21 | impl TcpServer { 22 | pub fn init(port: String, fusen_servers: HashMap) -> Self { 23 | TcpServer { 24 | port, 25 | fusen_servers, 26 | } 27 | } 28 | pub async fn run( 29 | self, 30 | shutdown: Shutdown, 31 | handler_context: Arc, 32 | ) -> Receiver<()> { 33 | let (shutdown_complete_tx, shutdown_complete_rx) = mpsc::channel(1); 34 | let route = Box::leak(Box::new(RpcServerFilter::new(self.fusen_servers).await)); 35 | let http_codec = Arc::new(FusenHttpCodec::new(route.get_path_cache())); 36 | let port = self.port; 37 | tokio::spawn(Self::monitor( 38 | port, 39 | route, 40 | http_codec, 41 | handler_context, 42 | shutdown, 43 | shutdown_complete_tx, 44 | )); 45 | shutdown_complete_rx 46 | } 47 | 48 | async fn monitor( 49 | port: String, 50 | route: &'static RpcServerFilter, 51 | http_codec: Arc, 52 | handler_context: Arc, 53 | mut shutdown: Shutdown, 54 | shutdown_complete_tx: mpsc::Sender<()>, 55 | ) -> crate::Result<()> { 56 | let notify_shutdown = broadcast::channel(1).0; 57 | let listener = TcpListener::bind(&format!("0.0.0.0:{}", port)).await?; 58 | let mut builder = hyper_util::server::conn::auto::Builder::new(TokioExecutor::new()); 59 | builder.http2().max_concurrent_streams(None); 60 | builder.http1().keep_alive(true); 61 | let builder = Arc::new(builder); 62 | loop { 63 | let tcp_stream = tokio::select! { 64 | _ = shutdown.recv() => { 65 | drop(notify_shutdown); 66 | drop(shutdown_complete_tx); 67 | return Ok(()); 68 | }, 69 | res = listener.accept() => res 70 | }; 71 | match tcp_stream { 72 | Ok(stream) => { 73 | let stream_handler = StreamHandler { 74 | builder: builder.clone(), 75 | tcp_stream: stream.0, 76 | route, 77 | http_codec: http_codec.clone(), 78 | handler_context: handler_context.clone(), 79 | shutdown: notify_shutdown.subscribe(), 80 | _shutdown_complete: shutdown_complete_tx.clone(), 81 | }; 82 | debug!("socket stream connect, addr: {:?}", stream.1); 83 | tokio::spawn(stream_handler.run_http()); 84 | } 85 | Err(err) => error!("tcp connect, err: {:?}", err), 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /fusen/src/protocol/socket.rs: -------------------------------------------------------------------------------- 1 | use fusen_common::error::FusenError; 2 | use fusen_procedural_macro::Data; 3 | use http::{Request, Response, Uri, Version}; 4 | use http_body_util::combinators::BoxBody; 5 | use hyper::body::Incoming; 6 | use hyper_tls::HttpsConnector; 7 | use hyper_util::client::legacy::{connect::HttpConnector, Client}; 8 | use std::{convert::Infallible, time::Duration}; 9 | use tracing::error; 10 | pub type HttpSocket = Client, BoxBody>; 11 | use crate::register::Resource; 12 | 13 | #[derive(Debug, Data)] 14 | pub struct InvokerAssets { 15 | resource: Resource, 16 | socket: Socket, 17 | } 18 | 19 | #[derive(Debug)] 20 | pub enum Socket { 21 | HTTP1(HttpSocket), 22 | HTTP2(HttpSocket), 23 | } 24 | 25 | impl Socket { 26 | pub fn new(protocol: Option<&str>) -> Self { 27 | let mut connector = HttpConnector::new(); 28 | connector.set_keepalive(Some(Duration::from_secs(90))); 29 | if protocol.is_some_and(|e| e.to_lowercase().contains("http2")) { 30 | Socket::HTTP2( 31 | Client::builder(hyper_util::rt::TokioExecutor::new()) 32 | .http2_only(true) 33 | .build(HttpsConnector::new_with_connector(connector)), 34 | ) 35 | } else { 36 | Socket::HTTP1( 37 | Client::builder(hyper_util::rt::TokioExecutor::new()) 38 | .build(HttpsConnector::new_with_connector(connector)), 39 | ) 40 | } 41 | } 42 | } 43 | 44 | impl InvokerAssets { 45 | pub fn new(resource: Resource, socket: Socket) -> Self { 46 | Self { resource, socket } 47 | } 48 | pub async fn send_request( 49 | &self, 50 | mut request: Request>, 51 | ) -> Result, FusenError> { 52 | match &self.socket { 53 | Socket::HTTP1(client) => { 54 | *request.version_mut() = Version::HTTP_11; 55 | send_http_request(client, &self.resource, request).await 56 | } 57 | Socket::HTTP2(client) => { 58 | *request.version_mut() = Version::HTTP_2; 59 | send_http_request(client, &self.resource, request).await 60 | } 61 | } 62 | } 63 | } 64 | 65 | async fn send_http_request( 66 | client: &Client, BoxBody>, 67 | resource: &Resource, 68 | mut request: Request>, 69 | ) -> Result, FusenError> { 70 | let org: &Uri = request.uri(); 71 | let temp_url: Uri = resource.get_addr().parse().unwrap(); 72 | let mut host = temp_url.host().unwrap().to_string(); 73 | if let Some(port) = temp_url.port_u16() { 74 | host.push_str(&format!(":{}", port)); 75 | } 76 | let new_uri = Uri::builder() 77 | .scheme(temp_url.scheme_str().unwrap_or("http")) 78 | .authority(host) 79 | .path_and_query(org.path_and_query().map_or("", |e| e.as_str())) 80 | .build()?; 81 | *request.uri_mut() = new_uri; 82 | let response = client.request(request).await.map_err(|e| { 83 | error!("error : {:?}", e); 84 | FusenError::from(e.to_string()) 85 | })?; 86 | Ok(response) 87 | } 88 | -------------------------------------------------------------------------------- /fusen/src/register/mod.rs: -------------------------------------------------------------------------------- 1 | use self::nacos::FusenNacos; 2 | use crate::protocol::socket::{InvokerAssets, Socket}; 3 | use fusen_common::{net::get_path, register::RegisterType, FusenFuture, MethodResource}; 4 | use fusen_procedural_macro::Data; 5 | use rand::{distributions::WeightedIndex, prelude::Distribution, thread_rng}; 6 | use serde::{Deserialize, Serialize}; 7 | use std::{collections::HashMap, sync::Arc}; 8 | use tokio::sync::{ 9 | mpsc::{self, UnboundedSender}, 10 | oneshot, 11 | }; 12 | pub mod nacos; 13 | 14 | pub struct RegisterBuilder { 15 | register_type: RegisterType, 16 | } 17 | 18 | impl RegisterBuilder { 19 | pub fn new(config_url: String) -> crate::Result { 20 | let info: Vec<&str> = config_url.split("://").collect(); 21 | if info[0] != "register" { 22 | return Err(format!("config url err is not register : {:?}", config_url).into()); 23 | } 24 | let info: Vec<&str> = info[1].split('?').collect(); 25 | let info = info[0].to_lowercase(); 26 | let register_type = if info.contains("nacos") { 27 | RegisterType::Nacos(config_url) 28 | } else { 29 | return Err(format!("config url err : {:?}", config_url).into()); 30 | }; 31 | Ok(RegisterBuilder { register_type }) 32 | } 33 | 34 | pub fn init(self, application_name: String) -> Box { 35 | match self.register_type { 36 | RegisterType::Nacos(url) => Box::new(FusenNacos::init(&url, application_name).unwrap()), 37 | } 38 | } 39 | } 40 | 41 | #[derive(Serialize, Deserialize, Debug, Clone, Data, Default)] 42 | pub struct Resource { 43 | server_name: String, 44 | category: Category, 45 | group: Option, 46 | version: Option, 47 | methods: Vec, 48 | host: String, 49 | port: Option, 50 | weight: Option, 51 | params: HashMap, 52 | } 53 | 54 | impl Resource { 55 | pub fn get_addr(&self) -> String { 56 | let mut host = self.host.clone(); 57 | if let Some(port) = &self.port { 58 | host.push(':'); 59 | host.push_str(port); 60 | } 61 | host 62 | } 63 | } 64 | 65 | #[derive(Serialize, Deserialize, Debug, Clone, Default)] 66 | pub enum Category { 67 | #[default] 68 | Client, 69 | Server, 70 | Service, 71 | } 72 | 73 | pub trait Register: Send + Sync { 74 | fn register(&self, resource: Resource) -> FusenFuture>; 75 | 76 | fn deregister(&self, resource: Resource) -> FusenFuture>; 77 | 78 | fn subscribe(&self, resource: Resource) -> FusenFuture>; 79 | } 80 | 81 | #[derive(Debug)] 82 | pub enum DirectorySender { 83 | GET, 84 | CHANGE(Vec), 85 | } 86 | 87 | pub enum DirectoryReceiver { 88 | GET(Arc), 89 | CHANGE, 90 | } 91 | 92 | #[derive(Clone, Debug)] 93 | pub struct Directory { 94 | sender: UnboundedSender<(DirectorySender, oneshot::Sender)>, 95 | } 96 | 97 | #[derive(Debug, Data)] 98 | pub struct ResourceInfo { 99 | dist: Option>, 100 | sockets: Vec>, 101 | } 102 | 103 | impl ResourceInfo { 104 | pub fn new(sockets: Vec>) -> Self { 105 | if sockets.is_empty() { 106 | Self { 107 | sockets, 108 | dist: None, 109 | } 110 | } else { 111 | let weights: Vec = sockets 112 | .iter() 113 | .map(|s| s.get_resource().get_weight().map_or(1_f64, |e| e)) 114 | .collect(); 115 | let dist = WeightedIndex::new(weights).unwrap(); 116 | Self { 117 | sockets, 118 | dist: Some(dist), 119 | } 120 | } 121 | } 122 | 123 | pub fn select(&self) -> Option> { 124 | self.dist.as_ref().map(|e| { 125 | self.sockets 126 | .get(e.sample(&mut thread_rng())) 127 | .cloned() 128 | .unwrap() 129 | }) 130 | } 131 | } 132 | 133 | impl Directory { 134 | pub async fn new(category: Category) -> Self { 135 | let (s, mut r) = 136 | mpsc::unbounded_channel::<(DirectorySender, oneshot::Sender)>(); 137 | tokio::spawn(async move { 138 | let mut cache: Arc = Arc::new(ResourceInfo::new(vec![])); 139 | while let Some(msg) = r.recv().await { 140 | match msg.0 { 141 | DirectorySender::GET => { 142 | let _ = msg.1.send(DirectoryReceiver::GET(cache.clone())); 143 | } 144 | DirectorySender::CHANGE(resources) => { 145 | let map = cache 146 | .get_sockets() 147 | .iter() 148 | .fold(HashMap::new(), |mut map, e| { 149 | let key = get_path( 150 | e.get_resource().get_host().clone(), 151 | e.get_resource().get_port().as_deref(), 152 | ); 153 | map.insert( 154 | format!("{}-{:?}", key, e.get_resource().get_weight()), 155 | e.clone(), 156 | ); 157 | map 158 | }); 159 | let mut res = vec![]; 160 | for item in resources { 161 | let key = get_path(item.host.clone(), item.port.as_deref()); 162 | res.push(match map.get(&format!("{}-{:?}", key, item.weight)) { 163 | Some(info) => info.clone(), 164 | None => Arc::new(InvokerAssets::new( 165 | item, 166 | Socket::new(if let Category::Service = category { 167 | Some("http2") 168 | } else { 169 | None 170 | }), 171 | )), 172 | }); 173 | } 174 | cache = Arc::new(ResourceInfo::new(res)); 175 | let _ = msg.1.send(DirectoryReceiver::CHANGE); 176 | } 177 | } 178 | } 179 | }); 180 | Self { sender: s } 181 | } 182 | 183 | pub async fn get(&self) -> Result, crate::Error> { 184 | let oneshot = oneshot::channel(); 185 | let _ = self.sender.send((DirectorySender::GET, oneshot.0)); 186 | let rev = oneshot.1.await.map_err(|e| e.to_string())?; 187 | match rev { 188 | DirectoryReceiver::GET(rev) => Ok(rev), 189 | DirectoryReceiver::CHANGE => Err("err receiver".into()), 190 | } 191 | } 192 | 193 | pub async fn change(&self, resource: Vec) -> Result<(), crate::Error> { 194 | let oneshot = oneshot::channel(); 195 | let _ = self 196 | .sender 197 | .send((DirectorySender::CHANGE(resource), oneshot.0)); 198 | let rev = oneshot.1.await.map_err(|e| e.to_string())?; 199 | match rev { 200 | DirectoryReceiver::GET(_) => Err("err receiver".into()), 201 | DirectoryReceiver::CHANGE => Ok(()), 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /fusen/src/register/nacos.rs: -------------------------------------------------------------------------------- 1 | use super::{Category, Directory, Register}; 2 | use crate::register::Resource; 3 | use fusen_common::FusenFuture; 4 | use fusen_procedural_macro::url_config; 5 | use nacos_sdk::api::{ 6 | naming::{ 7 | NamingChangeEvent, NamingEventListener, NamingService, NamingServiceBuilder, 8 | ServiceInstance, 9 | }, 10 | props::ClientProps, 11 | }; 12 | use std::{collections::HashMap, sync::Arc}; 13 | use tracing::{error, info}; 14 | 15 | #[derive(Clone)] 16 | pub struct FusenNacos { 17 | application_name: String, 18 | naming_service: Arc, 19 | config: Arc, 20 | } 21 | 22 | #[url_config(attr = register)] 23 | pub struct NacosConfig { 24 | server_addr: String, 25 | namespace: String, 26 | group: Option, 27 | username: String, 28 | password: String, 29 | } 30 | 31 | impl FusenNacos { 32 | pub fn init(url: &str, application_name: String) -> crate::Result { 33 | let mut client_props = ClientProps::new(); 34 | let config = NacosConfig::from_url(url)?; 35 | client_props = client_props 36 | .server_addr(config.server_addr.clone()) 37 | .namespace(config.namespace.clone()) 38 | .app_name(application_name.clone()) 39 | .auth_username(config.username.clone()) 40 | .auth_password(config.password.clone()); 41 | let builder = NamingServiceBuilder::new(client_props); 42 | let builder = if !config.username.is_empty() { 43 | builder.enable_auth_plugin_http() 44 | } else { 45 | builder 46 | }; 47 | let naming_service = Arc::new(builder.build()?); 48 | let config = Arc::new(config); 49 | let nacos = Self { 50 | application_name, 51 | naming_service: naming_service.clone(), 52 | config: config.clone(), 53 | }; 54 | Ok(nacos) 55 | } 56 | } 57 | 58 | impl Register for FusenNacos { 59 | fn register(&self, resource: super::Resource) -> FusenFuture> { 60 | let nacos = self.clone(); 61 | Box::pin(async move { 62 | let (nacos_service_name, group) = if let Category::Server = resource.category { 63 | (nacos.application_name.clone(), nacos.config.group.clone()) 64 | } else { 65 | (get_service_name(&resource), resource.group.clone()) 66 | }; 67 | let nacos_service_instance = 68 | get_instance(resource.host, resource.port.unwrap(), resource.params); 69 | info!("register service: {}", nacos_service_name); 70 | let ret = nacos 71 | .naming_service 72 | .register_instance(nacos_service_name, group, nacos_service_instance) 73 | .await; 74 | if let Err(e) = ret { 75 | error!("register to nacos occur an error: {:?}", e); 76 | return Err(format!("register to nacos occur an error: {:?}", e).into()); 77 | } 78 | Ok(()) 79 | }) 80 | } 81 | 82 | fn deregister(&self, resource: Resource) -> FusenFuture> { 83 | let nacos = self.clone(); 84 | Box::pin(async move { 85 | let (nacos_service_name, group) = if let Category::Server = resource.category { 86 | (nacos.application_name.clone(), nacos.config.group.clone()) 87 | } else { 88 | (get_service_name(&resource), resource.group.clone()) 89 | }; 90 | let nacos_service_instance = 91 | get_instance(resource.host, resource.port.unwrap(), resource.params); 92 | info!("deregister service: {}", nacos_service_name); 93 | let ret = nacos 94 | .naming_service 95 | .deregister_instance(nacos_service_name, group, nacos_service_instance) 96 | .await; 97 | if let Err(e) = ret { 98 | error!("deregister to nacos occur an error: {:?}", e); 99 | return Err(format!("deregister to nacos occur an error: {:?}", e).into()); 100 | } 101 | Ok(()) 102 | }) 103 | } 104 | 105 | fn subscribe(&self, resource: super::Resource) -> FusenFuture> { 106 | let nacos = self.clone(); 107 | Box::pin(async move { 108 | let nacos_service_name = if let Category::Server = &resource.category { 109 | get_application_name(&resource) 110 | } else { 111 | get_service_name(&resource) 112 | }; 113 | info!("subscribe service: {}", nacos_service_name); 114 | let directory = Directory::new(resource.category).await; 115 | let directory_clone = directory.clone(); 116 | let naming_service = nacos.naming_service.clone(); 117 | let service_instances = naming_service 118 | .get_all_instances( 119 | nacos_service_name.clone(), 120 | resource.group.clone(), 121 | Vec::new(), 122 | false, 123 | ) 124 | .await?; 125 | let service_instances = to_resources(service_instances); 126 | directory.change(service_instances).await?; 127 | let event_listener = ServiceChangeListener::new(directory); 128 | let event_listener = Arc::new(event_listener); 129 | naming_service 130 | .subscribe( 131 | nacos_service_name, 132 | resource.group, 133 | Vec::new(), 134 | event_listener, 135 | ) 136 | .await?; 137 | Ok(directory_clone) 138 | }) 139 | } 140 | } 141 | 142 | #[derive(Clone)] 143 | struct ServiceChangeListener { 144 | directory: Directory, 145 | } 146 | 147 | impl ServiceChangeListener { 148 | fn new(directory: Directory) -> Self { 149 | Self { directory } 150 | } 151 | } 152 | 153 | impl NamingEventListener for ServiceChangeListener { 154 | fn event(&self, event: Arc) { 155 | info!("service change: {}", event.service_name.clone()); 156 | info!("nacos event: {:?}", event); 157 | let directory = self.directory.clone(); 158 | let instances = event.instances.to_owned(); 159 | tokio::spawn(async move { 160 | let instances = instances; 161 | let resources = if let Some(instances) = instances { 162 | to_resources(instances) 163 | } else { 164 | vec![] 165 | }; 166 | let _ = directory.change(resources).await; 167 | }); 168 | } 169 | } 170 | 171 | fn to_resources(service_instances: Vec) -> Vec { 172 | service_instances.iter().fold(vec![], |mut vec, e| { 173 | let resource = Resource { 174 | server_name: e.service_name().unwrap().to_string(), 175 | category: Category::Server, 176 | group: e.metadata().get("group").cloned(), 177 | version: e.metadata().get("version").cloned(), 178 | methods: vec![], 179 | host: e.ip().to_string(), 180 | port: Some(e.port().to_string()), 181 | weight: Some(e.weight), 182 | params: e.metadata().clone(), 183 | }; 184 | vec.push(resource); 185 | vec 186 | }) 187 | } 188 | 189 | fn get_service_name(resource: &super::Resource) -> String { 190 | let category = "providers"; 191 | format!( 192 | "{}:{}:{}:{}", 193 | category, 194 | resource.server_name, 195 | resource.version.as_ref().map_or("", |e| e), 196 | resource.group.as_ref().map_or("", |e| e), 197 | ) 198 | } 199 | 200 | fn get_application_name(resource: &super::Resource) -> String { 201 | resource.server_name.clone() 202 | } 203 | 204 | fn get_instance(ip: String, port: String, params: HashMap) -> ServiceInstance { 205 | nacos_sdk::api::naming::ServiceInstance { 206 | ip, 207 | port: port.parse().unwrap(), 208 | metadata: params, 209 | ..Default::default() 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /fusen/src/route/client.rs: -------------------------------------------------------------------------------- 1 | use crate::register::{Category, Directory, Register, Resource, ResourceInfo}; 2 | use async_recursion::async_recursion; 3 | use fusen_common::FusenContext; 4 | use std::collections::HashMap; 5 | use std::sync::Arc; 6 | use tokio::sync::mpsc::{self, UnboundedSender}; 7 | use tokio::sync::oneshot; 8 | 9 | #[derive(Clone)] 10 | pub struct Route { 11 | register: Option>>, 12 | sender: UnboundedSender<(RouteSender, oneshot::Sender)>, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum RouteSender { 17 | GET(String), 18 | CHANGE((String, Directory)), 19 | } 20 | 21 | #[derive(Debug)] 22 | pub enum RouteReceiver { 23 | GET(Option), 24 | CHANGE, 25 | } 26 | 27 | impl Route { 28 | pub fn new(register: Option>>) -> Self { 29 | let (s, mut r) = mpsc::unbounded_channel::<(RouteSender, oneshot::Sender)>(); 30 | tokio::spawn(async move { 31 | let mut cache = HashMap::::new(); 32 | while let Some(msg) = r.recv().await { 33 | match msg.0 { 34 | RouteSender::GET(key) => { 35 | let _ = msg.1.send(RouteReceiver::GET(cache.get(&key).cloned())); 36 | } 37 | RouteSender::CHANGE(resources) => { 38 | cache.insert(resources.0, resources.1); 39 | let _ = msg.1.send(RouteReceiver::CHANGE); 40 | } 41 | } 42 | } 43 | }); 44 | Self { 45 | sender: s, 46 | register, 47 | } 48 | } 49 | 50 | #[async_recursion] 51 | pub async fn get_server_resource( 52 | &self, 53 | context: &FusenContext, 54 | ) -> crate::Result> { 55 | let name = context.get_context_info().get_class_name(); 56 | let version = context.get_context_info().get_version().as_ref(); 57 | let mut key = name.to_owned(); 58 | key.push_str(&format!(":{:?}", context.get_server_type())); 59 | if let Some(version) = version { 60 | key.push_str(&format!(":{}", version)); 61 | } 62 | let oneshot = oneshot::channel(); 63 | self.sender 64 | .send((RouteSender::GET(key.clone()), oneshot.0))?; 65 | let rev = oneshot.1.await.map_err(|e| e.to_string())?; 66 | match rev { 67 | RouteReceiver::GET(rev) => { 68 | if rev.is_none() { 69 | let category = match context.get_server_type() { 70 | fusen_common::register::Type::Dubbo => Category::Service, 71 | fusen_common::register::Type::SpringCloud => Category::Server, 72 | fusen_common::register::Type::Fusen => Category::Service, 73 | fusen_common::register::Type::Host(_) => Category::Server, 74 | }; 75 | let resource_server = Resource::default() 76 | .server_name(name.to_owned()) 77 | .category(category) 78 | .version(version.map(|e| e.to_owned())) 79 | .host(fusen_common::net::get_ip()) 80 | .params(context.get_meta_data().clone_map()); 81 | let directory = if let fusen_common::register::Type::Host(host) = 82 | context.get_server_type() 83 | { 84 | let directory = Directory::new(Category::Server).await; 85 | let resource_server = Resource::default() 86 | .server_name(name.to_owned()) 87 | .category(Category::Server) 88 | .host(host.clone()); 89 | let _ = directory.change(vec![resource_server]).await; 90 | directory 91 | } else if let Some(register) = &self.register { 92 | register.subscribe(resource_server).await? 93 | } else { 94 | return Err("must set register".into()); 95 | }; 96 | let oneshot = oneshot::channel(); 97 | self.sender 98 | .send((RouteSender::CHANGE((key, directory.clone())), oneshot.0))?; 99 | let rev = oneshot.1.await.map_err(|e| e.to_string())?; 100 | return match rev { 101 | RouteReceiver::GET(_) => Err("err receiver".into()), 102 | RouteReceiver::CHANGE => Ok(directory.get().await?), 103 | }; 104 | } 105 | let rev = rev.unwrap(); 106 | let info = rev.get().await?; 107 | return Ok(info); 108 | } 109 | RouteReceiver::CHANGE => return Err("err receiver".into()), 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /fusen/src/route/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod server; 3 | -------------------------------------------------------------------------------- /fusen/src/route/server.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use fusen_common::{ 3 | error::{BoxFusenError, FusenError}, 4 | FusenFuture, 5 | }; 6 | use http_body_util::{combinators::BoxBody, BodyExt, Full}; 7 | use hyper::{service::Service, Request, Response}; 8 | use std::{convert::Infallible, sync::Arc}; 9 | 10 | use crate::{ 11 | codec::{http_codec::FusenHttpCodec, HttpCodec}, 12 | filter::{FusenFilter, ProceedingJoinPoint}, 13 | handler::HandlerContext, 14 | }; 15 | 16 | #[derive(Clone)] 17 | pub struct FusenRouter { 18 | fusen_filter: &'static KF, 19 | http_codec: Arc, 20 | handler_context: Arc, 21 | } 22 | 23 | impl FusenRouter 24 | where 25 | KF: FusenFilter, 26 | { 27 | pub fn new( 28 | fusen_filter: &'static KF, 29 | http_codec: Arc, 30 | handler_context: Arc, 31 | ) -> Self { 32 | FusenRouter { 33 | fusen_filter, 34 | http_codec, 35 | handler_context, 36 | } 37 | } 38 | 39 | async fn call( 40 | request: Request, 41 | http_codec: Arc, 42 | fusen_filter: &'static KF, 43 | handler_context: Arc, 44 | ) -> Result>, FusenError> { 45 | let request = request.map(|e| e.boxed()); 46 | let context = http_codec.decode(request).await?; 47 | let mut handler = handler_context 48 | .get_controller(&context.get_context_info().get_handler_key()) 49 | .get_aspect(); 50 | handler.push_back(fusen_filter); 51 | let join_point = ProceedingJoinPoint::new(handler, context); 52 | let context = join_point.proceed().await?; 53 | let response = http_codec.encode(context).await?; 54 | Ok(response) 55 | } 56 | } 57 | 58 | impl Service> for FusenRouter 59 | where 60 | KF: FusenFilter, 61 | { 62 | type Response = Response>; 63 | type Error = BoxFusenError; 64 | type Future = FusenFuture>; 65 | 66 | fn call(&self, req: Request) -> Self::Future { 67 | let fusen_filter = self.fusen_filter; 68 | let http_codec = self.http_codec.clone(); 69 | let handler_context = self.handler_context.clone(); 70 | 71 | Box::pin(async move { 72 | Ok( 73 | match Self::call(req, http_codec, fusen_filter, handler_context).await { 74 | Ok(response) => response, 75 | Err(fusen_error) => { 76 | let mut status = 500; 77 | if let FusenError::NotFind = fusen_error { 78 | status = 404; 79 | } 80 | Response::builder() 81 | .status(status) 82 | .body( 83 | Full::new(Bytes::copy_from_slice( 84 | format!("{:?}", fusen_error).as_bytes(), 85 | )) 86 | .boxed(), 87 | ) 88 | .unwrap() 89 | } 90 | }, 91 | ) 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /fusen/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::{handler::HandlerContext, protocol::server::TcpServer, support::shutdown::Shutdown}; 2 | use fusen_common::server::RpcServer; 3 | use fusen_procedural_macro::Data; 4 | use std::{collections::HashMap, sync::Arc}; 5 | 6 | #[derive(Default, Data)] 7 | pub struct FusenServer { 8 | port: Option, 9 | fusen_servers: HashMap, 10 | handler_context: Arc, 11 | } 12 | 13 | impl FusenServer { 14 | pub fn new( 15 | port: Option, 16 | servers: HashMap>, 17 | handler_context: Arc, 18 | ) -> FusenServer { 19 | let mut fusen_servers: HashMap = HashMap::new(); 20 | for (key, server) in servers { 21 | fusen_servers.insert(key, Box::leak(server)); 22 | } 23 | FusenServer { 24 | port, 25 | fusen_servers, 26 | handler_context, 27 | } 28 | } 29 | 30 | pub async fn run(&mut self, shutdown: Shutdown) -> tokio::sync::mpsc::Receiver<()> { 31 | let tcp_server = TcpServer::init( 32 | self.port.as_ref().expect("not set server port").clone(), 33 | self.fusen_servers.clone(), 34 | ); 35 | tcp_server.run(shutdown, self.handler_context.clone()).await 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fusen/src/support/compression.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::collections::HashMap; 19 | 20 | use bytes::{Buf, BufMut, BytesMut}; 21 | use flate2::{ 22 | read::{GzDecoder, GzEncoder}, 23 | Compression, 24 | }; 25 | use lazy_static::lazy_static; 26 | 27 | pub const GRPC_ACCEPT_ENCODING: &str = "grpc-accept-encoding"; 28 | pub const GRPC_ENCODING: &str = "grpc-encoding"; 29 | 30 | #[derive(Debug, Clone, Copy)] 31 | pub enum CompressionEncoding { 32 | Gzip, 33 | } 34 | 35 | lazy_static! { 36 | pub static ref COMPRESSIONS: HashMap> = { 37 | let mut v = HashMap::new(); 38 | v.insert("gzip".to_string(), Some(CompressionEncoding::Gzip)); 39 | v 40 | }; 41 | } 42 | 43 | impl CompressionEncoding { 44 | pub fn from_accept_encoding(header: &http::HeaderMap) -> Option { 45 | let accept_encoding = header.get(GRPC_ACCEPT_ENCODING)?; 46 | let encodings = accept_encoding.to_str().ok()?; 47 | 48 | encodings 49 | .trim() 50 | .split(',') 51 | .map(|s| s.trim()) 52 | .into_iter() 53 | .find_map(|s| match s { 54 | "gzip" => Some(CompressionEncoding::Gzip), 55 | _ => None, 56 | }) 57 | } 58 | 59 | pub fn into_header_value(self) -> http::HeaderValue { 60 | match self { 61 | CompressionEncoding::Gzip => http::HeaderValue::from_static("gzip"), 62 | } 63 | } 64 | } 65 | 66 | pub fn compress( 67 | encoding: CompressionEncoding, 68 | src: &mut BytesMut, 69 | dst: &mut BytesMut, 70 | len: usize, 71 | ) -> Result<(), std::io::Error> { 72 | dst.reserve(len); 73 | 74 | match encoding { 75 | CompressionEncoding::Gzip => { 76 | let mut en = GzEncoder::new(src.reader(), Compression::default()); 77 | 78 | let mut dst_writer = dst.writer(); 79 | 80 | std::io::copy(&mut en, &mut dst_writer)?; 81 | } 82 | } 83 | 84 | Ok(()) 85 | } 86 | 87 | pub fn decompress( 88 | encoding: CompressionEncoding, 89 | src: &mut BytesMut, 90 | dst: &mut BytesMut, 91 | len: usize, 92 | ) -> Result<(), std::io::Error> { 93 | let capacity = len * 2; 94 | dst.reserve(capacity); 95 | 96 | match encoding { 97 | CompressionEncoding::Gzip => { 98 | let mut de = GzDecoder::new(src.reader()); 99 | 100 | let mut dst_writer = dst.writer(); 101 | 102 | std::io::copy(&mut de, &mut dst_writer)?; 103 | } 104 | } 105 | Ok(()) 106 | } 107 | 108 | #[test] 109 | fn test_compress() { 110 | let mut src = BytesMut::with_capacity(super::consts::BUFFER_SIZE); 111 | src.put(&b"test compress"[..]); 112 | let mut dst = BytesMut::new(); 113 | let len = src.len(); 114 | src.reserve(len); 115 | 116 | compress(CompressionEncoding::Gzip, &mut src, &mut dst, len).unwrap(); 117 | info!("src: {:?}, dst: {:?}", src, dst); 118 | 119 | let mut de_dst = BytesMut::with_capacity(super::consts::BUFFER_SIZE); 120 | let de_len = dst.len(); 121 | decompress(CompressionEncoding::Gzip, &mut dst, &mut de_dst, de_len).unwrap(); 122 | 123 | info!("src: {:?}, dst: {:?}", dst, de_dst); 124 | } 125 | -------------------------------------------------------------------------------- /fusen/src/support/dubbo.rs: -------------------------------------------------------------------------------- 1 | use crate::register::{Category, Resource}; 2 | use fusen_common::MethodResource; 3 | use std::vec; 4 | 5 | pub fn decode_url(url: &str) -> Result { 6 | let url = &fusen_common::url::decode_url(url)?[..]; 7 | get_info(url) 8 | } 9 | 10 | pub fn encode_url(resource: &Resource) -> String { 11 | let mut url = String::new(); 12 | match resource.get_category() { 13 | Category::Client => url.push_str("consumer://"), 14 | Category::Service => url.push_str("tri://"), 15 | Category::Server => (), 16 | } 17 | url.push_str(&(get_path(resource) + "/")); 18 | url.push_str(&(resource.get_server_name().to_owned() + "?")); 19 | url.push_str(&("interface=".to_owned() + resource.get_server_name())); 20 | url.push_str(&get_field_url( 21 | "&methods", 22 | &resource.get_methods().iter().fold(vec![], |mut vec, e| { 23 | vec.push(e.get_path()[1..].to_owned()); 24 | vec 25 | }), 26 | )); 27 | if let Some(version) = resource.get_version() { 28 | let value = vec![version.clone()]; 29 | url.push_str(&get_field_url("&version", &value)); 30 | } 31 | match resource.get_category() { 32 | Category::Client => url.push_str("&dubbo=2.0.2&release=3.3.0-beta.1&side=consumer"), 33 | Category::Service => url.push_str( 34 | "&dubbo=2.0.2&prefer.serialization=fastjson&release=3.3.0-beta.1&side=provider", 35 | ), 36 | _ => (), 37 | } 38 | "/".to_string() + &fusen_common::url::encode_url(&url) 39 | } 40 | 41 | fn get_ip(path: &str) -> (String, Option) { 42 | let path: Vec<&str> = path.split(':').collect(); 43 | let mut port = None; 44 | if path.len() > 1 { 45 | let _ = port.insert(path[1].to_string()); 46 | } 47 | (path[0].to_string(), port) 48 | } 49 | 50 | fn get_path(info: &Resource) -> String { 51 | let mut host = info.get_host().clone(); 52 | if let Some(port) = info.get_port().clone() { 53 | host.push(':'); 54 | host.push_str(&port); 55 | } 56 | host.to_string() 57 | } 58 | 59 | fn get_info(mut url: &str) -> crate::Result { 60 | let mut category = Category::Server; 61 | if url.starts_with("tri://") { 62 | url = &url[6..]; 63 | } else if url.starts_with("consumer://") { 64 | url = &url[11..]; 65 | category = Category::Client; 66 | } else { 67 | return Err(format!("err url : {}", url).into()); 68 | } 69 | let info: Vec<&str> = url.split('/').collect(); 70 | let path = get_ip(info[0]); 71 | let info: Vec<&str> = info[1].split('?').collect(); 72 | let server_name = info[0].to_string(); 73 | let vision = get_field_values(info[1], "version"); 74 | let mut revision = None; 75 | if !vision.is_empty() { 76 | let _ = revision.insert(vision[0].clone()); 77 | } 78 | let group = get_field_values(info[1], "group"); 79 | let mut regroup = None; 80 | if !group.is_empty() { 81 | let _ = regroup.insert(vision[0].clone()); 82 | } 83 | let info = Resource::default() 84 | .server_name(server_name) 85 | .category(category) 86 | .group(regroup) 87 | .version(revision) 88 | .methods( 89 | get_field_values(info[1], "methods") 90 | .iter() 91 | .fold(vec![], |mut vec, e| { 92 | vec.push(MethodResource::new( 93 | e.to_string(), 94 | "/".to_owned() + e, 95 | "POST".to_owned(), 96 | )); 97 | vec 98 | }), 99 | ) 100 | .host(path.0) 101 | .port(path.1); 102 | Ok(info) 103 | } 104 | 105 | fn get_field_values(str: &str, key: &str) -> Vec { 106 | let fields: Vec<&str> = str.split('&').collect(); 107 | let mut res = vec![]; 108 | for field in fields { 109 | let field: Vec<&str> = field.split('=').collect(); 110 | if field[0] == key { 111 | let velues: Vec<&str> = field[1].split(',').collect(); 112 | res = velues.iter().fold(res, |mut res, &e| { 113 | res.push(e.to_string()); 114 | res 115 | }); 116 | break; 117 | } 118 | } 119 | res 120 | } 121 | 122 | fn get_field_url(key: &str, values: &Vec) -> String { 123 | if values.is_empty() { 124 | return String::new(); 125 | } 126 | let mut res = String::new(); 127 | for value in values { 128 | res.push_str(&(value.to_owned() + ",")); 129 | } 130 | key.to_string() + "=" + &res[..res.len() - 1] 131 | } 132 | -------------------------------------------------------------------------------- /fusen/src/support/grpc.rs: -------------------------------------------------------------------------------- 1 | /// The `Status` type defines a logical error model that is suitable for different 2 | /// programming environments, including REST APIs and RPC APIs. It is used by 3 | /// \[gRPC\](). The error model is designed to be: 4 | /// 5 | /// - Simple to use and understand for most users 6 | /// - Flexible enough to meet unexpected needs 7 | /// 8 | /// # Overview 9 | /// 10 | /// The `Status` message contains three pieces of data: error code, error message, 11 | /// and error details. The error code should be an enum value of 12 | /// \[google.rpc.Code][google.rpc.Code\], but it may accept additional error codes if needed. The 13 | /// error message should be a developer-facing English message that helps 14 | /// developers *understand* and *resolve* the error. If a localized user-facing 15 | /// error message is needed, put the localized message in the error details or 16 | /// localize it in the client. The optional error details may contain arbitrary 17 | /// information about the error. There is a predefined set of error detail types 18 | /// in the package `google.rpc` which can be used for common error conditions. 19 | /// 20 | /// # Language mapping 21 | /// 22 | /// The `Status` message is the logical representation of the error model, but it 23 | /// is not necessarily the actual wire format. When the `Status` message is 24 | /// exposed in different client libraries and different wire protocols, it can be 25 | /// mapped differently. For example, it will likely be mapped to some exceptions 26 | /// in Java, but more likely mapped to some error codes in C. 27 | /// 28 | /// # Other uses 29 | /// 30 | /// The error model and the `Status` message can be used in a variety of 31 | /// environments, either with or without APIs, to provide a 32 | /// consistent developer experience across different environments. 33 | /// 34 | /// Example uses of this error model include: 35 | /// 36 | /// - Partial errors. If a service needs to return partial errors to the client, 37 | /// it may embed the `Status` in the normal response to indicate the partial 38 | /// errors. 39 | /// 40 | /// - Workflow errors. A typical workflow has multiple steps. Each step may 41 | /// have a `Status` message for error reporting purpose. 42 | /// 43 | /// - Batch operations. If a client uses batch request and batch response, the 44 | /// `Status` message should be used directly inside batch response, one for 45 | /// each error sub-response. 46 | /// 47 | /// - Asynchronous operations. If an API call embeds asynchronous operation 48 | /// results in its response, the status of those operations should be 49 | /// represented directly using the `Status` message. 50 | /// 51 | /// - Logging. If some API errors are stored in logs, the message `Status` could 52 | /// be used directly after any stripping needed for security/privacy reasons. 53 | #[allow(clippy::derive_partial_eq_without_eq)] 54 | #[derive(Clone, PartialEq, ::prost::Message)] 55 | pub struct Status { 56 | /// The status code, which should be an enum value of \[google.rpc.Code][google.rpc.Code\]. 57 | #[prost(int32, tag = "1")] 58 | pub code: i32, 59 | /// A developer-facing error message, which should be in English. Any 60 | /// user-facing error message should be localized and sent in the 61 | /// \[google.rpc.Status.details][google.rpc.Status.details\] field, or localized by the client. 62 | #[prost(string, tag = "2")] 63 | pub message: ::prost::alloc::string::String, 64 | /// A list of messages that carry the error details. There will be a 65 | /// common set of message types for APIs to use. 66 | #[prost(message, repeated, tag = "3")] 67 | pub details: ::prost::alloc::vec::Vec<::prost_types::Any>, 68 | } 69 | -------------------------------------------------------------------------------- /fusen/src/support/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dubbo; 2 | pub mod shutdown; 3 | pub mod triple; 4 | -------------------------------------------------------------------------------- /fusen/src/support/shutdown.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::broadcast; 2 | 3 | #[derive(Debug)] 4 | pub struct Shutdown { 5 | shutdown: bool, 6 | notify: broadcast::Receiver<()>, 7 | } 8 | 9 | impl Shutdown { 10 | pub fn new(notify: broadcast::Receiver<()>) -> Self { 11 | Shutdown { 12 | shutdown: false, 13 | notify, 14 | } 15 | } 16 | } 17 | 18 | impl Shutdown { 19 | pub fn is_shutdown(&self) -> bool { 20 | self.shutdown 21 | } 22 | 23 | pub fn _shutdown(&mut self) { 24 | self.shutdown = true; 25 | } 26 | 27 | pub async fn recv(&mut self) { 28 | if self.is_shutdown() { 29 | return; 30 | } 31 | let _ = self.notify.recv().await; 32 | self.shutdown = true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fusen/src/support/triple.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | use prost::Message; 3 | 4 | #[allow(clippy::derive_partial_eq_without_eq)] 5 | #[derive(Clone, PartialEq, ::prost::Message)] 6 | pub struct TripleRequestWrapper { 7 | /// hessian4 8 | /// json 9 | #[prost(string, tag = "1")] 10 | pub serialize_type: ::prost::alloc::string::String, 11 | #[prost(bytes = "vec", repeated, tag = "2")] 12 | pub args: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec>, 13 | #[prost(string, repeated, tag = "3")] 14 | pub arg_types: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, 15 | } 16 | #[allow(clippy::derive_partial_eq_without_eq)] 17 | #[derive(Clone, PartialEq, ::prost::Message)] 18 | pub struct TripleResponseWrapper { 19 | #[prost(string, tag = "1")] 20 | pub serialize_type: ::prost::alloc::string::String, 21 | #[prost(bytes = "vec", tag = "2")] 22 | pub data: ::prost::alloc::vec::Vec, 23 | #[prost(string, tag = "3")] 24 | pub r#type: ::prost::alloc::string::String, 25 | } 26 | #[allow(clippy::derive_partial_eq_without_eq)] 27 | #[derive(Clone, PartialEq, ::prost::Message)] 28 | pub struct TripleExceptionWrapper { 29 | #[prost(string, tag = "1")] 30 | pub language: ::prost::alloc::string::String, 31 | #[prost(string, tag = "2")] 32 | pub serialization: ::prost::alloc::string::String, 33 | #[prost(string, tag = "3")] 34 | pub class_name: ::prost::alloc::string::String, 35 | #[prost(bytes = "vec", tag = "4")] 36 | pub data: ::prost::alloc::vec::Vec, 37 | } 38 | 39 | impl TripleRequestWrapper { 40 | pub fn from(strs: Vec) -> Self { 41 | let mut trip = TripleRequestWrapper { 42 | serialize_type: "fastjson".to_string(), 43 | args: Default::default(), 44 | arg_types: Default::default(), 45 | }; 46 | trip.args = vec![]; 47 | for str in strs { 48 | trip.args.push(str.as_bytes().to_vec()); 49 | } 50 | trip 51 | } 52 | pub fn get_body(self) -> Bytes { 53 | let mut res = vec![]; 54 | for str in self.args { 55 | res.push(String::from_utf8(str).unwrap()); 56 | } 57 | Bytes::copy_from_slice(serde_json::to_string(&res).unwrap().as_bytes()) 58 | } 59 | } 60 | 61 | impl TripleResponseWrapper { 62 | pub fn form(data: Vec) -> Self { 63 | let mut trip = TripleResponseWrapper { 64 | serialize_type: "fastjson".to_string(), 65 | data: Default::default(), 66 | r#type: Default::default(), 67 | }; 68 | trip.data = data; 69 | trip 70 | } 71 | pub fn is_empty_body(&self) -> bool { 72 | self.data.starts_with("null".as_bytes()) 73 | } 74 | } 75 | 76 | impl TripleExceptionWrapper { 77 | pub fn get_buf(strs: String) -> Vec { 78 | let mut trip = TripleExceptionWrapper { 79 | language: Default::default(), 80 | serialization: "fastjson".to_string(), 81 | class_name: Default::default(), 82 | data: Default::default(), 83 | }; 84 | trip.data = strs.as_bytes().to_vec(); 85 | get_buf(trip.encode_to_vec()) 86 | } 87 | pub fn get_err_info(&self) -> String { 88 | serde_json::from_slice(&self.data[..]).unwrap_or("rpc error".to_owned()) 89 | } 90 | } 91 | 92 | pub fn get_buf(mut data: Vec) -> Vec { 93 | let mut len = data.len(); 94 | let mut u8_array = vec![0_u8; 5]; 95 | for idx in (1..5).rev() { 96 | u8_array[idx] = len as u8; 97 | len >>= 8; 98 | } 99 | u8_array.append(&mut data); 100 | u8_array 101 | } 102 | --------------------------------------------------------------------------------