├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE ├── README.md ├── README.zh.md ├── TODO.md ├── docs ├── .vuepress │ └── config.js ├── README.md ├── en_us │ ├── 0_Startup.md │ ├── 1_HttpClient.md │ ├── 2_WebsocketClient.md │ ├── 3_TcpSslClient.md │ ├── 4_HttpServer.md │ ├── 5_TcpSslServer.md │ ├── 6_Other.md │ └── README.md ├── package.json └── zh_hans │ ├── 0_Startup.md │ ├── 1_HttpClient.md │ ├── 2_WebsocketClient.md │ ├── 3_TcpSslClient.md │ ├── 4_HttpServer.md │ ├── 5_TcpSslServer.md │ ├── 6_Other.md │ └── README.md ├── include └── fv │ ├── common.hpp │ ├── common_funcs.hpp │ ├── conn.hpp │ ├── conn_impl.hpp │ ├── declare.hpp │ ├── fv.h │ ├── ioctx_pool.hpp │ ├── req_res.hpp │ ├── req_res_impl.hpp │ ├── server.hpp │ ├── session.hpp │ └── structs.hpp ├── libfv.sln └── libfv_test ├── CMakeLists.txt ├── libfv_test.cpp ├── libfv_test.vcxproj └── libfv_test.vcxproj.filters /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | 353 | 354 | [Oo]ut/ 355 | node_modules/ 356 | dist/ 357 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.8) 2 | 3 | project ("libfv") 4 | 5 | include_directories (include) 6 | file (GLOB HEADERS include/fv/*.h include/fv/*.hpp) 7 | add_library(fv ${HEADERS}) 8 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ "msvc_x64_x64" ], 8 | "buildRoot": "${projectDir}\\out\\build\\${name}", 9 | "installRoot": "${projectDir}\\out\\install\\${name}", 10 | "buildCommandArgs": "", 11 | "ctestCommandArgs": "", 12 | "cmakeToolchain": "D:/GitHub/vcpkg/scripts/buildsystems/vcpkg.cmake" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Fawdlstty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libfv 2 | 3 | English | [简体中文](./README.zh.md) 4 | 5 | libfv is C++20 header-only network library, support TCP/SSL/Http/websocket server and client 6 | 7 | You can use pure asynchronous networking with it, or you can use asynchronous wrappers without networking at all and support asynchronous development in your projects. 8 | 9 | In addition to providing network functions, the library also provides a variety of asynchronous tools, such as timers, semaphores, etc. 10 | 11 | 技术交流:[点击链接加入群【1018390466】](https://jq.qq.com/?_wv=1027&k=7ZQLihbT) 12 | 13 | ## Document 14 | 15 | [English Document](https://libfv.fawdlstty.com/en_us/) 16 | 17 | [简体中文文档](https://libfv.fawdlstty.com/zh_hans/) 18 | 19 | [Github Online Document](docs/) 20 | 21 | ## Description 22 | 23 | Compared with other network libraries, libfv's biggest advantage is that it supports pure asynchronous development mode. C++ is a very generational language for developers, mainly because C++20 introduced the asynchronous coroutine syntax, and the support for libraries has been slow to catch up, leaving asynchronous development options limited. libfv is one of the options of the new C++ asynchronous coroutine network development framework, which can make asynchronous coroutine development more pleasant from the library level. 24 | 25 | ## Why libfv 26 | 27 | The older HTTP libraries of C++ have two main implementations. The first is synchronous HTTP network access, such as code like this: 28 | 29 | ```cpp 30 | // pseudocode 31 | Response _r = HttpGet ("https://t.cn"); 32 | std::cout << _t.text; 33 | ``` 34 | 35 | Such code is simple to write, but there is a problem with it: HTTP network access is time-consuming, perhaps hundreds of milliseconds, so long that the thread will block here, consuming thread resources. If you need to initiate dozens or hundreds of requests at the same time, the system resources will be consumed. Obviously, it's not a good design. 36 | 37 | The second is callback notification, such as code like this: 38 | 39 | ```cpp 40 | // pseudocode 41 | HttpGet ("https://t.cn", [] (Response _r) { 42 | std::cout << _t.text; 43 | }); 44 | ``` 45 | 46 | This way to solve the threading problem, that is, dozens or hundreds of requests can be launched at the same time, only a small amount or a thread on the line, HTTP library internal implementation of the request internal management, after receiving the response to the request, call the callback function, so as to achieve efficient processing of the request. 47 | 48 | The problem with this approach is that if we need to forward the content of the request to the next request, this can cause a callback hell problem, such as code like this: 49 | 50 | ```cpp 51 | // pseudocode 52 | HttpGet ("https://t.cn", [] (Response _r) { 53 | HttpGet (_t.text, [] (Response _r) { 54 | HttpGet (_t.text, [] (Response _r) { 55 | HttpGet (_t.text, [] (Response _r) { 56 | HttpGet (_t.text, [] (Response _r) { 57 | std::cout << _t.text; 58 | }); 59 | }); 60 | }); 61 | }); 62 | }); 63 | ``` 64 | 65 | So, what are the improvements in libfv? Look at the code below: 66 | 67 | ```cpp 68 | fv::Response _r = co_await fv::Get ("https://t.cn"); 69 | ``` 70 | 71 | On the one hand it gets the benefit of the callback approach, where a small number of threads support a large number of requests at the same time, without the problem of callback hell. The above code is implemented via libfv and can be written as follows: 72 | 73 | ```cpp 74 | fv::Response _r = co_await fv::Get ("https://t.cn"); 75 | _r = co_await fv::Get (_r.text); 76 | _r = co_await fv::Get (_r.text); 77 | _r = co_await fv::Get (_r.text); 78 | _r = co_await fv::Get (_r.text); 79 | std::cout << _t.text; 80 | ``` 81 | 82 | Request both interfaces at the same time and concatenate the results. How was it handled before? The code looks like this: 83 | 84 | ```cpp 85 | // synchronize pseudocode 86 | std::string _ret1, _ret2; 87 | std::thread _t1 ([&_ret1] () { 88 | _ret1 = HttpGet ("https://t.cn/1"); 89 | }); 90 | std::thread _t2 ([&_ret2] () { 91 | _ret2 = HttpGet ("https://t.cn/2"); 92 | }); 93 | _t1.join (); 94 | _t2.join (); 95 | std::string _ret = _ret1 + _ret2; 96 | 97 | 98 | // callback pseudocode 99 | std::string _ret1 = "", _ret2 = ""; 100 | bool _bret1 = false, _bret2 = false; 101 | HttpGet ("https://t.cn/1", [&_ret1, &_bret1] (std::string _r) { 102 | _ret1 = _r; 103 | _bret1 = true; 104 | }); 105 | HttpGet ("https://t.cn/2", [&_ret2, &_bret2] (std::string _r) { 106 | _ret2 = _r; 107 | _bret2 = true; 108 | }); 109 | while (!_bret1 || !_bret2) 110 | std::this_thread::sleep_for (std::chrono::milliseconds (1)); 111 | std::string _ret = _ret1 + _ret2; 112 | ``` 113 | 114 | Use libfv: 115 | 116 | ```cpp 117 | Task _ret1 = fv::Get ("https://t.cn/1"); 118 | Task _ret2 = fv::Get ("https://t.cn/2"); 119 | std::string _ret = (co_await _ret1) + (co_await _ret2); 120 | ``` 121 | 122 | Not only is the code much simpler, it also saves extra thread creation costs. 123 | 124 | ## License 125 | 126 | MIT 127 | -------------------------------------------------------------------------------- /README.zh.md: -------------------------------------------------------------------------------- 1 | # libfv 2 | 3 | [English](./README.md) | 简体中文 4 | 5 | libfv 是 C++20 纯头文件网络库,支持 TCP/SSL/Http/websocket 服务器端与客户端 6 | 7 | 你可以通过它使用纯异步的网络功能,当然你也能完全不使用网络,仅使用异步包装功能,让你的项目支持异步开发。 8 | 9 | 库除了提供网络功能外,还提供多种异步工具,比如定时器、信号量等。 10 | 11 | 技术交流:[点击链接加入群【1018390466】](https://jq.qq.com/?_wv=1027&k=7ZQLihbT) 12 | 13 | ## 文档 14 | 15 | [简体中文文档](https://libfv.fawdlstty.com/zh_hans/) 16 | 17 | [English Document](https://libfv.fawdlstty.com/en_us/) 18 | 19 | [Github 在线文档](docs/) 20 | 21 | ## 项目描述 22 | 23 | libfv 相对于其他网络库来说,最大的优势是支持纯异步的开发方式。C++是一个非常有年代感的语言,对于开发者来说,主要体现在,C++20出了异步协程语法后,支持的库迟迟没能跟上,使得异步开发选择范围很少。libfv 为新C++的异步协程网络开发框架的选项之一,可以从库的层面使得异步协程开发更为轻松愉快。 24 | 25 | ## 选择 libfv 的理由 26 | 27 | C++ 较老的 HTTP 库有两种主要的实现方式,第一种是同步 HTTP 网络访问,比如这样的代码: 28 | 29 | ```cpp 30 | // 伪代码 31 | Response _r = HttpGet ("https://t.cn"); 32 | std::cout << _t.text; 33 | ``` 34 | 35 | 这样的代码写起来很简单,但它存在一个问题:HTTP 网络访问比较耗时,可能需要几百毫秒,这么长时间,这个线程将阻塞在这里,比较消耗线程资源。假如遇到需要同时发起几十、几百个请求,将较大消耗系统资源。很显然,它不是一个较好的设计。 36 | 37 | 第二种是回调通知,比如这样的代码: 38 | 39 | ```cpp 40 | // 伪代码 41 | HttpGet ("https://t.cn", [] (Response _r) { 42 | std::cout << _t.text; 43 | }); 44 | ``` 45 | 46 | 这种方式解决了线程问题,也就是,几十、几百个请求可以同时发起,只需要极少量或者一个线程就行,HTTP 库内部实现了请求的内部管理,在收到请求的回复后,调用回调函数,从而实现请求的高效处理。 47 | 48 | 但这种方式有个问题,假如我们需要根据请求结果内容转给下一个请求,这会带来一个回调地狱问题,比如这样的代码: 49 | 50 | ```cpp 51 | // 伪代码 52 | HttpGet ("https://t.cn", [] (Response _r) { 53 | HttpGet (_t.text, [] (Response _r) { 54 | HttpGet (_t.text, [] (Response _r) { 55 | HttpGet (_t.text, [] (Response _r) { 56 | HttpGet (_t.text, [] (Response _r) { 57 | std::cout << _t.text; 58 | }); 59 | }); 60 | }); 61 | }); 62 | }); 63 | ``` 64 | 65 | 那么,libfv 有哪些改进呢?看下面的代码 66 | 67 | ```cpp 68 | fv::Response _r = co_await fv::Get ("https://t.cn"); 69 | ``` 70 | 71 | 一方面它能获得回调方式的好处,也就是少量线程支撑同时大量的请求任务,同时它不会带来回调地狱问题。上面的代码通过libfv实现,代码可以这样写: 72 | 73 | ```cpp 74 | fv::Response _r = co_await fv::Get ("https://t.cn"); 75 | _r = co_await fv::Get (_r.text); 76 | _r = co_await fv::Get (_r.text); 77 | _r = co_await fv::Get (_r.text); 78 | _r = co_await fv::Get (_r.text); 79 | std::cout << _t.text; 80 | ``` 81 | 82 | 同时请求两个接口,并将结果拼接。以前怎样处理?代码大概如下: 83 | 84 | ```cpp 85 | // 同步伪代码 86 | std::string _ret1, _ret2; 87 | std::thread _t1 ([&_ret1] () { 88 | _ret1 = HttpGet ("https://t.cn/1"); 89 | }); 90 | std::thread _t2 ([&_ret2] () { 91 | _ret2 = HttpGet ("https://t.cn/2"); 92 | }); 93 | _t1.join (); 94 | _t2.join (); 95 | std::string _ret = _ret1 + _ret2; 96 | 97 | 98 | // 回调伪代码 99 | std::string _ret1 = "", _ret2 = ""; 100 | bool _bret1 = false, _bret2 = false; 101 | HttpGet ("https://t.cn/1", [&_ret1, &_bret1] (std::string _r) { 102 | _ret1 = _r; 103 | _bret1 = true; 104 | }); 105 | HttpGet ("https://t.cn/2", [&_ret2, &_bret2] (std::string _r) { 106 | _ret2 = _r; 107 | _bret2 = true; 108 | }); 109 | while (!_bret1 || !_bret2) 110 | std::this_thread::sleep_for (std::chrono::milliseconds (1)); 111 | std::string _ret = _ret1 + _ret2; 112 | ``` 113 | 114 | libfv 处理方式: 115 | 116 | ```cpp 117 | Task _ret1 = fv::Get ("https://t.cn/1"); 118 | Task _ret2 = fv::Get ("https://t.cn/2"); 119 | std::string _ret = (co_await _ret1) + (co_await _ret2); 120 | ``` 121 | 122 | 不仅代码简单很多,还节省了额外的创建线程成本。 123 | 124 | ## License 125 | 126 | MIT 127 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - Multithreading 4 | - UDP Server/Client 5 | - SSL Server/Https Server 6 | - Cancellation 7 | - Transfer progress callback 8 | - HTTP breakpoint resume (server/client) 9 | - HTTP pipeline multi request support 10 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | locales: { 3 | '/en_us/': { 4 | lang: 'English', 5 | title: 'libfv document', 6 | description: 'libfv is C++20 header-only network library, support TCP/SSL/Http/websocket server and client' 7 | }, 8 | '/zh_hans/': { 9 | lang: '简体中文', 10 | title: 'libfv 文档', 11 | description: 'libfv 是 C++20 纯头文件网络库,支持 TCP/SSL/Http/websocket 服务器端与客户端' 12 | } 13 | }, 14 | themeConfig: { 15 | nav: [ 16 | { text: 'Github', link: 'https://github.com/fawdlstty/libfv' } 17 | ], 18 | locales: { 19 | '/en_us/': { 20 | sidebar: [{ 21 | title: 'Home', 22 | path: '/en_us/', 23 | children: [] 24 | }, { 25 | title: 'Startup', 26 | path: '/en_us/0_Startup.md', 27 | children: [] 28 | }, { 29 | title: 'HTTP Client', 30 | path: '/en_us/1_HttpClient.md', 31 | children: [] 32 | }, { 33 | title: 'Websocket Client', 34 | path: '/en_us/2_WebsocketClient.md', 35 | children: [] 36 | }, { 37 | title: 'TCP & SSL Client', 38 | path: '/en_us/3_TcpSslClient.md', 39 | children: [] 40 | }, { 41 | title: 'HTTP Server', 42 | path: '/en_us/4_HttpServer.md', 43 | children: [] 44 | }, { 45 | title: 'TCP & SSL Server', 46 | path: '/en_us/5_TcpSslServer.md', 47 | children: [] 48 | }, { 49 | title: 'Another asynchronous functionality', 50 | path: '/en_us/6_Other.md', 51 | children: [] 52 | }] 53 | }, 54 | '/zh_hans/': { 55 | sidebar: [{ 56 | title: '首页', 57 | path: '/zh_hans/', 58 | children: [] 59 | }, { 60 | title: '开始使用', 61 | path: '/zh_hans/0_Startup.md', 62 | children: [] 63 | }, { 64 | title: 'HTTP 客户端', 65 | path: '/zh_hans/1_HttpClient.md', 66 | children: [] 67 | }, { 68 | title: 'Websocket 客户端', 69 | path: '/zh_hans/2_WebsocketClient.md', 70 | children: [] 71 | }, { 72 | title: 'TCP 及 SSL 客户端', 73 | path: '/zh_hans/3_TcpSslClient.md', 74 | children: [] 75 | }, { 76 | title: 'HTTP 服务器端', 77 | path: '/zh_hans/4_HttpServer.md', 78 | children: [] 79 | }, { 80 | title: 'TCP 及 SSL 服务器端', 81 | path: '/zh_hans/5_TcpSslServer.md', 82 | children: [] 83 | }, { 84 | title: '其他异步功能', 85 | path: '/zh_hans/6_Other.md', 86 | children: [] 87 | }] 88 | } 89 | } 90 | } 91 | } 92 | 93 | // npm run docs:dev 94 | // npm run docs:build 95 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # libfv Document 2 | 3 | - [Home](en_us/README.md) 4 | + [Startup](en_us/0_Startup.md) 5 | + [HTTP Client](en_us/1_HttpClient.md) 6 | + [Websocket Client](en_us/1_HttpClient.md) 7 | + [TCP & SSL Client](en_us/3_TcpSslClient.md) 8 | + [HTTP Server](en_us/4_HttpServer.md) 9 | + [TCP & SSL Server](en_us/5_TcpSslServer.md) 10 | + [Another asynchronous functionality](en_us/6_Other.md) 11 | - [首页](zh_hans/README.md) 12 | + [开始使用](zh_hans/0_Startup.md) 13 | + [HTTP 客户端](zh_hans/1_HttpClient.md) 14 | + [Websocket 客户端](zh_hans/1_HttpClient.md) 15 | + [TCP 及 SSL 客户端](zh_hans/3_TcpSslClient.md) 16 | + [HTTP 服务器端](zh_hans/4_HttpServer.md) 17 | + [TCP 及 SSL 服务器端](zh_hans/5_TcpSslServer.md) 18 | + [其他异步功能](zh_hans/6_Other.md) 19 | 20 | 29 | -------------------------------------------------------------------------------- /docs/en_us/0_Startup.md: -------------------------------------------------------------------------------- 1 | # Startup 2 | 3 | ## Configure environment 4 | 5 | ### Configure project 6 | 7 | - Windows - VS2019 8 | 1. Configuration Properties - General - C++ Language Standard, configure to Preview (/std:c++latest) 9 | 2. Configuration Properties - C++ - Command Line, add `/await` 10 | - Windows - VS2022 11 | 1. Configuration Properties - General - C++ Language Standard, configure to Preview (/std:c++latest) 12 | 2. Configuration Properties - C++ - Command Line, add `/await:strict` 13 | - Linux - gcc11.2+ 14 | 1. TODO 15 | 16 | ### Configure vcpkg 17 | 18 | First install `libfv` through `vcpkg`: 19 | 20 | ``` 21 | vcpkg install fawdlstty-libfv 22 | ``` 23 | 24 | Or, use the warehouse's latest code: 25 | 26 | ``` 27 | vcpkg remove fawdlstty-libfv 28 | vcpkg install asio fmt gzip-hpp nlohmann-json openssl zlib 29 | git clone git@github.com:fawdlstty/libfv.git 30 | ``` 31 | 32 | ## Initialize 33 | 34 | ```cpp 35 | // Import header file (default dependency on asio, macro need to be defined if use boost::asio) 36 | //#define FV_USE_BOOST_ASIO 37 | #include 38 | 39 | // Main function 40 | int main () { 41 | // Global initialize (you can specified thread number, specify CPU thread number - 1) 42 | fv::Tasks::Init (); 43 | 44 | // ... 45 | 46 | // Loop processing task (or quit when another code call `fv::Tasks::Stop ()`) 47 | fv::Tasks::Run (); 48 | return 0; 49 | } 50 | ``` 51 | 52 | ## Entry asynchronous 53 | 54 | When an asynchronous function has called, it is added to the task pool with `fv::Tasks::RunAsync` or `fv::Tasks::RunMainAsync` 55 | 56 | The one without Main is the task pool wrapped by the thread pool, and the one with Main is the task pool wrapped by a single thread (the thread that calls Run). It is recommended that the service category carry Main and other categories do not carry Main. 57 | 58 | ```cpp 59 | // Asynchronous function 60 | Task async_func () { 61 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa")); 62 | std::cout << _r.Content; 63 | } 64 | // Asynchronous function with parameter 65 | Task async_func2 (int n) { 66 | std::cout << n << std::endl; 67 | co_return; 68 | } 69 | 70 | // To execute asynchronous functions 71 | fv::Tasks::RunAsync (async_func); 72 | fv::Tasks::RunMainAsync (async_func2, 5); 73 | ``` 74 | 75 | ## Global configuration Settings 76 | 77 | ```cpp 78 | // Set SSL verification function (default no verification) 79 | fv::Config::SslVerifyFunc = [] (bool preverified, fv::Ssl::verify_context &ctx) { return true; }; 80 | 81 | // Setting global TCP transmission without delay (Used in scenarios requiring high real-time performance) 82 | fv::Config::NoDelay = true; 83 | 84 | // Setting the global HTTP header (client) 85 | fv::Request::SetDefaultHeader ("User-Agent", "libfv-0.0.1"); 86 | 87 | // Setting the global Websocket ping interval 88 | fv::Config::WebsocketAutoPing = std::chrono::minutes (1); 89 | 90 | // Set the connection pool automatic close interval 91 | fv::Config::SessionPoolTimeout = std::chrono::minutes (1); 92 | 93 | // Setting SSL version 94 | fv::Config::SslClientVer = fv::Ssl::context::tls; 95 | fv::Config::SslServerVer = fv::Ssl::context::tls; 96 | 97 | // Setting DNS resolve function 98 | fv::Config::DnsResolve = [] (std::string _host) -> Task { 99 | Tcp::resolver _resolver { Tasks::GetContext () }; 100 | auto _it = co_await _resolver.async_resolve (_host, "", UseAwaitable); 101 | co_return _it.begin ()->endpoint ().address ().to_string (); 102 | }; 103 | 104 | // Set the local client IP binding query function 105 | fv::Config::BindClientIP = [] () -> Task { 106 | std::string _ip = co_await fv::Config::DnsResolve (asio::ip::host_name ()); 107 | co_return _ip; 108 | }; 109 | ``` 110 | -------------------------------------------------------------------------------- /docs/en_us/1_HttpClient.md: -------------------------------------------------------------------------------- 1 | # HTTP Client 2 | 3 | ## Launch request 4 | 5 | A total of six HTTP requests are supported. You can use `fv::Head`、`fv::Option`、`fv::Get`、`fv::Post`、`fv::Put`、`fv::Delete` methods. 6 | 7 | ```cpp 8 | // Send HttpGet request 9 | fv::Response _r = co_await fv::Get ("https://t.cn"); 10 | ``` 11 | 12 | You can specify a timeout period or target server address at request time: 13 | 14 | ```cpp 15 | // specify a timeout period 16 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::timeout (std::chrono::seconds (10))); 17 | 18 | // specify a target server address 19 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::server ("106.75.237.200")); 20 | ``` 21 | 22 | A POST or PUT request can be requested with parameters: 23 | 24 | ```cpp 25 | // Send HttpPost request with `application/json` content type (default) 26 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa")); 27 | 28 | // Send HttpPost request with `application/x-www-form-urlencoded` content type 29 | fv::Response _r2 = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa"), fv::content_type ("application/x-www-form-urlencoded")); 30 | 31 | // Commit file 32 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_file ("a", "filename.txt", "content...")); 33 | 34 | // Send HttpPost request with Key-Value pair data 35 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kvs ({{ "a", "b" }, { "c", "d" }})); 36 | 37 | // Send HttpPost request with json data 38 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_json ("{\"a\":\"b\"}")); 39 | 40 | // Send HttpPost request with raw data 41 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_raw ("application/octet-stream", "aaa")); 42 | ``` 43 | 44 | You can specify HTTP headers at request time: 45 | 46 | ```cpp 47 | // Set custom http header 48 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::header ("X-WWW-Router", "123456789")); 49 | 50 | // Set http header `Authorization` value with jwt bearer authorization 51 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::authorization ("Bearer XXXXXXXXXXXXX==")); 52 | 53 | // Set http header `Authorization` value with username and password 54 | fv::Response _r1 = co_await fv::Get ("https://t.cn", fv::authorization ("admin", "123456")); 55 | 56 | // Set http header `Connection` value 57 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::connection ("keep-alive")); 58 | 59 | // Set http header `Content-Type` value 60 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::content_type ("application/octet-stream")); 61 | 62 | // Set http header `Referer` value 63 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::referer ("https://t.cn")); 64 | 65 | // Set http header `User-Agent` value 66 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::user_agent ("Mozilla/4.0 Chrome 2333")); 67 | ``` 68 | 69 | ## HTTP pipeline 70 | 71 | Libfv will maintain a link pool internally, providing reuse of requests for the same service address (same schema, domain name, port) without manual intervention. 72 | 73 | ## Example 74 | 75 | ```cpp 76 | #ifdef _MSC_VER 77 | # define _WIN32_WINNT 0x0601 78 | # pragma warning (disable: 4068) 79 | # pragma comment (lib, "Crypt32.lib") 80 | //# ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 81 | //# undef _RESUMABLE_FUNCTIONS_SUPPORTED 82 | //# endif 83 | //# ifndef __cpp_lib_coroutine 84 | //# define __cpp_lib_coroutine 85 | //# endif 86 | #endif 87 | 88 | #include 89 | #include 90 | #include 91 | 92 | 93 | 94 | Task test_client () { 95 | fv::Response _r = co_await fv::Get ("https://www.fawdlstty.com"); 96 | std::cout << "received content length: " << _r.Content.size () << '\n'; 97 | std::cout << "press any key to exit\n"; 98 | char ch; 99 | std::cin >> ch; 100 | fv::Tasks::Stop (); 101 | } 102 | 103 | int main () { 104 | fv::Tasks::Init (); 105 | fv::Tasks::RunAsync (test_client); 106 | fv::Tasks::Run (); 107 | return 0; 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /docs/en_us/2_WebsocketClient.md: -------------------------------------------------------------------------------- 1 | # Websocket Client 2 | 3 | ## Create connection 4 | 5 | ```cpp 6 | std::shared_ptr _conn = co_await fv::ConnectWS ("wss://t.cn/ws"); 7 | ``` 8 | 9 | To create `Websocket` connection, you can attach `HTTP` arguments, reference `HTTP Client` section. Example: 10 | 11 | ```cpp 12 | std::shared_ptr _conn = co_await fv::ConnectWS ("wss://t.cn/ws", fv::header ("Origin", "https://a.cn")); 13 | ``` 14 | 15 | ## Loop to receive content and print 16 | 17 | ```cpp 18 | // throw exception means link is broken 19 | // It can receive two types, `fv::WsType::Text`, `fv::WsType::Binary` 20 | while (true) { 21 | auto [_data, _type] = co_await _conn->Recv (); 22 | std::cout << _data << std::endl; 23 | } 24 | ``` 25 | 26 | ## Send data 27 | 28 | ```cpp 29 | // throw exception means link is broken 30 | std::string _str = "hello"; 31 | co_await _conn->SendText (_str.data (), _str.size ()); 32 | co_await _conn->SendBinary (_str.data (), _str.size ()); 33 | ``` 34 | 35 | ## Close connection 36 | 37 | Actively close the connection: 38 | 39 | ```cpp 40 | co_await _conn->Close (); 41 | ``` 42 | 43 | In addition to active closing, the connection is automatically closed as long as the connection object is not referenced by the code and is automatically freed by the smart pointer. 44 | 45 | ## Example 46 | 47 | ```cpp 48 | #ifdef _MSC_VER 49 | # define _WIN32_WINNT 0x0601 50 | # pragma warning (disable: 4068) 51 | # pragma comment (lib, "Crypt32.lib") 52 | //# ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 53 | //# undef _RESUMABLE_FUNCTIONS_SUPPORTED 54 | //# endif 55 | //# ifndef __cpp_lib_coroutine 56 | //# define __cpp_lib_coroutine 57 | //# endif 58 | #endif 59 | 60 | #include 61 | #include 62 | #include 63 | 64 | 65 | 66 | Task test_client () { 67 | try { 68 | std::shared_ptr _conn = co_await fv::ConnectWS ("ws://82.157.123.54:9010/ajaxchattest", fv::header ("Origin", "http://coolaf.com")); 69 | while (true) { 70 | std::cout << "press any char to continue (q to exit)\n"; 71 | char ch; 72 | std::cin >> ch; 73 | if (ch == 'q') 74 | break; 75 | 76 | std::string _str = "hello"; 77 | std::cout << "send" << std::endl; 78 | co_await _conn->SendText (_str.data (), _str.size ()); 79 | 80 | std::cout << "recv: "; 81 | auto [_data, _type] = co_await _conn->Recv (); 82 | std::cout << _data << std::endl; 83 | } 84 | } catch (std::exception &_e) { 85 | std::cout << "catch exception: " << _e.what () << std::endl; 86 | } catch (...) { 87 | std::cout << "catch exception" << std::endl; 88 | } 89 | fv::Tasks::Stop (); 90 | } 91 | 92 | int main () { 93 | fv::Tasks::Init (); 94 | fv::Tasks::RunAsync (test_client); 95 | fv::Tasks::Run (); 96 | return 0; 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /docs/en_us/3_TcpSslClient.md: -------------------------------------------------------------------------------- 1 | # TCP & SSL Client 2 | 3 | ## Create connection 4 | 5 | ```cpp 6 | // tcp 7 | std::shared_ptr _conn = co_await fv::Connect ("tcp://127.0.0.1:1234"); 8 | 9 | // ssl 10 | std::shared_ptr _conn2 = co_await fv::Connect ("ssl://127.0.0.1:1235"); 11 | ``` 12 | 13 | ## Loop to receive content and print 14 | 15 | Both TCP and SSL are streaming protocols. The length of a single packet cannot be accurately obtained. You need to specify the length in a customized format. 16 | 17 | ```cpp 18 | // throw exception means link is broken 19 | char _ch = co_await _conn->ReadChar (); 20 | std::string _line = co_await _conn->ReadLine (); 21 | 22 | // ReadCount and ReadCountVec will not return until specified length data has been received 23 | std::string _buf = co_await _conn->ReadCount (1024); 24 | std::vector _buf2 = co_await _conn->ReadCountVec (1024); 25 | ``` 26 | 27 | ## Send data 28 | 29 | ```cpp 30 | // throw exception means link is broken 31 | std::string _str = "hello"; 32 | co_await _conn->Send (_str.data (), _str.size ()); 33 | ``` 34 | 35 | ## Close connection 36 | 37 | As long as the connection object is not referenced by the code, it is automatically freed by the smart pointer and the link is closed automatically. 38 | 39 | ## Example 40 | 41 | TODO 42 | -------------------------------------------------------------------------------- /docs/en_us/4_HttpServer.md: -------------------------------------------------------------------------------- 1 | # HTTP Server 2 | 3 | **Note: HTTPS server is not supported now** 4 | 5 | ## Create a server object 6 | 7 | ```cpp 8 | fv::HttpServer _server {}; 9 | ``` 10 | 11 | ## Specify HTTP and Websocket request processing callbacks 12 | 13 | ```cpp 14 | _server.SetHttpHandler ("/hello", [] (fv::Request &_req) -> Task { 15 | co_return fv::Response::FromText ("hello world"); 16 | }); 17 | 18 | _server.SetHttpHandler ("/ws", [] (fv::Request &_req) -> Task { 19 | // Check whether it is a websocket request 20 | if (_req.IsWebsocket ()) { 21 | // Upgrade to websocket connect 22 | auto _conn = co_await _req.UpgradeWebsocket (); 23 | while (true) { 24 | auto [_data, _type] = co_await _conn->Recv (); 25 | if (_type == fv::WsType::Text) { 26 | co_await _conn->SendText (_data.data (), _data.size ()); 27 | } else if (_type == fv::WsType::Binary) { 28 | co_await _conn->SendBinary (_data.data (), _data.size ()); 29 | } 30 | } 31 | // Return empty after websocket upgrade 32 | co_return fv::Response::Empty (); 33 | } else { 34 | co_return fv::Response::FromText ("please use websocket"); 35 | } 36 | }); 37 | ``` 38 | 39 | ## Set before-request filtering 40 | 41 | ```cpp 42 | _server.OnBefore ([] (fv::Request &_req) -> std::optional> { 43 | // Return std::nullopt to indicate passing, otherwise intercept and return the currently returned result (Do not enter SetHttpHandler handling callback) 44 | co_return std::nullopt; 45 | }); 46 | ``` 47 | 48 | ## Set after-request filtering 49 | 50 | ```cpp 51 | _server.OnAfter ([] (fv::Request &_req, fv::Response &_res) -> Task { 52 | // Here you can do something with the returned content 53 | co_return std::nullopt; 54 | }); 55 | ``` 56 | 57 | ## Set unhandled request handling callback 58 | 59 | ```cpp 60 | _server.OnUnhandled ([] (fv::Request &_req) -> Task { 61 | co_return fv::Response::FromText ("not handled!"); 62 | }); 63 | ``` 64 | 65 | ## Start listen 66 | 67 | ```cpp 68 | co_await _server.Run (8080); 69 | ``` 70 | 71 | ## Example 72 | 73 | ```cpp 74 | #ifdef _MSC_VER 75 | # define _WIN32_WINNT 0x0601 76 | # pragma warning (disable: 4068) 77 | # pragma comment (lib, "Crypt32.lib") 78 | //# ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 79 | //# undef _RESUMABLE_FUNCTIONS_SUPPORTED 80 | //# endif 81 | //# ifndef __cpp_lib_coroutine 82 | //# define __cpp_lib_coroutine 83 | //# endif 84 | #endif 85 | 86 | #include 87 | #include 88 | #include 89 | 90 | 91 | 92 | Task test_server () { 93 | fv::HttpServer _server {}; 94 | _server.SetHttpHandler ("/hello", [] (fv::Request &_req) -> Task { 95 | co_return fv::Response::FromText ("hello world"); 96 | }); 97 | std::cout << "You can access from browser: http://127.0.0.1:8080/hello\n"; 98 | co_await _server.Run (8080); 99 | } 100 | 101 | int main () { 102 | fv::Tasks::Init (); 103 | fv::Tasks::RunMainAsync (test_server); 104 | fv::Tasks::Run (); 105 | return 0; 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /docs/en_us/5_TcpSslServer.md: -------------------------------------------------------------------------------- 1 | # TCP & SSL Server 2 | 3 | **Note: SSL server is not supported now** 4 | 5 | ## Create a server object 6 | 7 | ```cpp 8 | fv::TcpServer _tcpserver {}; 9 | ``` 10 | 11 | ## Sets the new link handler function 12 | 13 | ```cpp 14 | m_tcpserver.SetOnConnect ([&m_tcpserver] (std::shared_ptr _conn) -> Task { 15 | // Free play here, return then link close, usually `while (true)` 16 | 17 | // You can register client or unregister, specify the ID based on your profession 18 | m_tcpserver.RegisterClient (123, _conn); 19 | m_tcpserver.UnregisterClient (123, _conn); 20 | }); 21 | 22 | // If the handler function has registered internally, the external can send or broadcast messages directly to the corresponding client 23 | std::string _data = "hello"; 24 | bool _success = co_await m_tcpserver.SendData (123, _data.data (), _data.size ()); 25 | size_t _count = co_await m_tcpserver.BroadcastData (_data.data (), _data.size ()); 26 | ``` 27 | 28 | ## Start listen 29 | 30 | ```cpp 31 | co_await _server.Run (8080); 32 | ``` 33 | 34 | ## Example 35 | 36 | TODO 37 | -------------------------------------------------------------------------------- /docs/en_us/6_Other.md: -------------------------------------------------------------------------------- 1 | # Another asynchronous functionality 2 | 3 | ## Sleep task 4 | 5 | ```cpp 6 | // Sleep 10 seconds 7 | co_await fv::Tasks::Delay (std::chrono::seconds (10)); 8 | ``` 9 | 10 | ## If external needs io_context 11 | 12 | ```cpp 13 | fv::IoContext &_ctx = fv::Tasks::GetContext (); 14 | ``` 15 | 16 | ## Asynchronous mutex 17 | 18 | Asynchronous mutex is a mutex suitable for asynchronous environments. In contrast to `std::mutex`, there are the following features: 19 | 20 | - Supports asynchronous wait locking 21 | - Locking and unlocking do not require the same thread 22 | 23 | Create mutex 24 | 25 | ```cpp 26 | fv::AsyncMutex _mtx {}; // Pass the true argument to lock during initialization 27 | ``` 28 | 29 | Lock: 30 | 31 | ```cpp 32 | // try lock 33 | bool _locked = _mtx.TryLock (); 34 | 35 | // asynchronous lock 36 | co_await _mtx.Lock (); 37 | 38 | // asynchronous timeout lock 39 | bool _locked = co_await _mtx.Lock (std::chrono::seconds (1)); 40 | ``` 41 | 42 | Unlock: 43 | 44 | ```cpp 45 | _mtx.Unlock (); 46 | ``` 47 | 48 | To know if it is locked: 49 | 50 | ```cpp 51 | bool _locked = _mtx.IsLocked (); 52 | ``` 53 | 54 | ## Asynchronous semaphore 55 | 56 | An asynchronous semaphore is a semaphore suitable for asynchrony. In contrast to the library's `std::counting_semaphore`, there are the following features: 57 | 58 | - Supports asynchronous wait for acquire 59 | 60 | Create semaphore: 61 | 62 | ```cpp 63 | fv::AsyncSemaphore _sema { 1 }; // Parameter means the initial number of resources 64 | ``` 65 | 66 | Acquire resources: 67 | 68 | ```cpp 69 | // try acquire resources 70 | bool _acq = _sema.TryAcquire (); 71 | 72 | // asynchronous acquire resources 73 | co_await _mtx.Acquire (); 74 | 75 | // asynchronous timeout acquire resources 76 | bool _acq = co_await _mtx.Acquire (std::chrono::seconds (1)); 77 | ``` 78 | 79 | Release resources: 80 | 81 | ```cpp 82 | _mtx.Release (); 83 | ``` 84 | 85 | Get the count of available resources: 86 | 87 | ```cpp 88 | size_t _count = _mtx.GetResCount (); 89 | ``` 90 | 91 | ## Example 92 | 93 | TODO 94 | -------------------------------------------------------------------------------- /docs/en_us/README.md: -------------------------------------------------------------------------------- 1 | # Home 2 | 3 | Repository URL: 4 | 5 | ## Introduction 6 | 7 | libfv is C++20 header-only network library, support TCP/SSL/Http/websocket server and client 8 | 9 | You can use pure asynchronous networking with it, or you can use asynchronous wrappers without networking at all and support asynchronous development in your projects. 10 | 11 | In addition to providing network functions, the library also provides a variety of asynchronous tools, such as timers, semaphores, etc. 12 | 13 | ## License 14 | 15 | MIT 16 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "docs:dev": "vuepress dev .", 4 | "docs:build": "vuepress build ." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/zh_hans/0_Startup.md: -------------------------------------------------------------------------------- 1 | # 开始使用 2 | 3 | ## 配置环境 4 | 5 | ### 配置项目环境 6 | 7 | - Windows - VS2019 8 | 1. 配置属性 - 常规 - C++语言标准,设置为最新(`/std:c++latest`) 9 | 2. 配置属性 - C++ - 命令行,新增:`/await` 10 | - Windows - VS2022 11 | 1. 配置属性 - 常规 - C++语言标准,设置为最新(`/std:c++latest`) 12 | 2. 配置属性 - C++ - 命令行,新增:`/await:strict` 13 | - Linux - gcc11.2+ 14 | 1. TODO 15 | 16 | ### 配置 vcpkg 环境 17 | 18 | 通过 `vcpkg` 安装 `libfv`: 19 | 20 | ``` 21 | vcpkg install fawdlstty-libfv 22 | ``` 23 | 24 | 或者,使用仓库最新代码: 25 | 26 | ``` 27 | vcpkg remove fawdlstty-libfv 28 | vcpkg install asio fmt gzip-hpp nlohmann-json openssl zlib 29 | git clone git@github.com:fawdlstty/libfv.git 30 | ``` 31 | 32 | ## 初始化 33 | 34 | ```cpp 35 | // 引入库头文件(默认依赖独立asio,改用boost::asio则需定义宏) 36 | //#define FV_USE_BOOST_ASIO 37 | #include 38 | 39 | // 主函数 40 | int main () { 41 | // 全局初始化(参数可指线程数,核心多的建议CPU线程数-1) 42 | fv::Tasks::Init (); 43 | 44 | // ... 45 | 46 | // 循环处理任务(其他地方调用 `fv::Tasks::Stop ()` 可退出) 47 | fv::Tasks::Run (); 48 | return 0; 49 | } 50 | ``` 51 | 52 | ## 进入异步 53 | 54 | 调用异步函数时,通过 `fv::Tasks::RunAsync` 或 `fv::Tasks::RunMainAsync` 将其加入异步任务队列 55 | 56 | 其中不带Main的是线程池包装的任务池,带Main的是独立的单线程(调用Run的线程)包装的任务池。建议服务类别带Main,其他类别不带Main 57 | 58 | ```cpp 59 | // 异步函数 60 | Task async_func () { 61 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa")); 62 | std::cout << _r.Content; 63 | } 64 | // 带参异步函数 65 | Task async_func2 (int n) { 66 | std::cout << n << std::endl; 67 | co_return; 68 | } 69 | 70 | // 执行异步函数 71 | fv::Tasks::RunAsync (async_func); 72 | fv::Tasks::RunMainAsync (async_func2, 5); 73 | ``` 74 | 75 | ## 全局配置设置 76 | 77 | ```cpp 78 | // 设置SSL校验函数(默认不校验) 79 | fv::Config::SslVerifyFunc = [] (bool preverified, fv::Ssl::verify_context &ctx) { return true; }; 80 | 81 | // 设置全局TCP不延迟发送(对实时性要求较高场合使用) 82 | fv::Config::NoDelay = true; 83 | 84 | // 设置全局 http 头 85 | fv::Request::SetDefaultHeader ("User-Agent", "libfv-0.0.1"); 86 | 87 | // 设置全局 Websocket 自动 ping 时间间隔 88 | fv::Config::WebsocketAutoPing = std::chrono::minutes (1); 89 | 90 | // 设置连接池自动释放超时时长 91 | fv::Config::SessionPoolTimeout = std::chrono::minutes (1); 92 | 93 | // 设置 SSL 版本 94 | fv::Config::SslClientVer = fv::Ssl::context::tls; 95 | fv::Config::SslServerVer = fv::Ssl::context::tls; 96 | 97 | // 设置 DNS 查询函数 98 | fv::Config::DnsResolve = [] (std::string _host) -> Task { 99 | Tcp::resolver _resolver { Tasks::GetContext () }; 100 | auto _it = co_await _resolver.async_resolve (_host, "", UseAwaitable); 101 | co_return _it.begin ()->endpoint ().address ().to_string (); 102 | }; 103 | 104 | // 设置本地客户端IP绑定查询函数 105 | fv::Config::BindClientIP = [] () -> Task { 106 | std::string _ip = co_await fv::Config::DnsResolve (asio::ip::host_name ()); 107 | co_return _ip; 108 | }; 109 | ``` 110 | -------------------------------------------------------------------------------- /docs/zh_hans/1_HttpClient.md: -------------------------------------------------------------------------------- 1 | # HTTP 客户端 2 | 3 | ## 发起请求 4 | 5 | 共支持6种HTTP请求,可使用 `fv::Head`、`fv::Option`、`fv::Get`、`fv::Post`、`fv::Put`、`fv::Delete` 方法。 6 | 7 | ```cpp 8 | // 发送 HttpGet 请求 9 | fv::Response _r = co_await fv::Get ("https://t.cn"); 10 | ``` 11 | 12 | 可在请求时指定超时时长或目标服务器地址: 13 | 14 | ```cpp 15 | // 指定请求超时时长 16 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::timeout (std::chrono::seconds (10))); 17 | 18 | // 向指定服务器发送请求 19 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::server ("106.75.237.200")); 20 | ``` 21 | 22 | 对 POST 或 PUT 请求可在请求时附带参数: 23 | 24 | ```cpp 25 | // 发送 HttpPost 请求,提交 application/json 格式(默认) 26 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa")); 27 | 28 | // 发送 HttpPost 请求,提交 application/x-www-form-urlencoded 格式 29 | fv::Response _r2 = co_await fv::Post ("https://t.cn", fv::body_kv ("a", "aaa"), fv::content_type ("application/x-www-form-urlencoded")); 30 | 31 | // 提交文件 32 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_file ("a", "filename.txt", "content...")); 33 | 34 | // 发送 HttpPost 请求并提交 Key-Value Pair 内容 35 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_kvs ({{ "a", "b" }, { "c", "d" }})); 36 | 37 | // 发送 HttpPost 请求并提交 json 内容 38 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_json ("{\"a\":\"b\"}")); 39 | 40 | // 发送 HttpPost 请求并提交原始内容 41 | fv::Response _r = co_await fv::Post ("https://t.cn", fv::body_raw ("application/octet-stream", "aaa")); 42 | 43 | ``` 44 | 45 | 可在请求时指定 HTTP 头: 46 | 47 | ```cpp 48 | // 自定义http头 49 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::header ("X-WWW-Router", "123456789")); 50 | 51 | // 设置http头 `Authorization` 值之 jwt bearer 鉴权 52 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::authorization ("Bearer XXXXXXXXXXXXX==")); 53 | 54 | // 设置http头 `Authorization` 值之用户名密码鉴权 55 | fv::Response _r1 = co_await fv::Get ("https://t.cn", fv::authorization ("admin", "123456")); 56 | 57 | // 设置http头 `Connection` 值 58 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::connection ("keep-alive")); 59 | 60 | // 设置http头 `Content-Type` 值 61 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::content_type ("application/octet-stream")); 62 | 63 | // 设置http头 `Referer` 值 64 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::referer ("https://t.cn")); 65 | 66 | // 设置http头 `User-Agent` 值 67 | fv::Response _r = co_await fv::Get ("https://t.cn", fv::user_agent ("Mozilla/4.0 Chrome 2333")); 68 | ``` 69 | 70 | ## HTTP pipeline 71 | 72 | libfv内部将维护一个链接池,提供对相同服务地址(协议、域名、端口均相同)的请求的复用,无需手工干预 73 | 74 | ## 示例 75 | 76 | ```cpp 77 | #ifdef _MSC_VER 78 | # define _WIN32_WINNT 0x0601 79 | # pragma warning (disable: 4068) 80 | # pragma comment (lib, "Crypt32.lib") 81 | //# ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 82 | //# undef _RESUMABLE_FUNCTIONS_SUPPORTED 83 | //# endif 84 | //# ifndef __cpp_lib_coroutine 85 | //# define __cpp_lib_coroutine 86 | //# endif 87 | #endif 88 | 89 | #include 90 | #include 91 | #include 92 | 93 | 94 | 95 | Task test_client () { 96 | fv::Response _r = co_await fv::Get ("https://www.fawdlstty.com"); 97 | std::cout << "received content length: " << _r.Content.size () << '\n'; 98 | std::cout << "press any key to exit\n"; 99 | char ch; 100 | std::cin >> ch; 101 | fv::Tasks::Stop (); 102 | } 103 | 104 | int main () { 105 | fv::Tasks::Init (); 106 | fv::Tasks::RunAsync (test_client); 107 | fv::Tasks::Run (); 108 | return 0; 109 | } 110 | ``` 111 | -------------------------------------------------------------------------------- /docs/zh_hans/2_WebsocketClient.md: -------------------------------------------------------------------------------- 1 | # Websocket 客户端 2 | 3 | ## 建立连接 4 | 5 | ```cpp 6 | std::shared_ptr _conn = co_await fv::ConnectWS ("wss://t.cn/ws"); 7 | ``` 8 | 9 | 建立 `Websocket` 连接可附加 `HTTP` 参数,参考 `Http 客户端` 章节。示例: 10 | 11 | ```cpp 12 | std::shared_ptr _conn = co_await fv::ConnectWS ("wss://t.cn/ws", fv::header ("Origin", "https://a.cn")); 13 | ``` 14 | 15 | ## 循环接收数据 16 | 17 | ```cpp 18 | // 抛异常说明连接断开 19 | // 能接收到两种类型,`fv::WsType::Text`,`fv::WsType::Binary` 20 | while (true) { 21 | auto [_data, _type] = co_await _conn->Recv (); 22 | std::cout << _data << std::endl; 23 | } 24 | ``` 25 | 26 | ## 发送数据 27 | 28 | ```cpp 29 | // 抛异常说明连接断开 30 | std::string _str = "hello"; 31 | co_await _conn->SendText (_str.data (), _str.size ()); 32 | co_await _conn->SendBinary (_str.data (), _str.size ()); 33 | ``` 34 | 35 | ## 关闭连接 36 | 37 | 主动关闭连接: 38 | 39 | ```cpp 40 | co_await _conn->Close (); 41 | ``` 42 | 43 | 除了主动关闭外,只要连接对象不被代码所引用,受智能指针自动释放,也会自动关闭链接。 44 | 45 | ## 示例 46 | 47 | ```cpp 48 | #ifdef _MSC_VER 49 | # define _WIN32_WINNT 0x0601 50 | # pragma warning (disable: 4068) 51 | # pragma comment (lib, "Crypt32.lib") 52 | //# ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 53 | //# undef _RESUMABLE_FUNCTIONS_SUPPORTED 54 | //# endif 55 | //# ifndef __cpp_lib_coroutine 56 | //# define __cpp_lib_coroutine 57 | //# endif 58 | #endif 59 | 60 | #include 61 | #include 62 | #include 63 | 64 | 65 | 66 | Task test_client () { 67 | try { 68 | std::shared_ptr _conn = co_await fv::ConnectWS ("ws://82.157.123.54:9010/ajaxchattest", fv::header ("Origin", "http://coolaf.com")); 69 | while (true) { 70 | std::cout << "press any char to continue (q to exit)\n"; 71 | char ch; 72 | std::cin >> ch; 73 | if (ch == 'q') 74 | break; 75 | 76 | std::string _str = "hello"; 77 | std::cout << "send" << std::endl; 78 | co_await _conn->SendText (_str.data (), _str.size ()); 79 | 80 | std::cout << "recv: "; 81 | auto [_data, _type] = co_await _conn->Recv (); 82 | std::cout << _data << std::endl; 83 | } 84 | } catch (std::exception &_e) { 85 | std::cout << "catch exception: " << _e.what () << std::endl; 86 | } catch (...) { 87 | std::cout << "catch exception" << std::endl; 88 | } 89 | fv::Tasks::Stop (); 90 | } 91 | 92 | int main () { 93 | fv::Tasks::Init (); 94 | fv::Tasks::RunAsync (test_client); 95 | fv::Tasks::Run (); 96 | return 0; 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /docs/zh_hans/3_TcpSslClient.md: -------------------------------------------------------------------------------- 1 | # TCP 及 SSL 客户端 2 | 3 | ## 建立连接 4 | 5 | ```cpp 6 | // tcp 7 | std::shared_ptr _conn = co_await fv::Connect ("tcp://127.0.0.1:1234"); 8 | 9 | // ssl 10 | std::shared_ptr _conn2 = co_await fv::Connect ("ssl://127.0.0.1:1235"); 11 | ``` 12 | 13 | ## 循环接收数据 14 | 15 | TCP与SSL均为流式协议,无法准确获取单个数据包长度,请自定格式指定长度信息。 16 | 17 | ```cpp 18 | // 抛异常说明连接断开 19 | char _ch = co_await _conn->ReadChar (); 20 | std::string _line = co_await _conn->ReadLine (); 21 | 22 | // ReadCount 与 ReadCountVec 必须待接收到那么长数据之后才会返回 23 | std::string _buf = co_await _conn->ReadCount (1024); 24 | std::vector _buf2 = co_await _conn->ReadCountVec (1024); 25 | ``` 26 | 27 | ## 发送数据 28 | 29 | ```cpp 30 | // 抛异常说明连接断开 31 | std::string _str = "hello"; 32 | co_await _conn->Send (_str.data (), _str.size ()); 33 | ``` 34 | 35 | ## 关闭连接 36 | 37 | 只要连接对象不被代码所引用,受智能指针自动释放,就会自动关闭链接。 38 | 39 | ## 示例 40 | 41 | TODO 42 | -------------------------------------------------------------------------------- /docs/zh_hans/4_HttpServer.md: -------------------------------------------------------------------------------- 1 | # HTTP 服务器端 2 | 3 | **注:暂不支持 HTTPS 服务器端** 4 | 5 | ## 创建服务器端对象 6 | 7 | ```cpp 8 | fv::HttpServer _server {}; 9 | ``` 10 | 11 | ## 指定 HTTP 及 websocket 请求处理回调 12 | 13 | ```cpp 14 | _server.SetHttpHandler ("/hello", [] (fv::Request &_req) -> Task { 15 | co_return fv::Response::FromText ("hello world"); 16 | }); 17 | 18 | _server.SetHttpHandler ("/ws", [] (fv::Request &_req) -> Task { 19 | // 检查是否为 websocket 请求 20 | if (_req.IsWebsocket ()) { 21 | // 升级为 websocket 22 | auto _conn = co_await _req.UpgradeWebsocket (); 23 | while (true) { 24 | auto [_data, _type] = co_await _conn->Recv (); 25 | if (_type == fv::WsType::Text) { 26 | co_await _conn->SendText (_data.data (), _data.size ()); 27 | } else if (_type == fv::WsType::Binary) { 28 | co_await _conn->SendBinary (_data.data (), _data.size ()); 29 | } 30 | } 31 | // 请求完成 websocket 升级后返回空即可 32 | co_return fv::Response::Empty (); 33 | } else { 34 | co_return fv::Response::FromText ("please use websocket"); 35 | } 36 | }); 37 | ``` 38 | 39 | ## 设置前置请求过滤 40 | 41 | ```cpp 42 | _server.OnBefore ([] (fv::Request &_req) -> std::optional> { 43 | // 此处返回 std::nullopt 代表通过过滤,否则代表拦截请求(不进入 SetHttpHandler 处理回调) 44 | co_return std::nullopt; 45 | }); 46 | ``` 47 | 48 | ## 设置后置请求过滤 49 | 50 | ```cpp 51 | _server.OnAfter ([] (fv::Request &_req, fv::Response &_res) -> Task { 52 | // 此处可对返回内容做一些处理 53 | co_return std::nullopt; 54 | }); 55 | ``` 56 | 57 | ## 设置未 handle 的请求处理函数 58 | 59 | ```cpp 60 | _server.OnUnhandled ([] (fv::Request &_req) -> Task { 61 | co_return fv::Response::FromText ("not handled!"); 62 | }); 63 | ``` 64 | 65 | ## 开始监听并启动服务 66 | 67 | ```cpp 68 | co_await _server.Run (8080); 69 | ``` 70 | 71 | ## 示例 72 | 73 | ```cpp 74 | #ifdef _MSC_VER 75 | # define _WIN32_WINNT 0x0601 76 | # pragma warning (disable: 4068) 77 | # pragma comment (lib, "Crypt32.lib") 78 | //# ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 79 | //# undef _RESUMABLE_FUNCTIONS_SUPPORTED 80 | //# endif 81 | //# ifndef __cpp_lib_coroutine 82 | //# define __cpp_lib_coroutine 83 | //# endif 84 | #endif 85 | 86 | #include 87 | #include 88 | #include 89 | 90 | 91 | 92 | Task test_server () { 93 | fv::HttpServer _server {}; 94 | _server.SetHttpHandler ("/hello", [] (fv::Request &_req) -> Task { 95 | co_return fv::Response::FromText ("hello world"); 96 | }); 97 | std::cout << "You can access from browser: http://127.0.0.1:8080/hello\n"; 98 | co_await _server.Run (8080); 99 | } 100 | 101 | int main () { 102 | fv::Tasks::Init (); 103 | fv::Tasks::RunMainAsync (test_server); 104 | fv::Tasks::Run (); 105 | return 0; 106 | } 107 | ``` 108 | -------------------------------------------------------------------------------- /docs/zh_hans/5_TcpSslServer.md: -------------------------------------------------------------------------------- 1 | # TCP 及 SSL 服务器端 2 | 3 | **注:暂不支持 SSL 服务器端** 4 | 5 | ## 创建服务器端对象 6 | 7 | ```cpp 8 | fv::TcpServer _tcpserver {}; 9 | ``` 10 | 11 | ## 设置新链接处理函数 12 | 13 | ```cpp 14 | m_tcpserver.SetOnConnect ([&m_tcpserver] (std::shared_ptr _conn) -> Task { 15 | // 此处自由发挥,退出函数则链接断开,通常 `while (true)` 16 | 17 | // 可考虑注册客户端及取消注册,此处自己根据业务指定ID 18 | m_tcpserver.RegisterClient (123, _conn); 19 | m_tcpserver.UnregisterClient (123, _conn); 20 | }); 21 | 22 | // 假如处理函数内部注册后,外部可直接给对应客户端发消息或者广播消息 23 | std::string _data = "hello"; 24 | bool _success = co_await m_tcpserver.SendData (123, _data.data (), _data.size ()); 25 | size_t _count = co_await m_tcpserver.BroadcastData (_data.data (), _data.size ()); 26 | ``` 27 | 28 | ## 开始监听并启动服务 29 | 30 | ```cpp 31 | co_await _server.Run (8080); 32 | ``` 33 | 34 | ## 示例 35 | 36 | TODO 37 | -------------------------------------------------------------------------------- /docs/zh_hans/6_Other.md: -------------------------------------------------------------------------------- 1 | # 其他异步功能 2 | 3 | ## 暂停任务 4 | 5 | ```cpp 6 | // 暂停 10 秒 7 | co_await fv::Tasks::Delay (std::chrono::seconds (10)); 8 | ``` 9 | 10 | ## 外部需要用到 io_context 11 | 12 | ```cpp 13 | fv::IoContext &_ctx = fv::Tasks::GetContext (); 14 | ``` 15 | 16 | ## 异步锁 17 | 18 | 异步锁是一款适用于异步的锁。相对于标准库的 `std::mutex` 来说,有以下特性: 19 | 20 | - 支持异步等待加锁 21 | - 加锁与解锁不要求同一线程 22 | 23 | 创建锁: 24 | 25 | ```cpp 26 | fv::AsyncMutex _mtx {}; // 参数传 true 代表初始化时加锁 27 | ``` 28 | 29 | 加锁: 30 | 31 | ```cpp 32 | // 尝试加锁 33 | bool _locked = _mtx.TryLock (); 34 | 35 | // 异步加锁 36 | co_await _mtx.Lock (); 37 | 38 | // 异步超时锁 39 | bool _locked = co_await _mtx.Lock (std::chrono::seconds (1)); 40 | ``` 41 | 42 | 解锁: 43 | 44 | ```cpp 45 | _mtx.Unlock (); 46 | ``` 47 | 48 | 获知是否已锁: 49 | 50 | ```cpp 51 | bool _locked = _mtx.IsLocked (); 52 | ``` 53 | 54 | ## 异步信号量 55 | 56 | 异步信号量是一款适用于异步的信号量。相对于标准库的 `std::counting_semaphore` 来说,有以下特性: 57 | 58 | - 支持异步等待获取 59 | 60 | 创建信号量: 61 | 62 | ```cpp 63 | fv::AsyncSemaphore _sema { 1 }; // 参数代表初始资源数 64 | ``` 65 | 66 | 获取资源: 67 | 68 | ```cpp 69 | // 尝试获取资源 70 | bool _acq = _sema.TryAcquire (); 71 | 72 | // 异步获取资源 73 | co_await _mtx.Acquire (); 74 | 75 | // 异步超时获取资源 76 | bool _acq = co_await _mtx.Acquire (std::chrono::seconds (1)); 77 | ``` 78 | 79 | 释放资源: 80 | 81 | ```cpp 82 | _mtx.Release (); 83 | ``` 84 | 85 | 获知现有资源数: 86 | 87 | ```cpp 88 | size_t _count = _mtx.GetResCount (); 89 | ``` 90 | 91 | ## 示例 92 | 93 | TODO 94 | -------------------------------------------------------------------------------- /docs/zh_hans/README.md: -------------------------------------------------------------------------------- 1 | # 首页 2 | 3 | 仓库地址: 4 | 5 | ## 项目简介 6 | 7 | libfv 是 C++20 纯头文件网络库,支持 TCP/SSL/Http/websocket 服务器端与客户端 8 | 9 | 你可以通过它使用纯异步的网络功能,当然你也能完全不使用网络,仅使用异步包装功能,让你的项目支持异步开发。 10 | 11 | 库除了提供网络功能外,还提供多种异步工具,比如定时器、信号量等。 12 | 13 | ## 开源协议 14 | 15 | MIT 16 | -------------------------------------------------------------------------------- /include/fv/common.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_COMMON_HPP__ 2 | #define __FV_COMMON_HPP__ 3 | 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "declare.hpp" 14 | #include "ioctx_pool.hpp" 15 | 16 | 17 | 18 | namespace fv { 19 | struct CaseInsensitiveHash { 20 | size_t operator() (const std::string &str) const noexcept { 21 | size_t h = 0; 22 | std::hash hash {}; 23 | for (auto c : str) 24 | h ^= hash (::tolower (c)) + 0x9e3779b9 + (h << 6) + (h >> 2); 25 | return h; 26 | } 27 | }; 28 | struct CaseInsensitiveEqual { 29 | bool operator() (const std::string &str1, const std::string &str2) const noexcept { 30 | return str1.size () == str2.size () && std::equal (str1.begin (), str1.end (), str2.begin (), [] (char a, char b) { 31 | return tolower (a) == tolower (b); 32 | }); 33 | } 34 | }; 35 | using CaseInsensitiveMap = std::unordered_map; 36 | 37 | 38 | 39 | struct Exception: public std::exception { 40 | Exception (std::string _err): m_err (_err) {} 41 | //template 42 | //Exception (std::string _err, Args ..._args): m_err (fmt::format (_err, _args...)) {} 43 | const char* what() const noexcept override { return m_err.c_str (); } 44 | 45 | private: 46 | std::string m_err = ""; 47 | }; 48 | 49 | 50 | 51 | struct AsyncMutex { 52 | AsyncMutex (bool _init_locked = false): m_locked (_init_locked) {} 53 | 54 | bool IsLocked () { 55 | std::unique_lock _ul { m_mtx }; 56 | return m_locked; 57 | } 58 | 59 | bool TryLock () { 60 | std::unique_lock _ul { m_mtx }; 61 | if (!m_locked) { 62 | m_locked = true; 63 | return true; 64 | } else { 65 | return false; 66 | } 67 | } 68 | 69 | Task Lock () { 70 | std::unique_lock _ul { m_mtx, std::defer_lock }; 71 | while (true) { 72 | _ul.lock (); 73 | if (!m_locked) { 74 | m_locked = true; 75 | co_return; 76 | } 77 | _ul.unlock (); 78 | co_await _delay (std::chrono::milliseconds (1)); 79 | } 80 | } 81 | 82 | Task Lock (TimeSpan _timeout) { 83 | std::unique_lock _ul { m_mtx, std::defer_lock }; 84 | auto _elapsed = std::chrono::system_clock::now () + _timeout; 85 | while (_elapsed > std::chrono::system_clock::now ()) { 86 | _ul.lock (); 87 | if (!m_locked) { 88 | m_locked = true; 89 | co_return true; 90 | } 91 | _ul.unlock (); 92 | co_await _delay (std::chrono::milliseconds (1)); 93 | } 94 | co_return false; 95 | } 96 | 97 | void LockSync () { 98 | std::unique_lock _ul { m_mtx, std::defer_lock }; 99 | while (true) { 100 | _ul.lock (); 101 | if (!m_locked) { 102 | m_locked = true; 103 | return; 104 | } 105 | _ul.unlock (); 106 | std::this_thread::sleep_for (std::chrono::milliseconds (1)); 107 | } 108 | } 109 | 110 | void Unlock () { 111 | std::unique_lock _ul { m_mtx }; 112 | if (!m_locked) 113 | throw Exception ("Cannot unlock a unlocked mutex"); 114 | m_locked = false; 115 | } 116 | 117 | private: 118 | static Task _delay (TimeSpan _dt) { 119 | asio::steady_timer timer (co_await asio::this_coro::executor); 120 | timer.expires_after (_dt); 121 | co_await timer.async_wait (UseAwaitable); 122 | } 123 | 124 | bool m_locked = false; 125 | std::recursive_mutex m_mtx {}; 126 | }; 127 | 128 | 129 | 130 | struct AsyncSemaphore { 131 | AsyncSemaphore (size_t _init_count = 1): m_count (_init_count) {} 132 | 133 | size_t GetResCount () { 134 | std::unique_lock _ul { m_mtx }; 135 | return m_count; 136 | } 137 | 138 | bool TryAcquire () { 139 | std::unique_lock _ul { m_mtx }; 140 | if (m_count > 0) { 141 | --m_count; 142 | return true; 143 | } else { 144 | return false; 145 | } 146 | } 147 | 148 | Task Acquire () { 149 | std::unique_lock _ul { m_mtx, std::defer_lock }; 150 | while (true) { 151 | _ul.lock (); 152 | if (m_count > 0) { 153 | --m_count; 154 | co_return; 155 | } 156 | _ul.unlock (); 157 | co_await _delay (std::chrono::milliseconds (1)); 158 | } 159 | } 160 | 161 | Task Acquire (TimeSpan _timeout) { 162 | std::unique_lock _ul { m_mtx, std::defer_lock }; 163 | auto _elapsed = std::chrono::system_clock::now () + _timeout; 164 | while (_elapsed > std::chrono::system_clock::now ()) { 165 | _ul.lock (); 166 | if (m_count > 0) { 167 | --m_count; 168 | co_return true; 169 | } 170 | _ul.unlock (); 171 | co_await _delay (std::chrono::milliseconds (1)); 172 | } 173 | co_return false; 174 | } 175 | 176 | void Release () { 177 | std::unique_lock _ul { m_mtx }; 178 | ++m_count; 179 | } 180 | 181 | private: 182 | static Task _delay (TimeSpan _dt) { 183 | asio::steady_timer timer (co_await asio::this_coro::executor); 184 | timer.expires_after (_dt); 185 | co_await timer.async_wait (UseAwaitable); 186 | } 187 | 188 | size_t m_count; 189 | std::recursive_mutex m_mtx {}; 190 | }; 191 | 192 | struct CancelToken { 193 | CancelToken (std::chrono::system_clock::time_point _cancel_time) { m_cancel_time = _cancel_time; } 194 | CancelToken (TimeSpan _expire) { m_cancel_time = std::chrono::system_clock::now () + _expire; } 195 | void Cancel () { m_cancel_time = std::chrono::system_clock::now (); } 196 | bool IsCancel () { return std::chrono::system_clock::now () <= m_cancel_time; } 197 | TimeSpan GetRemaining () { return m_cancel_time - std::chrono::system_clock::now (); } 198 | 199 | private: 200 | std::chrono::system_clock::time_point m_cancel_time; 201 | }; 202 | 203 | struct Tasks { 204 | template 205 | static void RunAsync (F &&f) { 206 | std::unique_lock _ul { m_mtx }; 207 | if (!m_pool) 208 | throw Exception ("You should invoke Init method first"); 209 | // 210 | using TRet = decltype (f ()); 211 | if constexpr (std::is_void::value) { 212 | GetContext ().post (std::forward (f)); 213 | } else if constexpr (std::is_same>::value) { 214 | asio::co_spawn (GetContext (), std::forward (f), asio::detached); 215 | } else { 216 | static_assert (std::is_void::value || std::is_same>::value, "Unsupported returns type"); 217 | } 218 | } 219 | template 220 | static void RunMainAsync (F &&f) { 221 | std::unique_lock _ul { m_mtx }; 222 | if (!m_pool) 223 | throw Exception ("You should invoke Init method first"); 224 | // 225 | using TRet = decltype (f ()); 226 | if constexpr (std::is_void::value) { 227 | GetMainContext ().post (std::forward (f)); 228 | } else if constexpr (std::is_same>::value) { 229 | asio::co_spawn (GetMainContext (), std::forward (f), asio::detached); 230 | } else { 231 | static_assert (std::is_void::value || std::is_same>::value, "Unsupported returns type"); 232 | } 233 | } 234 | 235 | template 236 | static void RunAsync (F &&f, Args... args) { return RunAsync (std::bind (f, args...)); } 237 | template 238 | static void RunMainAsync (F &&f, Args... args) { return RunMainAsync (std::bind (f, args...)); } 239 | 240 | static Task Delay (TimeSpan _dt) { 241 | asio::steady_timer timer (co_await asio::this_coro::executor); 242 | timer.expires_after (_dt); 243 | co_await timer.async_wait (UseAwaitable); 244 | } 245 | 246 | static void Init (size_t _thread_num = 0) { 247 | ::srand ((unsigned int) ::time (NULL)); 248 | std::unique_lock _ul { m_mtx }; 249 | if (m_pool) 250 | throw Exception ("You should only invoke Init method once"); 251 | m_pool = std::make_shared (_thread_num); 252 | } 253 | 254 | static void Stop () { 255 | std::unique_lock _ul { m_mtx }; 256 | if (!m_pool) 257 | throw Exception ("You should invoke Init method first"); 258 | m_run = false; 259 | m_pool->Stop (); 260 | } 261 | 262 | static void Run () { 263 | std::unique_lock _ul { m_mtx }; 264 | if (!m_pool) 265 | throw Exception ("You should invoke Init method first"); 266 | m_run = true; 267 | _ul.unlock (); 268 | m_pool->Run (); 269 | } 270 | 271 | static IoContext &GetContext () { 272 | std::unique_lock _ul { m_mtx }; 273 | if (!m_pool) 274 | Init (); 275 | return m_pool->GetContext (); 276 | } 277 | static IoContext &GetMainContext () { 278 | std::unique_lock _ul { m_mtx }; 279 | if (!m_pool) 280 | Init (); 281 | return m_pool->GetMainContext (); 282 | } 283 | 284 | private: 285 | inline static std::recursive_mutex m_mtx {}; 286 | inline static bool m_run = false; 287 | inline static std::shared_ptr m_pool = nullptr; 288 | }; 289 | 290 | struct AsyncTimer { 291 | AsyncTimer () { } 292 | ~AsyncTimer () { Cancel (); } 293 | 294 | Task WaitTimeoutAsync (TimeSpan _elapse) { 295 | co_return !co_await m_mtx.Lock (_elapse); 296 | } 297 | 298 | template 299 | void WaitCallback (TimeSpan _elapse, F _cb) { 300 | if (!m_mtx.IsLocked ()) 301 | m_mtx.LockSync (); 302 | Tasks::RunAsync ([this] (TimeSpan _elapse, F _cb) -> Task { 303 | bool _lock = co_await m_mtx.Lock (_elapse); 304 | if (!_lock) { 305 | using TRet = typename std::decay; 306 | if constexpr (std::is_same::value) { 307 | _cb (); 308 | } else if constexpr (std::is_same>::value) { 309 | co_await _cb (); 310 | } else { 311 | static_assert (std::is_void::value || std::is_same>::value, "Unsupported returns type"); 312 | } 313 | } 314 | }, _elapse, _cb); 315 | } 316 | 317 | void Cancel () { 318 | if (m_mtx.IsLocked ()) 319 | m_mtx.Unlock (); 320 | } 321 | 322 | private: 323 | AsyncMutex m_mtx {}; 324 | }; 325 | } 326 | 327 | 328 | 329 | #endif //__FV_COMMON_HPP__ 330 | -------------------------------------------------------------------------------- /include/fv/common_funcs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_COMMON_FUNCS_HPP__ 2 | #define __FV_COMMON_FUNCS_HPP__ 3 | 4 | 5 | 6 | #include 7 | #include 8 | 9 | 10 | 11 | namespace fv { 12 | inline std::string _to_lower (std::string _s) { std::transform (_s.begin (), _s.end (), _s.begin (), ::tolower); return _s; } 13 | 14 | 15 | 16 | inline std::string _trim (std::string _s) { 17 | size_t _start = 0, _stop = _s.size (); 18 | while (_start < _stop) { 19 | char _ch = _s [_start]; 20 | if (_ch != ' ' && _ch != '\r') 21 | break; 22 | _start++; 23 | } 24 | while (_start < _stop) { 25 | char _ch = _s [_stop - 1]; 26 | if (_ch != ' ' && _ch != '\r') 27 | break; 28 | _stop--; 29 | } 30 | return _s.substr (_start, _stop - _start); 31 | }; 32 | 33 | 34 | 35 | inline std::string random_str (size_t _len) { 36 | static const std::string s_chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; 37 | std::string _str = ""; 38 | if (_len == 0 || _len == std::string::npos) 39 | return _str; 40 | _str.resize (_len); 41 | for (size_t i = 0; i < _len; ++i) 42 | _str [i] = s_chars [((size_t) ::rand ()) % s_chars.size ()]; 43 | return _str; 44 | } 45 | 46 | 47 | 48 | inline std::string percent_encode (std::string_view data) { 49 | static const char *hex_char = "0123456789ABCDEF"; 50 | std::string ret = ""; 51 | for (size_t i = 0; i < data.size (); ++i) { 52 | char ch = data [i]; 53 | if (isalnum ((unsigned char) ch) || ch == '-' || ch == '_' || ch == '.' || ch == '~') { 54 | ret += ch; 55 | } else if (ch == ' ') { 56 | ret += "+"; 57 | } else { 58 | ret += '%'; 59 | ret += hex_char [((unsigned char) ch) >> 4]; 60 | ret += hex_char [((unsigned char) ch) % 16]; 61 | } 62 | } 63 | return ret; 64 | } 65 | 66 | 67 | 68 | inline std::string base64_encode (std::string_view data) { 69 | static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 70 | std::string ret; 71 | int i = 0, j = 0; 72 | unsigned char char_3 [3], char_4 [4]; 73 | size_t in_len = data.size (); 74 | unsigned char *bytes_to_encode = (unsigned char *) &data [0]; 75 | while (in_len--) { 76 | char_3 [i++] = *(bytes_to_encode++); 77 | if (i == 3) { 78 | char_4 [0] = (char_3 [0] & 0xfc) >> 2; 79 | char_4 [1] = ((char_3 [0] & 0x03) << 4) + ((char_3 [1] & 0xf0) >> 4); 80 | char_4 [2] = ((char_3 [1] & 0x0f) << 2) + ((char_3 [2] & 0xc0) >> 6); 81 | char_4 [3] = char_3 [2] & 0x3f; 82 | 83 | for (i = 0; i < 4; i++) 84 | ret += base64_chars [char_4 [i]]; 85 | i = 0; 86 | } 87 | } 88 | if (i) { 89 | for (j = i; j < 3; j++) 90 | char_3 [j] = '\0'; 91 | char_4 [0] = (char_3 [0] & 0xfc) >> 2; 92 | char_4 [1] = ((char_3 [0] & 0x03) << 4) + ((char_3 [1] & 0xf0) >> 4); 93 | char_4 [2] = ((char_3 [1] & 0x0f) << 2) + ((char_3 [2] & 0xc0) >> 6); 94 | for (j = 0; j < i + 1; j++) 95 | ret += base64_chars [char_4 [j]]; 96 | while ((i++ < 3)) 97 | ret += '='; 98 | } 99 | return ret; 100 | } 101 | 102 | 103 | 104 | inline std::tuple _parse_url (std::string _url) { 105 | size_t _p = _url.find ('#'); 106 | if (_p != std::string::npos) 107 | _url = _url.substr (0, _p); 108 | std::string _schema = "http"; 109 | _p = _url.find ("://"); 110 | if (_p != std::string::npos) { 111 | _schema = _to_lower (_url.substr (0, _p)); 112 | _url = _url.substr (_p + 3); 113 | } 114 | // 115 | _p = _url.find ('/'); 116 | std::string _path = "/"; 117 | if (_p != std::string::npos) { 118 | _path = _url.substr (_p); 119 | _url = _url.substr (0, _p); 120 | } 121 | // 122 | _p = _url.find (':'); 123 | std::string _host = "", _port = ""; 124 | if (_p != std::string::npos) { 125 | _host = _url.substr (0, _p); 126 | _port = _url.substr (_p + 1); 127 | } else { 128 | _host = _url; 129 | if (_schema == "http" || _schema == "ws") { 130 | _port = "80"; 131 | } else if (_schema == "https" || _schema == "wss") { 132 | _port = "443"; 133 | } else { 134 | throw Exception ("Unknown Port"); 135 | } 136 | } 137 | return { _schema, _host, _port, _path }; 138 | } 139 | } 140 | 141 | 142 | 143 | #endif //__FV_COMMON_FUNCS_HPP__ 144 | -------------------------------------------------------------------------------- /include/fv/conn.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_CONN_HPP__ 2 | #define __FV_CONN_HPP__ 3 | 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "common.hpp" 11 | #include "structs.hpp" 12 | #include "req_res.hpp" 13 | 14 | 15 | 16 | namespace fv { 17 | struct IConn2 { 18 | std::string m_host = "", m_port = ""; 19 | 20 | IConn2 () = default; 21 | virtual ~IConn2 () = default; 22 | virtual bool IsConnect () = 0; 23 | virtual Task Send (char *_data, size_t _size) = 0; 24 | virtual void Cancel () = 0; 25 | 26 | Task ReadChar (); 27 | Task ReadLine (); 28 | Task ReadCount (size_t _count); 29 | Task> ReadCountVec (size_t _count); 30 | Task ReadSome (); 31 | 32 | protected: 33 | virtual Task RecvImpl (char *_data, size_t _size) = 0; 34 | std::string TmpData = ""; 35 | }; 36 | 37 | 38 | 39 | struct IConn: public IConn2 { 40 | virtual Task Connect (std::string _host, std::string _port) = 0; 41 | virtual Task Reconnect () = 0; 42 | }; 43 | 44 | 45 | 46 | struct TcpConn: public IConn { 47 | std::shared_ptr Socket; 48 | 49 | virtual ~TcpConn () { Cancel (); } 50 | Task Connect (std::string _host, std::string _port) override; 51 | Task Reconnect () override; 52 | bool IsConnect () override { return Socket->is_open (); } 53 | Task Send (char *_data, size_t _size) override; 54 | void Cancel () override; 55 | 56 | protected: 57 | Task RecvImpl (char *_data, size_t _size) override; 58 | }; 59 | 60 | 61 | 62 | struct TcpConn2: public IConn2 { 63 | Tcp::socket Socket; 64 | int64_t Id = -1; 65 | 66 | TcpConn2 (Tcp::socket _sock); 67 | 68 | virtual ~TcpConn2 () { Cancel (); } 69 | bool IsConnect () override { return Socket.is_open (); } 70 | Task Send (char *_data, size_t _size) override; 71 | void Cancel () override; 72 | 73 | protected: 74 | Task RecvImpl (char *_data, size_t _size) override; 75 | }; 76 | 77 | 78 | 79 | struct SslConn: public IConn { 80 | Ssl::context SslCtx { Config::SslClientVer }; 81 | std::shared_ptr> SslSocket; 82 | 83 | virtual ~SslConn () { Cancel (); } 84 | Task Connect (std::string _host, std::string _port) override; 85 | Task Reconnect () override; 86 | bool IsConnect () override { return SslSocket && SslSocket->next_layer ().is_open (); } 87 | Task Send (char *_data, size_t _size) override; 88 | void Cancel () override; 89 | 90 | protected: 91 | Task RecvImpl (char *_data, size_t _size) override; 92 | }; 93 | 94 | 95 | 96 | struct SslConn2: public IConn2 { 97 | Ssl::stream SslSocket; 98 | int64_t Id = -1; 99 | 100 | SslConn2 (Ssl::stream _sock); 101 | 102 | virtual ~SslConn2 () { Cancel (); } 103 | bool IsConnect () override { return SslSocket.next_layer ().is_open (); } 104 | Task Send (char *_data, size_t _size) override; 105 | void Cancel () override; 106 | 107 | protected: 108 | Task RecvImpl (char *_data, size_t _size) override; 109 | }; 110 | 111 | 112 | 113 | struct WsConn: public std::enable_shared_from_this { 114 | std::shared_ptr Parent; 115 | bool IsClient = true; 116 | std::atomic_bool Run { true }; 117 | 118 | WsConn (std::shared_ptr _parent, bool _is_client); 119 | void Init (); 120 | bool IsConnect () { return Parent && Parent->IsConnect (); } 121 | Task SendText (char *_data, size_t _size) { co_await _Send (_data, _size, WsType::Text); } 122 | Task SendBinary (char *_data, size_t _size) { co_await _Send (_data, _size, WsType::Binary); } 123 | Task Close () { Run.store (false); co_await _Send (nullptr, 0, WsType::Close); Parent = nullptr; } 124 | Task> Recv (); 125 | 126 | private: 127 | Task _Send (char *_data, size_t _size, WsType _type); 128 | }; 129 | 130 | Task> Connect (std::string _url); 131 | template 132 | inline Task> ConnectWS (std::string _url, _Ops ..._ops); 133 | } 134 | 135 | 136 | 137 | #endif //__FV_CONN_HPP__ 138 | -------------------------------------------------------------------------------- /include/fv/conn_impl.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_CONN_IMPL_HPP__ 2 | #define __FV_CONN_IMPL_HPP__ 3 | 4 | 5 | 6 | #include 7 | #include 8 | 9 | #include "common.hpp" 10 | #include "structs.hpp" 11 | #include "conn.hpp" 12 | 13 | 14 | 15 | namespace fv { 16 | inline Task IConn2::ReadChar () { 17 | if (TmpData.size () == 0) { 18 | char _buf [1024]; 19 | size_t _n = co_await RecvImpl (_buf, sizeof (_buf)); 20 | TmpData.resize (_n); 21 | ::memcpy (&TmpData [0], _buf, _n); 22 | } 23 | char _ret = TmpData [0]; 24 | TmpData.erase (0); 25 | co_return _ret; 26 | } 27 | 28 | inline Task IConn2::ReadLine () { 29 | std::string _tmp = ""; 30 | size_t _p; 31 | while ((_p = TmpData.find ('\n')) == std::string::npos) { 32 | char _buf [1024]; 33 | size_t _readed = co_await RecvImpl (_buf, sizeof (_buf)); 34 | TmpData += std::string_view { _buf, _readed }; 35 | } 36 | _tmp = TmpData.substr (0, _p); 37 | if (_tmp [_p - 1] == '\r') 38 | _tmp.erase (_p - 1); 39 | TmpData.erase (0, _p + 1); 40 | co_return _tmp; 41 | } 42 | 43 | inline Task IConn2::ReadCount (size_t _count) { 44 | if (_count == 0) 45 | co_return ""; 46 | std::string _tmp = ""; 47 | while (TmpData.size () < _count) { 48 | char _buf [1024]; 49 | size_t _readed = co_await RecvImpl (_buf, sizeof (_buf)); 50 | TmpData += std::string_view { _buf, _readed }; 51 | } 52 | _tmp = TmpData.substr (0, _count); 53 | TmpData.erase (0, _count); 54 | co_return _tmp; 55 | } 56 | 57 | inline Task> IConn2::ReadCountVec (size_t _count) { 58 | std::string _tmp = co_await ReadCount (_count); 59 | std::vector _vec; 60 | _vec.assign ((uint8_t *) &_tmp [0], (uint8_t *) &_tmp [_count]); 61 | co_return _vec; 62 | } 63 | 64 | inline Task IConn2::ReadSome () { 65 | if (TmpData.size () > 0) { 66 | std::string _ret = std::move (TmpData); 67 | TmpData = ""; 68 | co_return _ret; 69 | } else { 70 | char _buf [1024]; 71 | size_t _len = co_await RecvImpl (_buf, sizeof (_buf)); 72 | co_return std::string (_buf, _len); 73 | } 74 | } 75 | 76 | 77 | 78 | inline Task TcpConn::Connect (std::string _host, std::string _port) { 79 | m_host = _host; 80 | m_port = _port; 81 | co_await Reconnect (); 82 | } 83 | 84 | inline Task TcpConn::Reconnect () { 85 | Socket = nullptr; 86 | TmpData = ""; 87 | std::regex _rgx { "(\\d+\\.){3}\\d+" }; 88 | std::string _ip = m_host; 89 | if (!std::regex_match (m_host, _rgx)) { 90 | _ip = co_await Config::DnsResolve (m_host); 91 | if (_ip == "") 92 | _ip = m_host; 93 | } 94 | uint16_t _sport = (uint16_t) std::stoi (m_port); 95 | if (Config::BindClientIP) { 96 | Tcp::endpoint _ep (asio::ip::address::from_string (co_await Config::BindClientIP ()), 0); 97 | Socket = std::make_shared (Tasks::GetContext (), _ep); 98 | } else { 99 | Socket = std::make_shared (Tasks::GetContext ()); 100 | } 101 | co_await Socket->async_connect (Tcp::endpoint { asio::ip::address::from_string (_ip), _sport }, UseAwaitable); 102 | if (!Socket->is_open ()) 103 | throw Exception (fmt::format ("Cannot connect to server {}", m_host)); 104 | if (Config::NoDelay) 105 | Socket->set_option (Tcp::no_delay { true }); 106 | } 107 | 108 | inline Task TcpConn::Send (char *_data, size_t _size) { 109 | if (!Socket->is_open ()) 110 | throw Exception ("Cannot send data to a closed connection."); 111 | size_t _sended = 0; 112 | while (_sended < _size) { 113 | size_t _tmp_send = co_await Socket->async_send (asio::buffer (&_data [_sended], _size - _sended), UseAwaitable); 114 | if (_tmp_send == 0) 115 | throw Exception ("Connection temp closed."); 116 | _sended += _tmp_send; 117 | } 118 | } 119 | 120 | inline Task TcpConn::RecvImpl (char *_data, size_t _size) { 121 | if (!Socket->is_open ()) 122 | co_return 0; 123 | size_t _count = co_await Socket->async_read_some (asio::buffer (_data, _size), UseAwaitable); 124 | co_return _count; 125 | } 126 | 127 | inline void TcpConn::Cancel () { 128 | try { 129 | if (Socket) 130 | Socket->cancel (); 131 | } catch (...) { 132 | } 133 | Socket = nullptr; 134 | } 135 | 136 | 137 | 138 | inline TcpConn2::TcpConn2 (Tcp::socket _sock): Socket (std::move (_sock)) { 139 | if (Config::NoDelay) 140 | Socket.set_option (Tcp::no_delay { true }); 141 | } 142 | 143 | inline Task TcpConn2::Send (char *_data, size_t _size) { 144 | if (!Socket.is_open ()) 145 | throw Exception ("Cannot send data to a closed connection."); 146 | size_t _sended = 0; 147 | while (_sended < _size) { 148 | size_t _tmp_send = co_await Socket.async_send (asio::buffer (&_data [_sended], _size - _sended), UseAwaitable); 149 | if (_tmp_send == 0) 150 | throw Exception ("Connection temp closed."); 151 | _sended += _tmp_send; 152 | } 153 | } 154 | 155 | inline void TcpConn2::Cancel () { 156 | try { 157 | if (Socket.is_open ()) 158 | Socket.cancel (); 159 | } catch (...) { 160 | } 161 | } 162 | 163 | inline Task TcpConn2::RecvImpl (char *_data, size_t _size) { 164 | if (!Socket.is_open ()) 165 | co_return 0; 166 | size_t _count = co_await Socket.async_read_some (asio::buffer (_data, _size), UseAwaitable); 167 | co_return _count; 168 | } 169 | 170 | 171 | 172 | inline Task SslConn::Connect (std::string _host, std::string _port) { 173 | m_host = _host; 174 | m_port = _port; 175 | co_await Reconnect (); 176 | } 177 | 178 | inline Task SslConn::Reconnect () { 179 | SslSocket = nullptr; 180 | TmpData = ""; 181 | std::regex _rgx { "(\\d+\\.){3}\\d+" }; 182 | std::string _ip = m_host; 183 | if (!std::regex_match (m_host, _rgx)) { 184 | _ip = co_await Config::DnsResolve (m_host); 185 | if (_ip == "") 186 | _ip = m_host; 187 | } 188 | uint16_t _sport = (uint16_t) std::stoi (m_port); 189 | if (Config::BindClientIP) { 190 | Tcp::endpoint _ep (asio::ip::address::from_string (co_await Config::BindClientIP ()), 0); 191 | SslSocket = std::make_shared> (Tcp::socket (Tasks::GetContext (), _ep), SslCtx); 192 | } else { 193 | SslSocket = std::make_shared> (Tasks::GetContext (), SslCtx); 194 | } 195 | if (!::SSL_set_tlsext_host_name (SslSocket->native_handle (), m_host.data ())) 196 | throw Exception (fmt::format ("Cannot set connect sni: {}", m_host)); 197 | SslSocket->set_verify_mode (Ssl::verify_peer); 198 | SslSocket->set_verify_callback (Config::SslVerifyFunc); 199 | co_await SslSocket->next_layer ().async_connect (Tcp::endpoint { asio::ip::address::from_string (_ip), _sport }, UseAwaitable); 200 | if (!SslSocket->next_layer ().is_open ()) 201 | throw Exception (fmt::format ("Cannot connect to server {}", m_host)); 202 | if (Config::NoDelay) 203 | SslSocket->next_layer ().set_option (Tcp::no_delay { true }); 204 | co_await SslSocket->async_handshake (Ssl::stream_base::client, UseAwaitable); 205 | } 206 | 207 | inline Task SslConn::Send (char *_data, size_t _size) { 208 | if (!SslSocket->next_layer ().is_open ()) 209 | throw Exception ("Cannot send data to a closed connection."); 210 | size_t _sended = 0; 211 | while (_sended < _size) { 212 | size_t _tmp_send = co_await SslSocket->async_write_some (asio::buffer (&_data [_sended], _size - _sended), UseAwaitable); 213 | if (_tmp_send == 0) 214 | throw Exception ("Connection temp closed."); 215 | _sended += _tmp_send; 216 | } 217 | } 218 | 219 | inline Task SslConn::RecvImpl (char *_data, size_t _size) { 220 | co_return co_await SslSocket->async_read_some (asio::buffer (_data, _size), UseAwaitable); 221 | } 222 | 223 | inline void SslConn::Cancel () { 224 | try { 225 | if (SslSocket) 226 | SslSocket->next_layer ().cancel (); 227 | } catch (...) { 228 | } 229 | SslSocket = nullptr; 230 | } 231 | 232 | 233 | 234 | inline SslConn2::SslConn2 (Ssl::stream _sock): SslSocket (std::move (_sock)) { 235 | if (Config::NoDelay) 236 | SslSocket.next_layer ().set_option (Tcp::no_delay { true }); 237 | } 238 | 239 | inline Task SslConn2::Send (char *_data, size_t _size) { 240 | if (!SslSocket.next_layer ().is_open ()) 241 | throw Exception ("Cannot send data to a closed connection."); 242 | size_t _sended = 0; 243 | while (_sended < _size) { 244 | size_t _tmp_send = co_await SslSocket.async_write_some (asio::buffer (&_data [_sended], _size - _sended), UseAwaitable); 245 | if (_tmp_send == 0) 246 | throw Exception ("Connection temp closed."); 247 | _sended += _tmp_send; 248 | } 249 | } 250 | 251 | inline void SslConn2::Cancel () { 252 | try { 253 | if (SslSocket.next_layer ().is_open ()) 254 | SslSocket.next_layer ().cancel (); 255 | } catch (...) { 256 | } 257 | } 258 | 259 | inline Task SslConn2::RecvImpl (char *_data, size_t _size) { 260 | if (!SslSocket.next_layer ().is_open ()) 261 | co_return 0; 262 | size_t _count = co_await SslSocket.async_read_some (asio::buffer (_data, _size), UseAwaitable); 263 | co_return _count; 264 | } 265 | 266 | 267 | 268 | inline WsConn::WsConn (std::shared_ptr _parent, bool _is_client): Parent (_parent), IsClient (_is_client) {} 269 | 270 | 271 | 272 | inline void WsConn::Init () { 273 | fv::Tasks::RunAsync ([_wptr = std::weak_ptr (shared_from_this ())] ()->Task { 274 | while (true) { 275 | co_await Tasks::Delay (Config::WebsocketAutoPing); 276 | auto _ptr = _wptr.lock (); 277 | if (!_ptr || !_ptr->IsConnect()) 278 | co_return; 279 | _ptr->_Send (nullptr, 0, WsType::Ping); 280 | } 281 | }); 282 | } 283 | 284 | 285 | 286 | inline Task> WsConn::Recv () { 287 | std::function> ()> _read_pack = [this] () -> Task> { 288 | std::vector _tmp = co_await Parent->ReadCountVec (2); 289 | bool _is_eof = (_tmp [0] & 0x80) != 0; 290 | WsType _type = (WsType) (_tmp [0] & 0xf); 291 | bool _mask = _tmp [1] & 0x80; 292 | int64_t _payload_length = (int64_t) (_tmp [1] & 0x7f); 293 | if (_payload_length <= 0) 294 | co_return std::make_tuple (_is_eof, _type, std::string ("")); 295 | if (_payload_length == 126) { 296 | _tmp = co_await Parent->ReadCountVec (2); 297 | _payload_length = (((int64_t) _tmp [0]) << 8) + _tmp [1]; 298 | } else if (_payload_length == 127) { 299 | _tmp = co_await Parent->ReadCountVec (8); 300 | _payload_length = 0; 301 | for (int i = 0; i < 8; ++i) 302 | _payload_length = (_payload_length << 8) + _tmp [i]; 303 | } 304 | if (_mask) 305 | _tmp = co_await Parent->ReadCountVec (4); 306 | std::string _s = co_await Parent->ReadCount ((size_t) _payload_length); 307 | if (_mask) { 308 | for (size_t i = 0; i < _s.size (); ++i) 309 | _s [i] ^= _tmp [i % 4]; 310 | } 311 | co_return std::make_tuple (_is_eof, _type, std::move (_s)); 312 | }; 313 | std::string _data2; 314 | auto [_is_eof, _type, _data] = co_await _read_pack (); 315 | while ((!_is_eof) || _type == WsType::Continue) { 316 | std::tie (_is_eof, _type, _data2) = co_await _read_pack (); 317 | _data += _data2; 318 | } 319 | // 320 | if (_type == WsType::Close) { 321 | throw Exception ("Remote send close msg."); 322 | } else if (_type == WsType::Ping) { 323 | co_await _Send (nullptr, 0, WsType::Pong); 324 | co_return co_await Recv (); 325 | } else if (_type == WsType::Pong) { 326 | //co_return std::make_tuple (std::string (""), WsType::Pong); 327 | co_return co_await Recv (); 328 | } else if (_type == WsType::Text || _type == WsType::Binary) { 329 | co_return std::make_tuple (_data, _type); 330 | } else { 331 | Parent = nullptr; 332 | throw Exception ("Unparsed websocket frame."); 333 | } 334 | } 335 | 336 | inline Task WsConn::_Send (char *_data, size_t _size, WsType _type) { 337 | if (!IsConnect ()) { 338 | if (_type == WsType::Close) 339 | co_return; 340 | throw Exception ("Cannot send data to a closed connection."); 341 | } 342 | std::stringstream _ss; 343 | _ss << (char) (0x80 | (char) _type); 344 | static std::vector _mask = { (char) 0xfa, (char) 0xfb, (char) 0xfc, (char) 0xfd }; 345 | if (_size > 0) { 346 | if (_size < 0x7e) { 347 | _ss << (char) (IsClient ? (0x80 | _size) : _size); 348 | } else if (_size < 0xffff) { 349 | _ss << (char) (IsClient ? 0xfe : 0x7e) << (char) ((_size >> 8) & 0xff) << (char) (_size & 0xff); 350 | } else { 351 | _ss << (char) (IsClient ? 0xff : 0x7f); 352 | int64_t _size64 = (int64_t) _size; 353 | for (int i = 7; i >= 0; --i) 354 | _ss << (char) ((_size64 >> (i * 8)) & 0xff); 355 | } 356 | if (IsClient) { 357 | for (size_t i = 0; i < 4; ++i) 358 | _ss << _mask [i]; 359 | } 360 | _ss << std::string_view { _data, _size }; 361 | } 362 | std::string _to_send = _ss.str (); 363 | if (IsClient) { 364 | for (size_t i = 0, _offset = _to_send.size () - _size; i < _size; ++i) 365 | _to_send [_offset + i] ^= _mask [i % 4]; 366 | } 367 | try { 368 | co_await Parent->Send (_to_send.data (), _to_send.size ()); 369 | } catch (...) { 370 | if (_type != WsType::Close) 371 | throw; 372 | } 373 | } 374 | 375 | 376 | 377 | inline Task> Connect (std::string _url) { 378 | auto [_schema, _host, _port, _path] = _parse_url (_url); 379 | // 380 | if (_schema == "tcp") { 381 | if (_path != "/") 382 | throw Exception ("Url format error"); 383 | auto _conn = std::shared_ptr (new TcpConn {}); 384 | co_await _conn->Connect (_host, _port); 385 | co_return _conn; 386 | } else { 387 | throw Exception (fmt::format ("Unknown protocol: {}", _schema)); 388 | } 389 | } 390 | 391 | template 392 | inline Task> ConnectWS (std::string _url, _Ops ..._ops) { 393 | auto [_schema, _host, _port, _path] = _parse_url (_url); 394 | if (_schema == "ws" || _schema == "wss") { 395 | // generate data 396 | Request _r { _url, MethodType::Get }; 397 | _r.Headers ["Connection"] = "Upgrade"; 398 | _r.Headers ["Upgrade"] = "websocket"; 399 | _r.Headers ["Sec-WebSocket-Version"] = "13"; 400 | _r.Headers ["Sec-WebSocket-Key"] = base64_encode (random_str (16)); 401 | //_r.Headers ["Sec-WebSocket-Extensions"] = "chat"; 402 | if constexpr (sizeof...(_ops) > 0) 403 | _OptionApplys (_r, _ops...); 404 | 405 | // connect 406 | auto _conn = std::shared_ptr (_schema == "ws" ? (IConn *) new TcpConn {} : new SslConn {}); 407 | co_await _conn->Connect (_r.Server != "" ? _r.Server : _host, _port); 408 | 409 | // send 410 | std::string _data = _r.Serilize (_host, _port, _path); 411 | co_await _conn->Send (_data.data (), _data.size ()); 412 | 413 | // recv 414 | co_await Response::GetFromConn (_conn); 415 | auto _wsconn = std::make_shared (_conn, true); 416 | _wsconn->Init (); 417 | co_return _wsconn; 418 | } else { 419 | throw Exception (fmt::format ("Unknown protocol: {}", _schema)); 420 | } 421 | } 422 | } 423 | 424 | 425 | 426 | #endif //__FV_CONN_IMPL_HPP__ 427 | -------------------------------------------------------------------------------- /include/fv/declare.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_DECLARE_HPP__ 2 | #define __FV_DECLARE_HPP__ 3 | 4 | 5 | 6 | #ifndef FV_USE_BOOST_ASIO 7 | #ifndef ASIO_HAS_CO_AWAIT 8 | #define ASIO_HAS_CO_AWAIT 9 | #endif 10 | #include 11 | #include 12 | #else 13 | #ifndef BOOST_ASIO_HAS_CO_AWAIT 14 | #define BOOST_ASIO_HAS_CO_AWAIT 15 | #endif 16 | #include 17 | #include 18 | #endif 19 | 20 | 21 | 22 | namespace fv { 23 | #ifdef FV_USE_BOOST_ASIO 24 | namespace asio = boost::asio; 25 | #define Task boost::asio::awaitable 26 | #else 27 | #define Task asio::awaitable 28 | #endif 29 | 30 | using Tcp = asio::ip::tcp; 31 | using Udp = asio::ip::udp; 32 | namespace Ssl = asio::ssl; 33 | using IoContext = asio::io_context; 34 | using SocketBase = asio::socket_base; 35 | using SslCheckCb = std::function; 36 | inline decltype (asio::use_awaitable) &UseAwaitable = asio::use_awaitable; 37 | using TimeSpan = std::chrono::system_clock::duration; 38 | } 39 | 40 | 41 | 42 | #endif //__FV_DECLARE_HPP__ 43 | -------------------------------------------------------------------------------- /include/fv/fv.h: -------------------------------------------------------------------------------- 1 | #ifndef __FV_H__ 2 | #define __FV_H__ 3 | 4 | 5 | 6 | // 7 | // Project Name: libfv 8 | // Repository URL: https://github.com/fawdlstty/libfv 9 | // Author: fawdlstty 10 | // License: MIT 11 | // Version: 0.0.9 prerelease 12 | // Last updated: Aug 15, 2022 13 | // 14 | 15 | 16 | 17 | namespace fv { 18 | inline const char *version = "libfv-0.0.9-prerelease"; 19 | } 20 | 21 | 22 | 23 | #include "declare.hpp" 24 | #include "structs.hpp" 25 | #include "common.hpp" 26 | #include "common_funcs.hpp" 27 | #include "conn.hpp" 28 | #include "conn_impl.hpp" 29 | #include "ioctx_pool.hpp" 30 | #include "req_res.hpp" 31 | #include "req_res_impl.hpp" 32 | #include "server.hpp" 33 | #include "session.hpp" 34 | 35 | 36 | 37 | #endif //__FV_H__ 38 | -------------------------------------------------------------------------------- /include/fv/ioctx_pool.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_IOCTX_POOL_HPP__ 2 | #define __FV_IOCTX_POOL_HPP__ 3 | 4 | 5 | 6 | #include "declare.hpp" 7 | #include "common.hpp" 8 | 9 | 10 | 11 | namespace fv { 12 | class IoCtxPool { 13 | public: 14 | explicit IoCtxPool (size_t _nthread) { 15 | if (_nthread == 0) { 16 | _nthread = std::thread::hardware_concurrency () - 1; 17 | if (_nthread == 0) 18 | _nthread = 1; 19 | } 20 | // 21 | for (size_t i = 0; i < _nthread; ++i) { 22 | std::shared_ptr _ioctx = std::make_shared (); 23 | std::shared_ptr _work = std::make_shared (*_ioctx); 24 | m_ioctxs.push_back (_ioctx); 25 | m_works.push_back (_work); 26 | } 27 | m_main_ioctx = std::make_shared (); 28 | m_main_work = std::make_shared (*m_main_ioctx); 29 | } 30 | 31 | void Run () { 32 | std::vector> _threads; 33 | for (size_t i = 0; i < m_ioctxs.size (); ++i) { 34 | _threads.emplace_back (std::make_shared ([] (std::shared_ptr svr) { svr->run (); }, m_ioctxs [i])); 35 | } 36 | m_main_ioctx->run (); 37 | for (size_t i = 0; i < _threads.size (); ++i) { 38 | _threads [i]->join (); 39 | } 40 | } 41 | 42 | void Stop () { 43 | m_main_work = nullptr; 44 | m_works.clear (); 45 | m_main_ioctx->stop (); 46 | for (size_t i = 0; i < m_ioctxs.size (); ++i) 47 | m_ioctxs [i]->stop (); 48 | } 49 | 50 | fv::IoContext &GetMainContext () { 51 | return *m_main_ioctx; 52 | } 53 | 54 | fv::IoContext &GetContext () { 55 | return *m_ioctxs [m_cur_index++ % m_ioctxs.size ()]; 56 | } 57 | 58 | private: 59 | std::vector> m_ioctxs; 60 | std::vector> m_works; 61 | std::shared_ptr m_main_ioctx; 62 | std::shared_ptr m_main_work; 63 | size_t m_cur_index = 0; 64 | }; 65 | } 66 | 67 | 68 | 69 | #endif //__FV_IOCTX_POOL_HPP__ 70 | -------------------------------------------------------------------------------- /include/fv/req_res.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_REQ_RES_HPP__ 2 | #define __FV_REQ_RES_HPP__ 3 | 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "common.hpp" 14 | #include "structs.hpp" 15 | 16 | 17 | 18 | namespace fv { 19 | struct IConn; 20 | struct IConn2; 21 | struct WsConn; 22 | 23 | struct Request { 24 | Request () {} 25 | Request (std::string _url, MethodType _method): Url (_url), Method (_method) {} 26 | 27 | TimeSpan Timeout = std::chrono::seconds (0); 28 | std::string Server = ""; 29 | // 30 | std::string Url = ""; 31 | MethodType Method = MethodType::Get; 32 | std::string Schema = ""; 33 | std::string UrlPath = ""; 34 | std::string Content = ""; 35 | std::vector QueryItems; 36 | std::vector> ContentItems; 37 | CaseInsensitiveMap Headers = DefaultHeaders (); 38 | std::unordered_map Cookies; 39 | 40 | static Task GetFromConn (std::shared_ptr _conn, uint16_t _listen_port); 41 | static CaseInsensitiveMap DefaultHeaders () { return m_def_headers; } 42 | static void SetDefaultHeader (std::string _key, std::string _value) { m_def_headers [_key] = _value; } 43 | 44 | std::string Serilize (std::string _host, std::string _port, std::string _path); 45 | bool IsWebsocket (); 46 | Task> UpgradeWebsocket (); 47 | bool IsUpgraded () { return Upgrade; } 48 | 49 | private: 50 | bool _content_raw_contains_files (); 51 | std::shared_ptr Conn; 52 | bool Upgrade = false; 53 | 54 | inline static CaseInsensitiveMap m_def_headers { { "Accept", "*/*" }, { "Accept-Encoding", "gzip" }, { "Accept-Language", "zh-CN,zh,q=0.9" }, { "Pragma", "no-cache" }, { "Cache-Control", "no-cache" }, { "Connection", "keep-alive" }, { "User-Agent", version } }; 55 | }; 56 | 57 | 58 | 59 | struct Response { 60 | int HttpCode = -1; 61 | std::string Content = ""; 62 | CaseInsensitiveMap Headers; 63 | 64 | static Task GetFromConn (std::shared_ptr _conn); 65 | static Response Empty () { return Response {}; } 66 | static Response FromNotFound (); 67 | static Response FromText (std::string _text); 68 | static Response FromUpgradeWebsocket (Request &_r); 69 | 70 | std::string Serilize (); 71 | 72 | private: 73 | static void InitDefaultHeaders (CaseInsensitiveMap &_map); 74 | }; 75 | } 76 | 77 | 78 | 79 | #endif //__FV_REQ_RES_HPP__ 80 | -------------------------------------------------------------------------------- /include/fv/req_res_impl.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fawdlstty/libfv/b2ee4a04a7c4303e000158cba8c1475fbe3f33d7/include/fv/req_res_impl.hpp -------------------------------------------------------------------------------- /include/fv/server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_TCP_SERVER_HPP___ 2 | #define __FV_TCP_SERVER_HPP___ 3 | 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "common.hpp" 13 | #include "conn.hpp" 14 | 15 | 16 | 17 | namespace fv { 18 | struct TcpServer { 19 | void SetOnConnect (std::function (std::shared_ptr)> _on_connect) { OnConnect = _on_connect; } 20 | void RegisterClient (int64_t _id, std::shared_ptr _conn) { std::unique_lock _ul { Mutex }; Clients [_id] = _conn; } 21 | void UnregisterClient (int64_t _id, std::shared_ptr _conn) { 22 | std::unique_lock _ul { Mutex }; 23 | if (Clients [_id].get () == _conn.get ()) 24 | Clients.erase (_id); 25 | } 26 | Task SendData (int64_t _id, char *_data, size_t _size) { 27 | try { 28 | std::unique_lock _ul { Mutex }; 29 | if (Clients.contains (_id)) { 30 | auto _conn = Clients [_id]; 31 | _ul.unlock (); 32 | co_await _conn->Send (_data, _size); 33 | co_return true; 34 | } 35 | } catch (...) { 36 | } 37 | co_return false; 38 | } 39 | Task BroadcastData (char *_data, size_t _size) { 40 | std::unique_lock _ul { Mutex }; 41 | std::unordered_set> _conns; 42 | for (auto [_key, _val] : Clients) 43 | _conns.emplace (_val); 44 | _ul.unlock (); 45 | size_t _count = 0; 46 | for (auto _conn : _conns) { 47 | try { 48 | co_await _conn->Send (_data, _size); 49 | _count++; 50 | } catch (...) { 51 | } 52 | } 53 | co_return _count; 54 | } 55 | Task Run (std::string _ip, uint16_t _port) { 56 | if (IsRun.load ()) 57 | co_return; 58 | IsRun.store (true); 59 | auto _executor = co_await asio::this_coro::executor; 60 | Tcp::endpoint _ep { asio::ip::address::from_string (_ip), _port }; 61 | Acceptor = std::make_unique (_executor, _ep, true); 62 | try { 63 | for (; IsRun.load ();) { 64 | std::shared_ptr _conn = std::shared_ptr ((IConn2 *) new TcpConn2 (co_await Acceptor->async_accept (UseAwaitable))); 65 | Tasks::RunAsync ([this, _conn] () -> Task { 66 | co_await OnConnect (_conn); 67 | }); 68 | } 69 | } catch (...) { 70 | } 71 | } 72 | Task Run (uint16_t _port) { 73 | co_await Run ("0.0.0.0", _port); 74 | } 75 | void Stop () { 76 | IsRun.store (false); 77 | if (Acceptor) 78 | Acceptor->cancel (); 79 | } 80 | 81 | private: 82 | std::function (std::shared_ptr)> OnConnect; 83 | std::mutex Mutex; 84 | std::unordered_map> Clients; 85 | 86 | std::unique_ptr Acceptor; 87 | std::atomic_bool IsRun { false }; 88 | }; 89 | 90 | 91 | 92 | struct HttpServer { 93 | void OnBefore (std::function> (fv::Request &)> _cb) { m_before = _cb; } 94 | void SetHttpHandler (std::string _path, std::function (fv::Request &)> _cb) { m_map_proc [_path] = _cb; } 95 | void OnUnhandled (std::function (fv::Request &)> _cb) { m_unhandled_proc = _cb; } 96 | void OnAfter (std::function (fv::Request &, fv::Response &)> _cb) { m_after = _cb; } 97 | 98 | Task Run (uint16_t _port) { 99 | m_tcpserver.SetOnConnect ([this, _port] (std::shared_ptr _conn) -> Task { 100 | while (true) { 101 | Request _req = co_await Request::GetFromConn (_conn, _port); 102 | if (m_before) { 103 | std::optional _ores = co_await m_before (_req); 104 | if (_ores.has_value ()) { 105 | if (m_after) 106 | co_await m_after (_req, _ores.value ()); 107 | std::string _str_res = _ores.value ().Serilize (); 108 | co_await _conn->Send (_str_res.data (), _str_res.size ()); 109 | continue; 110 | } 111 | } 112 | Response _res {}; 113 | if (m_map_proc.contains (_req.UrlPath)) { 114 | try { 115 | _res = co_await m_map_proc [_req.UrlPath] (_req); 116 | } catch (...) { 117 | } 118 | } 119 | if (_res.HttpCode == -1) { 120 | try { 121 | _res = co_await m_unhandled_proc (_req); 122 | } catch (...) { 123 | } 124 | } 125 | if (_req.IsUpgraded ()) 126 | break; 127 | if (_res.HttpCode == -1) 128 | _res = Response::FromNotFound (); 129 | if (m_after) 130 | co_await m_after (_req, _res); 131 | std::string _str_res = _res.Serilize (); 132 | co_await _conn->Send (_str_res.data (), _str_res.size ()); 133 | } 134 | }); 135 | co_await m_tcpserver.Run (_port); 136 | } 137 | 138 | void Stop () { m_tcpserver.Stop (); } 139 | 140 | private: 141 | TcpServer m_tcpserver {}; 142 | std::function> (Request &)> m_before; 143 | std::unordered_map (Request &)>> m_map_proc; 144 | std::function (Request &)> m_unhandled_proc = [] (Request &) -> Task { co_return Response::FromNotFound (); }; 145 | std::function (Request &, Response &)> m_after; 146 | }; 147 | } 148 | 149 | 150 | 151 | #endif //__FV_TCP_SERVER_HPP___ 152 | -------------------------------------------------------------------------------- /include/fv/session.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_SESSION_HPP__ 2 | #define __FV_SESSION_HPP__ 3 | 4 | 5 | 6 | #include 7 | #include 8 | 9 | #include "common.hpp" 10 | #include "common_funcs.hpp" 11 | #include "conn.hpp" 12 | #include "req_res.hpp" 13 | 14 | 15 | 16 | namespace fv { 17 | template 18 | inline void _OptionApply (Request &_r, _Op1 &_op) { throw Exception ("Unsupported dest type template instance"); } 19 | template<> inline void _OptionApply (Request &_r, timeout &_t) { _r.Timeout = _t.m_exp; } 20 | template<> inline void _OptionApply (Request &_r, server &_s) { _r.Server = _s.m_ip; } 21 | template<> inline void _OptionApply (Request &_r, header &_hh) { _r.Headers [_hh.m_key] = _hh.m_value; } 22 | template<> inline void _OptionApply (Request &_r, authorization &_auth) { _r.Headers [_auth.m_key] = _auth.m_value; } 23 | template<> inline void _OptionApply (Request &_r, connection &_co) { _r.Headers [_co.m_key] = _co.m_value; } 24 | template<> inline void _OptionApply (Request &_r, content_type &_ct) { _r.Headers [_ct.m_key] = _ct.m_value; } 25 | template<> inline void _OptionApply (Request &_r, referer &_re) { _r.Headers [_re.m_key] = _re.m_value; } 26 | template<> inline void _OptionApply (Request &_r, user_agent &_ua) { _r.Headers [_ua.m_key] = _ua.m_value; } 27 | template<> inline void _OptionApply (Request &_r, url_kv &_pd) { _r.QueryItems.push_back (_pd); } 28 | template<> inline void _OptionApply (Request &_r, body_kv &_pd) { _r.ContentItems.push_back (_pd); } 29 | template<> inline void _OptionApply (Request &_r, body_file &_pf) { _r.ContentItems.push_back (_pf); } 30 | template<> inline void _OptionApply (Request &_r, body_kvs &_body) { 31 | for (auto &[_k, _v] : _body.Kvs) 32 | _r.ContentItems.push_back (body_kv { _k, _v }); 33 | } 34 | 35 | template 36 | inline void _OptionApplyBody (Request &_r, _Op1 &_op) { throw Exception ("Unsupported dest type template instance"); } 37 | template<> inline void _OptionApplyBody (Request &_r, body_json &_body) { 38 | _r.Headers ["Content-Type"] = "application/json"; 39 | _r.Content = _body.Content; 40 | } 41 | template<> inline void _OptionApplyBody (Request &_r, body_raw &_body) { 42 | _r.Headers ["Content-Type"] = _body.ContentType; 43 | _r.Content = _body.Content; 44 | } 45 | 46 | template 47 | inline void _OptionApplys (Request &_r, _Op1 _op1) { _OptionApply (_r, _op1); } 48 | template 49 | inline void _OptionApplys (Request &_r, _Op1 _op1, _Ops ..._ops) { _OptionApply (_r, _op1); _OptionApplys (_r, _ops...); } 50 | 51 | 52 | 53 | struct Session { 54 | std::shared_ptr Conn; 55 | std::string ConnFlag = ""; 56 | std::chrono::steady_clock::time_point LastUseTime = std::chrono::steady_clock::now (); 57 | 58 | Session () {} 59 | Session (const Session &_sess): Conn (_sess.Conn), ConnFlag (_sess.ConnFlag), LastUseTime (_sess.LastUseTime) {} 60 | Session &operator= (const Session &_sess) { Conn = _sess.Conn; ConnFlag = _sess.ConnFlag; LastUseTime = _sess.LastUseTime; return *this; } 61 | bool IsConnect () { return Conn && Conn->IsConnect (); } 62 | 63 | Task DoMethod (Request _r) { 64 | LastUseTime = std::chrono::steady_clock::now (); 65 | auto [_schema, _host, _port, _path] = _parse_url (_r.Url); 66 | std::string _conn_flag = fmt::format ("{}://{}:{}", _schema, _host, _port); 67 | if (!Conn || ConnFlag != _conn_flag) { 68 | ConnFlag = _conn_flag; 69 | if (_schema == "https") { 70 | Conn = std::shared_ptr (new SslConn {}); 71 | } else { 72 | Conn = std::shared_ptr (new TcpConn {}); 73 | } 74 | co_await Conn->Connect (_host, _port); 75 | } 76 | 77 | _r.Schema = _schema; 78 | _r.UrlPath = _path; 79 | 80 | //// cancel 81 | //AsyncTimer _timer {}; 82 | //if (std::chrono::duration_cast (_r.Timeout).count () > 0) { 83 | // _timer.WaitCallback (_r.Timeout, [_tconn = std::weak_ptr (Conn)] ()->Task { 84 | // auto _conn = _tconn.lock (); 85 | // if (_conn) 86 | // _conn->Cancel (); 87 | // }); 88 | //} 89 | 90 | // generate data 91 | std::string _data = _r.Serilize (_host, _port, _path); 92 | 93 | // try once 94 | bool _suc = true; 95 | try { 96 | co_await Conn->Send (_data.data (), _data.size ()); 97 | } catch (...) { 98 | _suc = false; 99 | } 100 | if (_suc) { 101 | co_return co_await Response::GetFromConn (Conn); 102 | } 103 | 104 | // try second 105 | co_await Conn->Reconnect (); 106 | co_await Conn->Send (_data.data (), _data.size ()); 107 | co_return co_await Response::GetFromConn (Conn); 108 | //_timer.Cancel (); 109 | } 110 | 111 | Task Head (std::string _url) { 112 | co_return co_await DoMethod (Request { _url, MethodType::Head }); 113 | } 114 | template 115 | Task Head (std::string _url, _Ops ..._ops) { 116 | Request _r { _url, MethodType::Head }; 117 | _OptionApplys (_r, _ops...); 118 | co_return co_await DoMethod (_r); 119 | } 120 | 121 | Task Option (std::string _url) { 122 | co_return co_await DoMethod (Request { _url, MethodType::Option }); 123 | } 124 | template 125 | Task Option (std::string _url, _Ops ..._ops) { 126 | Request _r { _url, MethodType::Option }; 127 | _OptionApplys (_r, _ops...); 128 | co_return co_await DoMethod (_r); 129 | } 130 | 131 | Task Get (std::string _url) { 132 | co_return co_await DoMethod (Request { _url, MethodType::Get }); 133 | } 134 | template 135 | Task Get (std::string _url, _Ops ..._ops) { 136 | Request _r { _url, MethodType::Get }; 137 | _OptionApplys (_r, _ops...); 138 | co_return co_await DoMethod (_r); 139 | } 140 | 141 | template 142 | Task Post (std::string _url, _Ops ..._ops) { 143 | Request _r { _url, MethodType::Post }; 144 | _OptionApplys (_r, _ops...); 145 | co_return co_await DoMethod (_r); 146 | } 147 | template 148 | Task Post (std::string _url, _Body _body) { 149 | Request _r { _url, MethodType::Post }; 150 | _OptionApplyBody (_r, _body); 151 | co_return co_await DoMethod (_r); 152 | } 153 | template 154 | Task Post (std::string _url, _Body _body, _Ops ..._ops) { 155 | Request _r { _url, MethodType::Post }; 156 | _OptionApplyBody (_r, _body); 157 | _OptionApplys (_r, _ops...); 158 | co_return co_await DoMethod (_r); 159 | } 160 | 161 | template 162 | Task Put (std::string _url, _Ops ..._ops) { 163 | Request _r { _url, MethodType::Put }; 164 | _OptionApplys (_r, _ops...); 165 | co_return co_await DoMethod (_r); 166 | } 167 | template 168 | Task Put (std::string _url, _Body _body) { 169 | Request _r { _url, MethodType::Put }; 170 | _OptionApplyBody (_r, _body); 171 | co_return co_await DoMethod (_r); 172 | } 173 | template 174 | Task Put (std::string _url, _Body _body, _Ops ..._ops) { 175 | Request _r { _url, MethodType::Put }; 176 | _OptionApplyBody (_r, _body); 177 | _OptionApplys (_r, _ops...); 178 | co_return co_await DoMethod (_r); 179 | } 180 | 181 | Task Delete (std::string _url) { 182 | co_return co_await DoMethod (Request { _url, MethodType::Delete }); 183 | } 184 | template 185 | Task Delete (std::string _url, _Ops ..._ops) { 186 | Request _r { _url, MethodType::Delete }; 187 | _OptionApplys (_r, _ops...); 188 | co_return co_await DoMethod (_r); 189 | } 190 | }; 191 | 192 | 193 | 194 | struct SessionPool { 195 | inline static std::mutex m_mtx; 196 | inline static std::map> m_pool; 197 | 198 | inline static Session GetSession (std::string _url) { 199 | auto [_schema, _host, _port, _path] = _parse_url (_url); 200 | std::string _conn_flag = fmt::format ("{}://{}:{}", _schema, _host, _port); 201 | std::unique_lock _ul { m_mtx }; 202 | if (m_pool.contains (_conn_flag)) { 203 | auto &_v = m_pool [_conn_flag]; 204 | if (!_v.empty ()) { 205 | Session _sess = _v [0]; 206 | _v.erase (_v.begin ()); 207 | return _sess; 208 | } 209 | } else { 210 | m_pool [_conn_flag] = std::vector {}; 211 | } 212 | 213 | _ul.unlock (); 214 | return Session {}; 215 | } 216 | 217 | inline static void FreeSession (const Session &_sess) { 218 | std::unique_lock _ul { m_mtx }; 219 | m_pool [_sess.ConnFlag].emplace_back (_sess); 220 | } 221 | 222 | inline static void TimeClear () { 223 | std::unique_lock _ul { m_mtx }; 224 | auto _now = std::chrono::steady_clock::now (); 225 | for (auto &[_key, _val] : m_pool) { 226 | while (!_val.empty ()) { 227 | if (_val [0].LastUseTime + Config::SessionPoolTimeout > _now) 228 | break; 229 | _val.erase (_val.begin ()); 230 | } 231 | } 232 | } 233 | }; 234 | 235 | 236 | 237 | inline Task Head (std::string _url) { 238 | Session _sess = SessionPool::GetSession (_url); 239 | Response _ret = co_await _sess.Head (_url); 240 | SessionPool::FreeSession (_sess); 241 | co_return _ret; 242 | } 243 | template 244 | inline Task Head (std::string _url, _Ops ..._ops) { 245 | Session _sess = SessionPool::GetSession (_url); 246 | Response _ret = co_await _sess.Head (_url, std::forward<_Ops> (_ops)...); 247 | SessionPool::FreeSession (_sess); 248 | co_return _ret; 249 | } 250 | 251 | inline Task Option (std::string _url) { 252 | Session _sess = SessionPool::GetSession (_url); 253 | Response _ret = co_await _sess.Option (_url); 254 | SessionPool::FreeSession (_sess); 255 | co_return _ret; 256 | } 257 | template 258 | inline Task Option (std::string _url, _Ops ..._ops) { 259 | Session _sess = SessionPool::GetSession (_url); 260 | Response _ret = co_await _sess.Option (_url, std::forward<_Ops> (_ops)...); 261 | SessionPool::FreeSession (_sess); 262 | co_return _ret; 263 | } 264 | 265 | inline Task Get (std::string _url) { 266 | Session _sess = SessionPool::GetSession (_url); 267 | Response _ret = co_await _sess.Get (_url); 268 | SessionPool::FreeSession (_sess); 269 | co_return _ret; 270 | } 271 | template 272 | inline Task Get (std::string _url, _Ops ..._ops) { 273 | Session _sess = SessionPool::GetSession (_url); 274 | Response _ret = co_await _sess.Get (_url, std::forward<_Ops> (_ops)...); 275 | SessionPool::FreeSession (_sess); 276 | co_return _ret; 277 | } 278 | 279 | template 280 | inline Task Post (std::string _url, _Ops ..._ops) { 281 | Session _sess = SessionPool::GetSession (_url); 282 | Response _ret = co_await _sess.Post (_url, std::forward<_Ops> (_ops)...); 283 | SessionPool::FreeSession (_sess); 284 | co_return _ret; 285 | } 286 | template 287 | inline Task Post (std::string _url, _Body _body) { 288 | Session _sess = SessionPool::GetSession (_url); 289 | Response _ret = co_await _sess.Post (_url, std::forward<_Body> (_body)); 290 | SessionPool::FreeSession (_sess); 291 | co_return _ret; 292 | } 293 | template 294 | inline Task Post (std::string _url, _Body _body, _Ops ..._ops) { 295 | Session _sess = SessionPool::GetSession (_url); 296 | Response _ret = co_await _sess.Post (_url, std::forward<_Body> (_body), std::forward<_Ops> (_ops)...); 297 | SessionPool::FreeSession (_sess); 298 | co_return _ret; 299 | } 300 | 301 | template 302 | inline Task Put (std::string _url, _Ops ..._ops) { 303 | Session _sess = SessionPool::GetSession (_url); 304 | Response _ret = co_await _sess.Put (_url, std::forward<_Ops> (_ops)...); 305 | SessionPool::FreeSession (_sess); 306 | co_return _ret; 307 | } 308 | template 309 | inline Task Put (std::string _url, _Body _body) { 310 | Session _sess = SessionPool::GetSession (_url); 311 | Response _ret = co_await _sess.Put (_url, std::forward<_Body> (_body)); 312 | SessionPool::FreeSession (_sess); 313 | co_return _ret; 314 | } 315 | template 316 | inline Task Put (std::string _url, _Body _body, _Ops ..._ops) { 317 | Session _sess = SessionPool::GetSession (_url); 318 | Response _ret = co_await _sess.Put (_url, std::forward<_Body> (_body), std::forward<_Ops> (_ops)...); 319 | SessionPool::FreeSession (_sess); 320 | co_return _ret; 321 | } 322 | 323 | inline Task Delete (std::string _url) { 324 | Session _sess = SessionPool::GetSession (_url); 325 | Response _ret = co_await _sess.Delete (_url); 326 | SessionPool::FreeSession (_sess); 327 | co_return _ret; 328 | } 329 | template 330 | inline Task Delete (std::string _url, _Ops ..._ops) { 331 | Session _sess = SessionPool::GetSession (_url); 332 | Response _ret = co_await _sess.Delete (_url, std::forward<_Ops> (_ops)...); 333 | SessionPool::FreeSession (_sess); 334 | co_return _ret; 335 | } 336 | } 337 | 338 | 339 | 340 | #endif //__FV_SESSION_HPP__ 341 | -------------------------------------------------------------------------------- /include/fv/structs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FV_STRUCTS_HPP__ 2 | #define __FV_STRUCTS_HPP__ 3 | 4 | 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "common.hpp" 12 | #include "common_funcs.hpp" 13 | 14 | 15 | 16 | namespace fv { 17 | enum class MethodType { Head, Option, Get, Post, Put, Delete }; 18 | enum class WsType { Continue = 0, Text = 1, Binary = 2, Close = 8, Ping = 9, Pong = 10 }; 19 | 20 | 21 | 22 | struct Config { 23 | inline static SslCheckCb SslVerifyFunc = [] (bool preverified, Ssl::verify_context &ctx) { return true; }; 24 | inline static TimeSpan ConnectTimeout = std::chrono::seconds (2); 25 | inline static bool NoDelay = false; 26 | inline static TimeSpan WebsocketAutoPing = std::chrono::minutes (1); 27 | inline static TimeSpan SessionPoolTimeout = std::chrono::minutes (1); 28 | inline static Ssl::context::method SslClientVer = Ssl::context::tls, SslServerVer = Ssl::context::tls; 29 | inline static std::function (std::string)> DnsResolve = [] (std::string _host) -> Task { 30 | Tcp::resolver _resolver { Tasks::GetContext () }; 31 | auto _it = co_await _resolver.async_resolve (_host, "", UseAwaitable); 32 | co_return _it.begin ()->endpoint ().address ().to_string (); 33 | }; 34 | inline static std::function ()> BindClientIP; 35 | }; 36 | 37 | 38 | 39 | struct timeout { TimeSpan m_exp; timeout (TimeSpan _exp): m_exp (_exp) {} }; 40 | struct server { std::string m_ip; server (std::string _ip): m_ip (_ip) {} }; 41 | struct header { 42 | std::string m_key, m_value; 43 | header (std::string _key, std::string _value): m_key (_key), m_value (_value) {} 44 | }; 45 | struct authorization: public header { 46 | authorization (std::string _auth): header ("Authorization", _auth) {} 47 | authorization (std::string _uid, std::string _pwd): header ("Authorization", fmt::format ("Basic {}", base64_encode (fmt::format ("{}:{}", _uid, _pwd)))) {} 48 | }; 49 | struct connection: public header { connection (std::string _co): header ("Connection", _co) {} }; 50 | struct content_type: public header { content_type (std::string _co): header ("Content-Type", _co) {} }; 51 | struct referer: public header { referer (std::string _r): header ("Referer", _r) {} }; 52 | struct user_agent: public header { user_agent (std::string _ua): header ("User-Agent", _ua) {} }; 53 | struct url_kv { 54 | std::string Name, Value; 55 | url_kv (std::string _name, std::string _value): Name (_name), Value (_value) {} 56 | }; 57 | struct body_kv { 58 | std::string Name, Value; 59 | body_kv (std::string _name, std::string _value): Name (_name), Value (_value) {} 60 | }; 61 | struct body_file { 62 | std::string Name, FileName, FileContent; 63 | body_file (std::string _name, std::string _filename, std::string _content): Name (_name), FileName (_filename), FileContent (_content) {} 64 | }; 65 | struct body_kvs { 66 | std::map Kvs; 67 | body_kvs (std::map _kvs): Kvs (std::move (_kvs)) {} 68 | }; 69 | struct body_json { 70 | std::string Content; 71 | body_json (std::string _content): Content (_content) {} 72 | }; 73 | struct body_raw { 74 | std::string ContentType, Content; 75 | body_raw (std::string _content_type, std::string _content): ContentType (_content_type), Content (_content) {} 76 | }; 77 | template 78 | concept TOption = std::is_same::value || std::is_same::value || 79 | std::is_same::value || std::is_same::value || std::is_same::value || 80 | std::is_same::value || std::is_same::value || std::is_same::value || 81 | std::is_same::value; 82 | template 83 | concept TFormOption = std::is_same::value || std::is_same::value || 84 | std::is_same::value || std::is_same::value || std::is_same::value || 85 | std::is_same::value || std::is_same::value || std::is_same::value || 86 | std::is_same::value || std::is_same::value || std::is_same::value || 87 | std::is_same::value; 88 | template 89 | concept TBodyOption = std::is_same::value || std::is_same::value; 90 | } 91 | 92 | 93 | 94 | #endif //__FV_STRUCTS_HPP__ 95 | -------------------------------------------------------------------------------- /libfv.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32616.157 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfv_test", "libfv_test\libfv_test.vcxproj", "{BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Debug|x64.ActiveCfg = Debug|x64 17 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Debug|x64.Build.0 = Debug|x64 18 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Debug|x86.ActiveCfg = Debug|Win32 19 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Debug|x86.Build.0 = Debug|Win32 20 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Release|x64.ActiveCfg = Release|x64 21 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Release|x64.Build.0 = Release|x64 22 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Release|x86.ActiveCfg = Release|Win32 23 | {BB474A4E-1C34-4CEE-ABB0-B4ED9A6C7CAD}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {628E11F5-7CD0-47EB-8ED7-E7E175B105B9} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /libfv_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeList.txt: libfv 的 CMake 项目,在此处包括源代码并定义 2 | # 项目特定的逻辑。 3 | # 4 | cmake_minimum_required (VERSION 3.8) 5 | 6 | # 将源代码添加到此项目的可执行文件。 7 | add_executable (libfv_test "libfv_test.cpp") 8 | set_property (TARGET libfv_test PROPERTY CXX_STANDARD 20) 9 | include_directories ("../include") 10 | 11 | # TODO: 如有需要,请添加测试并安装目标。 12 | 13 | #find_package (Boost REQUIRED [COMPONENTS ...]) 14 | #target_link_libraries (libfv_test PRIVATE Boost::boost Boost:: Boost:: ...) 15 | find_package (asio CONFIG REQUIRED) 16 | target_link_libraries (libfv_test PRIVATE asio asio::asio) 17 | 18 | find_package(fmt CONFIG REQUIRED) 19 | target_link_libraries(libfv_test PRIVATE fmt::fmt) 20 | 21 | find_path (GZIP_HPP_INCLUDE_DIRS "gzip/compress.hpp") 22 | target_include_directories (libfv_test PRIVATE ${GZIP_HPP_INCLUDE_DIRS}) 23 | 24 | find_package (nlohmann_json CONFIG REQUIRED) 25 | target_link_libraries (libfv_test PRIVATE nlohmann_json::nlohmann_json) 26 | 27 | find_package (OpenSSL REQUIRED) 28 | target_link_libraries (libfv_test PRIVATE OpenSSL::SSL OpenSSL::Crypto) 29 | 30 | find_package (ZLIB REQUIRED) 31 | target_link_libraries (libfv_test PRIVATE ZLIB::ZLIB) 32 | -------------------------------------------------------------------------------- /libfv_test/libfv_test.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | # define _WIN32_WINNT 0x0601 3 | # pragma warning (disable: 4068) 4 | # pragma comment (lib, "Crypt32.lib") 5 | //# ifdef _RESUMABLE_FUNCTIONS_SUPPORTED 6 | //# undef _RESUMABLE_FUNCTIONS_SUPPORTED 7 | //# endif 8 | //# ifndef __cpp_lib_coroutine 9 | //# define __cpp_lib_coroutine 10 | //# endif 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | 19 | Task test_server () { 20 | fv::HttpServer _server {}; 21 | _server.SetHttpHandler ("/hello", [] (fv::Request &_req) -> Task { 22 | co_return fv::Response::FromText ("hello world"); 23 | }); 24 | std::cout << "You can access from browser: http://127.0.0.1:8080/hello\n"; 25 | co_await _server.Run (8080); 26 | } 27 | 28 | int main () { 29 | fv::Tasks::Init (); 30 | fv::Tasks::RunMainAsync (test_server); 31 | fv::Tasks::Run (); 32 | return 0; 33 | } -------------------------------------------------------------------------------- /libfv_test/libfv_test.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {bb474a4e-1c34-4cee-abb0-b4ed9a6c7cad} 25 | libfvtest 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v143 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v143 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v143 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v143 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | $(SolutionDir)include;$(IncludePath) 75 | 76 | 77 | $(SolutionDir)include;$(IncludePath) 78 | 79 | 80 | $(SolutionDir)include;$(IncludePath) 81 | 82 | 83 | $(SolutionDir)include;$(IncludePath) 84 | 85 | 86 | true 87 | 88 | 89 | true 90 | 91 | 92 | true 93 | 94 | 95 | true 96 | 97 | 98 | 99 | Level3 100 | true 101 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 102 | true 103 | stdcpplatest 104 | stdc17 105 | MultiThreadedDebug 106 | /await %(AdditionalOptions) 107 | 108 | 109 | Console 110 | true 111 | 112 | 113 | 114 | 115 | Level3 116 | true 117 | true 118 | true 119 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 120 | true 121 | stdcpplatest 122 | stdc17 123 | MultiThreaded 124 | /await %(AdditionalOptions) 125 | 126 | 127 | Console 128 | true 129 | true 130 | true 131 | 132 | 133 | 134 | 135 | Level3 136 | true 137 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 138 | true 139 | stdcpplatest 140 | stdc17 141 | MultiThreadedDebug 142 | /await %(AdditionalOptions) 143 | 144 | 145 | Console 146 | true 147 | 148 | 149 | 150 | 151 | Level3 152 | true 153 | true 154 | true 155 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 156 | true 157 | stdcpplatest 158 | stdc17 159 | MultiThreaded 160 | /await %(AdditionalOptions) 161 | 162 | 163 | Console 164 | true 165 | true 166 | true 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /libfv_test/libfv_test.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {81770d38-ae84-4679-9413-341cb265e397} 18 | 19 | 20 | 21 | 22 | 源文件 23 | 24 | 25 | 26 | 27 | 头文件\fv 28 | 29 | 30 | 头文件\fv 31 | 32 | 33 | 头文件\fv 34 | 35 | 36 | 头文件\fv 37 | 38 | 39 | 头文件\fv 40 | 41 | 42 | 头文件\fv 43 | 44 | 45 | 头文件\fv 46 | 47 | 48 | 头文件\fv 49 | 50 | 51 | 头文件\fv 52 | 53 | 54 | 头文件\fv 55 | 56 | 57 | 头文件\fv 58 | 59 | 60 | 头文件\fv 61 | 62 | 63 | --------------------------------------------------------------------------------