diff --git a/.bazelrc b/.bazelrc index dd5370e..606222a 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,2 +1,2 @@ -#build --@boost.mysql//:ssl=boringssl +build --@boost.mysql//:ssl=boringssl build --@boost.asio//:ssl=boringssl diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..44931da --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +9.1.1 diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml index ecd6cc7..ac175c2 100644 --- a/.github/workflows/bazel.yml +++ b/.github/workflows/bazel.yml @@ -61,7 +61,7 @@ jobs: - name: Bazel Test run: | - bazel test framework/... + bazel test framework/... --test_output=errors --test_verbose_timeout_warnings --verbose_failures example: needs: build strategy: diff --git a/.gitignore b/.gitignore index 9987f9d..4d22c07 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ node_modules /.clwb/targets/ /.clwb/aspects/ .clwb -web_root \ No newline at end of file +web_root +MODULE.bazel.lock \ No newline at end of file diff --git a/MODULE.bazel b/MODULE.bazel index d1c5610..86939f6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,35 +1,27 @@ module( name = "khttpd", - version = "0.1.0", + version = "0.2.0", ) -bazel_dep(name = "platforms", version = "1.0.0") -bazel_dep(name = "bazel_skylib", version = "1.8.2") -bazel_dep(name = "rules_cc", version = "0.2.13") -bazel_dep(name = "rules_shell", version = "0.6.1") -bazel_dep(name = "rules_perl", version = "0.4.3") -bazel_dep(name = "rules_foreign_cc", version = "0.14.0") -single_version_override( - module_name = "rules_foreign_cc", - patches = [ - "//patches:rules_foreign_cc/foreign_cc.built_tools.pkgconfig_build.bzl.patch", - ], - version = "0.14.0", -) - -bazel_dep(name = "fmt", version = "12.0.0") +bazel_dep(name = "platforms", version = "1.1.0") +bazel_dep(name = "bazel_skylib", version = "1.9.0") +bazel_dep(name = "rules_cc", version = "0.2.20") +bazel_dep(name = "rules_shell", version = "0.8.0") +bazel_dep(name = "rules_perl", version = "1.1.1") +bazel_dep(name = "fmt", version = "12.1.0") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") -bazel_dep(name = "sqlite3", version = "3.50.4") -bazel_dep(name = "openssl", version = "3.3.1.bcr.9") -bazel_dep(name = "boringssl", version = "0.20251110.0") -bazel_dep(name = "boost", version = "1.89.0.bcr.2") -bazel_dep(name = "boost.asio", version = "1.89.0.bcr.2") -bazel_dep(name = "boost.json", version = "1.89.0.bcr.2") -bazel_dep(name = "boost.mysql", version = "1.89.0.bcr.2") -bazel_dep(name = "boost.beast", version = "1.89.0.bcr.2") -bazel_dep(name = "boost.filesystem", version = "1.89.0.bcr.2") -bazel_dep(name = "boost.url", version = "1.89.0.bcr.2") -bazel_dep(name = "boost.uuid", version = "1.89.0.bcr.2") +bazel_dep(name = "sqlite3", version = "3.53.2") +bazel_dep(name = "openssl", version = "3.5.5.bcr.4") +bazel_dep(name = "boringssl", version = "0.20260616.0") +bazel_dep(name = "boost", version = "1.90.0.bcr.1") +bazel_dep(name = "boost.asio", version = "1.90.0.bcr.1") +bazel_dep(name = "boost.json", version = "1.90.0.bcr.1") +bazel_dep(name = "boost.mysql", version = "1.90.0.bcr.1") +bazel_dep(name = "boost.beast", version = "1.90.0.bcr.1") +bazel_dep(name = "boost.filesystem", version = "1.90.0.bcr.1") +bazel_dep(name = "boost.url", version = "1.90.0.bcr.1") +bazel_dep(name = "boost.uuid", version = "1.90.0.bcr.1") +bazel_dep(name = "spdlog", version = "1.17.0") cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension") use_repo(cc_configure, "local_config_cc") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock deleted file mode 100644 index c0d94cb..0000000 --- a/MODULE.bazel.lock +++ /dev/null @@ -1,984 +0,0 @@ -{ - "lockFileVersion": 26, - "registryFileHashes": { - "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", - "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", - "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", - "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", - "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", - "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", - "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", - "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", - "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", - "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", - "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", - "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", - "https://bcr.bazel.build/modules/apple_support/1.22.1/MODULE.bazel": "90bd1a660590f3ceffbdf524e37483094b29352d85317060b2327fff8f3f4458", - "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", - "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", - "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", - "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", - "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", - "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", - "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", - "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", - "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", - "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", - "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", - "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", - "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", - "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", - "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", - "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", - "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", - "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", - "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", - "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", - "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", - "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", - "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", - "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", - "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", - "https://bcr.bazel.build/modules/boost.algorithm/1.89.0.bcr.2/MODULE.bazel": "9226438a199b01a2dfa82325b03b6576df0b46e634f9d01770b84cdfe4fc3dcb", - "https://bcr.bazel.build/modules/boost.algorithm/1.89.0.bcr.2/source.json": "2ac6d36809c332f4b9802ea16c8e9a971bb68bc728231592d51d4786bf6f1130", - "https://bcr.bazel.build/modules/boost.align/1.89.0.bcr.2/MODULE.bazel": "81ceb2549f6338a7f07d09702d0ceccb26091354720ef63300cd3096aad1d2d9", - "https://bcr.bazel.build/modules/boost.align/1.89.0.bcr.2/source.json": "cc6fbe6294be2469046b529c350338027517f069517a16eb230b58b78a027d09", - "https://bcr.bazel.build/modules/boost.array/1.89.0.bcr.2/MODULE.bazel": "463870cdf4e7f880fc914639c2e93bfa87fed5f8256bdb95ae2a65f9842113cd", - "https://bcr.bazel.build/modules/boost.array/1.89.0.bcr.2/source.json": "9e6a9f007e3a91a31a4189eab26081254bd0f79fdcb7a68ec71d39377e7331a9", - "https://bcr.bazel.build/modules/boost.asio/1.89.0.bcr.2/MODULE.bazel": "ca2b137215a1d4c9d610926b81d2f0d665c0cd64f774e713159d9e30bc6e5b88", - "https://bcr.bazel.build/modules/boost.asio/1.89.0.bcr.2/source.json": "8beecc68e2d8d86bcf83fab4153d0f2204ed730cdcd7a57d9124d99957fe5ba7", - "https://bcr.bazel.build/modules/boost.assert/1.89.0.bcr.2/MODULE.bazel": "4dcc63c9e2e86228530b480a6ff43d8394be6654f5ff8e3b29c127b5ba28409d", - "https://bcr.bazel.build/modules/boost.assert/1.89.0.bcr.2/source.json": "d6f097672e50f354c7eca861c8a9151a913b152140ceba8109c98ac54d79bbbd", - "https://bcr.bazel.build/modules/boost.atomic/1.89.0.bcr.2/MODULE.bazel": "886179aae9c25002080ccf88b2f0067433ddcbb92a7872d3b9052422ed8f99d8", - "https://bcr.bazel.build/modules/boost.atomic/1.89.0.bcr.2/source.json": "aaf0eb36439b65ad40ccc166c1c1037f7d9b7bc33c68f7e48cd8e14f0c09393b", - "https://bcr.bazel.build/modules/boost.beast/1.89.0.bcr.2/MODULE.bazel": "cefa0a1965ee23bc12a31c35bb5970161ba3251b9898ea67fff15dd39a5177b1", - "https://bcr.bazel.build/modules/boost.beast/1.89.0.bcr.2/source.json": "54b2e64604348fa5e9b51df38e9ebc4921233c94e6112d5c89776fc885ec4b4f", - "https://bcr.bazel.build/modules/boost.bind/1.89.0.bcr.2/MODULE.bazel": "187e9c72f966f301973032e84b21db39998a12e76c1b6ee9430881108c5972d9", - "https://bcr.bazel.build/modules/boost.bind/1.89.0.bcr.2/source.json": "43241f26ddd73333aa11559efed86cc0157e81ac496e5562b21b2d5fd07d5908", - "https://bcr.bazel.build/modules/boost.charconv/1.89.0.bcr.2/MODULE.bazel": "1a79ce1516c19c8d9d0b4bad6f9c699f325dea9854d44f3cfe6d039f826850ab", - "https://bcr.bazel.build/modules/boost.charconv/1.89.0.bcr.2/source.json": "b6b26935653f31f27d9dbf6ba03d77bba017c38883d646fa1d5a0e73011f399c", - "https://bcr.bazel.build/modules/boost.chrono/1.89.0.bcr.2/MODULE.bazel": "45a0d051e7dd15e4f56bd930dfcb4e152d9d7dab573db25462fb55e12a1b2781", - "https://bcr.bazel.build/modules/boost.chrono/1.89.0.bcr.2/source.json": "7f6509a03d1a7570a268c146b990be3e7f539d0b2c4ea93f429b9ffa2ad978d7", - "https://bcr.bazel.build/modules/boost.compat/1.89.0.bcr.2/MODULE.bazel": "68ffb721640f94cd6828df650c5c62e816b11884d7f8e51d2ba43a08d438080f", - "https://bcr.bazel.build/modules/boost.compat/1.89.0.bcr.2/source.json": "8ebc36cd5489ee29dfe144f591f43fdfa32bb68838615438bbb6acf9da507eb1", - "https://bcr.bazel.build/modules/boost.concept_check/1.89.0.bcr.2/MODULE.bazel": "26d7e0938fff39efb402ced4cc35e173c35154be953c6c9779b9b867b96295cf", - "https://bcr.bazel.build/modules/boost.concept_check/1.89.0.bcr.2/source.json": "d204651e0e453a9455619df534c1ba7c5b35865d4f0ba8f8de8f76571864adeb", - "https://bcr.bazel.build/modules/boost.config/1.89.0.bcr.2/MODULE.bazel": "249452bd78a172360b1fe52e7a1ff86be4464e0ce1d6ee0d3225dc980764f253", - "https://bcr.bazel.build/modules/boost.config/1.89.0.bcr.2/source.json": "0cfdc2ca5b02f63d9c3f66d3c92855a750969424f09809662c3e277cddca76a8", - "https://bcr.bazel.build/modules/boost.container/1.89.0.bcr.2/MODULE.bazel": "e03e357bd31d04de4ead268e380c8cbc5468bb30fbda945c668aa3927e9dc442", - "https://bcr.bazel.build/modules/boost.container/1.89.0.bcr.2/source.json": "c56713d1190c5c152ad9993f87cefa772bbf2420bd2a99c3ded5865fa7473e76", - "https://bcr.bazel.build/modules/boost.container_hash/1.89.0.bcr.2/MODULE.bazel": "4c9504529f491c52892a407dad38b142e9c05698d018b2304ef9591594ed53bb", - "https://bcr.bazel.build/modules/boost.container_hash/1.89.0.bcr.2/source.json": "b6144b67916b2d734b3183aeec3cbef15c819a13213c518915a3e9356e62e423", - "https://bcr.bazel.build/modules/boost.context/1.89.0.bcr.2/MODULE.bazel": "ccfa144c34905a3dd68e2a3083f88a89722d57faf0b11504101d94164d0bb490", - "https://bcr.bazel.build/modules/boost.context/1.89.0.bcr.2/source.json": "0d91755c3e834e2407693ccc889e8ef929ced16c06c07adb9182f1dd7d1431bc", - "https://bcr.bazel.build/modules/boost.conversion/1.89.0.bcr.2/MODULE.bazel": "e915be1dac2f24ebc6c05ed940f989706ba7015b5e9cbb20ff880dc59041bcb7", - "https://bcr.bazel.build/modules/boost.conversion/1.89.0.bcr.2/source.json": "9bf91fcb3450f510ea14788149033a8851eb7875cd2d3f1db293821456e9c6a5", - "https://bcr.bazel.build/modules/boost.core/1.89.0.bcr.2/MODULE.bazel": "8f729977386597cb86353a4a2b58274134be55dadc229196650a7f37fec9496a", - "https://bcr.bazel.build/modules/boost.core/1.89.0.bcr.2/source.json": "59044798075cb7064bf29e2e87a3bb5531771bfa64270f4975f408dfebe2a907", - "https://bcr.bazel.build/modules/boost.coroutine/1.89.0.bcr.2/MODULE.bazel": "ae398f99a1b6b9a75cff722ba3c1f61bc3e6b0463ae4044a89dc07189c3562e0", - "https://bcr.bazel.build/modules/boost.coroutine/1.89.0.bcr.2/source.json": "7fabdb484f429ead8738aa97b85d4a266cf2957376e1f0c1a187b7bce2d007cc", - "https://bcr.bazel.build/modules/boost.date_time/1.89.0.bcr.2/MODULE.bazel": "f22a1408518b201ab2899e23d381b3061f9d2e7cc9704c8eb6d4bc7fa49a0ad3", - "https://bcr.bazel.build/modules/boost.date_time/1.89.0.bcr.2/source.json": "f237a2f2175a9258afaf625ffb0e440e7749dd7d658a854ee7fd2fe47cd79cfd", - "https://bcr.bazel.build/modules/boost.describe/1.89.0.bcr.2/MODULE.bazel": "948aaff9e29f47ec5459ea63d688a093576c01b6e9cd1fe9c70d3a0bfe3be6bf", - "https://bcr.bazel.build/modules/boost.describe/1.89.0.bcr.2/source.json": "251fdfc6ac551ab8e8a33f62ef1d3fba9a7f85c7893ec87c495b562e25d4f0e6", - "https://bcr.bazel.build/modules/boost.detail/1.89.0.bcr.2/MODULE.bazel": "d316fd4bcf6bc5d84edc0b15d447da5c5af571992cc9e0c07db544e8f6ccd9a1", - "https://bcr.bazel.build/modules/boost.detail/1.89.0.bcr.2/source.json": "dbfd712fa8277eb303f312be503d4596a650f787d723aca19e0fd2020e85dfc7", - "https://bcr.bazel.build/modules/boost.endian/1.89.0.bcr.2/MODULE.bazel": "7c759f9b44726eeeab31b5b1485769f1ea3b6bad41d130e3727257e3ba6f8316", - "https://bcr.bazel.build/modules/boost.endian/1.89.0.bcr.2/source.json": "82981901adfb45471923772f5b53f494c11b12ef15bb0bc6fbb3fb3d7dbcd571", - "https://bcr.bazel.build/modules/boost.exception/1.89.0.bcr.2/MODULE.bazel": "602cd716068438f492e424de704dbfa8c6de21a7fcb676394fd784f0cc99b045", - "https://bcr.bazel.build/modules/boost.exception/1.89.0.bcr.2/source.json": "ce2c164e4d36a90bab9433987e1b0be5668e8a80343c40a00740ae8e13628995", - "https://bcr.bazel.build/modules/boost.filesystem/1.89.0.bcr.2/MODULE.bazel": "87eac947dde0399d3ebd7b22b68584cb0b2563beb69dbb0065194f6f0790937b", - "https://bcr.bazel.build/modules/boost.filesystem/1.89.0.bcr.2/source.json": "1af84a53fcf63bc71b55a3601ab321649f3d1345a3b51e24c1a6a6985c414437", - "https://bcr.bazel.build/modules/boost.function/1.89.0.bcr.2/MODULE.bazel": "1fef02b53708af88a3a406bf1fb34ba13ccaef5b78eab933b674270055b3a8ad", - "https://bcr.bazel.build/modules/boost.function/1.89.0.bcr.2/source.json": "d05ae7a9c561684f6ea9e19e93866d385d3fbaaf1ea9f2614b689f90b6f4bdd8", - "https://bcr.bazel.build/modules/boost.function_types/1.89.0.bcr.2/MODULE.bazel": "995bad58a12e2f9e8e52f1eb4d5c40b85628dd2f13a78af720c690f246f33eb3", - "https://bcr.bazel.build/modules/boost.function_types/1.89.0.bcr.2/source.json": "4e7893df241b597a8df895244a8218fa6cd4b122a17b1ea5964d31d4b654aaa8", - "https://bcr.bazel.build/modules/boost.functional/1.89.0.bcr.2/MODULE.bazel": "94f7f9569381e3dba326e8eaa0dc84dbcd6feef0e0226ae5b6b99231a492786a", - "https://bcr.bazel.build/modules/boost.functional/1.89.0.bcr.2/source.json": "8b08b2da504cac1d4d02d1e969f56fc1316cef96130e78b8c5412b9455480432", - "https://bcr.bazel.build/modules/boost.fusion/1.89.0.bcr.2/MODULE.bazel": "cec1a7d23b4d602b885c320baa8c1d1cf53508c7e45268bc6602399372ec8302", - "https://bcr.bazel.build/modules/boost.fusion/1.89.0.bcr.2/source.json": "29da66d3ef5ec661c1a8f7bf64a3f419cd09274a626fdc0dfe41927d5658e4b9", - "https://bcr.bazel.build/modules/boost.integer/1.89.0.bcr.2/MODULE.bazel": "c40f2ff6f5c1752149851457db724a59b2f58187625c382935e043a8397e14bb", - "https://bcr.bazel.build/modules/boost.integer/1.89.0.bcr.2/source.json": "c3d8330b6ca622bb44ff6595f64a37419c7047b2775d4289023ae45128b4ee85", - "https://bcr.bazel.build/modules/boost.intrusive/1.89.0.bcr.2/MODULE.bazel": "7f7db8aa25b004a9e19c197ac43dd42c98ece575eb13293ad3400c1dbdad562b", - "https://bcr.bazel.build/modules/boost.intrusive/1.89.0.bcr.2/source.json": "5cb2e5f318b4b9631b696db3e8600347e73ac25c2910797558ce1f9b540323d2", - "https://bcr.bazel.build/modules/boost.io/1.89.0.bcr.2/MODULE.bazel": "18a553abe398f1ece2c62ebc94be5af73d2f8324570f28159709fcf7159d19b6", - "https://bcr.bazel.build/modules/boost.io/1.89.0.bcr.2/source.json": "cea27aa54b11367b7d6c78bf604d20fdb5f6efa2040979a38d54e8806022294b", - "https://bcr.bazel.build/modules/boost.iterator/1.89.0.bcr.2/MODULE.bazel": "b02c2d8241d4941b404d9817be1df0dbb93098432d5e3ee03bd23677b600c2c9", - "https://bcr.bazel.build/modules/boost.iterator/1.89.0.bcr.2/source.json": "78244dbe14b24d31857e235253b3500d038d57ef96b21e7f68bcd82b7a1a6d46", - "https://bcr.bazel.build/modules/boost.json/1.89.0.bcr.2/MODULE.bazel": "b49ac5bfa2ada2a583990b9c3a4cd48223d691f6da63432ec0c18fed66cae17d", - "https://bcr.bazel.build/modules/boost.json/1.89.0.bcr.2/source.json": "05f7c4ee04d9668c6bef98ace47ab3477df370018c01492d4f04d1b96d032949", - "https://bcr.bazel.build/modules/boost.lexical_cast/1.89.0.bcr.2/MODULE.bazel": "0026e75546d68c121cee24c21eb5cb6623000e06e577e963c56906ea005f350f", - "https://bcr.bazel.build/modules/boost.lexical_cast/1.89.0.bcr.2/source.json": "48e142dfe7890008fffc328d66c72c05d9c7fb1f52c8d7b02714af398ec640d8", - "https://bcr.bazel.build/modules/boost.logic/1.89.0.bcr.2/MODULE.bazel": "e158daf719db4a948536f8ca8adc03a27a9aa84f2392260a28ab6ad75b9b1677", - "https://bcr.bazel.build/modules/boost.logic/1.89.0.bcr.2/source.json": "4dfbe1c65b1b693dc0d98b44a4f1618f7b3aa263b38d1c1d40b2d2dc68d11118", - "https://bcr.bazel.build/modules/boost.move/1.89.0.bcr.2/MODULE.bazel": "1e5ab022babf5cb55360841c99b4c4d8413f673f449de3a4e664c65784e05e5d", - "https://bcr.bazel.build/modules/boost.move/1.89.0.bcr.2/source.json": "e176cf4a83046cd7cac53ebecce1ba8fa10bb1faac7152763d053c3a5a684452", - "https://bcr.bazel.build/modules/boost.mp11/1.89.0.bcr.2/MODULE.bazel": "5a1ee1918830f8901406ebc2a1690ff75c5e111509fe7f75397abf53f78a6c64", - "https://bcr.bazel.build/modules/boost.mp11/1.89.0.bcr.2/source.json": "a7e3933d16f6e946130a116ebcc782bd4b9f89e6a88d62ecbd8757b774636640", - "https://bcr.bazel.build/modules/boost.mpl/1.89.0.bcr.2/MODULE.bazel": "387815889ad8b241c85ea0977c19b527d6c5bad3d6cd7d54bba43a63fc4c1713", - "https://bcr.bazel.build/modules/boost.mpl/1.89.0.bcr.2/source.json": "80eaac00451e3f10123b88ccc58ae18fb7208251f80f24db3a5834d6625d8cd9", - "https://bcr.bazel.build/modules/boost.mysql/1.89.0.bcr.2/MODULE.bazel": "600d22fef990c598e5eb07ee00b130f146ba5289a9f0ca03f7482ed55b899221", - "https://bcr.bazel.build/modules/boost.mysql/1.89.0.bcr.2/source.json": "42ae64f863ebacf60fdf2d9ad91508aa27770a2c9f37386fd692348315d251f3", - "https://bcr.bazel.build/modules/boost.numeric_conversion/1.89.0.bcr.2/MODULE.bazel": "4398a5ced7436db1b3d5af048b1ce6f9ec4eee0b3f11da46494c16888f54bf2f", - "https://bcr.bazel.build/modules/boost.numeric_conversion/1.89.0.bcr.2/source.json": "da4b3078e91bec702cd721e09120b84c115e42691b22524052075dfe02e7652b", - "https://bcr.bazel.build/modules/boost.optional/1.89.0.bcr.2/MODULE.bazel": "7d1002b160027914ff2d927872a5856601e07b3cba525717bdf303928998236c", - "https://bcr.bazel.build/modules/boost.optional/1.89.0.bcr.2/source.json": "dd27d8a9b75d0ed474dc062ab2243bf5ce38aabf5411fbd68fb120887ef2e967", - "https://bcr.bazel.build/modules/boost.pfr/1.89.0.bcr.2/MODULE.bazel": "29baeabad039e0013cec72e3ac30f66c7947e334ff61be335455da89c53e05db", - "https://bcr.bazel.build/modules/boost.pfr/1.89.0.bcr.2/source.json": "c9b4d245c8724d417f52d45e20db54c7b5c70c9aa4df844f8e874f554cee2e00", - "https://bcr.bazel.build/modules/boost.pool/1.89.0.bcr.2/MODULE.bazel": "02167d4fba550c5ca2f8167e420ebb5c07e93cb0a9ec93770059238171439c7d", - "https://bcr.bazel.build/modules/boost.pool/1.89.0.bcr.2/source.json": "6e4467e16ac68088b6e095a332f5b15e7a49a48d9087a89663ec7f729ccd24b6", - "https://bcr.bazel.build/modules/boost.predef/1.89.0.bcr.2/MODULE.bazel": "e27b429f279c8b9c261cf6782c3cbdcc7f8b31460ad092314badf6a03dfd974b", - "https://bcr.bazel.build/modules/boost.predef/1.89.0.bcr.2/source.json": "2a799742cfdc33a790cc12d119728e2668605e8a5756590dac90a0a542595ed2", - "https://bcr.bazel.build/modules/boost.preprocessor/1.89.0.bcr.2/MODULE.bazel": "a17322cca21dc9fa6d9cee49b836f50963c2862156ff24c6fd42c7c2425fd92d", - "https://bcr.bazel.build/modules/boost.preprocessor/1.89.0.bcr.2/source.json": "e2cdaa6a55f870c57cf70724d03fb3dd4368e06e5d3a3860dfca986660c64911", - "https://bcr.bazel.build/modules/boost.range/1.89.0.bcr.2/MODULE.bazel": "39c6289001173cecbf6f8f8c403d758fbab16339abceb1bb08d5423c8ba5e4a1", - "https://bcr.bazel.build/modules/boost.range/1.89.0.bcr.2/source.json": "cf6969ed27e260adeb3891c0a45801399c5cb5ad7660604a197ed6128f0b3bf6", - "https://bcr.bazel.build/modules/boost.ratio/1.89.0.bcr.2/MODULE.bazel": "99ae08a4a21810cefd7ba274c49d3079cdac5e27bccab71b5791d9be2b24fbe7", - "https://bcr.bazel.build/modules/boost.ratio/1.89.0.bcr.2/source.json": "30ebe9a8916175ec812d5b7f67467b4fe3a07d8105bd7a74e753466a9a8311ce", - "https://bcr.bazel.build/modules/boost.regex/1.89.0.bcr.2/MODULE.bazel": "40f9f43e11d6770e32f3823a68c47550fb599025896a1a44161c0afab1568753", - "https://bcr.bazel.build/modules/boost.regex/1.89.0.bcr.2/source.json": "3e1562878b359d9ea3df7b2e6ecb6e1eb3857d3d6628959790fa113fe6db4b0c", - "https://bcr.bazel.build/modules/boost.scope/1.89.0.bcr.2/MODULE.bazel": "8175df9769301998e6ab173662e8eb1c9d7c1754d868a3e1d13cb8189c3ed092", - "https://bcr.bazel.build/modules/boost.scope/1.89.0.bcr.2/source.json": "bccd896a3fc01315f827930290c0c34a58ea75868d70db00a99a2ffdd5858575", - "https://bcr.bazel.build/modules/boost.smart_ptr/1.89.0.bcr.2/MODULE.bazel": "4e5af9c03ed1c2daa8b89bdc622f2441f3ecb3aff6112607daef7e081ea02713", - "https://bcr.bazel.build/modules/boost.smart_ptr/1.89.0.bcr.2/source.json": "e5ac0f5418c474503aa827fe81250bb064f7d65c7325e0629ddd162f7d7eddf1", - "https://bcr.bazel.build/modules/boost.static_assert/1.89.0.bcr.2/MODULE.bazel": "759c3f29ac03e0a35131c650d700d6105230b10f8a01d841d08fcf9f616925c3", - "https://bcr.bazel.build/modules/boost.static_assert/1.89.0.bcr.2/source.json": "b8441521d3283aa418f6637533fc82631bc6b8a9da28c3ed505ad6dc5f47a119", - "https://bcr.bazel.build/modules/boost.static_string/1.89.0.bcr.2/MODULE.bazel": "2a12310358a8d315bbe2fbba07bfc552e095c9e00d8799b06e857da8594be55a", - "https://bcr.bazel.build/modules/boost.static_string/1.89.0.bcr.2/source.json": "2044b13b997fcdf82953057dbefa75ae9fefe8be2a1c7a1e68a2273d90fc370f", - "https://bcr.bazel.build/modules/boost.system/1.89.0.bcr.2/MODULE.bazel": "d36e9d9ffffd5739bf9677744ab9dc1e981bf6a0b1744ea71d47ef17b82fa83b", - "https://bcr.bazel.build/modules/boost.system/1.89.0.bcr.2/source.json": "54e83d383135450a580d4902b31c8d14eeaf0e80dc487063374caedc2ac15e26", - "https://bcr.bazel.build/modules/boost.throw_exception/1.89.0.bcr.2/MODULE.bazel": "e16a1195c2006a1d73fb55c0ba0d910a64cae2d8a1d8ef42ba74e83d145a46d0", - "https://bcr.bazel.build/modules/boost.throw_exception/1.89.0.bcr.2/source.json": "7571bb5f51c1af38b89263df99cb3f62b83ef7406b9f3f7a9f4aa13abdf9f2bf", - "https://bcr.bazel.build/modules/boost.tokenizer/1.89.0.bcr.2/MODULE.bazel": "5dcf0d8648cceff4ccac1e983ece15561b9df1a5ee002a771acc6656e981721d", - "https://bcr.bazel.build/modules/boost.tokenizer/1.89.0.bcr.2/source.json": "8cf2a99636da398b604f02737a319ae3d817202e5b1e2735e5b67c9c0452fd6a", - "https://bcr.bazel.build/modules/boost.tuple/1.89.0.bcr.2/MODULE.bazel": "477ac9bacab71378f0e67815c847a68eb17bf2627f9583185ba41b05c7b925ba", - "https://bcr.bazel.build/modules/boost.tuple/1.89.0.bcr.2/source.json": "f3b660bbf0ed61f7404b9ed57cf94ffa43d435bc52b981c09f235f1774894d7a", - "https://bcr.bazel.build/modules/boost.type_index/1.89.0.bcr.2/MODULE.bazel": "2efce3aec8647fa3c400088d762db07475d02ded6fc4f3dc13362ad83ad6a8c9", - "https://bcr.bazel.build/modules/boost.type_index/1.89.0.bcr.2/source.json": "45d090f2a77ff9673b4cba720a312048c745b1642000b0bffa9d5703def4936e", - "https://bcr.bazel.build/modules/boost.type_traits/1.89.0.bcr.2/MODULE.bazel": "fe59e07c640e56f1063b3f9c049dccf274382771beb51554c2e93875947bf820", - "https://bcr.bazel.build/modules/boost.type_traits/1.89.0.bcr.2/source.json": "2badffa618ca01afa77b5b86d6eea209dccc7ffc2b60644e0bee109529bd04c5", - "https://bcr.bazel.build/modules/boost.typeof/1.89.0.bcr.2/MODULE.bazel": "1016c641742e5c05db2b3fc56f71ff425376366ff2851b63298df08ac9563331", - "https://bcr.bazel.build/modules/boost.typeof/1.89.0.bcr.2/source.json": "a21770014c6065036197028d79abda87a8506076d5915264666233224a7d1ec9", - "https://bcr.bazel.build/modules/boost.unordered/1.89.0.bcr.2/MODULE.bazel": "a8ce9fa18920ac98aecbc8a11f79925d22c94666463a2ac70f7b74ea9f5ba823", - "https://bcr.bazel.build/modules/boost.unordered/1.89.0.bcr.2/source.json": "f3ca89da116ae95423c48e24d6ac005020d8534646f57dc6ef9ac7e8279956d7", - "https://bcr.bazel.build/modules/boost.url/1.89.0.bcr.2/MODULE.bazel": "7ae0df4846b3d30110729678386f05ce753f02f4158e5b0e573f3d0f55f6bdf2", - "https://bcr.bazel.build/modules/boost.url/1.89.0.bcr.2/source.json": "a82245644e93ef96368cc985614ecb49254f4bf18210e9ddc82ec85c950d6c24", - "https://bcr.bazel.build/modules/boost.utility/1.89.0.bcr.2/MODULE.bazel": "0892266c631affaa46cc6eb799208068a5bec7a43597ccdd0558026691e40616", - "https://bcr.bazel.build/modules/boost.utility/1.89.0.bcr.2/source.json": "d70bda2408a40e0d27a1329ac37f099a5600e486305a84b2d07e7be82db5e061", - "https://bcr.bazel.build/modules/boost.uuid/1.89.0.bcr.2/MODULE.bazel": "08e0e072b1913aa40535a407f05d10c741ea5d1e01b7656c1bbee3bfcf4309f0", - "https://bcr.bazel.build/modules/boost.uuid/1.89.0.bcr.2/source.json": "19cc5fa60c4d389f8a79c5d4c01f6443d4163ae1c9089744d6a49c17282def2b", - "https://bcr.bazel.build/modules/boost.variant2/1.89.0.bcr.2/MODULE.bazel": "4b0a2cc09a3aeec58faa1e78f0cd370cbeec00f0b9c78c4120932c7709f954f5", - "https://bcr.bazel.build/modules/boost.variant2/1.89.0.bcr.2/source.json": "092571cb05ffeabebae1953df0d2f52785840e168eb8af348554e7d751480e1d", - "https://bcr.bazel.build/modules/boost.winapi/1.89.0.bcr.2/MODULE.bazel": "62bfa08886bd8fb88f470faa9f437939ec38e69178a8f4189607b650a6703041", - "https://bcr.bazel.build/modules/boost.winapi/1.89.0.bcr.2/source.json": "5d1ecf9cb690198d032af0ba9d4906ca53347b96bda9916ce8ff87a2a49339cf", - "https://bcr.bazel.build/modules/boost/1.89.0.bcr.2/MODULE.bazel": "76b396b9b330aa638717ca3d7ec5f7fd171938c245d14a15f95e5907570e7319", - "https://bcr.bazel.build/modules/boost/1.89.0.bcr.2/source.json": "d6e61b11e988a54d6b7ca691c18a6c69262c4b3a692df387e614c4b92b176fb6", - "https://bcr.bazel.build/modules/boringssl/0.20251002.0/MODULE.bazel": "d27433ae3dbb180193dffcd80aaa612bd0d63136f09629dd809a4c71ba114cdd", - "https://bcr.bazel.build/modules/boringssl/0.20251110.0/MODULE.bazel": "a7472f6b886e838d09824534b56b44cd07022c903fd3f441cf3f19c1c4cfe2c3", - "https://bcr.bazel.build/modules/boringssl/0.20251110.0/source.json": "b2821067608ea5c56a055d9259cb02c0a50f49eed0f4f1cda7d6b9a4c4293af9", - "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", - "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", - "https://bcr.bazel.build/modules/fmt/12.0.0/MODULE.bazel": "5308b44200f97df17217c053367537c6d469fe46a61ab0dfc1038c04ceb1d735", - "https://bcr.bazel.build/modules/fmt/12.0.0/source.json": "20a9d47908eaa8fd46ee7b2fbb0fd9ff02175addfdc1658817798c52604882c1", - "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", - "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", - "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", - "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", - "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", - "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.1/MODULE.bazel": "9f8e815fba6e81dee850a33068166989000eabcf7690d2127a975c2ebda6baae", - "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "827f54f492a3ce549c940106d73de332c2b30cebd0c20c0bc5d786aba7f116cb", - "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/source.json": "3664514073a819992320ffbce5825e4238459df344d8b01748af2208f8d2e1eb", - "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", - "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", - "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", - "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", - "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", - "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", - "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", - "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.9/MODULE.bazel": "bf9dd8479c65bfec1c82773a5cc6ae06eda4c663c2731cfcfcb8b6b46ac8d365", - "https://bcr.bazel.build/modules/openssl/3.3.1.bcr.9/source.json": "c72e6b4db6b18e47a3050fbb3315ddf3353f55c81c2be7cba775856259edb7c5", - "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", - "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", - "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", - "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", - "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", - "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", - "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", - "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", - "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", - "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", - "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a", - "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12", - "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", - "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", - "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", - "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", - "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", - "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", - "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", - "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", - "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", - "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", - "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", - "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/MODULE.bazel": "2d746fda559464b253b2b2e6073cb51643a2ac79009ca02100ebbc44b4548656", - "https://bcr.bazel.build/modules/pybind11_bazel/2.13.6/source.json": "6aa0703de8efb20cc897bbdbeb928582ee7beaf278bcd001ac253e1605bddfae", - "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", - "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", - "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", - "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/MODULE.bazel": "e09b434b122bfb786a69179f9b325e35cb1856c3f56a7a81dd61609260ed46e1", - "https://bcr.bazel.build/modules/re2/2025-08-12.bcr.1/source.json": "a8ae7c09533bf67f9f6e5122d884d5741600b09d78dca6fc0f2f8d2ee0c2d957", - "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", - "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", - "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", - "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", - "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", - "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", - "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", - "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", - "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", - "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", - "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", - "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", - "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", - "https://bcr.bazel.build/modules/rules_cc/0.1.4/MODULE.bazel": "bb03a452a7527ac25a7518fb86a946ef63df860b9657d8323a0c50f8504fb0b9", - "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", - "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", - "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", - "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", - "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", - "https://bcr.bazel.build/modules/rules_foreign_cc/0.14.0/MODULE.bazel": "56fb9a239503bab4183d06ba6cabb01cd73aae296ab499085b9193624a8a66e2", - "https://bcr.bazel.build/modules/rules_foreign_cc/0.14.0/source.json": "64ccb6c4bff8afc336a24af2487b4557b8d2b13f981f2d8190983bc196b36a68", - "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", - "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", - "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", - "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", - "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64", - "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", - "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", - "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", - "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", - "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", - "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", - "https://bcr.bazel.build/modules/rules_java/9.1.0/MODULE.bazel": "ee63f27e36a3fada80342869361182f120a9819c74320e8e65b1e04ba0cd7a9d", - "https://bcr.bazel.build/modules/rules_java/9.1.0/source.json": "da589573c1dee2c9ac4a568b301269a2e8191110ff0345c1a959fa7ea6c4dfd6", - "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", - "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", - "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", - "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", - "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", - "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", - "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", - "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", - "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", - "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", - "https://bcr.bazel.build/modules/rules_perl/0.4.3/MODULE.bazel": "3a8f67f2acf9c0b127e59a1fe902606a78cfcb843b783602ada65ea06f9ee617", - "https://bcr.bazel.build/modules/rules_perl/0.5.0/MODULE.bazel": "1bff473031644dfb23bd57abe3befe9780f003f0d2156b082527a5c477008792", - "https://bcr.bazel.build/modules/rules_perl/0.5.0/source.json": "0076e22051d1b8aedf6d1bf3655bd9e895e9b01dbd3ccc82d80d3bbcae863c34", - "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", - "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", - "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", - "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", - "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", - "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", - "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", - "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", - "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", - "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", - "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", - "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", - "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", - "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", - "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", - "https://bcr.bazel.build/modules/rules_python/0.34.0/MODULE.bazel": "1d623d026e075b78c9fde483a889cda7996f5da4f36dffb24c246ab30f06513a", - "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/1.1.0/MODULE.bazel": "57e01abae22956eb96d891572490d20e07d983e0c065de0b2170cafe5053e788", - "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", - "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", - "https://bcr.bazel.build/modules/rules_python/1.5.1/MODULE.bazel": "acfe65880942d44a69129d4c5c3122d57baaf3edf58ae5a6bd4edea114906bf5", - "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", - "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", - "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", - "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.4.0/MODULE.bazel": "0f8f11bb3cd11755f0b48c1de0bbcf62b4b34421023aa41a2fc74ef68d9584f0", - "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", - "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", - "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", - "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", - "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", - "https://bcr.bazel.build/modules/sqlite3/3.50.4/MODULE.bazel": "97e6be83033408655454db5fe12a2814e8f28d3cf031251b1de3ea3353363520", - "https://bcr.bazel.build/modules/sqlite3/3.50.4/source.json": "26e2ca8a21b215b562fdf0a2d69e3f89c2ba74a6017138339cb9ddcdac39a5ea", - "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", - "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", - "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", - "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", - "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", - "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", - "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", - "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", - "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" - }, - "selectedYankedVersions": {}, - "moduleExtensions": { - "@@rules_foreign_cc+//foreign_cc:extensions.bzl%tools": { - "general": { - "bzlTransitiveDigest": "bTLENWEOzsR+6g/mQ/Ni27xVnSYd1Ziscsy+nQwfAqk=", - "usagesDigest": "Eyh4mAOi6L+Nn/lY/wQBJclQrmBnWdQM+B4lZeq6azA=", - "recordedInputs": [ - "REPO_MAPPING:rules_foreign_cc+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_foreign_cc+,rules_foreign_cc rules_foreign_cc+" - ], - "generatedRepoSpecs": { - "rules_foreign_cc_framework_toolchain_linux": { - "repoRuleId": "@@rules_foreign_cc+//foreign_cc/private/framework:toolchain.bzl%framework_toolchain_repository", - "attributes": { - "commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:linux_commands.bzl", - "exec_compatible_with": [ - "@platforms//os:linux" - ] - } - }, - "rules_foreign_cc_framework_toolchain_freebsd": { - "repoRuleId": "@@rules_foreign_cc+//foreign_cc/private/framework:toolchain.bzl%framework_toolchain_repository", - "attributes": { - "commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:freebsd_commands.bzl", - "exec_compatible_with": [ - "@platforms//os:freebsd" - ] - } - }, - "rules_foreign_cc_framework_toolchain_windows": { - "repoRuleId": "@@rules_foreign_cc+//foreign_cc/private/framework:toolchain.bzl%framework_toolchain_repository", - "attributes": { - "commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:windows_commands.bzl", - "exec_compatible_with": [ - "@platforms//os:windows" - ] - } - }, - "rules_foreign_cc_framework_toolchain_macos": { - "repoRuleId": "@@rules_foreign_cc+//foreign_cc/private/framework:toolchain.bzl%framework_toolchain_repository", - "attributes": { - "commands_src": "@rules_foreign_cc//foreign_cc/private/framework/toolchains:macos_commands.bzl", - "exec_compatible_with": [ - "@platforms//os:macos" - ] - } - }, - "rules_foreign_cc_framework_toolchains": { - "repoRuleId": "@@rules_foreign_cc+//foreign_cc/private/framework:toolchain.bzl%framework_toolchain_repository_hub", - "attributes": {} - }, - "cmake_src": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n", - "sha256": "f316b40053466f9a416adf981efda41b160ca859e97f6a484b447ea299ff26aa", - "strip_prefix": "cmake-3.23.2", - "urls": [ - "https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2.tar.gz" - ], - "patches": [ - "@@rules_foreign_cc+//toolchains/patches:cmake-c++11.patch" - ] - } - }, - "gnumake_src": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n", - "sha256": "dd16fb1d67bfab79a72f5e8390735c49e3e8e70b4945a15ab1f81ddb78658fb3", - "strip_prefix": "make-4.4.1", - "urls": [ - "https://mirror.bazel.build/ftpmirror.gnu.org/gnu/make/make-4.4.1.tar.gz", - "http://ftpmirror.gnu.org/gnu/make/make-4.4.1.tar.gz" - ] - } - }, - "ninja_build_src": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n", - "integrity": "sha256-ghvf9Io/aDvEuztvC1/nstZHz2XVKutjMoyRpsbfKFo=", - "strip_prefix": "ninja-1.12.1", - "urls": [ - "https://mirror.bazel.build/github.com/ninja-build/ninja/archive/v1.12.1.tar.gz", - "https://github.com/ninja-build/ninja/archive/v1.12.1.tar.gz" - ] - } - }, - "meson_src": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "exports_files([\"meson.py\"])\n\nfilegroup(\n name = \"runtime\",\n # NOTE: excluding __pycache__ is important to avoid rebuilding due to pyc\n # files, see https://github.com/bazel-contrib/rules_foreign_cc/issues/1342\n srcs = glob([\"mesonbuild/**\"], exclude = [\"**/__pycache__/*\"]),\n visibility = [\"//visibility:public\"],\n)\n", - "sha256": "567e533adf255de73a2de35049b99923caf872a455af9ce03e01077e0d384bed", - "strip_prefix": "meson-1.5.1", - "urls": [ - "https://mirror.bazel.build/github.com/mesonbuild/meson/releases/download/1.5.1/meson-1.5.1.tar.gz", - "https://github.com/mesonbuild/meson/releases/download/1.5.1/meson-1.5.1.tar.gz" - ] - } - }, - "glib_dev": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "\ncc_import(\n name = \"glib_dev\",\n hdrs = glob([\"include/**\"]),\n shared_library = \"@glib_runtime//:bin/libglib-2.0-0.dll\",\n visibility = [\"//visibility:public\"],\n)\n ", - "sha256": "bdf18506df304d38be98a4b3f18055b8b8cca81beabecad0eece6ce95319c369", - "urls": [ - "https://mirror.bazel.build/download.gnome.org/binaries/win64/glib/2.26/glib-dev_2.26.1-1_win64.zip", - "https://download.gnome.org/binaries/win64/glib/2.26/glib-dev_2.26.1-1_win64.zip" - ] - } - }, - "glib_src": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "\ncc_import(\n name = \"msvc_hdr\",\n hdrs = [\"msvc_recommended_pragmas.h\"],\n visibility = [\"//visibility:public\"],\n)\n ", - "sha256": "bc96f63112823b7d6c9f06572d2ad626ddac7eb452c04d762592197f6e07898e", - "strip_prefix": "glib-2.26.1", - "urls": [ - "https://mirror.bazel.build/download.gnome.org/sources/glib/2.26/glib-2.26.1.tar.gz", - "https://download.gnome.org/sources/glib/2.26/glib-2.26.1.tar.gz" - ] - } - }, - "glib_runtime": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "\nexports_files(\n [\n \"bin/libgio-2.0-0.dll\",\n \"bin/libglib-2.0-0.dll\",\n \"bin/libgmodule-2.0-0.dll\",\n \"bin/libgobject-2.0-0.dll\",\n \"bin/libgthread-2.0-0.dll\",\n ],\n visibility = [\"//visibility:public\"],\n)\n ", - "sha256": "88d857087e86f16a9be651ee7021880b3f7ba050d34a1ed9f06113b8799cb973", - "urls": [ - "https://mirror.bazel.build/download.gnome.org/binaries/win64/glib/2.26/glib_2.26.1-1_win64.zip", - "https://download.gnome.org/binaries/win64/glib/2.26/glib_2.26.1-1_win64.zip" - ] - } - }, - "gettext_runtime": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "\ncc_import(\n name = \"gettext_runtime\",\n shared_library = \"bin/libintl-8.dll\",\n visibility = [\"//visibility:public\"],\n)\n ", - "sha256": "1f4269c0e021076d60a54e98da6f978a3195013f6de21674ba0edbc339c5b079", - "urls": [ - "https://mirror.bazel.build/download.gnome.org/binaries/win64/dependencies/gettext-runtime_0.18.1.1-2_win64.zip", - "https://download.gnome.org/binaries/win64/dependencies/gettext-runtime_0.18.1.1-2_win64.zip" - ] - } - }, - "pkgconfig_src": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file_content": "filegroup(\n name = \"all_srcs\",\n srcs = glob([\"**\"]),\n visibility = [\"//visibility:public\"],\n)\n", - "sha256": "6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591", - "strip_prefix": "pkg-config-0.29.2", - "patches": [ - "@@rules_foreign_cc+//toolchains/patches:pkgconfig-detectenv.patch", - "@@rules_foreign_cc+//toolchains/patches:pkgconfig-makefile-vc.patch", - "@@rules_foreign_cc+//toolchains/patches:pkgconfig-builtin-glib-int-conversion.patch" - ], - "urls": [ - "https://pkgconfig.freedesktop.org/releases/pkg-config-0.29.2.tar.gz", - "https://mirror.bazel.build/pkgconfig.freedesktop.org/releases/pkg-config-0.29.2.tar.gz" - ] - } - }, - "bazel_features": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "ba1282c1aa1d1fffdcf994ab32131d7c7551a9bc960fbf05f42d55a1b930cbfb", - "strip_prefix": "bazel_features-1.15.0", - "url": "https://github.com/bazel-contrib/bazel_features/releases/download/v1.15.0/bazel_features-v1.15.0.tar.gz" - } - }, - "bazel_skylib": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "bc283cdfcd526a52c3201279cda4bc298652efa898b10b4db0837dc51652756f", - "urls": [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz" - ] - } - }, - "rules_cc": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/bazelbuild/rules_cc/releases/download/0.0.17/rules_cc-0.0.17.tar.gz" - ], - "sha256": "abc605dd850f813bb37004b77db20106a19311a96b2da1c92b789da529d28fe1", - "strip_prefix": "rules_cc-0.0.17" - } - }, - "rules_python": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "0a158f883fc494724f25e2ce6a5c3d31fd52163a92d4b7180aef0ff9a0622f70", - "strip_prefix": "rules_python-1.1.0-rc0", - "url": "https://github.com/bazelbuild/rules_python/releases/download/1.1.0-rc0/rules_python-1.1.0-rc0.tar.gz" - } - }, - "rules_shell": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "d8cd4a3a91fc1dc68d4c7d6b655f09def109f7186437e3f50a9b60ab436a0c53", - "strip_prefix": "rules_shell-0.3.0", - "url": "https://github.com/bazelbuild/rules_shell/releases/download/v0.3.0/rules_shell-v0.3.0.tar.gz" - } - }, - "cmake-3.23.2-linux-aarch64": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-linux-aarch64.tar.gz" - ], - "sha256": "f2654bf780b53f170bbbec44d8ac67d401d24788e590faa53036a89476efa91e", - "strip_prefix": "cmake-3.23.2-linux-aarch64", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n" - } - }, - "cmake-3.23.2-linux-x86_64": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-linux-x86_64.tar.gz" - ], - "sha256": "aaced6f745b86ce853661a595bdac6c5314a60f8181b6912a0a4920acfa32708", - "strip_prefix": "cmake-3.23.2-linux-x86_64", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n" - } - }, - "cmake-3.23.2-macos-universal": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-macos-universal.tar.gz" - ], - "sha256": "853a0f9af148c5ef47282ffffee06c4c9f257be2635936755f39ca13c3286c88", - "strip_prefix": "cmake-3.23.2-macos-universal/CMake.app/Contents", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n" - } - }, - "cmake-3.23.2-windows-i386": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-windows-i386.zip" - ], - "sha256": "6a4fcd6a2315b93cb23c93507efccacc30c449c2bf98f14d6032bb226c582e07", - "strip_prefix": "cmake-3.23.2-windows-i386", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake.exe\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake.exe\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n" - } - }, - "cmake-3.23.2-windows-x86_64": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/Kitware/CMake/releases/download/v3.23.2/cmake-3.23.2-windows-x86_64.zip" - ], - "sha256": "2329387f3166b84c25091c86389fb891193967740c9bcf01e7f6d3306f7ffda0", - "strip_prefix": "cmake-3.23.2-windows-x86_64", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"cmake_bin\",\n srcs = [\"bin/cmake.exe\"],\n)\n\nfilegroup(\n name = \"cmake_data\",\n srcs = glob(\n [\n \"**\",\n ],\n exclude = [\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"**/* *\",\n ],\n ),\n)\n\nnative_tool_toolchain(\n name = \"cmake_tool\",\n path = \"bin/cmake.exe\",\n target = \":cmake_data\",\n env = {\"CMAKE\": \"$(execpath :cmake_bin)\"},\n tools = [\":cmake_bin\"],\n)\n" - } - }, - "cmake_3.23.2_toolchains": { - "repoRuleId": "@@rules_foreign_cc+//toolchains:prebuilt_toolchains_repository.bzl%prebuilt_toolchains_repository", - "attributes": { - "repos": { - "cmake-3.23.2-linux-aarch64": [ - "@platforms//cpu:aarch64", - "@platforms//os:linux" - ], - "cmake-3.23.2-linux-x86_64": [ - "@platforms//cpu:x86_64", - "@platforms//os:linux" - ], - "cmake-3.23.2-macos-universal": [ - "@platforms//os:macos" - ], - "cmake-3.23.2-windows-i386": [ - "@platforms//cpu:x86_32", - "@platforms//os:windows" - ], - "cmake-3.23.2-windows-x86_64": [ - "@platforms//cpu:x86_64", - "@platforms//os:windows" - ] - }, - "tool": "cmake" - } - }, - "ninja_1.12.1_linux": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip" - ], - "sha256": "6f98805688d19672bd699fbbfa2c2cf0fc054ac3df1f0e6a47664d963d530255", - "strip_prefix": "", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n" - } - }, - "ninja_1.12.1_linux-aarch64": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux-aarch64.zip" - ], - "sha256": "5c25c6570b0155e95fce5918cb95f1ad9870df5768653afe128db822301a05a1", - "strip_prefix": "", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n" - } - }, - "ninja_1.12.1_mac": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip" - ], - "sha256": "89a287444b5b3e98f88a945afa50ce937b8ffd1dcc59c555ad9b1baf855298c9", - "strip_prefix": "", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n" - } - }, - "ninja_1.12.1_mac_aarch64": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-mac.zip" - ], - "sha256": "89a287444b5b3e98f88a945afa50ce937b8ffd1dcc59c555ad9b1baf855298c9", - "strip_prefix": "", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n" - } - }, - "ninja_1.12.1_win": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip" - ], - "sha256": "f550fec705b6d6ff58f2db3c374c2277a37691678d6aba463adcbb129108467a", - "strip_prefix": "", - "build_file_content": "load(\"@rules_foreign_cc//toolchains/native_tools:native_tools_toolchain.bzl\", \"native_tool_toolchain\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\nfilegroup(\n name = \"ninja_bin\",\n srcs = [\"ninja.exe\"],\n)\n\nnative_tool_toolchain(\n name = \"ninja_tool\",\n env = {\"NINJA\": \"$(execpath :ninja_bin)\"},\n path = \"$(execpath :ninja_bin)\",\n target = \":ninja_bin\",\n)\n" - } - }, - "ninja_1.12.1_toolchains": { - "repoRuleId": "@@rules_foreign_cc+//toolchains:prebuilt_toolchains_repository.bzl%prebuilt_toolchains_repository", - "attributes": { - "repos": { - "ninja_1.12.1_linux": [ - "@platforms//cpu:x86_64", - "@platforms//os:linux" - ], - "ninja_1.12.1_linux-aarch64": [ - "@platforms//cpu:aarch64", - "@platforms//os:linux" - ], - "ninja_1.12.1_mac": [ - "@platforms//cpu:x86_64", - "@platforms//os:macos" - ], - "ninja_1.12.1_mac_aarch64": [ - "@platforms//cpu:aarch64", - "@platforms//os:macos" - ], - "ninja_1.12.1_win": [ - "@platforms//cpu:x86_64", - "@platforms//os:windows" - ] - }, - "tool": "ninja" - } - } - } - } - }, - "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { - "general": { - "bzlTransitiveDigest": "Ga4z8lQy1YQ5rAMy+dOl0dqcCEBnYNCXku8x3YQmDZI=", - "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedInputs": [ - "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" - ], - "generatedRepoSpecs": { - "com_github_jetbrains_kotlin_git": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", - "attributes": { - "urls": [ - "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" - ], - "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" - } - }, - "com_github_jetbrains_kotlin": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", - "attributes": { - "git_repository_name": "com_github_jetbrains_kotlin_git", - "compiler_version": "1.9.23" - } - }, - "com_github_google_ksp": { - "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", - "attributes": { - "urls": [ - "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" - ], - "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", - "strip_version": "1.9.23-1.0.20" - } - }, - "com_github_pinterest_ktlint": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", - "attributes": { - "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", - "urls": [ - "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" - ], - "executable": true - } - }, - "rules_android": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", - "strip_prefix": "rules_android-0.1.1", - "urls": [ - "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" - ] - } - } - } - } - }, - "@@rules_python+//python/extensions:config.bzl%config": { - "general": { - "bzlTransitiveDigest": "iibnRYgg8LpcfmH7EAnVwYePC3jsVaJ6Id8XxUjSZps=", - "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", - "recordedInputs": [ - "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", - "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", - "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", - "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", - "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", - "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", - "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", - "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", - "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", - "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", - "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", - "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", - "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", - "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", - "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" - ], - "generatedRepoSpecs": { - "rules_python_internal": { - "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", - "attributes": { - "transition_setting_generators": {}, - "transition_settings": [] - } - }, - "pypi__build": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", - "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__click": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", - "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__colorama": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", - "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__importlib_metadata": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", - "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__installer": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", - "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__more_itertools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", - "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__packaging": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", - "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pep517": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", - "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", - "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip_tools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", - "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pyproject_hooks": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", - "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__setuptools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", - "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__tomli": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", - "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__wheel": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", - "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__zipp": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", - "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - } - } - } - }, - "@@rules_python+//python/uv:uv.bzl%uv": { - "general": { - "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", - "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", - "recordedInputs": [ - "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_python+,platforms platforms" - ], - "generatedRepoSpecs": { - "uv": { - "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", - "attributes": { - "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", - "toolchain_names": [ - "none" - ], - "toolchain_implementations": { - "none": "'@@rules_python+//python:none'" - }, - "toolchain_compatible_with": { - "none": [ - "@platforms//:incompatible" - ] - }, - "toolchain_target_settings": {} - } - } - } - } - } - }, - "facts": {} -} diff --git a/example/BUILD.bazel b/example/BUILD.bazel index 6822005..d6a9621 100644 --- a/example/BUILD.bazel +++ b/example/BUILD.bazel @@ -10,5 +10,6 @@ cc_binary( ], deps = [ "@khttpd", + "@spdlog//:spdlog", ], ) diff --git a/example/HelloWsController.hpp b/example/HelloWsController.hpp index f77c3f3..7db8f6b 100644 --- a/example/HelloWsController.hpp +++ b/example/HelloWsController.hpp @@ -6,6 +6,7 @@ #define HELLOWSCONTROLLER_HPP #include "controller/http_controller.hpp" +#include class HelloWsController : public khttpd::framework::BaseController { @@ -41,23 +42,23 @@ class HelloWsController : public khttpd::framework::BaseController #include #include -#include #include +#include #include "HelloController.hpp" #include "HelloStreamController.hpp" @@ -24,7 +24,7 @@ int main(int argc, char* argv[]) auto const port = static_cast(8080); auto const num_threads = std::max(1, static_cast(std::thread::hardware_concurrency())); - fmt::print("Starting khttpd server with {} worker threads...\n", num_threads); + spdlog::info("Starting khttpd server with {} worker threads...", num_threads); // 定义 Web 根目录 std::string web_root_path = "web_root"; @@ -145,7 +145,7 @@ int main(int argc, char* argv[]) { response_msg += fmt::format("Uploaded File: {} ({} bytes, type: {})\n", file.filename, file.data.length(), file.content_type); - // fmt::print("File '{}' content: \n{}\n", file.filename, file.data); + // spdlog::debug("File '{}' content: \n{}", file.filename, file.data); } } else @@ -225,24 +225,24 @@ int main(int argc, char* argv[]) // onopen []([[maybe_unused]] khttpd::framework::WebsocketContext& ctx) { - fmt::print("[WS: {}] Connection opened.\n", ctx.path); + spdlog::info("[WS: {}] Connection opened.", ctx.path); ctx.send("Welcome to the echo service!"); }, // onmessage [](khttpd::framework::WebsocketContext& ctx) { - fmt::print("[WS: {}] Received: {}\n", ctx.path, ctx.message); + spdlog::info("[WS: {}] Received: {}", ctx.path, ctx.message); ctx.send("Echo: " + ctx.message, ctx.is_text); }, // onclose []([[maybe_unused]] khttpd::framework::WebsocketContext& ctx) { - fmt::print("[WS: {}] Connection closed.\n", ctx.path); + spdlog::info("[WS: {}] Connection closed.", ctx.path); }, // onerror [](khttpd::framework::WebsocketContext& ctx) { - fmt::print(stderr, "[WS: {}] Error: {}\n", ctx.path, ctx.error_code.message()); + spdlog::error("[WS: {}] Error: {}", ctx.path, ctx.error_code.message()); } ); @@ -251,30 +251,30 @@ int main(int argc, char* argv[]) // onopen []([[maybe_unused]] khttpd::framework::WebsocketContext& ctx) { - fmt::print("[WS: {}] Chat connection opened. Say hello!\n", ctx.path); + spdlog::info("[WS: {}] Chat connection opened. Say hello!", ctx.path); ctx.send("Welcome to the chat!"); }, // onmessage [](khttpd::framework::WebsocketContext& ctx) { - fmt::print("[WS: {}] Chat message: {}\n", ctx.path, ctx.message); + spdlog::info("[WS: {}] Chat message: {}", ctx.path, ctx.message); ctx.send("You said: " + ctx.message, ctx.is_text); }, // onclose []([[maybe_unused]] khttpd::framework::WebsocketContext& ctx) { - fmt::print("[WS: {}] Chat connection closed.\n", ctx.path); + spdlog::info("[WS: {}] Chat connection closed.", ctx.path); }, // onerror [](khttpd::framework::WebsocketContext& ctx) { - fmt::print(stderr, "[WS: {}] Chat error: {}\n", ctx.path, ctx.error_code.message()); + spdlog::error("[WS: {}] Chat error: {}", ctx.path, ctx.error_code.message()); } ); server->run(); - fmt::print("Application exited.\n"); + spdlog::info("Application exited."); return 0; } diff --git a/framework/BUILD.bazel b/framework/BUILD.bazel index f1d4f00..baf17b4 100644 --- a/framework/BUILD.bazel +++ b/framework/BUILD.bazel @@ -41,5 +41,6 @@ cc_library( "@boost.url", "@boost.uuid", "@fmt", # 用于日志输出 + "@spdlog//:spdlog", ], ) diff --git a/framework/client/host_pool.cpp b/framework/client/host_pool.cpp index 112f947..0aa6999 100644 --- a/framework/client/host_pool.cpp +++ b/framework/client/host_pool.cpp @@ -30,6 +30,7 @@ namespace khttpd::framework::client } std::uniform_int_distribution dist(1, total_weight_); + std::lock_guard lock{rng_mutex_}; int r = dist(rng_); auto it = std::lower_bound(cumulative_weights_.begin(), cumulative_weights_.end(), r); diff --git a/framework/client/host_pool.hpp b/framework/client/host_pool.hpp index 938b017..2139110 100644 --- a/framework/client/host_pool.hpp +++ b/framework/client/host_pool.hpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace khttpd::framework::client { @@ -34,6 +35,7 @@ namespace khttpd::framework::client std::vector cumulative_weights_; int total_weight_; std::mt19937 rng_; + std::mutex rng_mutex_; }; } diff --git a/framework/client/http_client.cpp b/framework/client/http_client.cpp index 6618b03..e8cf60f 100644 --- a/framework/client/http_client.cpp +++ b/framework/client/http_client.cpp @@ -1,5 +1,7 @@ #include "http_client.hpp" #include +#include +#include #include #include "io_context_pool.hpp" @@ -28,6 +30,7 @@ namespace khttpd::framework::client http::response res_; beast::flat_buffer buffer_; std::chrono::seconds timeout_; + std::atomic completed_{false}; public: Session(HttpClient::ResponseCallback callback, std::chrono::seconds timeout) @@ -37,12 +40,22 @@ namespace khttpd::framework::client virtual ~Session() = default; virtual void run(const std::string& host, const std::string& port, http::request req) = 0; + virtual void cancel() = 0; protected: + void complete(beast::error_code ec, http::response res) + { + if (!completed_.exchange(true) && callback_) + { + callback_(ec, std::move(res)); + } + } + void on_fail(beast::error_code ec, const char* what) { - // Log if needed: std::cerr << what << ": " << ec.message() << "\n"; - if (callback_) callback_(ec, {}); + // Log if needed: spdlog::error("{}: {}", what, ec.message()); + boost::ignore_unused(what); + complete(ec, {}); } }; @@ -62,7 +75,7 @@ namespace khttpd::framework::client public: HttpSession(net::io_context& ioc, HttpClient::ResponseCallback cb, std::chrono::seconds timeout) - : Session(std::move(cb), timeout), stream_(ioc), resolver_(ioc) + : Session(std::move(cb), timeout), stream_(net::make_strand(ioc)), resolver_(stream_.get_executor()) { } @@ -74,6 +87,18 @@ namespace khttpd::framework::client beast::bind_front_handler(&HttpSession::on_resolve, get_shared())); } + void cancel() override + { + net::post(stream_.get_executor(), [self = get_shared()]() + { + beast::error_code ignored; + self->resolver_.cancel(); + self->stream_.cancel(); + self->stream_.socket().shutdown(tcp::socket::shutdown_both, ignored); + self->stream_.socket().close(ignored); + }); + } + void on_resolve(beast::error_code ec, tcp::resolver::results_type results) { if (ec) return on_fail(ec, "resolve"); @@ -104,8 +129,9 @@ namespace khttpd::framework::client boost::ignore_unused(bytes_transferred); if (ec) return on_fail(ec, "read"); - stream_.socket().shutdown(tcp::socket::shutdown_both, ec); - if (callback_) callback_(ec, std::move(res_)); + beast::error_code ignored; + stream_.socket().shutdown(tcp::socket::shutdown_both, ignored); + complete({}, std::move(res_)); } }; @@ -124,7 +150,7 @@ namespace khttpd::framework::client public: HttpsSession(net::io_context& ioc, ssl::context& ctx, HttpClient::ResponseCallback cb, std::chrono::seconds timeout) - : Session(std::move(cb), timeout), stream_(ioc, ctx), resolver_(ioc) + : Session(std::move(cb), timeout), stream_(net::make_strand(ioc), ctx), resolver_(stream_.get_executor()) { } @@ -136,12 +162,25 @@ namespace khttpd::framework::client beast::error_code ec{static_cast(::ERR_get_error()), net::error::get_ssl_category()}; return on_fail(ec, "ssl_setup"); } + stream_.set_verify_callback(ssl::host_name_verification(host)); stream_.next_layer().expires_after(timeout_); resolver_.async_resolve(host, port, beast::bind_front_handler(&HttpsSession::on_resolve, get_shared())); } + void cancel() override + { + net::post(stream_.get_executor(), [self = get_shared()]() + { + beast::error_code ignored; + self->resolver_.cancel(); + beast::get_lowest_layer(self->stream_).cancel(); + beast::get_lowest_layer(self->stream_).socket().shutdown(tcp::socket::shutdown_both, ignored); + beast::get_lowest_layer(self->stream_).socket().close(ignored); + }); + } + void on_resolve(beast::error_code ec, tcp::resolver::results_type results) { if (ec) return on_fail(ec, "resolve"); @@ -178,7 +217,15 @@ namespace khttpd::framework::client void on_read(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - if (ec) return on_fail(ec, "read"); + if (ec) + { + if ((ec == ssl::error::stream_truncated || ec == net::error::eof) && res_.result_int() != 0) + { + complete({}, std::move(res_)); + return; + } + return on_fail(ec, "read"); + } stream_.async_shutdown(beast::bind_front_handler(&HttpsSession::on_shutdown, get_shared())); } @@ -187,7 +234,7 @@ namespace khttpd::framework::client { if (ec == net::error::eof || ec == ssl::error::stream_truncated) ec = {}; - if (callback_) callback_(ec, std::move(res_)); + complete(ec, std::move(res_)); } }; @@ -198,7 +245,7 @@ namespace khttpd::framework::client // 同样的默认 SSL 初始化逻辑 own_ssl_ctx_ = std::make_shared(ssl::context::tls_client); own_ssl_ctx_->set_default_verify_paths(); - own_ssl_ctx_->set_verify_mode(ssl::verify_none); + own_ssl_ctx_->set_verify_mode(ssl::verify_peer); ssl_ctx_ptr_ = own_ssl_ctx_.get(); } @@ -215,7 +262,7 @@ namespace khttpd::framework::client { own_ssl_ctx_ = std::make_shared(ssl::context::tls_client); own_ssl_ctx_->set_default_verify_paths(); - own_ssl_ctx_->set_verify_mode(ssl::verify_none); + own_ssl_ctx_->set_verify_mode(ssl::verify_peer); ssl_ctx_ptr_ = own_ssl_ctx_.get(); } @@ -390,16 +437,67 @@ namespace khttpd::framework::client const std::string& body, const std::map& headers) { - std::promise>> p; - auto f = p.get_future(); + auto p = std::make_shared>>>(); + auto completed = std::make_shared>(false); + auto f = p->get_future(); + std::shared_ptr session; - this->request(method, path, query_params, body, headers, - [&p](beast::error_code ec, http::response res) - { - p.set_value({ec, std::move(res)}); - }); + try + { + auto parts = parse_target(path, query_params); - f.wait(); + http::request req{method, parts.target, 11}; + req.set(http::field::host, parts.host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + for (const auto& h : default_headers_) req.set(h.first, h.second); + for (const auto& h : headers) req.set(h.first, h.second); + + if (!body.empty()) + { + req.body() = body; + req.prepare_payload(); + } + + auto callback = [p, completed](beast::error_code ec, http::response res) + { + if (!completed->exchange(true)) + { + p->set_value({ec, std::move(res)}); + } + }; + + if (parts.scheme == "https") + { + if (!ssl_ctx_ptr_) + { + throw boost::system::system_error( + beast::error_code(beast::errc::operation_not_supported, beast::system_category())); + } + session = std::make_shared(ioc_, *ssl_ctx_ptr_, std::move(callback), timeout_); + } + else + { + session = std::make_shared(ioc_, std::move(callback), timeout_); + } + session->run(parts.host, parts.port, std::move(req)); + } + catch (const boost::system::system_error&) + { + throw; + } + catch (const std::exception&) + { + throw boost::system::system_error( + beast::error_code(beast::errc::invalid_argument, beast::system_category())); + } + + if (f.wait_for(timeout_ + std::chrono::seconds(1)) != std::future_status::ready) + { + completed->store(true); + if (session) session->cancel(); + throw boost::system::system_error(beast::error_code(beast::errc::timed_out, beast::system_category())); + } auto result = f.get(); if (result.first) diff --git a/framework/client/websocket_client.cpp b/framework/client/websocket_client.cpp index 4b3ce7e..702f54f 100644 --- a/framework/client/websocket_client.cpp +++ b/framework/client/websocket_client.cpp @@ -1,23 +1,38 @@ #include "websocket_client.hpp" #include +#include #include +#include #include "io_context_pool.hpp" namespace khttpd::framework::client { + struct WebsocketClient::State + { + std::mutex mutex; + bool alive = true; + bool close_notified = false; + MessageHandler on_message; + ErrorHandler on_error; + CloseHandler on_close; + }; + // ========================================== // Internal Session Abstraction // ========================================== struct WebsocketSessionImpl : public std::enable_shared_from_this { - WebsocketClient* owner_; + std::weak_ptr state_; std::string host_; beast::flat_buffer buffer_; std::deque write_queue_; // 写队列 bool is_writing_ = false; + bool closing_ = false; + bool close_started_ = false; + bool connect_notified_ = false; - explicit WebsocketSessionImpl(WebsocketClient* owner) : owner_(owner) + explicit WebsocketSessionImpl(std::shared_ptr state) : state_(std::move(state)) { } @@ -38,8 +53,63 @@ namespace khttpd::framework::client virtual net::any_io_executor get_executor() = 0; virtual void do_write_from_queue() = 0; + void notify_message(const std::string& message) + { + auto state = state_.lock(); + if (!state) return; + WebsocketClient::MessageHandler handler; + { + std::lock_guard lock{state->mutex}; + if (!state->alive) return; + handler = state->on_message; + } + if (handler) handler(message); + } + + void notify_error(beast::error_code ec) + { + auto state = state_.lock(); + if (!state) return; + WebsocketClient::ErrorHandler handler; + { + std::lock_guard lock{state->mutex}; + if (!state->alive) return; + handler = state->on_error; + } + if (handler) handler(ec); + } + + void notify_close() + { + auto state = state_.lock(); + if (!state) return; + WebsocketClient::CloseHandler handler; + { + std::lock_guard lock{state->mutex}; + if (!state->alive || state->close_notified) return; + state->close_notified = true; + handler = state->on_close; + } + if (handler) handler(); + } + + void notify_connect(WebsocketClient::ConnectCallback& callback, beast::error_code ec) + { + if (connect_notified_ || closing_) return; + connect_notified_ = true; + + auto state = state_.lock(); + if (!state) return; + { + std::lock_guard lock{state->mutex}; + if (!state->alive) return; + } + if (callback) callback(ec); + } + void on_queue_write(std::string message) { + if (closing_) return; write_queue_.push_back(std::move(message)); if (!is_writing_) { @@ -60,21 +130,19 @@ namespace khttpd::framework::client ec == net::error::eof || ec == ssl::error::stream_truncated || ec == boost::asio::error::connection_reset || + ec == boost::asio::error::bad_descriptor || ec == boost::asio::error::operation_aborted) { - if (owner_->on_close_) owner_->on_close_(); + notify_close(); } else { - if (owner_->on_error_) owner_->on_error_(ec); + notify_error(ec); } return; } - if (owner_->on_message_) - { - owner_->on_message_(beast::buffers_to_string(buffer_.data())); - } + notify_message(beast::buffers_to_string(buffer_.data())); buffer_.consume(buffer_.size()); } @@ -84,7 +152,14 @@ namespace khttpd::framework::client if (ec) { is_writing_ = false; // Stop writing on error - if (owner_->on_error_) owner_->on_error_(ec); + if (ec == websocket::error::closed || ec == net::error::operation_aborted || ec == net::error::bad_descriptor) + { + notify_close(); + } + else + { + notify_error(ec); + } return; } @@ -111,8 +186,8 @@ namespace khttpd::framework::client WebsocketClient::ConnectCallback connect_cb_; public: - PlainWebsocketSession(net::io_context& ioc, WebsocketClient* owner) - : WebsocketSessionImpl(owner), ws_(net::make_strand(ioc)), resolver_(ioc) + PlainWebsocketSession(net::io_context& ioc, std::shared_ptr state) + : WebsocketSessionImpl(std::move(state)), ws_(net::make_strand(ioc)), resolver_(ws_.get_executor()) { } @@ -133,13 +208,25 @@ namespace khttpd::framework::client { net::post(ws_.get_executor(), [self = std::static_pointer_cast(shared_from_this())]() { - if (self->ws_.is_open()) + self->closing_ = true; + self->write_queue_.clear(); + beast::error_code ignored; + self->resolver_.cancel(); + beast::get_lowest_layer(self->ws_).cancel(); + + if (self->ws_.is_open() && !self->close_started_) { + self->close_started_ = true; self->ws_.async_close(websocket::close_code::normal, [self](beast::error_code) { /* ignore close error */ + self->notify_close(); }); + return; } + + beast::get_lowest_layer(self->ws_).socket().shutdown(tcp::socket::shutdown_both, ignored); + beast::get_lowest_layer(self->ws_).socket().close(ignored); }); } @@ -155,6 +242,7 @@ namespace khttpd::framework::client void on_resolve(std::string target, std::map headers, beast::error_code ec, tcp::resolver::results_type results) { + if (closing_) return; if (ec) return fail(ec); beast::get_lowest_layer(ws_).async_connect(results, beast::bind_front_handler( &PlainWebsocketSession::on_connect, @@ -165,6 +253,7 @@ namespace khttpd::framework::client void on_connect(std::string target, std::map headers, beast::error_code ec, tcp::resolver::results_type::endpoint_type) { + if (closing_) return; if (ec) return fail(ec); ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client)); @@ -184,8 +273,9 @@ namespace khttpd::framework::client void on_handshake(beast::error_code ec) { + if (closing_) return; if (ec) return fail(ec); - if (connect_cb_) connect_cb_(ec); + notify_connect(connect_cb_, ec); do_read(); } @@ -209,7 +299,7 @@ namespace khttpd::framework::client void fail(beast::error_code ec) { - if (connect_cb_) connect_cb_(ec); + notify_connect(connect_cb_, ec); } }; @@ -223,8 +313,8 @@ namespace khttpd::framework::client WebsocketClient::ConnectCallback connect_cb_; public: - SslWebsocketSession(net::io_context& ioc, ssl::context& ctx, WebsocketClient* owner) - : WebsocketSessionImpl(owner), ws_(net::make_strand(ioc), ctx), resolver_(ioc) + SslWebsocketSession(net::io_context& ioc, ssl::context& ctx, std::shared_ptr state) + : WebsocketSessionImpl(std::move(state)), ws_(net::make_strand(ioc), ctx), resolver_(ws_.get_executor()) { } @@ -240,6 +330,7 @@ namespace khttpd::framework::client { return fail(beast::error_code(static_cast(::ERR_get_error()), net::error::get_ssl_category())); } + ws_.next_layer().set_verify_callback(ssl::host_name_verification(host)); resolver_.async_resolve(host, port, beast::bind_front_handler(&SslWebsocketSession::on_resolve, std::static_pointer_cast( @@ -250,12 +341,24 @@ namespace khttpd::framework::client { net::post(ws_.get_executor(), [self = std::static_pointer_cast(shared_from_this())]() { - if (self->ws_.is_open()) + self->closing_ = true; + self->write_queue_.clear(); + beast::error_code ignored; + self->resolver_.cancel(); + beast::get_lowest_layer(self->ws_).cancel(); + + if (self->ws_.is_open() && !self->close_started_) { + self->close_started_ = true; self->ws_.async_close(websocket::close_code::normal, [self](beast::error_code) { + self->notify_close(); }); + return; } + + beast::get_lowest_layer(self->ws_).socket().shutdown(tcp::socket::shutdown_both, ignored); + beast::get_lowest_layer(self->ws_).socket().close(ignored); }); } @@ -271,6 +374,7 @@ namespace khttpd::framework::client void on_resolve(std::string target, std::map headers, beast::error_code ec, tcp::resolver::results_type results) { + if (closing_) return; if (ec) return fail(ec); beast::get_lowest_layer(ws_).async_connect(results, beast::bind_front_handler( &SslWebsocketSession::on_connect, @@ -281,6 +385,7 @@ namespace khttpd::framework::client void on_connect(std::string target, std::map headers, beast::error_code ec, tcp::resolver::results_type::endpoint_type) { + if (closing_) return; if (ec) return fail(ec); ws_.next_layer().async_handshake(ssl::stream_base::client, beast::bind_front_handler(&SslWebsocketSession::on_ssl_handshake, @@ -290,6 +395,7 @@ namespace khttpd::framework::client void on_ssl_handshake(std::string target, std::map headers, beast::error_code ec) { + if (closing_) return; if (ec) return fail(ec); ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client)); @@ -306,8 +412,9 @@ namespace khttpd::framework::client void on_handshake(beast::error_code ec) { + if (closing_) return; if (ec) return fail(ec); - if (connect_cb_) connect_cb_(ec); + notify_connect(connect_cb_, ec); do_read(); } @@ -331,7 +438,7 @@ namespace khttpd::framework::client void fail(beast::error_code ec) { - if (connect_cb_) connect_cb_(ec); + notify_connect(connect_cb_, ec); } }; @@ -340,30 +447,40 @@ namespace khttpd::framework::client // ========================================== WebsocketClient::WebsocketClient() - : ioc_(IoContextPool::instance().get_io_context()) + : ioc_(IoContextPool::instance().get_io_context()), + state_(std::make_shared()) { own_ssl_ctx_ = std::make_shared(ssl::context::tls_client); own_ssl_ctx_->set_default_verify_paths(); - own_ssl_ctx_->set_verify_mode(ssl::verify_none); + own_ssl_ctx_->set_verify_mode(ssl::verify_peer); ssl_ctx_ptr_ = own_ssl_ctx_.get(); } - WebsocketClient::WebsocketClient(net::io_context& ioc) : ioc_(ioc) + WebsocketClient::WebsocketClient(net::io_context& ioc) + : ioc_(ioc), + state_(std::make_shared()) { // Default SSL Context own_ssl_ctx_ = std::make_shared(ssl::context::tls_client); own_ssl_ctx_->set_default_verify_paths(); - own_ssl_ctx_->set_verify_mode(ssl::verify_none); + own_ssl_ctx_->set_verify_mode(ssl::verify_peer); ssl_ctx_ptr_ = own_ssl_ctx_.get(); } WebsocketClient::WebsocketClient(net::io_context& ioc, ssl::context& ssl_ctx) - : ioc_(ioc), ssl_ctx_ptr_(&ssl_ctx) + : ioc_(ioc), ssl_ctx_ptr_(&ssl_ctx), state_(std::make_shared()) { } WebsocketClient::~WebsocketClient() { + { + std::lock_guard lock{state_->mutex}; + state_->alive = false; + state_->on_message = nullptr; + state_->on_error = nullptr; + state_->on_close = nullptr; + } close(); } @@ -396,13 +513,21 @@ namespace khttpd::framework::client if (callback) callback(beast::error_code(beast::errc::operation_not_supported, beast::system_category())); return; } - auto s = std::make_shared(ioc_, *ssl_ctx_ptr_, this); + { + std::lock_guard lock{state_->mutex}; + state_->close_notified = false; + } + auto s = std::make_shared(ioc_, *ssl_ctx_ptr_, state_); session_ = s; s->run(host, port, target, headers_, std::move(callback)); } else { - auto s = std::make_shared(ioc_, this); + { + std::lock_guard lock{state_->mutex}; + state_->close_notified = false; + } + auto s = std::make_shared(ioc_, state_); session_ = s; s->run(host, port, target, headers_, std::move(callback)); } @@ -425,7 +550,21 @@ namespace khttpd::framework::client } } - void WebsocketClient::set_on_message(MessageHandler handler) { on_message_ = std::move(handler); } - void WebsocketClient::set_on_error(ErrorHandler handler) { on_error_ = std::move(handler); } - void WebsocketClient::set_on_close(CloseHandler handler) { on_close_ = std::move(handler); } + void WebsocketClient::set_on_message(MessageHandler handler) + { + std::lock_guard lock{state_->mutex}; + state_->on_message = std::move(handler); + } + + void WebsocketClient::set_on_error(ErrorHandler handler) + { + std::lock_guard lock{state_->mutex}; + state_->on_error = std::move(handler); + } + + void WebsocketClient::set_on_close(CloseHandler handler) + { + std::lock_guard lock{state_->mutex}; + state_->on_close = std::move(handler); + } } diff --git a/framework/client/websocket_client.hpp b/framework/client/websocket_client.hpp index 15e275a..3d00489 100644 --- a/framework/client/websocket_client.hpp +++ b/framework/client/websocket_client.hpp @@ -29,6 +29,8 @@ namespace khttpd::framework::client class WebsocketClient : public std::enable_shared_from_this { public: + struct State; + using ConnectCallback = std::function; using MessageHandler = std::function; using ErrorHandler = std::function; @@ -63,10 +65,7 @@ namespace khttpd::framework::client std::shared_ptr own_ssl_ctx_; ssl::context* ssl_ctx_ptr_; - // Callbacks - MessageHandler on_message_; - ErrorHandler on_error_; - CloseHandler on_close_; + std::shared_ptr state_; // Headers to send during handshake std::map headers_; diff --git a/framework/context/http_context.cpp b/framework/context/http_context.cpp index 4905392..733f744 100644 --- a/framework/context/http_context.cpp +++ b/framework/context/http_context.cpp @@ -1,9 +1,11 @@ // framework/context/http_context.cpp #include "http_context.hpp" #include +#include #include // for std::remove_if #include // for std::quoted (not directly used here, but useful for debugging) #include +#include #include #include @@ -79,10 +81,8 @@ namespace khttpd::framework else { cached_path_ = std::string(req_.target()); - fmt::print( - stderr, - "Warning: Failed to parse request target '{}' as relative-ref: {}. Query parameters may not be available.\n", - req_.target(), url_result.error().message()); + spdlog::warn("Failed to parse request target '{}' as relative-ref: {}. Query parameters may not be available.", + std::string(req_.target()), url_result.error().message()); } url_parsed_ = true; } @@ -165,12 +165,12 @@ namespace khttpd::framework } catch (const boost::system::system_error& e) { - fmt::print(stderr, "Error parsing JSON body: {}\n", e.what()); + spdlog::warn("Error parsing JSON body: {}", e.what()); return std::nullopt; } catch (const std::exception& e) { - fmt::print(stderr, "Unexpected error parsing JSON body: {}\n", e.what()); + spdlog::warn("Unexpected error parsing JSON body: {}", e.what()); return std::nullopt; } } @@ -199,7 +199,7 @@ namespace khttpd::framework } else { - fmt::print(stderr, "Error parsing x-www-form-urlencoded body: {}\n", url_query_result.error().message()); + spdlog::warn("Error parsing x-www-form-urlencoded body: {}", url_query_result.error().message()); } form_params_parsed_ = true; } @@ -233,12 +233,16 @@ namespace khttpd::framework size_t boundary_pos = content_type_header->find("boundary="); if (boundary_pos == std::string::npos) { - fmt::print(stderr, "Multipart/form-data: No boundary found in Content-Type header.\n"); + spdlog::warn("Multipart/form-data: No boundary found in Content-Type header."); multipart_parsed_ = true; return; } std::string boundary = content_type_header->substr(boundary_pos + std::string("boundary=").length()); - boundary = trim(boundary); // Trim potential quotes or whitespace + boundary = trim(boundary); + if (boundary.length() >= 2 && boundary.front() == '"' && boundary.back() == '"') + { + boundary = boundary.substr(1, boundary.length() - 2); + } std::string full_boundary = "--" + boundary; std::string final_boundary = full_boundary + "--"; @@ -250,7 +254,7 @@ namespace khttpd::framework current_body_pos = body_str.find(full_boundary); if (current_body_pos == std::string::npos) { - fmt::print(stderr, "Multipart/form-data: First boundary not found.\n"); + spdlog::warn("Multipart/form-data: First boundary not found."); multipart_parsed_ = true; return; } @@ -263,7 +267,7 @@ namespace khttpd::framework size_t header_end_pos = body_str.find("\r\n\r\n", current_body_pos); if (header_end_pos == std::string::npos) { - fmt::print(stderr, "Multipart/form-data: Malformed part - no header end found.\n"); + spdlog::warn("Multipart/form-data: Malformed part - no header end found."); break; } @@ -274,27 +278,22 @@ namespace khttpd::framework part_headers = part_headers.substr(2); } - // Find start of next boundary (or final boundary) - size_t next_boundary_pos = body_str.find(full_boundary, header_end_pos + 4); // +4 for \r\n\r\n - if (next_boundary_pos == std::string::npos) + const size_t data_start = header_end_pos + 4; + size_t boundary_marker_pos = body_str.find("\r\n" + full_boundary, data_start); + if (boundary_marker_pos == std::string::npos) { - fmt::print(stderr, "Multipart/form-data: Next boundary not found. Malformed or premature end.\n"); + spdlog::warn("Multipart/form-data: Next boundary not found. Malformed or premature end."); break; // Malformed or end of stream } + const size_t next_boundary_pos = boundary_marker_pos + 2; - std::string part_data = body_str.substr(header_end_pos + 4, next_boundary_pos - (header_end_pos + 4)); - // Trim trailing \r\n from data before boundary - if (part_data.length() >= 2 && part_data[part_data.length() - 2] == '\r' && part_data[part_data.length() - 1] == - '\n') - { - part_data = part_data.substr(0, part_data.length() - 2); - } + std::string part_data = body_str.substr(data_start, boundary_marker_pos - data_start); // Parse Content-Disposition std::string content_disposition = extract_header_value(part_headers, "Content-Disposition"); if (content_disposition.empty()) { - fmt::print(stderr, "Multipart/form-data: Part with no Content-Disposition header.\n"); + spdlog::warn("Multipart/form-data: Part with no Content-Disposition header."); current_body_pos = next_boundary_pos + full_boundary.length(); continue; } @@ -432,13 +431,13 @@ namespace khttpd::framework key.find('\r') != std::string::npos || key.find('\n') != std::string::npos || key.find('=') != std::string::npos) { - fmt::print(stderr, "Warning: Invalid cookie key '{}' - contains prohibited characters\n", key); + spdlog::warn("Invalid cookie key '{}' - contains prohibited characters", key); return; } if (value.find(';') != std::string::npos || value.find(',') != std::string::npos || value.find('\r') != std::string::npos || value.find('\n') != std::string::npos) { - fmt::print(stderr, "Warning: Invalid cookie value '{}' - contains prohibited characters\n", value); + spdlog::warn("Invalid cookie value '{}' - contains prohibited characters", value); return; } diff --git a/framework/context/websocket_context.cpp b/framework/context/websocket_context.cpp index 3765788..2a0384d 100644 --- a/framework/context/websocket_context.cpp +++ b/framework/context/websocket_context.cpp @@ -1,7 +1,7 @@ // framework/context/websocket_context.cpp #include "websocket_context.hpp" #include "websocket/websocket_session.hpp" -#include +#include #include @@ -36,7 +36,7 @@ namespace khttpd::framework } else { - fmt::print(stderr, "Error: Attempted to send WS message to expired session (path: {}).\n", path); + spdlog::error("Attempted to send WS message to expired session (path: {}).", path); } } } diff --git a/framework/cron/CronJob.hpp b/framework/cron/CronJob.hpp index ba21ea2..fb67e25 100644 --- a/framework/cron/CronJob.hpp +++ b/framework/cron/CronJob.hpp @@ -4,11 +4,11 @@ #include #include #include -#include #include #include #include #include +#include #include "croncpp.hpp" #include "io_context_pool.hpp" @@ -28,7 +28,7 @@ namespace khttpd::framework } catch (const std::exception& e) { - std::cerr << "[CronJob] Invalid expression '" << expression << "': " << e.what() << std::endl; + spdlog::error("[CronJob] Invalid expression '{}': {}", expression, e.what()); throw; } } @@ -99,7 +99,7 @@ namespace khttpd::framework if (ec) { - std::cerr << "[CronJob] Timer error: " << ec.message() << std::endl; + spdlog::error("[CronJob] Timer error: {}", ec.message()); return; } @@ -109,7 +109,7 @@ namespace khttpd::framework } catch (const std::exception& e) { - std::cerr << "[CronJob] Task exception: " << e.what() << std::endl; + spdlog::error("[CronJob] Task exception: {}", e.what()); } if (is_running_) diff --git a/framework/cron/CronScheduler.hpp b/framework/cron/CronScheduler.hpp index 69f420c..85aa803 100644 --- a/framework/cron/CronScheduler.hpp +++ b/framework/cron/CronScheduler.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace khttpd::framework { @@ -62,13 +63,56 @@ namespace khttpd::framework auto job = std::make_shared(expression, std::move(task)); job->start(delay_ms); - // 这里的 job 即使出了作用域,也会因为 CronJob 内部 async_wait 捕获了 shared_from_this 而存活。 - // 返回它只是为了让调用者有控制权。 + { + std::lock_guard lock{mtx_}; + jobs_.push_back(job); + } return job; } + void stop_all() + { + std::vector> jobs; + { + std::lock_guard lock{mtx_}; + jobs.swap(jobs_); + } + for (const auto& job : jobs) + { + job->stop(); + } + } + + void unschedule(const std::shared_ptr& job) + { + if (!job) return; + job->stop(); + + std::lock_guard lock{mtx_}; + jobs_.erase(std::remove(jobs_.begin(), jobs_.end(), job), jobs_.end()); + } + + void prune_stopped() + { + std::lock_guard lock{mtx_}; + jobs_.erase(std::remove_if(jobs_.begin(), jobs_.end(), + [](const std::shared_ptr& job) + { + return !job || !job->is_running(); + }), + jobs_.end()); + } + + std::size_t job_count() const + { + std::lock_guard lock{mtx_}; + return jobs_.size(); + } + private: CronScheduler() = default; + mutable std::mutex mtx_; + std::vector> jobs_; }; } diff --git a/framework/di/di_container.hpp b/framework/di/di_container.hpp index d771f5f..1430b0a 100644 --- a/framework/di/di_container.hpp +++ b/framework/di/di_container.hpp @@ -4,7 +4,6 @@ #ifndef DI_CONTAINER_HPP #define DI_CONTAINER_HPP -#include #include // For std::shared_ptr #include // For std::type_index #include // For std::map @@ -13,6 +12,7 @@ #include // For std::runtime_error #include // For typeid(T).name() #include // For std::mutex +#include namespace khttpd { @@ -41,10 +41,10 @@ namespace khttpd std::unique_lock lock(mtx_); if (component_factories_.count(type_idx)) { - std::cerr << "Warning: Component " << typeid(T).name() << " already registered. Overwriting." << std::endl; + spdlog::warn("Component {} already registered. Overwriting.", typeid(T).name()); } - auto factory = [this](const DI_Container& container) -> std::shared_ptr + auto factory = [](const DI_Container& container) -> std::shared_ptr { return std::make_shared(container.resolve()...); }; @@ -97,6 +97,7 @@ namespace khttpd catch (...) { // Clean up resolving set on exception + lock.lock(); resolving_.erase(type_idx); throw; } diff --git a/framework/io_context_pool.hpp b/framework/io_context_pool.hpp index 6b0252e..3781c70 100644 --- a/framework/io_context_pool.hpp +++ b/framework/io_context_pool.hpp @@ -18,6 +18,11 @@ namespace khttpd::framework // 获取单例实例 static IoContextPool& instance(unsigned int num_threads = 0) { + if (num_threads == 0) + { + num_threads = std::thread::hardware_concurrency(); + if (num_threads == 0) num_threads = 1; + } static IoContextPool instance{num_threads}; return instance; } diff --git a/framework/router/http_router.cpp b/framework/router/http_router.cpp index 3309e8d..14e0723 100644 --- a/framework/router/http_router.cpp +++ b/framework/router/http_router.cpp @@ -1,6 +1,7 @@ // framework/router/http_router.cpp #include "http_router.hpp" #include +#include #include namespace khttpd::framework @@ -104,7 +105,8 @@ namespace khttpd::framework if (entry.original_path == path_pattern) { entry.handlers[method] = std::move(handler); - fmt::print("Updated handler for route: {} {}\n", boost::beast::http::to_string(method), path_pattern); + spdlog::debug("Updated handler for route: {} {}", std::string(boost::beast::http::to_string(method)), + path_pattern); return; } } @@ -120,8 +122,8 @@ namespace khttpd::framework routes_.push_back(std::move(new_entry)); std::sort(routes_.begin(), routes_.end(), RouteEntry::compare_specificity); - fmt::print("Registered dynamic route: {} {} (literal:{}, dynamic:{})\n", - boost::beast::http::to_string(method), path_pattern, literal_count, dynamic_count); + spdlog::debug("Registered dynamic route: {} {} (literal:{}, dynamic:{})", + std::string(boost::beast::http::to_string(method)), path_pattern, literal_count, dynamic_count); } void HttpRouter::get(const std::string& path, HttpHandler handler) @@ -183,7 +185,12 @@ namespace khttpd::framework { if (std::smatch matches; std::regex_match(request_path, matches, entry.path_regex)) { - if (const auto method_it = entry.handlers.find(request_method); method_it != entry.handlers.end()) + auto method_it = entry.handlers.find(request_method); + if (method_it == entry.handlers.end() && request_method == boost::beast::http::verb::head) + { + method_it = entry.handlers.find(boost::beast::http::verb::get); + } + if (method_it != entry.handlers.end()) { std::map path_params; for (size_t i = 0; i < entry.param_names.size(); ++i) @@ -220,7 +227,7 @@ namespace khttpd::framework ctx.set_content_type("text/html"); ctx.set_body(fmt::format("

404 Not Found

The resource '{}' was not found on this server.

", ctx.path())); - fmt::print(stderr, "404 Not Found: {}\n", ctx.path()); + spdlog::warn("404 Not Found: {}", ctx.path()); } void HttpRouter::handle_method_not_allowed(HttpContext& ctx, @@ -240,7 +247,8 @@ namespace khttpd::framework first = false; } ctx.set_header(boost::beast::http::field::allow, allowed_methods_str); - fmt::print(stderr, "405 Method Not Allowed: {} {}\n", boost::beast::http::to_string(ctx.method()), ctx.path()); + spdlog::warn("405 Method Not Allowed: {} {}", std::string(boost::beast::http::to_string(ctx.method())), + ctx.path()); } void HttpRouter::add_exception_handler(std::shared_ptr handler) @@ -258,7 +266,7 @@ namespace khttpd::framework if (!eptr) { // Should not happen, but safeguard against null pointer - fmt::print(stderr, "handle_exception called with null exception_ptr\n"); + spdlog::error("handle_exception called with null exception_ptr"); handle_unknown_exception(ctx); return; } @@ -278,7 +286,7 @@ namespace khttpd::framework } catch (const std::exception& e) { - fmt::print(stderr, "Unhandled exception: {}\n", e.what()); + spdlog::error("Unhandled exception: {}", e.what()); ctx.set_status(boost::beast::http::status::internal_server_error); ctx.set_content_type("text/html"); ctx.set_body(fmt::format("

500 Internal Server Error

Exception: {}

", e.what())); @@ -300,7 +308,7 @@ namespace khttpd::framework return; } - fmt::print(stderr, "Unknown exception occurred.\n"); + spdlog::error("Unknown exception occurred."); ctx.set_status(boost::beast::http::status::internal_server_error); ctx.set_content_type("text/html"); ctx.set_body("

500 Internal Server Error

An unknown error occurred.

"); diff --git a/framework/router/websocket_router.cpp b/framework/router/websocket_router.cpp index 4ea1580..d4605b8 100644 --- a/framework/router/websocket_router.cpp +++ b/framework/router/websocket_router.cpp @@ -1,6 +1,6 @@ // framework/router/websocket_router.cpp #include "websocket_router.hpp" -#include +#include #include "websocket/websocket_session.hpp" namespace khttpd::framework @@ -29,7 +29,7 @@ namespace khttpd::framework entry.on_close = std::move(on_close); entry.on_error = std::move(on_error); handlers_[path] = entry; - fmt::print("Registered WebSocket handlers for path: {}\n", path); + spdlog::debug("Registered WebSocket handlers for path: {}", path); } void WebsocketRouter::dispatch_open(const std::string& path, WebsocketContext& ctx) @@ -37,7 +37,7 @@ namespace khttpd::framework const auto it = handlers_.find(path); if (it == handlers_.end() || !it->second.on_open) { - fmt::print(stderr, "No on_open handler found for WS path: {}\n", path); + spdlog::warn("No on_open handler found for WS path: {}", path); return; } it->second.on_open(ctx); @@ -48,7 +48,7 @@ namespace khttpd::framework const auto it = handlers_.find(path); if (it == handlers_.end() || !it->second.on_message) { - fmt::print(stderr, "No on_message handler found for WS path: {}\n", path); + spdlog::warn("No on_message handler found for WS path: {}", path); // Default behavior: if no handler, just close connection? Echo? // For now, nothing happens. return; @@ -61,7 +61,7 @@ namespace khttpd::framework const auto it = handlers_.find(path); if (it == handlers_.end() || !it->second.on_close) { - fmt::print(stderr, "No on_close handler found for WS path: {}\n", path); + spdlog::warn("No on_close handler found for WS path: {}", path); return; } it->second.on_close(ctx); @@ -72,7 +72,7 @@ namespace khttpd::framework const auto it = handlers_.find(path); if (it == handlers_.end() || !it->second.on_error) { - fmt::print(stderr, "No on_error handler found for WS path: {} (error: {})\n", path, ctx.error_code.message()); + spdlog::warn("No on_error handler found for WS path: {} (error: {})", path, ctx.error_code.message()); return; } it->second.on_error(ctx); diff --git a/framework/server.cpp b/framework/server.cpp index 290c7a2..a895b2a 100644 --- a/framework/server.cpp +++ b/framework/server.cpp @@ -2,6 +2,7 @@ #include "server.hpp" #include "session/http_session.hpp" // 需要HttpSession #include +#include #include #include @@ -19,28 +20,28 @@ namespace khttpd::framework acceptor_.open(endpoint.protocol(), ec); if (ec) { - fmt::print(stderr, "Server open error: {}\n", ec.message()); + spdlog::error("Server open error: {}", ec.message()); throw std::runtime_error(fmt::format("Failed to open acceptor: {}", ec.message())); } acceptor_.set_option(net::socket_base::reuse_address(true), ec); if (ec) { - fmt::print(stderr, "Server set_option reuse_address error: {}\n", ec.message()); + spdlog::error("Server set_option reuse_address error: {}", ec.message()); throw std::runtime_error(fmt::format("Failed to set reuse_address: {}", ec.message())); } acceptor_.bind(endpoint, ec); if (ec) { - fmt::print(stderr, "Server bind error: {}\n", ec.message()); + spdlog::error("Server bind error: {}", ec.message()); throw std::runtime_error(fmt::format("Failed to bind acceptor: {}", ec.message())); } acceptor_.listen(net::socket_base::max_listen_connections, ec); if (ec) { - fmt::print(stderr, "Server listen error: {}\n", ec.message()); + spdlog::error("Server listen error: {}", ec.message()); throw std::runtime_error(fmt::format("Failed to listen: {}", ec.message())); } @@ -49,18 +50,18 @@ namespace khttpd::framework canonical_web_root_ = boost::filesystem::canonical(web_root_, path_ec); if (path_ec) { - fmt::print(stderr, "Warning: Cannot canonicalize web_root '{}': {}\n", web_root_, path_ec.message()); + spdlog::warn("Cannot canonicalize web_root '{}': {}", web_root_, path_ec.message()); } if (!boost::filesystem::exists(web_root_, ec)) { - fmt::print(stderr, "Warning: Web root directory '{}' does not exist. Static file serving may fail. Error: {}\n", - web_root_, ec.message()); + spdlog::warn("Web root directory '{}' does not exist. Static file serving may fail. Error: {}", + web_root_, ec.message()); } else if (!boost::filesystem::is_directory(web_root_, ec)) { - fmt::print(stderr, "Warning: Web root path '{}' is not a directory. Static file serving may fail. Error: {}\n", - web_root_, ec.message()); + spdlog::warn("Web root path '{}' is not a directory. Static file serving may fail. Error: {}", + web_root_, ec.message()); } } @@ -89,10 +90,15 @@ namespace khttpd::framework return websocket_router_; } + tcp::endpoint Server::local_endpoint() const + { + return acceptor_.local_endpoint(); + } + void Server::run() { - fmt::print("Server listening on {}:{}\n", acceptor_.local_endpoint().address().to_string(), - acceptor_.local_endpoint().port()); + spdlog::info("Server listening on {}:{}", acceptor_.local_endpoint().address().to_string(), + acceptor_.local_endpoint().port()); signals_.async_wait(beast::bind_front_handler(&Server::handle_signal, shared_from_this())); @@ -100,7 +106,7 @@ namespace khttpd::framework IoContextPool::instance().get_io_context().run(); - fmt::print("Server workers stopped.\n"); + spdlog::info("Server workers stopped."); } void Server::stop() @@ -109,11 +115,11 @@ namespace khttpd::framework acceptor_.close(ec); if (ec) { - fmt::print(stderr, "Server acceptor close error: {}\n", ec.message()); + spdlog::error("Server acceptor close error: {}", ec.message()); } IoContextPool::instance().stop(); - fmt::print("Server stopped.\n"); + spdlog::info("Server stopped."); } void Server::do_accept() @@ -129,7 +135,7 @@ namespace khttpd::framework { if (ec != boost::system::errc::operation_canceled) { - fmt::print(stderr, "Server on_accept error: {}\n", ec.message()); + spdlog::error("Server on_accept error: {}", ec.message()); } } else @@ -147,7 +153,7 @@ namespace khttpd::framework { if (!error) { - fmt::print("Received signal {}, shutting down gracefully...\n", signal_number); + spdlog::info("Received signal {}, shutting down gracefully...", signal_number); stop(); } } diff --git a/framework/server.hpp b/framework/server.hpp index 97283e9..e40825c 100644 --- a/framework/server.hpp +++ b/framework/server.hpp @@ -30,6 +30,8 @@ namespace khttpd::framework WebsocketRouter& get_websocket_router(); const WebsocketRouter& get_websocket_router() const; + tcp::endpoint local_endpoint() const; + void run(); void stop(); diff --git a/framework/session/http_session.cpp b/framework/session/http_session.cpp index 0521d17..4db8600 100644 --- a/framework/session/http_session.cpp +++ b/framework/session/http_session.cpp @@ -2,13 +2,30 @@ #include "context/http_context.hpp" #include -#include +#include #include +#include #include using namespace khttpd::framework; +struct HttpSession::ChunkWriteState +{ + std::queue queue; + std::mutex mutex; + bool writing = false; + bool final_queued = false; + bool completed = false; + beast::error_code error; + net::executor_work_guard guard; + + explicit ChunkWriteState(beast::tcp_stream::executor_type executor) + : guard(executor) + { + } +}; + HttpSession::HttpSession(tcp::socket&& socket, HttpRouter& router, WebsocketRouter& ws_router, const std::string& web_root, const boost::filesystem::path& canonical_web_root) @@ -47,13 +64,13 @@ void HttpSession::on_read(const beast::error_code& ec, std::size_t bytes_transfe } if (ec) { - fmt::print(stderr, "HttpSession on_read error: {}\n", ec.message()); + spdlog::error("HttpSession on_read error: {}", ec.message()); return; } if (beast::websocket::is_upgrade(req_)) { - fmt::print("Detected WebSocket upgrade request for target: {}\n", req_.target()); + spdlog::debug("Detected WebSocket upgrade request for target: {}", std::string(req_.target())); handle_websocket_upgrade(); return; } @@ -121,14 +138,34 @@ void HttpSession::handle_request() } // Extract path from request target (query-stripped) -static std::string extract_path_from_target(std::string_view target) +namespace { - auto qpos = target.find('?'); - if (qpos != std::string_view::npos) + std::string extract_path_from_target(std::string_view target) { - return std::string(target.substr(0, qpos)); + auto qpos = target.find('?'); + if (qpos != std::string_view::npos) + { + return std::string(target.substr(0, qpos)); + } + return std::string(target); + } + + bool is_path_within_root(const boost::filesystem::path& candidate, + const boost::filesystem::path& root) + { + auto root_it = root.begin(); + auto candidate_it = candidate.begin(); + + for (; root_it != root.end(); ++root_it, ++candidate_it) + { + if (candidate_it == candidate.end() || *root_it != *candidate_it) + { + return false; + } + } + + return true; } - return std::string(target); } bool HttpSession::do_serve_static_file() @@ -158,7 +195,7 @@ bool HttpSession::do_serve_static_file() { return false; } - fmt::print(stderr, "Error canonicalizing path '{}': {}\n", full_local_path.string(), ec.message()); + spdlog::error("Error canonicalizing path '{}': {}", full_local_path.string(), ec.message()); http::response forbidden_res{http::status::forbidden, req_.version()}; forbidden_res.keep_alive(req_.keep_alive()); forbidden_res.set(http::field::server, BOOST_BEAST_VERSION_STRING); @@ -170,10 +207,7 @@ bool HttpSession::do_serve_static_file() } // 2. Security: ensure path is within web root - const std::string& full_path_str = full_local_path.string(); - const std::string& root_path_str = canonical_web_root_path_.string(); - if (full_path_str.size() < root_path_str.size() || - full_path_str.substr(0, root_path_str.size()) != root_path_str) + if (!is_path_within_root(full_local_path, canonical_web_root_path_)) { http::response forbidden_res{http::status::forbidden, req_.version()}; forbidden_res.keep_alive(req_.keep_alive()); @@ -190,7 +224,7 @@ bool HttpSession::do_serve_static_file() { if (ec) { - fmt::print(stderr, "Error checking if path is directory '{}': {}\n", full_local_path.string(), ec.message()); + spdlog::error("Error checking if path is directory '{}': {}", full_local_path.string(), ec.message()); return false; } boost::filesystem::path index_file_path = full_local_path / "index.html"; @@ -227,7 +261,7 @@ bool HttpSession::do_serve_static_file() file_res.body().open(full_local_path.string().c_str(), beast::file_mode::scan, ec); if (ec) { - fmt::print(stderr, "Error opening file {}: {}\n", full_local_path.string(), ec.message()); + spdlog::error("Error opening file {}: {}", full_local_path.string(), ec.message()); http::response internal_error_res{http::status::internal_server_error, req_.version()}; internal_error_res.keep_alive(req_.keep_alive()); internal_error_res.set(http::field::server, BOOST_BEAST_VERSION_STRING); @@ -244,6 +278,17 @@ bool HttpSession::do_serve_static_file() file_res.prepare_payload(); + if (req_.method() == http::verb::head) + { + http::response head_res{http::status::ok, req_.version()}; + head_res.keep_alive(req_.keep_alive()); + head_res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + head_res.set(http::field::content_type, mime_type_from_extension(extension)); + head_res.content_length(file_res.body().size()); + send_response(std::move(head_res)); + return true; + } + send_response(std::move(file_res)); return true; } @@ -253,12 +298,6 @@ void HttpSession::send_chunked_response() res_.body() = ""; sr_.emplace(res_); - // Initialize chunked writing state - chunk_queue_ = std::make_shared>(); - chunk_mtx_ = std::make_shared(); - chunk_writing_ = std::make_shared(false); - chunk_error_ = std::make_shared(); - http::async_write_header(stream_, *sr_, beast::bind_front_handler( &HttpSession::on_write_header, @@ -277,62 +316,107 @@ void HttpSession::on_write_header(beast::error_code ec, std::size_t bytes_transf boost::ignore_unused(bytes_transferred); if (ec) { - fmt::print(stderr, "HttpSession on_write_header error: {}\n", ec.message()); + spdlog::error("HttpSession on_write_header error: {}", ec.message()); return; } - // Async chunk writer: posts each chunk write to the io_context executor. - // The WriteHandler synchronously waits for the async write to complete - // so the user's HttpStreamHandler can use a simple synchronous loop. - auto write_chunk = [this](const std::string& buffer) -> bool + // The stream handler may run blocking user code, while writes are serialized + // back onto the stream executor to preserve Beast's async_write contract. + auto self = shared_from_this(); + auto state = std::make_shared(stream_.get_executor()); + + auto schedule_write = [self, state]() { - struct WriteState + bool should_start = false; + { + std::unique_lock lock{state->mutex}; + if (!state->writing) + { + state->writing = true; + should_start = true; + } + } + if (should_start) { - std::mutex mtx; - std::condition_variable cv; - beast::error_code ec; - bool done = false; - }; - auto state = std::make_shared(); - - // Build chunk: hex-length \r\n body \r\n + net::post(self->stream_.get_executor(), [self, state]() + { + self->do_write_chunk(state); + }); + } + }; + + auto write_chunk = [state, schedule_write](const std::string& buffer) -> bool + { std::stringstream ss; ss << std::hex << buffer.length() << "\r\n" << buffer << "\r\n"; - auto data = std::make_shared(ss.str()); - - // Post async write to executor - net::post(stream_.get_executor(), - [self = shared_from_this(), data, state]() - { - net::async_write(self->stream_, net::buffer(*data), - [state](beast::error_code ec, std::size_t) - { - std::unique_lock lock{state->mtx}; - state->ec = ec; - state->done = true; - state->cv.notify_one(); - }); - }); - - // Wait for async write to complete - std::unique_lock lock{state->mtx}; - state->cv.wait(lock, [&state] { return state->done; }); - - if (state->ec) + { - fmt::print(stderr, "Chunked write error: {}\n", state->ec.message()); - return false; + std::unique_lock lock{state->mutex}; + if (state->error) + { + return false; + } + state->queue.push(ss.str()); } + schedule_write(); return true; }; - // Invoke the user's stream handler with our async-backed WriteHandler - if (ctx->get_stream_handler()) + std::thread([self, state, write_chunk, schedule_write]() { - ctx->get_stream_handler()(*ctx, write_chunk); + if (self->ctx->get_stream_handler()) + { + self->ctx->get_stream_handler()(*self->ctx, write_chunk); + } + + { + std::unique_lock lock{state->mutex}; + state->queue.push("0\r\n\r\n"); + state->final_queued = true; + } + schedule_write(); + }).detach(); +} + +void HttpSession::do_write_chunk(std::shared_ptr state) +{ + std::shared_ptr data; + { + std::unique_lock lock{state->mutex}; + if (state->queue.empty()) + { + state->writing = false; + if (state->final_queued && !state->completed) + { + state->completed = true; + lock.unlock(); + state->guard.reset(); + on_write(res_.keep_alive(), {}, 0); + } + return; + } + + data = std::make_shared(std::move(state->queue.front())); + state->queue.pop(); } - do_write_final_chunk(); + net::async_write(stream_, net::buffer(*data), + [self = shared_from_this(), state, data](beast::error_code ec, std::size_t bytes) + { + if (ec) + { + { + std::unique_lock lock{state->mutex}; + state->error = ec; + state->writing = false; + state->completed = true; + } + state->guard.reset(); + self->on_write(self->res_.keep_alive(), ec, bytes); + return; + } + self->do_write_chunk(state); + }); } void HttpSession::do_write_final_chunk() @@ -354,7 +438,7 @@ void HttpSession::on_write(bool keep_alive, beast::error_code ec, std::size_t by if (ec) { - fmt::print(stderr, "HttpSession on_write error: {}\n", ec.message()); + spdlog::error("HttpSession on_write error: {}", ec.message()); return; } @@ -372,7 +456,7 @@ void HttpSession::do_close() stream_.socket().shutdown(tcp::socket::shutdown_send, ec); if (ec) { - fmt::print(stderr, "HttpSession shutdown error: {}\n", ec.message()); + spdlog::error("HttpSession shutdown error: {}", ec.message()); } } diff --git a/framework/session/http_session.hpp b/framework/session/http_session.hpp index 0f1a368..365d5d2 100644 --- a/framework/session/http_session.hpp +++ b/framework/session/http_session.hpp @@ -33,6 +33,8 @@ namespace khttpd::framework void run(); private: + struct ChunkWriteState; + bool disable_web_root_ = false; beast::tcp_stream stream_; beast::flat_buffer buffer_; @@ -62,6 +64,7 @@ namespace khttpd::framework void send_chunked_response(); void send_response(http::message_generator msg); void on_write_header(beast::error_code ec, std::size_t bytes_transferred); + void do_write_chunk(std::shared_ptr state); void on_write(bool keep_alive, beast::error_code ec, std::size_t bytes_transferred); void do_write_final_chunk(); diff --git a/framework/tests/BUILD.bazel b/framework/tests/BUILD.bazel index db4f82b..b3b234a 100644 --- a/framework/tests/BUILD.bazel +++ b/framework/tests/BUILD.bazel @@ -25,6 +25,7 @@ cc_test( ], deps = [ "//framework", + "@spdlog//:spdlog", "@googletest//:gtest", "@googletest//:gtest_main", ], @@ -119,3 +120,41 @@ cc_test( "@googletest//:gtest_main", ], ) + +cc_test( + name = "session_test", + srcs = ["session_test.cpp"], + copts = [ + "-std=c++17", + "-Wall", + "-pedantic", + ], + deps = [ + "//framework", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( + name = "server_stability_test", + srcs = ["server_stability_test.cpp"], + copts = select({ + "@platforms//os:windows": [ + "/std:c++17", + "/wd4865", + "/wd5026", + "/wd5039", + ], + "//conditions:default": [ + "-std=c++17", + "-Wall", + "-pedantic", + ], + }), + deps = [ + "//framework", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) diff --git a/framework/tests/client_test.cpp b/framework/tests/client_test.cpp index 30ff186..c13cd1d 100644 --- a/framework/tests/client_test.cpp +++ b/framework/tests/client_test.cpp @@ -4,14 +4,267 @@ #include "framework/client/host_pool.hpp" #include #include +#include +#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include #include "io_context_pool.hpp" using namespace khttpd::framework::client; namespace http = boost::beast::http; +namespace beast = boost::beast; +namespace websocket = boost::beast::websocket; +namespace net = boost::asio; +using tcp = boost::asio::ip::tcp; + +namespace +{ +class ClientTestLogger : public ::testing::EmptyTestEventListener +{ +public: + void OnTestStart(const ::testing::TestInfo& test_info) override + { + spdlog::info("[client_test] START {}.{}", test_info.test_suite_name(), test_info.name()); + } + + void OnTestEnd(const ::testing::TestInfo& test_info) override + { + spdlog::info("[client_test] END {}.{} result={}", test_info.test_suite_name(), test_info.name(), + test_info.result()->Passed() ? "PASS" : "FAIL"); + } +}; + +class ClientTestLoggerEnvironment : public ::testing::Environment +{ +public: + void SetUp() override + { + spdlog::set_level(spdlog::level::info); + ::testing::UnitTest::GetInstance()->listeners().Append(new ClientTestLogger()); + spdlog::info("[client_test] logger installed"); + } +}; + +const auto* const kClientTestLoggerEnvironment = + ::testing::AddGlobalTestEnvironment(new ClientTestLoggerEnvironment()); + +class LocalHttpEchoServer +{ +public: + LocalHttpEchoServer() + : acceptor_(ioc_, tcp::endpoint(net::ip::address_v4::loopback(), 0)), + port_(acceptor_.local_endpoint().port()) + { + do_accept(); + thread_ = std::thread([this]() { ioc_.run(); }); + } + + ~LocalHttpEchoServer() + { + spdlog::info("[client_test] LocalHttpEchoServer stopping"); + boost::system::error_code ignored; + acceptor_.close(ignored); + ioc_.stop(); + if (thread_.joinable()) thread_.join(); + std::vector workers; + { + std::lock_guard lock(workers_mutex_); + workers.swap(workers_); + } + for (auto& worker : workers) + { + if (worker.joinable()) worker.join(); + } + spdlog::info("[client_test] LocalHttpEchoServer stopped"); + } + + std::string base_url() const + { + return "http://127.0.0.1:" + std::to_string(port_); + } + +private: + void do_accept() + { + acceptor_.async_accept([this](boost::system::error_code ec, tcp::socket socket) + { + if (!ec) + { + std::lock_guard lock(workers_mutex_); + workers_.emplace_back([socket = std::move(socket)]() mutable + { + handle_session(std::move(socket)); + }); + } + + if (acceptor_.is_open()) + { + do_accept(); + } + }); + } + + static std::string query_value(std::string target, const std::string& key) + { + const auto query_pos = target.find('?'); + if (query_pos == std::string::npos) return ""; + std::string query = target.substr(query_pos + 1); + std::istringstream parts(query); + std::string item; + while (std::getline(parts, item, '&')) + { + const auto eq_pos = item.find('='); + if (eq_pos != std::string::npos && item.substr(0, eq_pos) == key) + { + return item.substr(eq_pos + 1); + } + } + return ""; + } + + static void handle_session(tcp::socket socket) + { + beast::flat_buffer buffer; + boost::system::error_code ec; + http::request req; + http::read(socket, buffer, req, ec); + if (ec) return; + + http::response res{http::status::ok, req.version()}; + res.set(http::field::server, "khttpd-local-echo"); + res.set(http::field::content_type, "application/json"); + res.keep_alive(false); + + const std::string target(req.target()); + if (target.rfind("/get", 0) == 0) + { + res.body() = "{\"foo\":\"" + query_value(target, "foo") + "\",\"id\":\"" + + query_value(target, "id") + "\",\"msg\":\"" + query_value(target, "msg") + "\"}"; + } + else if (target.rfind("/headers", 0) == 0) + { + std::string body = "{"; + for (const auto& field : req) + { + body += "\"" + std::string(field.name_string()) + "\":\"" + std::string(field.value()) + "\","; + } + body += "\"done\":true}"; + res.body() = std::move(body); + } + else if (target.rfind("/post", 0) == 0) + { + res.body() = "{\"data\":" + req.body() + "}"; + } + else + { + res.body() = "{\"target\":\"" + target + "\"}"; + } + + res.prepare_payload(); + http::write(socket, res, ec); + socket.shutdown(tcp::socket::shutdown_both, ec); + } + + net::io_context ioc_; + tcp::acceptor acceptor_; + unsigned short port_; + std::thread thread_; + std::mutex workers_mutex_; + std::vector workers_; +}; + +class LocalWebSocketEchoServer +{ +public: + LocalWebSocketEchoServer() + : acceptor_(ioc_, tcp::endpoint(net::ip::address_v4::loopback(), 0)), + port_(acceptor_.local_endpoint().port()) + { + do_accept(); + thread_ = std::thread([this]() { ioc_.run(); }); + } + + ~LocalWebSocketEchoServer() + { + spdlog::info("[client_test] LocalWebSocketEchoServer stopping"); + net::post(ioc_, [this]() + { + boost::system::error_code ignored; + acceptor_.close(ignored); + if (ws_) + { + beast::get_lowest_layer(*ws_).cancel(ignored); + beast::get_lowest_layer(*ws_).close(ignored); + } + ioc_.stop(); + }); + if (thread_.joinable()) thread_.join(); + spdlog::info("[client_test] LocalWebSocketEchoServer stopped"); + } + + std::string url() const + { + return "ws://127.0.0.1:" + std::to_string(port_); + } + +private: + void do_accept() + { + acceptor_.async_accept([this](boost::system::error_code ec, tcp::socket socket) + { + if (ec) return; + + ws_ = std::make_shared>(std::move(socket)); + ws_->async_accept([this, ws = ws_](boost::system::error_code accept_ec) + { + if (accept_ec) return; + do_read(ws, 0); + }); + }); + } + + void do_read(std::shared_ptr> ws, int count) + { + if (count >= 5) + { + ws->async_close(websocket::close_code::normal, [](boost::system::error_code) {}); + return; + } + + auto buffer = std::make_shared(); + ws->async_read(*buffer, [this, ws, buffer, count](boost::system::error_code ec, std::size_t) + { + if (ec) return; + ws->text(ws->got_text()); + ws->async_write(buffer->data(), [this, ws, count](boost::system::error_code write_ec, std::size_t) + { + if (write_ec) return; + do_read(ws, count + 1); + }); + }); + } + + net::io_context ioc_; + tcp::acceptor acceptor_; + unsigned short port_; + std::thread thread_; + std::shared_ptr> ws_; +}; + +LocalHttpEchoServer& local_http_echo_server() +{ + static LocalHttpEchoServer server; + return server; +} +} // ========================================== // 1. 定义 PostmanEchoClient 类 @@ -22,7 +275,7 @@ class PostmanEchoClient : public HttpClient // 构造函数:注入 ioc,并设置默认 Base URL PostmanEchoClient() { - set_base_url("https://postman-echo.com"); + set_base_url(local_http_echo_server().base_url()); // 设置一个较长的超时时间,防止 CI 环境网络慢 set_timeout(std::chrono::seconds(10)); } @@ -275,6 +528,81 @@ TEST(EasyModeTest, AsyncRequest) future.wait(); } +TEST(HttpClientLocalTest, SyncRequestWithUnrunExternalIoContextTimesOut) +{ + boost::asio::io_context ioc; + HttpClient client(ioc); + client.set_base_url("http://127.0.0.1:9"); + client.set_timeout(std::chrono::seconds(0)); + + auto start = std::chrono::steady_clock::now(); + EXPECT_THROW( + client.request_sync(http::verb::get, "/", {}, "", {}), + boost::system::system_error); + auto elapsed = std::chrono::steady_clock::now() - start; + + EXPECT_LT(elapsed, std::chrono::seconds(2)); +} + +TEST(HttpClientLocalTest, SyncRequestTimeoutClosesStalledConnection) +{ + boost::asio::io_context server_ioc; + tcp::acceptor acceptor(server_ioc, {boost::asio::ip::make_address("127.0.0.1"), 0}); + const auto endpoint = acceptor.local_endpoint(); + std::atomic accepted{false}; + std::atomic client_closed{false}; + auto server_socket = std::make_shared(server_ioc); + auto read_buffer = std::make_shared>(); + auto read_until_close = std::make_shared>(); + *read_until_close = [server_socket, read_buffer, read_until_close, &client_closed]() + { + server_socket->async_read_some(boost::asio::buffer(*read_buffer), + [read_until_close, &client_closed](boost::system::error_code read_ec, + std::size_t) + { + if (read_ec) + { + client_closed = true; + return; + } + (*read_until_close)(); + }); + }; + + acceptor.async_accept(*server_socket, [&](boost::system::error_code ec) + { + ASSERT_FALSE(ec) << ec.message(); + accepted = true; + (*read_until_close)(); + }); + + std::thread server_thread([&] { server_ioc.run(); }); + + boost::asio::io_context client_ioc; + auto work = boost::asio::make_work_guard(client_ioc); + std::thread client_thread([&] { client_ioc.run(); }); + + HttpClient client(client_ioc); + client.set_base_url("http://127.0.0.1:" + std::to_string(endpoint.port())); + client.set_timeout(std::chrono::seconds(1)); + + EXPECT_THROW(client.request_sync(http::verb::get, "/", {}, "", {}), boost::system::system_error); + + for (int i = 0; i < 100 && (!accepted || !client_closed); ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + work.reset(); + client_ioc.stop(); + server_ioc.stop(); + if (client_thread.joinable()) client_thread.join(); + if (server_thread.joinable()) server_thread.join(); + + EXPECT_TRUE(accepted); + EXPECT_TRUE(client_closed); +} + // ========================================== // WebSocket 测试 @@ -299,7 +627,8 @@ class WebsocketTest : public ::testing::Test TEST_F(WebsocketTest, WssEchoAndWriteQueue) { - std::string url = "wss://echo.websocket.org"; + LocalWebSocketEchoServer server; + std::string url = server.url(); const int message_count = 5; int received_count = 0; @@ -317,7 +646,7 @@ TEST_F(WebsocketTest, WssEchoAndWriteQueue) if (msg.find("Request served by") != std::string::npos) return; received_count++; - // std::cout << "Msg: " << msg << std::endl; + // spdlog::debug("Msg: {}", msg); if (received_count >= message_count) { @@ -337,7 +666,7 @@ TEST_F(WebsocketTest, WssEchoAndWriteQueue) // 忽略操作取消(通常是 close() 导致的 pending read 取消) if (ec == boost::asio::error::operation_aborted) return; - std::cerr << "WS Error: " << ec.message() << std::endl; + spdlog::error("WS Error: {}", ec.message()); has_error = true; timer.cancel(); // 发生错误也停止测试 }); @@ -393,9 +722,66 @@ TEST_F(WebsocketTest, ConnectFailure) EXPECT_TRUE(failed); } +TEST_F(WebsocketTest, CloseBeforeHandshakeSuppressesConnectCallback) +{ + boost::asio::io_context server_ioc; + tcp::acceptor acceptor(server_ioc, {boost::asio::ip::make_address("127.0.0.1"), 0}); + const auto endpoint = acceptor.local_endpoint(); + auto server_socket = std::make_shared(server_ioc); + + acceptor.async_accept(*server_socket, [](boost::system::error_code) {}); + std::thread server_thread([&] { server_ioc.run(); }); + + std::atomic connect_calls{0}; + ws_client->connect("ws://127.0.0.1:" + std::to_string(endpoint.port()), [&](boost::beast::error_code) + { + ++connect_calls; + }); + ws_client->close(); + + boost::asio::steady_timer timer(ioc, std::chrono::milliseconds(300)); + timer.async_wait([&](boost::system::error_code) { ioc.stop(); }); + ioc.run(); + + server_ioc.stop(); + if (server_thread.joinable()) server_thread.join(); + + EXPECT_EQ(connect_calls.load(), 0); +} + +TEST(WebsocketClientLifecycleTest, DestructorSuppressesPendingConnectCallback) +{ + boost::asio::io_context server_ioc; + tcp::acceptor acceptor(server_ioc, {boost::asio::ip::make_address("127.0.0.1"), 0}); + const auto endpoint = acceptor.local_endpoint(); + auto server_socket = std::make_shared(server_ioc); + + acceptor.async_accept(*server_socket, [](boost::system::error_code) {}); + std::thread server_thread([&] { server_ioc.run(); }); + + boost::asio::io_context client_ioc; + std::atomic connect_calls{0}; + { + auto client = std::make_shared(client_ioc); + client->connect("ws://127.0.0.1:" + std::to_string(endpoint.port()), [&](boost::beast::error_code) + { + ++connect_calls; + }); + } + + boost::asio::steady_timer timer(client_ioc, std::chrono::milliseconds(300)); + timer.async_wait([&](boost::system::error_code) { client_ioc.stop(); }); + client_ioc.run(); + + server_ioc.stop(); + if (server_thread.joinable()) server_thread.join(); + + EXPECT_EQ(connect_calls.load(), 0); +} + TEST_F(ClientTest, ThreadPoolVerify) { - std::cout << "Pool Size: " << khttpd::framework::IoContextPool::instance().get_thread_count() << std::endl; + spdlog::debug("Pool Size: {}", khttpd::framework::IoContextPool::instance().get_thread_count()); std::promise p1, p2; auto f1 = p1.get_future(); @@ -404,13 +790,13 @@ TEST_F(ClientTest, ThreadPoolVerify) // 发起两个请求 client->echo_get("A", 1, [&](auto, auto) { - std::cout << "Req 1 processed on thread: " << std::this_thread::get_id() << std::endl; + spdlog::debug("Req 1 processed on thread hash: {}", std::hash{}(std::this_thread::get_id())); p1.set_value(); }); client->echo_get("B", 2, [&](auto, auto) { - std::cout << "Req 2 processed on thread: " << std::this_thread::get_id() << std::endl; + spdlog::debug("Req 2 processed on thread hash: {}", std::hash{}(std::this_thread::get_id())); p2.set_value(); }); @@ -423,7 +809,7 @@ TEST_F(ClientTest, ThreadPoolVerify) // ========================================== // Define API client using KHTTPD_API_CLIENT (single host, endpoints use API_CALL) -KHTTPD_API_CLIENT(EchoClient, "https://postman-echo.com") +KHTTPD_API_CLIENT(EchoClient, "http://127.0.0.1:1") API_CALL(http::verb::get, "/get", get_echo, QUERY(std::string, msg, "msg")) API_CALL(http::verb::post, "/post", post_echo, @@ -432,8 +818,8 @@ KHTTPD_API_CLIENT_END() // Define API client using KHTTPD_API_CLIENT_POOL (multi-host with weights) KHTTPD_API_CLIENT_POOL(MultiHostClient, - KHTTPD_HOST("https://postman-echo.com", 3) - KHTTPD_HOST("https://postman-echo.com", 1) + KHTTPD_HOST("http://127.0.0.1:1", 3) + KHTTPD_HOST("http://127.0.0.1:1", 1) ) API_CALL(http::verb::get, "/get", get_echo, QUERY(std::string, msg, "msg")) @@ -458,6 +844,7 @@ TEST(ApiMacrosTest, VerbFromString) TEST_F(ClientTest, OatppStyleSingleHost) { auto echo = std::make_shared(); + echo->set_base_url(local_http_echo_server().base_url()); echo->set_timeout(std::chrono::seconds(10)); std::promise done; @@ -480,6 +867,7 @@ TEST_F(ClientTest, OatppStyleSingleHost) TEST_F(ClientTest, OatppStyleSync) { auto echo = std::make_shared(); + echo->set_base_url(local_http_echo_server().base_url()); echo->set_timeout(std::chrono::seconds(10)); try { @@ -516,10 +904,42 @@ TEST(ApiMacrosTest, HostPoolWeighted) } } +TEST(ApiMacrosTest, HostPoolPickIsThreadSafe) +{ + std::vector hosts = { + {"http://host-a.com", 3}, + {"http://host-b.com", 1}, + }; + HostPool pool(hosts); + std::atomic picks{0}; + std::vector threads; + + for (int t = 0; t < 8; ++t) + { + threads.emplace_back([&]() + { + for (int i = 0; i < 1000; ++i) + { + const auto& picked = pool.pick(); + ASSERT_TRUE(picked == "http://host-a.com" || picked == "http://host-b.com"); + picks++; + } + }); + } + + for (auto& thread : threads) + { + thread.join(); + } + + ASSERT_EQ(picks.load(), 8000); +} + // Test multi-host API client TEST_F(ClientTest, MultiHostClientPool) { auto mc = std::make_shared(); + mc->set_base_url(local_http_echo_server().base_url()); mc->set_timeout(std::chrono::seconds(10)); std::promise done; diff --git a/framework/tests/cronjob_test.cpp b/framework/tests/cronjob_test.cpp index 120db10..b72196c 100644 --- a/framework/tests/cronjob_test.cpp +++ b/framework/tests/cronjob_test.cpp @@ -223,7 +223,12 @@ class CronSchedulerTest : public ::testing::Test void SetUp() override { - // 每个测试开始前重置计数器等(如果需要) + CronScheduler::instance().stop_all(); + } + + void TearDown() override + { + CronScheduler::instance().stop_all(); } }; @@ -329,3 +334,33 @@ TEST_F(CronSchedulerTest, InvalidExpression) CronScheduler::instance().schedule("invalid cron", [](){}); }, std::exception); } + +TEST_F(CronSchedulerTest, UnscheduleStopsAndRemovesJob) +{ + auto counter = std::make_shared(); + auto job = CronScheduler::instance().schedule("* * * * * *", [counter]() { counter->tick(); }); + + EXPECT_EQ(CronScheduler::instance().job_count(), 1u); + + CronScheduler::instance().unschedule(job); + + EXPECT_FALSE(job->is_running()); + EXPECT_EQ(CronScheduler::instance().job_count(), 0u); + EXPECT_TRUE(counter->ensure_no_execution_for(1200ms)); +} + +TEST_F(CronSchedulerTest, PruneStoppedRemovesManuallyStoppedJobs) +{ + auto job1 = CronScheduler::instance().schedule("* * * * * *", []() {}); + auto job2 = CronScheduler::instance().schedule("* * * * * *", []() {}); + + EXPECT_EQ(CronScheduler::instance().job_count(), 2u); + + job1->stop(); + CronScheduler::instance().prune_stopped(); + + EXPECT_EQ(CronScheduler::instance().job_count(), 1u); + + CronScheduler::instance().unschedule(job2); + EXPECT_EQ(CronScheduler::instance().job_count(), 0u); +} diff --git a/framework/tests/di_container_test.cpp b/framework/tests/di_container_test.cpp index 585a588..64f0846 100644 --- a/framework/tests/di_container_test.cpp +++ b/framework/tests/di_container_test.cpp @@ -23,13 +23,13 @@ class DependencyA : public ComponentBase { constructed = true; s_DependencyA_count++; - // std::cout << "DependencyA constructed. Count: " << s_DependencyA_count << std::endl; + // spdlog::debug("DependencyA constructed. Count: {}", s_DependencyA_count); } ~DependencyA() override { s_DependencyA_count--; - // std::cout << "DependencyA destructed. Count: " << s_DependencyA_count << std::endl; + // spdlog::debug("DependencyA destructed. Count: {}", s_DependencyA_count); } }; @@ -43,13 +43,13 @@ class DependencyB : public ComponentBase { constructed = true; s_DependencyB_count++; - // std::cout << "DependencyB constructed. Count: " << s_DependencyB_count << std::endl; + // spdlog::debug("DependencyB constructed. Count: {}", s_DependencyB_count); } ~DependencyB() override { s_DependencyB_count--; - // std::cout << "DependencyB destructed. Count: " << s_DependencyB_count << std::endl; + // spdlog::debug("DependencyB destructed. Count: {}", s_DependencyB_count); } [[nodiscard]] std::shared_ptr getDepA() const @@ -68,13 +68,13 @@ class MainComponent : public ComponentBase { constructed = true; s_MainComponent_count++; - // std::cout << "MainComponent constructed. Count: " << s_MainComponent_count << std::endl; + // spdlog::debug("MainComponent constructed. Count: {}", s_MainComponent_count); } ~MainComponent() override { s_MainComponent_count--; - // std::cout << "MainComponent destructed. Count: " << s_MainComponent_count << std::endl; + // spdlog::debug("MainComponent destructed. Count: {}", s_MainComponent_count); } [[nodiscard]] std::shared_ptr getDepB() const @@ -204,7 +204,7 @@ TEST_F(DIContainerTest, ComponentIsSingleton) TEST_F(DIContainerTest, OverwriteRegistrationWarning) { container.register_component(); - // std::cerr 在GTest中通常会被捕获或重定向,这里我们只是确保它不崩溃 + // GTest usually captures or redirects warning logs; this only verifies it does not crash. container.register_component(); // 验证仍能解析 diff --git a/framework/tests/http_context_multipart_edge_test.cpp b/framework/tests/http_context_multipart_edge_test.cpp index 9dadeca..68b4be5 100644 --- a/framework/tests/http_context_multipart_edge_test.cpp +++ b/framework/tests/http_context_multipart_edge_test.cpp @@ -99,3 +99,43 @@ TEST_F(MultipartEdgeTest, MissingBoundary) ASSERT_FALSE(ctx.get_multipart_field("any_field").has_value()); } + +TEST_F(MultipartEdgeTest, QuotedBoundary) +{ + std::string boundary = "----QuotedBoundary"; + std::string multipart_body = + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"field\"\r\n\r\n" + "quoted boundary works\r\n" + "--" + boundary + "--\r\n"; + + http::request req = make_request(http::verb::post, "/form", 11, multipart_body); + req.set(http::field::content_type, "multipart/form-data; boundary=\"" + boundary + "\""); + http::response res; + khttpd_fw::HttpContext ctx(req, res); + + ASSERT_TRUE(ctx.get_multipart_field("field").has_value()); + ASSERT_EQ(ctx.get_multipart_field("field").value(), "quoted boundary works"); +} + +TEST_F(MultipartEdgeTest, BoundaryTextInsideFileDataIsPreserved) +{ + std::string boundary = "----BoundaryInData"; + std::string file_data = "line one --" + boundary + " is not a delimiter"; + std::string multipart_body = + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"file\"; filename=\"data.txt\"\r\n" + "Content-Type: text/plain\r\n\r\n" + + file_data + "\r\n" + "--" + boundary + "--\r\n"; + + http::request req = make_request(http::verb::post, "/upload", 11, multipart_body); + req.set(http::field::content_type, "multipart/form-data; boundary=" + boundary); + http::response res; + khttpd_fw::HttpContext ctx(req, res); + + const auto* files = ctx.get_uploaded_files("file"); + ASSERT_NE(files, nullptr); + ASSERT_EQ(files->size(), 1); + ASSERT_EQ(files->at(0).data, file_data); +} diff --git a/framework/tests/router_test.cpp b/framework/tests/router_test.cpp index f2957ae..0ed7d58 100644 --- a/framework/tests/router_test.cpp +++ b/framework/tests/router_test.cpp @@ -238,6 +238,28 @@ TEST(HttpRouterTest, MethodNotAllowed) ASSERT_FALSE(allow_header.find("PUT") != std::string::npos); } +TEST(HttpRouterTest, HeadFallsBackToGetHandler) +{ + khttpd_fw::HttpRouter router; + bool called = false; + + router.get("/resource", [&](khttpd_fw::HttpContext& ctx) + { + called = true; + ctx.set_status(http::status::ok); + ctx.set_body("body generated by get"); + }); + + http::request req = make_request(http::verb::head, "/resource"); + http::response res; + khttpd_fw::HttpContext ctx = create_http_context(req, res); + + router.dispatch(ctx); + + ASSERT_TRUE(called); + ASSERT_EQ(ctx.get_response().result(), http::status::ok); +} + // --- Exception Handling Tests --- class TestException : public std::runtime_error diff --git a/framework/tests/server_stability_test.cpp b/framework/tests/server_stability_test.cpp new file mode 100644 index 0000000..4ee9d80 --- /dev/null +++ b/framework/tests/server_stability_test.cpp @@ -0,0 +1,135 @@ +#include "framework/server.hpp" +#include "framework/context/http_context.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; +namespace http = beast::http; +namespace net = boost::asio; +using tcp = boost::asio::ip::tcp; +namespace fs = boost::filesystem; +namespace khttpd_fw = khttpd::framework; + +namespace +{ + constexpr int kThreadCount = 8; + constexpr int kRequestsPerThread = 50; + + struct TempWebRoot + { + fs::path path; + + TempWebRoot() + : path(fs::temp_directory_path() / fs::unique_path("khttpd-server-stability-%%%%-%%%%-%%%%")) + { + fs::create_directories(path); + std::ofstream((path / "index.html").string()) << "ok"; + } + + ~TempWebRoot() + { + boost::system::error_code ignored; + fs::remove_all(path, ignored); + } + }; + + bool request_once(unsigned short port, int request_id) + { + try + { + net::io_context ioc; + beast::tcp_stream stream(ioc); + stream.expires_after(std::chrono::seconds(5)); + stream.connect(tcp::endpoint(net::ip::address_v4::loopback(), port)); + + http::request req{http::verb::get, "/ping?id=" + std::to_string(request_id), 11}; + req.set(http::field::host, "127.0.0.1"); + req.set(http::field::user_agent, "khttpd-stability-test"); + req.keep_alive(false); + + http::write(stream, req); + + beast::flat_buffer buffer; + http::response res; + http::read(stream, buffer, res); + + beast::error_code ignored; + stream.socket().shutdown(tcp::socket::shutdown_both, ignored); + + return res.result() == http::status::ok && res.body() == "pong"; + } + catch (...) + { + return false; + } + } +} + +TEST(ServerStabilityTest, HandlesManyConcurrentRequests) +{ + TempWebRoot web_root; + auto server = std::make_shared( + tcp::endpoint(tcp::v4(), 0), + web_root.path.string(), + 4); + + std::atomic handled{0}; + server->get_http_router().get("/ping", [&handled](khttpd_fw::HttpContext& ctx) + { + handled.fetch_add(1, std::memory_order_relaxed); + ctx.set_status(http::status::ok); + ctx.set_body("pong"); + ctx.set_content_type("text/plain"); + }); + + const auto port = server->local_endpoint().port(); + std::thread server_thread([server]() + { + server->run(); + }); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + std::atomic successes{0}; + std::vector clients; + clients.reserve(kThreadCount); + + for (int t = 0; t < kThreadCount; ++t) + { + clients.emplace_back([port, t, &successes]() + { + for (int i = 0; i < kRequestsPerThread; ++i) + { + if (request_once(port, t * kRequestsPerThread + i)) + { + successes.fetch_add(1, std::memory_order_relaxed); + } + } + }); + } + + for (auto& client : clients) + { + client.join(); + } + + server->stop(); + if (server_thread.joinable()) + { + server_thread.join(); + } + + constexpr int total_requests = kThreadCount * kRequestsPerThread; + EXPECT_EQ(successes.load(), total_requests); + EXPECT_EQ(handled.load(), total_requests); +} diff --git a/framework/tests/session_test.cpp b/framework/tests/session_test.cpp new file mode 100644 index 0000000..c855fd6 --- /dev/null +++ b/framework/tests/session_test.cpp @@ -0,0 +1,220 @@ +#include "framework/session/http_session.hpp" +#include "framework/router/http_router.hpp" +#include "framework/router/websocket_router.hpp" +#include "framework/context/http_context.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; +namespace http = beast::http; +namespace websocket = beast::websocket; +namespace net = boost::asio; +using tcp = boost::asio::ip::tcp; +namespace fs = boost::filesystem; +namespace khttpd_fw = khttpd::framework; + +namespace +{ + struct TempStaticTree + { + fs::path base; + fs::path web; + fs::path web_evil; + + TempStaticTree() + { + base = fs::temp_directory_path() / fs::unique_path("khttpd-session-test-%%%%-%%%%-%%%%"); + web = base / "web"; + web_evil = base / "web_evil"; + fs::create_directories(web); + fs::create_directories(web_evil); + } + + ~TempStaticTree() + { + boost::system::error_code ec; + fs::remove_all(base, ec); + } + }; + + template + http::response round_trip(khttpd_fw::HttpRouter& router, + khttpd_fw::WebsocketRouter& websocket_router, + const fs::path& web_root, + http::request req, + bool skip_body = false) + { + net::io_context ioc; + tcp::acceptor acceptor(ioc, {net::ip::make_address("127.0.0.1"), 0}); + const auto endpoint = acceptor.local_endpoint(); + const auto canonical_web_root = fs::canonical(web_root); + + acceptor.async_accept([&](beast::error_code ec, tcp::socket socket) + { + ASSERT_FALSE(ec) << ec.message(); + std::make_shared( + std::move(socket), router, websocket_router, web_root.string(), canonical_web_root)->run(); + }); + + std::thread server_thread([&] + { + ioc.run(); + }); + + net::io_context client_ioc; + tcp::socket client(client_ioc); + client.connect(endpoint); + http::write(client, req); + + beast::flat_buffer buffer; + http::response_parser parser; + parser.skip(skip_body); + http::read(client, buffer, parser); + auto res = parser.release(); + + beast::error_code ignored; + client.shutdown(tcp::socket::shutdown_both, ignored); + client.close(ignored); + ioc.stop(); + server_thread.join(); + + return res; + } +} + +TEST(HttpSessionTest, StaticFileRejectsSiblingPrefixTraversal) +{ + TempStaticTree tree; + std::ofstream((tree.web / "index.txt").string()) << "public"; + std::ofstream((tree.web_evil / "secret.txt").string()) << "secret"; + + khttpd_fw::HttpRouter router; + khttpd_fw::WebsocketRouter websocket_router; + http::request req{http::verb::get, "/../web_evil/secret.txt", 11}; + req.keep_alive(false); + + auto res = round_trip(router, websocket_router, tree.web, std::move(req)); + + EXPECT_EQ(res.result(), http::status::forbidden); + EXPECT_EQ(res.body().find("secret"), std::string::npos); +} + +TEST(HttpSessionTest, StaticHeadReturnsHeadersWithoutBody) +{ + TempStaticTree tree; + std::ofstream((tree.web / "index.txt").string()) << "hello"; + + khttpd_fw::HttpRouter router; + khttpd_fw::WebsocketRouter websocket_router; + http::request req{http::verb::head, "/index.txt", 11}; + req.keep_alive(false); + + auto res = round_trip(router, websocket_router, tree.web, std::move(req), true); + + EXPECT_EQ(res.result(), http::status::ok); + ASSERT_TRUE(res.has_content_length()); + EXPECT_EQ(res[http::field::content_length], "5"); +} + +TEST(HttpSessionTest, ChunkedResponseCompletesWithSingleIoThread) +{ + TempStaticTree tree; + + khttpd_fw::HttpRouter router; + khttpd_fw::WebsocketRouter websocket_router; + router.get("/stream", [](khttpd_fw::HttpContext& ctx) + { + ctx.set_content_type("text/plain"); + ctx.chunked([](khttpd_fw::HttpContext&, const khttpd_fw::HttpContext::WriteHandler& write) + { + ASSERT_TRUE(write("one")); + ASSERT_TRUE(write("two")); + }); + }); + + http::request req{http::verb::get, "/stream", 11}; + req.keep_alive(false); + + auto start = std::chrono::steady_clock::now(); + auto res = round_trip(router, websocket_router, tree.web, std::move(req)); + auto elapsed = std::chrono::steady_clock::now() - start; + + EXPECT_LT(elapsed, std::chrono::seconds(2)); + EXPECT_EQ(res.result(), http::status::ok); + EXPECT_TRUE(res.chunked()); + EXPECT_EQ(res.body(), "onetwo"); +} + +TEST(HttpSessionTest, WebSocketDrainsQueuedMessages) +{ + TempStaticTree tree; + net::io_context server_ioc; + khttpd_fw::HttpRouter router; + khttpd_fw::WebsocketRouter websocket_router; + std::atomic closed{false}; + + websocket_router.add_handler( + "/ws", + nullptr, + [](khttpd_fw::WebsocketContext& ctx) + { + ctx.send("first"); + ctx.send("second"); + }, + [&closed](khttpd_fw::WebsocketContext&) + { + closed = true; + }, + nullptr); + + tcp::acceptor acceptor(server_ioc, {net::ip::make_address("127.0.0.1"), 0}); + const auto endpoint = acceptor.local_endpoint(); + const auto canonical_web_root = fs::canonical(tree.web); + + acceptor.async_accept([&](beast::error_code ec, tcp::socket socket) + { + ASSERT_FALSE(ec) << ec.message(); + std::make_shared( + std::move(socket), router, websocket_router, tree.web.string(), canonical_web_root)->run(); + }); + + std::thread server_thread([&] + { + server_ioc.run(); + }); + + net::io_context client_ioc; + websocket::stream client(client_ioc); + client.next_layer().connect(endpoint); + client.handshake("127.0.0.1", "/ws"); + client.write(net::buffer(std::string("go"))); + + beast::flat_buffer first_buffer; + client.read(first_buffer); + const auto first = beast::buffers_to_string(first_buffer.data()); + + beast::flat_buffer second_buffer; + client.read(second_buffer); + const auto second = beast::buffers_to_string(second_buffer.data()); + + beast::error_code ignored; + client.close(websocket::close_code::normal, ignored); + for (int i = 0; i < 100 && !closed; ++i) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + server_ioc.stop(); + server_thread.join(); + + EXPECT_TRUE(closed); + EXPECT_EQ(first, "first"); + EXPECT_EQ(second, "second"); +} diff --git a/framework/websocket/websocket_session.cpp b/framework/websocket/websocket_session.cpp index 8f99204..77f1bb5 100644 --- a/framework/websocket/websocket_session.cpp +++ b/framework/websocket/websocket_session.cpp @@ -1,7 +1,7 @@ // framework/websocket/websocket_session.cpp #include "websocket_session.hpp" #include "context/websocket_context.hpp" -#include +#include #include namespace khttpd::framework @@ -33,11 +33,11 @@ namespace khttpd::framework { if (ec) { - fmt::print(stderr, "WebSocket handshake error for path '{}': {}\n", initial_path_, ec.message()); + spdlog::error("WebSocket handshake error for path '{}': {}", initial_path_, ec.message()); do_close(ec); return; } - fmt::print("WebSocket handshake successful for path: {}\n", initial_path_); + spdlog::debug("WebSocket handshake successful for path: {}", initial_path_); WebsocketContext open_ctx(shared_from_this(), initial_path_); { @@ -61,13 +61,13 @@ namespace khttpd::framework if (ec == ws::error::closed) { - fmt::print("WebSocket connection for path '{}' closed by client.\n", initial_path_); + spdlog::debug("WebSocket connection for path '{}' closed by client.", initial_path_); do_close(ec); return; } if (ec) { - fmt::print(stderr, "WebSocket read error for path '{}': {}\n", initial_path_, ec.message()); + spdlog::error("WebSocket read error for path '{}': {}", initial_path_, ec.message()); do_close(ec); return; } @@ -75,7 +75,7 @@ namespace khttpd::framework std::string received_message = beast::buffers_to_string(buffer_.data()); bool is_text = ws_.got_text(); - fmt::print("Received WS message on path '{}': {}\n", initial_path_, received_message); + spdlog::debug("Received WS message on path '{}': {}", initial_path_, received_message); buffer_.consume(buffer_.size()); @@ -87,20 +87,48 @@ namespace khttpd::framework void WebsocketSession::send_message(const std::string& msg, bool is_text_msg) { + auto self = shared_from_this(); auto ss = std::make_shared(msg); - write_queue_.emplace(ss, is_text_msg); - if (!writing_) + net::post(ws_.get_executor(), [self, ss, is_text_msg]() { - writing_ = true; - do_write_next(); - } + if (self->closed_) + { + return; + } + self->write_queue_.emplace(ss, is_text_msg); + if (!self->writing_) + { + self->writing_ = true; + self->do_write_next(); + } + }); } void WebsocketSession::do_write_next() { + if (closed_) + { + while (!write_queue_.empty()) + { + write_queue_.pop(); + } + writing_ = false; + if (close_pending_) + { + close_pending_ = false; + close_stream(); + } + return; + } + if (write_queue_.empty()) { writing_ = false; + if (close_pending_) + { + close_pending_ = false; + close_stream(); + } return; } @@ -176,14 +204,48 @@ namespace khttpd::framework if (ec) { - fmt::print(stderr, "WebSocket write error for path '{}': {}\n", initial_path_, ec.message()); + spdlog::error("WebSocket write error for path '{}': {}", initial_path_, ec.message()); + writing_ = false; do_close(ec); return; } + do_write_next(); + } + + void WebsocketSession::close_stream() + { + if (!ws_.is_open()) + { + return; + } + ws_.async_close(ws::close_code::normal, + [self = shared_from_this()](beast::error_code close_ec) + { + if (close_ec && close_ec != boost::asio::error::operation_aborted) + { + spdlog::error("WebSocket close error for path '{}': {}", + self->initial_path_, close_ec.message()); + } + }); } void WebsocketSession::do_close(beast::error_code ec) { + if (closed_) + { + return; + } + closed_ = true; + while (!write_queue_.empty()) + { + write_queue_.pop(); + } + + { + std::unique_lock lock{m_sessions_mutex}; + m_sessions_id_.erase(id); + } + if (ec && ec != ws::error::closed && ec != boost::asio::error::eof) { WebsocketContext error_ctx(shared_from_this(), initial_path_, ec); @@ -192,18 +254,14 @@ namespace khttpd::framework else { WebsocketContext close_ctx(shared_from_this(), initial_path_, ec); - { - std::unique_lock lock{m_sessions_mutex}; - m_sessions_id_.erase(id); - } websocket_router_.dispatch_close(initial_path_, close_ctx); } - // Close the WebSocket stream to properly release the TCP connection - beast::error_code close_ec; - ws_.close(ws::close_code::normal, close_ec); - if (close_ec && close_ec != boost::asio::error::operation_aborted) + + if (writing_) { - fmt::print(stderr, "WebSocket close error for path '{}': {}\n", initial_path_, close_ec.message()); + close_pending_ = true; + return; } + close_stream(); } } diff --git a/framework/websocket/websocket_session.hpp b/framework/websocket/websocket_session.hpp index 73d93da..91f979c 100644 --- a/framework/websocket/websocket_session.hpp +++ b/framework/websocket/websocket_session.hpp @@ -54,12 +54,15 @@ namespace khttpd::framework // Write queue to serialize concurrent async_write calls std::queue, bool>> write_queue_; bool writing_ = false; + bool closed_ = false; + bool close_pending_ = false; void on_handshake(beast::error_code ec); void do_read(); void on_read(beast::error_code ec, std::size_t bytes_transferred); void do_write_next(); void on_write(beast::error_code ec, std::size_t bytes_transferred); + void close_stream(); void do_close(beast::error_code ec = {}); };