mirror of
https://github.com/yhirose/cpp-httplib
synced 2026-06-08 18:30:49 +00:00
Compare commits
645 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d3a3d805c | |||
| a058e31a3a | |||
| c87d442aac | |||
| de296af3eb | |||
| 41899acece | |||
| bb22553087 | |||
| 8bffbe3ff2 | |||
| 0e4f104141 | |||
| 74807d24a7 | |||
| 2e124cde02 | |||
| fdb589d97e | |||
| 843ad379c7 | |||
| cda9680cdc | |||
| e906c31a79 | |||
| 2d669c3636 | |||
| b2d76658fc | |||
| 7444646627 | |||
| 82a61a6b60 | |||
| 63ede29db1 | |||
| ae64a5ee90 | |||
| f441fc33fb | |||
| a9fc935919 | |||
| b6cd71d4ff | |||
| bda599bfb4 | |||
| 797758a742 | |||
| 85b18a9c64 | |||
| 0853ce7753 | |||
| c99d7472b5 | |||
| defd907c74 | |||
| c6c75e4c69 | |||
| c2002f6e06 | |||
| 7c33fd47bf | |||
| 21243b3c9e | |||
| e068da4f6b | |||
| f6aec98145 | |||
| f29bb15f9d | |||
| c53d93d145 | |||
| b4d16a582d | |||
| e61a8bcec7 | |||
| 33dbe00cce | |||
| f1f8ff53d5 | |||
| 9b9bda6b6e | |||
| 2280f1d191 | |||
| 43cf1822c6 | |||
| 17f301877f | |||
| 4f17fbaa03 | |||
| 0d5bf55c73 | |||
| 718d7d92b9 | |||
| c41c5fb8a9 | |||
| ab96f72b96 | |||
| b2430249d2 | |||
| 464867a9ce | |||
| d4180e923f | |||
| 8d225afc8c | |||
| ed5c5d325b | |||
| c1ee85d89e | |||
| 14e37bd75b | |||
| c0adbb4b20 | |||
| f80864ca03 | |||
| 4e75a84b39 | |||
| 77d945d3b6 | |||
| f69737a838 | |||
| a188913b02 | |||
| 02e4c53685 | |||
| 8c4370247a | |||
| 1f1a799d13 | |||
| a875292153 | |||
| f0b7d4161d | |||
| 2867b74f13 | |||
| 4e14bc8948 | |||
| 5d717e6d91 | |||
| 8b4146324f | |||
| 94b5038eb3 | |||
| 9248ce3bfe | |||
| 4639b696ab | |||
| 5ead179b8e | |||
| 1942e0ef01 | |||
| 6be32a540d | |||
| dc6faf5c17 | |||
| 71bb17fb0e | |||
| 094bf112bb | |||
| fbec2a3466 | |||
| c3fa06112b | |||
| f73e694f0c | |||
| 191bfb2ea4 | |||
| ad5839f0d1 | |||
| 02dfb97fd6 | |||
| a38a076571 | |||
| 0e1b52b23e | |||
| c0469eba96 | |||
| 7dec57d1eb | |||
| b85aa76bd2 | |||
| cea018f2cd | |||
| 1111219f17 | |||
| a7e1d14b15 | |||
| 6eff49e1fb | |||
| bd95e67c23 | |||
| 2e2e47bab1 | |||
| 59905c7f0d | |||
| 8d03ef1615 | |||
| 23a1d79a66 | |||
| 781c55f120 | |||
| 40f7985e02 | |||
| f85f30a637 | |||
| 6da7f0c61c | |||
| 2ba0e7a797 | |||
| ded82448aa | |||
| 98048a033a | |||
| 7ae794a6bf | |||
| 385adefb11 | |||
| b7c2f04318 | |||
| d23cf77cd0 | |||
| 5304464a53 | |||
| db98efee5a | |||
| cdf0d33258 | |||
| 25688258ad | |||
| f0990ca96d | |||
| 0461cb770c | |||
| 51b704b902 | |||
| 7eb03e81fc | |||
| 6a6d4161d1 | |||
| 63b07ada43 | |||
| 2de4c59bc2 | |||
| b7097f1386 | |||
| 681d388247 | |||
| ae94d64f67 | |||
| 3401877d3d | |||
| bce08e62f9 | |||
| f4ecb96e54 | |||
| c23764269d | |||
| f441cd2a44 | |||
| c3613c6977 | |||
| 87c2b4e584 | |||
| c795ad1c32 | |||
| 3e0fa33559 | |||
| 27b73f050e | |||
| dbd5ca4bf2 | |||
| 143019a38c | |||
| 1d36013fc3 | |||
| 8bba34eebc | |||
| 0a9102ff6b | |||
| c1fa5e1710 | |||
| 84796738fc | |||
| adf58bf474 | |||
| 337fbb0793 | |||
| 9e7861b0b4 | |||
| 27ee115a60 | |||
| 59882752aa | |||
| 61e9f7ce8f | |||
| 1acf18876f | |||
| 4b2b851dbb | |||
| 551f96d4a2 | |||
| eacc1ca98e | |||
| ac9ebb0ee3 | |||
| 11eed05ce7 | |||
| f3bba0646a | |||
| 2da189f88c | |||
| 6e0f211cff | |||
| 318a3fe425 | |||
| 2d8d524178 | |||
| afa88dbe70 | |||
| 08133b593b | |||
| 8aedbf4547 | |||
| cde29362ef | |||
| bae40fcdf2 | |||
| db561f5552 | |||
| 35c52c1ab9 | |||
| 23ff9a5605 | |||
| 41be1e24e3 | |||
| 6e52d0a057 | |||
| f72b4582e6 | |||
| 89c932f313 | |||
| eb5a65e0df | |||
| 7a3b92bbd9 | |||
| eb11032797 | |||
| 54e75dc8ef | |||
| b20b5fdd1f | |||
| f4cc542d4b | |||
| 4285d33992 | |||
| 92b4f53012 | |||
| b8e21eac89 | |||
| 3fae5f1473 | |||
| fe7fe15d2e | |||
| fbd6ce7a3f | |||
| dffce89514 | |||
| 3f44c80fd3 | |||
| a2bb6f6c1e | |||
| 7012e765e1 | |||
| b1c1fa2dc6 | |||
| fbee136dca | |||
| 70cca55caf | |||
| cdaed14925 | |||
| b52d7d8411 | |||
| acf28a362d | |||
| 0b3758ec36 | |||
| a5d4c143e5 | |||
| c0c36f021d | |||
| 8e8a23e3d2 | |||
| 890a2dd85d | |||
| ca5fe354fb | |||
| dd98d2a24d | |||
| 1f110b54d8 | |||
| 7b6867bcdf | |||
| 8b28577ec6 | |||
| 55b38907dc | |||
| 53ea9e8bb4 | |||
| ecfd84c171 | |||
| b5b2a1d1c8 | |||
| 17ba303889 | |||
| 802743264c | |||
| 9dbaed75ef | |||
| c551e97297 | |||
| 4ff7a1c858 | |||
| 082acacd45 | |||
| 52163ed982 | |||
| af7a69bcf6 | |||
| 145fc8b021 | |||
| af73377611 | |||
| a3f5569196 | |||
| a636a094bf | |||
| cb85e573de | |||
| 120405beac | |||
| ceff2c1154 | |||
| 0c08c378d7 | |||
| 9a0571513e | |||
| cfb56c0b78 | |||
| 083fe43ad3 | |||
| 9e36247665 | |||
| fd03483237 | |||
| d5409ab541 | |||
| 0b875e0747 | |||
| 292f9a6c55 | |||
| 7c303bb871 | |||
| ea850cbfa7 | |||
| e6ff3d7ac2 | |||
| e1ab5a604b | |||
| 696c02f2bc | |||
| a183dba9fc | |||
| d37373a983 | |||
| b2bf172393 | |||
| 1729aa8c1f | |||
| aabd0634ae | |||
| de5a255ac6 | |||
| b6c55c6030 | |||
| 28dcf379e8 | |||
| 91e79e9a63 | |||
| 27879c4874 | |||
| 08a0452fb2 | |||
| 3a1f379e75 | |||
| fd8da4d8e4 | |||
| 365cbe37fa | |||
| 4a7aae5469 | |||
| fd324e1412 | |||
| 366eda72dc | |||
| c216dc94d2 | |||
| 61893a00a4 | |||
| 3af7f2c161 | |||
| a0de42ebc4 | |||
| 7b752106ac | |||
| 9589519d58 | |||
| caf7c55785 | |||
| 9e4aed482e | |||
| 65d6316d65 | |||
| 3e3a8cc02f | |||
| b7e33b08f1 | |||
| 0dbe8ba144 | |||
| dbc4af819a | |||
| 7dbf5471ce | |||
| 72b35befb2 | |||
| 65ce51aed7 | |||
| 787a34ad7f | |||
| 7a212cfe40 | |||
| 0be0526085 | |||
| 87a5ae64a4 | |||
| 33acccb346 | |||
| c765584e6b | |||
| 0bda3a7d1a | |||
| 2eaa2ea64f | |||
| 94a4028821 | |||
| a8d6172250 | |||
| 2f39723d08 | |||
| a9ba0a4dff | |||
| 37399af996 | |||
| 48084d55f2 | |||
| 5a1ecc3958 | |||
| 85b5cdd78d | |||
| f2928d7152 | |||
| ee0bee3907 | |||
| 71ba7e7b1b | |||
| ebe7efa1cc | |||
| 22d90c29b4 | |||
| b944f942ee | |||
| 550f728165 | |||
| a4b2c61a65 | |||
| 5c0135fa5d | |||
| 2b5d1eea8d | |||
| d274c0abe5 | |||
| dda2e007a0 | |||
| 321a86d9f2 | |||
| ada97046a2 | |||
| 6e73a63153 | |||
| cdc223019a | |||
| 574f5ce93e | |||
| 2996cecee0 | |||
| 32bf5c9c09 | |||
| 735e5930eb | |||
| 748f47b377 | |||
| 4cb8ff9f90 | |||
| 985cd9f6a2 | |||
| 233f0fb1b8 | |||
| 03cf43ebaa | |||
| 3c4b96024f | |||
| d74e4a7c9c | |||
| bfa2f735f2 | |||
| b6ab8435d7 | |||
| 39a64fb4e7 | |||
| d7c14b6f3a | |||
| 1880693aef | |||
| dd20342825 | |||
| a268d65c4f | |||
| b397c768e4 | |||
| 8e22a7676a | |||
| 8a7c536ad5 | |||
| 8aad481c69 | |||
| 5814e121df | |||
| 7adbccbaf7 | |||
| eb10c22db1 | |||
| 708f860e3a | |||
| eb30f15363 | |||
| 4941d5b56b | |||
| 9bbb4741b4 | |||
| 282f2feb77 | |||
| 60a1f00618 | |||
| 9104054ca5 | |||
| d69f144a99 | |||
| 929dfbd348 | |||
| 3047183fd9 | |||
| ef5e4044f1 | |||
| 3779800322 | |||
| 986a20fb7d | |||
| 8311e1105f | |||
| ba6845925d | |||
| 343a0fc073 | |||
| 54f8a4d0f3 | |||
| 9c36aae4b7 | |||
| b766025a83 | |||
| 9b5f76f833 | |||
| d647f484a4 | |||
| 8794792baa | |||
| b85768c1f3 | |||
| e6d71bd702 | |||
| 258992a160 | |||
| a7bc00e330 | |||
| 11a40584e9 | |||
| 3e86bdb4d8 | |||
| c817d65695 | |||
| 51dee793fe | |||
| 457fc4306e | |||
| 4f5b003e76 | |||
| 5421e27106 | |||
| fe07660f40 | |||
| da2f9e476e | |||
| 1a7a7ed1c3 | |||
| 413994912d | |||
| 01dcf1d0ad | |||
| 8e378779c2 | |||
| 970b52897c | |||
| 412ba04d19 | |||
| bfef4b3e9b | |||
| 7bd316f3d0 | |||
| 26208363ee | |||
| b1b4bb8850 | |||
| 9dd565b6e3 | |||
| 924f214303 | |||
| 5c1a34e766 | |||
| fa90d06dd5 | |||
| d869054318 | |||
| 0cc1ca9a8d | |||
| 3701195033 | |||
| f884a56258 | |||
| d79633ff52 | |||
| e0ebc431dc | |||
| 131bc6c674 | |||
| 10d68cff50 | |||
| 996acc5253 | |||
| 7c4799d0cf | |||
| c239087332 | |||
| 7018e9263d | |||
| 4990b4b4b7 | |||
| 5064373c23 | |||
| 6c93aea59a | |||
| 6553cdedab | |||
| a61b2427b0 | |||
| af4ece3d5f | |||
| e64379c3d7 | |||
| 5053912534 | |||
| 932b1cbc32 | |||
| de36ea7755 | |||
| 9f8db2c230 | |||
| 3f00e1b321 | |||
| 7ab9c119ef | |||
| 3f2922b3fa | |||
| 509f583dca | |||
| 2d01e71286 | |||
| e612154694 | |||
| 82fcbe3901 | |||
| dbd2465b56 | |||
| ea79494b29 | |||
| f35aff84c2 | |||
| 7b18ae6f16 | |||
| a79c56d06b | |||
| 3d6e315a4c | |||
| 4c27f9c6ef | |||
| d173a37d17 | |||
| 7fd346a2ca | |||
| c673d502b9 | |||
| c43c51362a | |||
| 3e86d93d13 | |||
| f6e4e2d0f3 | |||
| 01a52aa8bd | |||
| 8415bf0823 | |||
| 327ff263f5 | |||
| 61c418048d | |||
| 9720ef8c34 | |||
| 978a4f6345 | |||
| 80fb03628b | |||
| 2480c0342c | |||
| eb6f610a45 | |||
| cb74e4191b | |||
| dfa641ca41 | |||
| 969a9f99d5 | |||
| c099b42ba3 | |||
| b8315278cb | |||
| 485f8f2411 | |||
| 953e4f3841 | |||
| adf65cfe61 | |||
| 12c829f6d3 | |||
| 913314f1b1 | |||
| ef63f97afe | |||
| bda74db01d | |||
| 9ff3ff9446 | |||
| c75d071615 | |||
| b4989130da | |||
| 4fc0303bda | |||
| 3d9cc51851 | |||
| f69587656f | |||
| d5fc340c30 | |||
| d79a547dc9 | |||
| bd1da4346a | |||
| 4c2a608a0c | |||
| ee4eb8deaa | |||
| 7196ac8a07 | |||
| c88b09bc6b | |||
| 87fab847b8 | |||
| 4e6055f084 | |||
| 975cf0dae5 | |||
| 4854a694cd | |||
| b1f8e986bf | |||
| c5ee208775 | |||
| ddfdacfa49 | |||
| 2514ebc20f | |||
| 4f9c6540b2 | |||
| 21c9a6a1ff | |||
| 7f6d413ddd | |||
| 88277139e7 | |||
| 6cdd3493a1 | |||
| 9c91b6f4a6 | |||
| cee838e335 | |||
| d82c82db2c | |||
| ba638ff38e | |||
| da0c6579fa | |||
| 52a18c78a5 | |||
| 048edec9ed | |||
| af56b7ec0b | |||
| 6c3e8482f7 | |||
| 390f2c41f6 | |||
| aa04feebb4 | |||
| 45f3694f82 | |||
| c5c54b31e2 | |||
| 69c84c9597 | |||
| ae63b89cbf | |||
| ff038f98b7 | |||
| e00fd06355 | |||
| 521529d24d | |||
| ed0719f2bc | |||
| 6a848b1a16 | |||
| c8bcaf8a91 | |||
| 8cd0ed0509 | |||
| 177d8420a1 | |||
| 388a8c007c | |||
| bdefdce1ae | |||
| 9e4f93d87e | |||
| 0b657d28cf | |||
| c1a09daf15 | |||
| 8438df4a95 | |||
| 67fd7e3d09 | |||
| d44031615d | |||
| 98cc1ec344 | |||
| 25b1e0d906 | |||
| 05f9f83240 | |||
| fb739dbaec | |||
| 50fce538c6 | |||
| 3b6597bba9 | |||
| f10720ed69 | |||
| 2bc550b2f0 | |||
| ce36b8a6e5 | |||
| 560854a961 | |||
| 2064462c35 | |||
| 07288888ad | |||
| 34d392cf3d | |||
| 825c3fbbb1 | |||
| 00bdf73ec6 | |||
| f44ab9b3da | |||
| a61f2b89be | |||
| b8bafbc291 | |||
| 548dfff0ae | |||
| 4dd2f3d03d | |||
| 6791a8364d | |||
| d1a1c8a158 | |||
| b4d26badf2 | |||
| c5a0673c93 | |||
| ad40bd6a00 | |||
| 5c00bbf36b | |||
| 9d6f5372a3 | |||
| f06fd934f6 | |||
| 80c0cc445e | |||
| 762024b890 | |||
| 82a90a2325 | |||
| b7cac4f4b8 | |||
| e323374d2a | |||
| ffc294d37e | |||
| fceada9ef4 | |||
| 5f0f73fad9 | |||
| 530d6ee098 | |||
| 420c9759c6 | |||
| 2ce7c22218 | |||
| 4ef9ed80cd | |||
| 44b3fe6277 | |||
| 449801990f | |||
| af2928d316 | |||
| d948e38820 | |||
| 65218ce222 | |||
| 55e99c4030 | |||
| b63d50671d | |||
| eba980846b | |||
| 374d058de7 | |||
| 31cdcc3c3a | |||
| ad9f6423e2 | |||
| cbca63f091 | |||
| b4748a226c | |||
| 5b943d9bb8 | |||
| c86f69a105 | |||
| d39fda0657 | |||
| 37f8dc4382 | |||
| 3a8adda381 | |||
| 8aa38aecaf | |||
| f1dec77f46 | |||
| cddaedaff8 | |||
| cefb5a8822 | |||
| e426a38c3e | |||
| f14accb7b6 | |||
| c5c704cb3b | |||
| 115a786581 | |||
| 5ef4cfd263 | |||
| 03fecb2f78 | |||
| 7fc8682a0a | |||
| f1431311a4 | |||
| 1d14e051a5 | |||
| 97ae6733ed | |||
| 1d6b22b5f0 | |||
| 1a49076b5b | |||
| d0e4cb3f07 | |||
| e2813d9d4d | |||
| 20a7f088ce | |||
| f63ba7d013 | |||
| 0a629d7391 | |||
| a609330e4c | |||
| c029597a5a | |||
| 30b7732565 | |||
| 6650632e7f | |||
| afe627e7af | |||
| 67f6ff7fa9 | |||
| c7ed1796a7 | |||
| 44c62d838e | |||
| 6bb580cda8 | |||
| 00a8cb8e5d | |||
| 2e34a39673 | |||
| 01b90829bc | |||
| e699bd0730 | |||
| 961a9379d5 | |||
| ec87b04aff | |||
| aabf752a51 | |||
| afb0674ccb | |||
| ee625232a4 | |||
| 52d8dd41f1 | |||
| be07d2d7a9 | |||
| 0f1b62c2b3 | |||
| c30906a541 | |||
| 3533503323 | |||
| 82acdca638 | |||
| a1e56a567b | |||
| 0c17d172a2 | |||
| 5d8e7c761f | |||
| 17fc522b75 | |||
| f1daa5b88b | |||
| fe9a1949a6 | |||
| 50cba6db9f | |||
| 18592e7f98 | |||
| bd9612b81e | |||
| 7ab5fb65b2 | |||
| 8df5fedc35 | |||
| 4a61f68fa4 | |||
| 067890133c | |||
| d3076f5a70 | |||
| ed129f057f | |||
| eab5ea01d7 | |||
| 3e287b3a26 | |||
| 4f33637b43 | |||
| 698a1e51ec | |||
| 27c0e1186c | |||
| c54c71a3e5 | |||
| 3409c00e6f | |||
| f8ef5fab64 | |||
| 5b397d455d | |||
| f977558a28 | |||
| c2e156e0e0 | |||
| 7aba2938d3 | |||
| d587548250 | |||
| 21f9c51556 | |||
| 985ceba525 | |||
| e62a4b02e5 | |||
| ff34749572 | |||
| e5804d4a50 | |||
| 3956a2b790 | |||
| b33aa52dc2 | |||
| 76230db97f | |||
| a66a013ed7 | |||
| f4b02dfdc1 | |||
| 4cf218643e | |||
| 8f96b69a25 | |||
| d262033ded | |||
| 5745eabe69 | |||
| 5f18642271 | |||
| 88a9278872 | |||
| 9bb3ca8169 |
@@ -0,0 +1,2 @@
|
||||
/test/www*/dir/*.html text eol=lf
|
||||
/test/www*/dir/*.txt text eol=lf
|
||||
@@ -0,0 +1,70 @@
|
||||
# SPDX-FileCopyrightText: 2025 Andrea Pappacoda <andrea@pappacoda.it>
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
name: abidiff
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: sh
|
||||
|
||||
jobs:
|
||||
abi:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
container:
|
||||
image: debian:testing
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: apt -y --update install --no-install-recommends
|
||||
abigail-tools
|
||||
ca-certificates
|
||||
g++
|
||||
git
|
||||
libbrotli-dev
|
||||
libssl-dev
|
||||
libzstd-dev
|
||||
meson
|
||||
pkg-config
|
||||
python3
|
||||
zlib1g-dev
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: current
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
path: previous
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout previous
|
||||
working-directory: previous
|
||||
run: |
|
||||
git switch master
|
||||
git describe --tags --abbrev=0 master | xargs git checkout
|
||||
|
||||
- name: Build current
|
||||
working-directory: current
|
||||
run: |
|
||||
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
|
||||
ninja -C build
|
||||
|
||||
- name: Build previous
|
||||
working-directory: previous
|
||||
run: |
|
||||
meson setup --buildtype=debug -Dcpp-httplib_compile=true build
|
||||
ninja -C build
|
||||
|
||||
- name: Run abidiff
|
||||
run: abidiff
|
||||
--headers-dir1 previous/build
|
||||
--headers-dir2 current/build
|
||||
previous/build/libcpp-httplib.so
|
||||
current/build/libcpp-httplib.so
|
||||
@@ -1,5 +1,11 @@
|
||||
name: CIFuzz
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -19,7 +25,7 @@ jobs:
|
||||
dry-run: false
|
||||
language: c++
|
||||
- name: Upload Crash
|
||||
uses: actions/upload-artifact@v1
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
name: docs
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
- 'docs-src/**'
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install docs-gen
|
||||
run: cargo install docs-gen
|
||||
- name: Build
|
||||
run: docs-gen build docs-src docs
|
||||
- uses: actions/configure-pages@v5
|
||||
- uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs
|
||||
- id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Release Docker Image
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history and tags
|
||||
|
||||
- name: Extract tag (manual)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
id: set_tag_manual
|
||||
run: |
|
||||
# Checkout the latest tag and set output
|
||||
git fetch --tags
|
||||
LATEST_TAG=$(git describe --tags --abbrev=0)
|
||||
git checkout $LATEST_TAG
|
||||
echo "tag=${LATEST_TAG#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Extract tag (release)
|
||||
if: github.event_name == 'release'
|
||||
id: set_tag_release
|
||||
run: echo "tag=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64 # Build for both amd64 and arm64
|
||||
# Use extracted tag without leading 'v'
|
||||
tags: |
|
||||
yhirose4dockerhub/cpp-httplib-server:latest
|
||||
yhirose4dockerhub/cpp-httplib-server:${{ steps.set_tag_manual.outputs.tag || steps.set_tag_release.outputs.tag }}
|
||||
+246
-29
@@ -1,41 +1,258 @@
|
||||
name: test
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gtest_filter:
|
||||
description: 'Google Test filter'
|
||||
test_linux:
|
||||
description: 'Test on Linux'
|
||||
type: boolean
|
||||
default: true
|
||||
test_macos:
|
||||
description: 'Test on MacOS'
|
||||
type: boolean
|
||||
default: true
|
||||
test_windows:
|
||||
description: 'Test on Windows'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GTEST_FILTER: ${{ github.event.inputs.gtest_filter || '*' }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
style-check:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: run style check
|
||||
run: |
|
||||
clang-format --version
|
||||
cd test && make style_check
|
||||
|
||||
build-error-check-on-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macOS-latest, ubuntu-latest, windows-latest]
|
||||
|
||||
config:
|
||||
- arch_flags: -m32
|
||||
arch_suffix: :i386
|
||||
name: (32-bit)
|
||||
steps:
|
||||
- name: prepare git for checkout on windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install libraries
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libc6-dev${{ matrix.config.arch_suffix }} libstdc++-13-dev${{ matrix.config.arch_suffix }} \
|
||||
libssl-dev${{ matrix.config.arch_suffix }} libcurl4-openssl-dev${{ matrix.config.arch_suffix }} \
|
||||
zlib1g-dev${{ matrix.config.arch_suffix }} libbrotli-dev${{ matrix.config.arch_suffix }} \
|
||||
libzstd-dev${{ matrix.config.arch_suffix }}
|
||||
- name: build and run tests (expect failure)
|
||||
run: cd test && make test EXTRA_CXXFLAGS="${{ matrix.config.arch_flags }}"
|
||||
continue-on-error: true
|
||||
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
tls_backend: [openssl, mbedtls, wolfssl]
|
||||
name: ubuntu (${{ matrix.tls_backend }})
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install common libraries
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
||||
- name: install OpenSSL
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- name: install Mbed TLS
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: sudo apt-get install -y libmbedtls-dev
|
||||
- name: install wolfSSL
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: sudo apt-get install -y libwolfssl-dev
|
||||
- name: build and run tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_split && make test_openssl_parallel
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
- name: build and run tests (Mbed TLS)
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||
- name: build and run tests (wolfSSL)
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
||||
- name: run fuzz test target
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make fuzz_test
|
||||
- name: build and run WebSocket heartbeat test
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
|
||||
- name: build and run ThreadPool test
|
||||
run: cd test && make test_thread_pool && ./test_thread_pool
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_macos == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
tls_backend: [openssl, mbedtls, wolfssl]
|
||||
name: macos (${{ matrix.tls_backend }})
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install Mbed TLS
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: brew install mbedtls@3
|
||||
- name: install wolfSSL
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: brew install wolfssl
|
||||
- name: build and run tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_split && make test_openssl_parallel
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
- name: build and run tests (Mbed TLS)
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: cd test && make test_split_mbedtls && make test_mbedtls_parallel
|
||||
- name: build and run tests (wolfSSL)
|
||||
if: matrix.tls_backend == 'wolfssl'
|
||||
run: cd test && make test_split_wolfssl && make test_wolfssl_parallel
|
||||
- name: run fuzz test target
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make fuzz_test
|
||||
- name: build and run WebSocket heartbeat test
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat
|
||||
- name: build and run ThreadPool test
|
||||
run: cd test && make test_thread_pool && ./test_thread_pool
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_windows == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
config:
|
||||
- with_ssl: false
|
||||
compiled: false
|
||||
run_tests: true
|
||||
name: without SSL
|
||||
- with_ssl: true
|
||||
compiled: false
|
||||
run_tests: true
|
||||
name: with SSL
|
||||
- with_ssl: false
|
||||
compiled: true
|
||||
run_tests: false
|
||||
name: compiled
|
||||
name: windows ${{ matrix.config.name }}
|
||||
steps:
|
||||
- name: Prepare Git for Checkout on Windows
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: install brotli library on ubuntu
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: sudo apt update && sudo apt-get install -y libbrotli-dev
|
||||
- name: install brotli library on macOS
|
||||
if: matrix.os == 'macOS-latest'
|
||||
run: brew install brotli
|
||||
- name: make
|
||||
if: matrix.os != 'windows-latest'
|
||||
run: cd test && make -j2
|
||||
- name: check fuzz test target
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: cd test && make fuzz_test
|
||||
- name: setup msbuild on windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
- name: make-windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
- name: Setup msbuild on windows
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
- name: Cache vcpkg packages
|
||||
id: vcpkg-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:/vcpkg/installed
|
||||
key: vcpkg-installed-windows-gtest-curl-zlib-brotli-zstd
|
||||
- name: Install vcpkg dependencies
|
||||
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
|
||||
run: vcpkg install gtest curl zlib brotli zstd
|
||||
- name: Install OpenSSL
|
||||
if: ${{ matrix.config.with_ssl }}
|
||||
run: choco install openssl
|
||||
- name: Configure CMake ${{ matrix.config.name }}
|
||||
run: >
|
||||
cmake -B build -S .
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
|
||||
-DHTTPLIB_TEST=ON
|
||||
-DHTTPLIB_COMPILE=${{ matrix.config.compiled && 'ON' || 'OFF' }}
|
||||
-DHTTPLIB_USE_OPENSSL_IF_AVAILABLE=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
|
||||
-DHTTPLIB_REQUIRE_ZLIB=ON
|
||||
-DHTTPLIB_REQUIRE_BROTLI=ON
|
||||
-DHTTPLIB_REQUIRE_ZSTD=ON
|
||||
-DHTTPLIB_REQUIRE_OPENSSL=${{ matrix.config.with_ssl && 'ON' || 'OFF' }}
|
||||
- name: Build ${{ matrix.config.name }}
|
||||
run: cmake --build build --config Release -- /v:m /clp:ShowCommandLine
|
||||
- name: Run tests ${{ matrix.config.name }}
|
||||
if: ${{ matrix.config.run_tests }}
|
||||
shell: pwsh
|
||||
working-directory: build/test
|
||||
run: |
|
||||
cd test
|
||||
msbuild.exe test.sln /verbosity:minimal /t:Build "/p:Configuration=Release;Platform=x64"
|
||||
x64\Release\test.exe
|
||||
$shards = 4
|
||||
$procs = @()
|
||||
for ($i = 0; $i -lt $shards; $i++) {
|
||||
$log = "shard_${i}.log"
|
||||
$procs += Start-Process -FilePath ./Release/httplib-test.exe `
|
||||
-ArgumentList "--gtest_color=yes","--gtest_filter=${{ github.event.inputs.gtest_filter || '*' }}-*BenchmarkTest*" `
|
||||
-NoNewWindow -PassThru -RedirectStandardOutput $log -RedirectStandardError "${log}.err" `
|
||||
-Environment @{ GTEST_TOTAL_SHARDS="$shards"; GTEST_SHARD_INDEX="$i" }
|
||||
}
|
||||
$procs | Wait-Process
|
||||
$failed = $false
|
||||
for ($i = 0; $i -lt $shards; $i++) {
|
||||
$log = "shard_${i}.log"
|
||||
if (Select-String -Path $log -Pattern "\[ PASSED \]" -Quiet) {
|
||||
$passed = (Select-String -Path $log -Pattern "\[ PASSED \]").Line
|
||||
Write-Host "Shard ${i}: $passed"
|
||||
} else {
|
||||
Write-Host "=== Shard $i FAILED ==="
|
||||
Get-Content $log
|
||||
if (Test-Path "${log}.err") { Get-Content "${log}.err" }
|
||||
$failed = $true
|
||||
}
|
||||
}
|
||||
if ($failed) { exit 1 }
|
||||
Write-Host "All shards passed."
|
||||
- name: Run benchmark tests with retry ${{ matrix.config.name }}
|
||||
if: ${{ matrix.config.run_tests }}
|
||||
run: ctest --output-on-failure --test-dir build -C Release -R "BenchmarkTest" --repeat until-pass:5
|
||||
|
||||
env:
|
||||
VCPKG_ROOT: "C:/vcpkg"
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: No Exceptions Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-no-exceptions:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential libssl-dev zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev
|
||||
|
||||
- name: Run tests with CPPHTTPLIB_NO_EXCEPTIONS
|
||||
run: |
|
||||
cd test && make test_split EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS" && make test_openssl_parallel EXTRA_CXXFLAGS="-fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS"
|
||||
@@ -0,0 +1,62 @@
|
||||
name: test_offline
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
test_linux:
|
||||
description: 'Test on Linux'
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GTEST_FILTER: "-*.*_Online"
|
||||
|
||||
jobs:
|
||||
ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
(github.event_name == 'push') ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name) ||
|
||||
(github.event_name == 'workflow_dispatch' && github.event.inputs.test_linux == 'true')
|
||||
strategy:
|
||||
matrix:
|
||||
tls_backend: [openssl, no-tls]
|
||||
name: ubuntu (${{ matrix.tls_backend }})
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: install common libraries
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libcurl4-openssl-dev zlib1g-dev libbrotli-dev libzstd-dev
|
||||
- name: install OpenSSL
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- name: disable network
|
||||
run: |
|
||||
sudo iptables -A OUTPUT -o lo -j ACCEPT
|
||||
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
sudo iptables -A OUTPUT -j REJECT
|
||||
sudo ip6tables -A OUTPUT -o lo -j ACCEPT
|
||||
sudo ip6tables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
sudo ip6tables -A OUTPUT -j REJECT
|
||||
- name: build and run tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make test_split && make test_openssl_parallel
|
||||
env:
|
||||
LSAN_OPTIONS: suppressions=lsan_suppressions.txt
|
||||
- name: build and run tests (No TLS)
|
||||
if: matrix.tls_backend == 'no-tls'
|
||||
run: cd test && make test_no_tls_parallel
|
||||
- name: restore network
|
||||
if: always()
|
||||
run: |
|
||||
sudo iptables -F OUTPUT
|
||||
sudo ip6tables -F OUTPUT
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Proxy Test
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test-proxy:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
strategy:
|
||||
matrix:
|
||||
tls_backend: [openssl, mbedtls]
|
||||
name: proxy (${{ matrix.tls_backend }})
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install common dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential zlib1g-dev libcurl4-openssl-dev libbrotli-dev libzstd-dev netcat-openbsd
|
||||
- name: Install OpenSSL
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: sudo apt-get install -y libssl-dev
|
||||
- name: Install Mbed TLS
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: sudo apt-get install -y libmbedtls-dev
|
||||
|
||||
- name: Run proxy tests (OpenSSL)
|
||||
if: matrix.tls_backend == 'openssl'
|
||||
run: cd test && make proxy
|
||||
- name: Run proxy tests (Mbed TLS)
|
||||
if: matrix.tls_backend == 'mbedtls'
|
||||
run: cd test && make proxy_mbedtls
|
||||
+42
-2
@@ -1,29 +1,67 @@
|
||||
tags
|
||||
|
||||
# Ignore executables (no extension) but not source files
|
||||
example/server
|
||||
!example/server.*
|
||||
example/client
|
||||
!example/client.*
|
||||
example/hello
|
||||
!example/hello.*
|
||||
example/simplecli
|
||||
!example/simplecli.*
|
||||
example/simplesvr
|
||||
!example/simplesvr.*
|
||||
example/benchmark
|
||||
!example/benchmark.*
|
||||
example/redirect
|
||||
example/sse*
|
||||
!example/redirect.*
|
||||
example/ssecli
|
||||
!example/ssecli.*
|
||||
example/ssecli-stream
|
||||
!example/ssecli-stream.*
|
||||
example/ssesvr
|
||||
!example/ssesvr.*
|
||||
example/upload
|
||||
!example/upload.*
|
||||
example/one_time_request
|
||||
!example/one_time_request.*
|
||||
example/server_and_client
|
||||
!example/server_and_client.*
|
||||
example/accept_header
|
||||
!example/accept_header.*
|
||||
example/wsecho
|
||||
!example/wsecho.*
|
||||
example/*.pem
|
||||
test/httplib.cc
|
||||
test/httplib.h
|
||||
test/test
|
||||
test/test_mbedtls
|
||||
test/test_wolfssl
|
||||
test/test_no_tls
|
||||
test/server_fuzzer
|
||||
test/test_proxy
|
||||
test/test_proxy_mbedtls
|
||||
test/test_proxy_wolfssl
|
||||
test/test_split
|
||||
test/test_split_mbedtls
|
||||
test/test_split_wolfssl
|
||||
test/test_split_no_tls
|
||||
test/test_websocket_heartbeat
|
||||
test/test_thread_pool
|
||||
test/test.xcodeproj/xcuser*
|
||||
test/test.xcodeproj/*/xcuser*
|
||||
test/*.o
|
||||
test/*.pem
|
||||
test/*.srl
|
||||
test/*.log
|
||||
test/_build_*
|
||||
work/
|
||||
benchmark/server*
|
||||
docs-gen/target/
|
||||
|
||||
*.swp
|
||||
|
||||
build/
|
||||
Debug
|
||||
Release
|
||||
*.vcxproj.user
|
||||
@@ -33,5 +71,7 @@ Release
|
||||
*.db
|
||||
ipch
|
||||
*.dSYM
|
||||
*.pyc
|
||||
.*
|
||||
!/.travis.yml
|
||||
!/.gitattributes
|
||||
!/.github
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v18.1.8 # 最新バージョンを使用
|
||||
hooks:
|
||||
- id: clang-format
|
||||
files: \.(cpp|cc|h)$
|
||||
args: [-i] # インプレースで修正
|
||||
+245
-64
@@ -1,20 +1,31 @@
|
||||
#[[
|
||||
Build options:
|
||||
* BUILD_SHARED_LIBS (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* Standard BUILD_SHARED_LIBS is supported and sets HTTPLIB_SHARED default value.
|
||||
* HTTPLIB_USE_OPENSSL_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_WOLFSSL_IF_AVAILABLE (default off)
|
||||
* HTTPLIB_USE_MBEDTLS_IF_AVAILABLE (default off)
|
||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
|
||||
* HTTPLIB_BUILD_MODULES (default off)
|
||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||
* HTTPLIB_REQUIRE_WOLFSSL (default off)
|
||||
* HTTPLIB_REQUIRE_MBEDTLS (default off)
|
||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||
* HTTPLIB_REQUIRE_ZSTD (default off)
|
||||
* HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES (default off)
|
||||
* HTTPLIB_USE_NON_BLOCKING_GETADDRINFO (default on)
|
||||
* HTTPLIB_COMPILE (default off)
|
||||
* HTTPLIB_INSTALL (default on)
|
||||
* HTTPLIB_SHARED (default off) builds as a shared library (if HTTPLIB_COMPILE is ON)
|
||||
* HTTPLIB_TEST (default off)
|
||||
* BROTLI_USE_STATIC_LIBS - tells Cmake to use the static Brotli libs (only works if you have them installed).
|
||||
* OPENSSL_USE_STATIC_LIBS - tells Cmake to use the static OpenSSL libs (only works if you have them installed).
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL ZLIB Brotli) is available.
|
||||
After installation with Cmake, a find_package(httplib COMPONENTS OpenSSL wolfssl MbedTLS ZLIB Brotli zstd) is available.
|
||||
This creates a httplib::httplib target (if found and if listed components are supported).
|
||||
It can be linked like so:
|
||||
|
||||
@@ -41,8 +52,13 @@
|
||||
These variables are available after you run find_package(httplib)
|
||||
* HTTPLIB_HEADER_PATH - this is the full path to the installed header (e.g. /usr/include/httplib.h).
|
||||
* HTTPLIB_IS_USING_OPENSSL - a bool for if OpenSSL support is enabled.
|
||||
* HTTPLIB_IS_USING_WOLFSSL - a bool for if wolfSSL support is enabled.
|
||||
* HTTPLIB_IS_USING_MBEDTLS - a bool for if MbedTLS support is enabled.
|
||||
* HTTPLIB_IS_USING_ZLIB - a bool for if ZLIB support is enabled.
|
||||
* HTTPLIB_IS_USING_BROTLI - a bool for if Brotli support is enabled.
|
||||
* HTTPLIB_IS_USING_ZSTD - a bool for if ZSTD support is enabled.
|
||||
* HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES - a bool for if support of loading system certs from the Apple Keychain is enabled.
|
||||
* HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO - a bool for if nonblocking getaddrinfo is enabled.
|
||||
* HTTPLIB_IS_COMPILED - a bool for if the library is compiled, or otherwise header-only.
|
||||
* HTTPLIB_INCLUDE_DIR - the root path to httplib's header (e.g. /usr/include).
|
||||
* HTTPLIB_LIBRARY - the full path to the library if compiled (e.g. /usr/lib/libhttplib.so).
|
||||
@@ -56,7 +72,6 @@
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
FindPython3 requires Cmake v3.12
|
||||
ARCH_INDEPENDENT option of write_basic_package_version_file() requires Cmake v3.14
|
||||
]]
|
||||
cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
@@ -66,75 +81,191 @@ cmake_minimum_required(VERSION 3.14.0 FATAL_ERROR)
|
||||
# This is so the maintainer doesn't actually need to update this manually.
|
||||
file(STRINGS httplib.h _raw_version_string REGEX "CPPHTTPLIB_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"")
|
||||
|
||||
# Needed since git tags have "v" prefixing them.
|
||||
# Also used if the fallback to user agent string is being used.
|
||||
# Extracts just the version string itself from the whole string contained in _raw_version_string
|
||||
# since _raw_version_string would contain the entire line of code where it found the version string
|
||||
string(REGEX MATCH "([0-9]+\\.?)+" _httplib_version "${_raw_version_string}")
|
||||
|
||||
project(httplib VERSION ${_httplib_version} LANGUAGES CXX)
|
||||
project(httplib
|
||||
VERSION ${_httplib_version}
|
||||
LANGUAGES CXX
|
||||
DESCRIPTION "A C++ header-only HTTP/HTTPS server and client library."
|
||||
HOMEPAGE_URL "https://github.com/yhirose/cpp-httplib"
|
||||
)
|
||||
|
||||
# Change as needed to set an OpenSSL minimum version.
|
||||
# This is used in the installed Cmake config file.
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "1.1.1")
|
||||
set(_HTTPLIB_OPENSSL_MIN_VER "3.0.0")
|
||||
|
||||
# Lets you disable C++ exception during CMake configure time.
|
||||
# The value is used in the install CMake config file.
|
||||
option(HTTPLIB_NO_EXCEPTIONS "Disable the use of C++ exceptions" OFF)
|
||||
# Allow for a build to require OpenSSL to pass, instead of just being optional
|
||||
option(HTTPLIB_REQUIRE_OPENSSL "Requires OpenSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_WOLFSSL "Requires wolfSSL to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_MBEDTLS "Requires MbedTLS to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_REQUIRE_ZLIB "Requires ZLIB to be found & linked, or fails build." OFF)
|
||||
# Allow for a build to casually enable OpenSSL/ZLIB support, but silently continue if not found.
|
||||
# Make these options so their automatic use can be specifically disabled (as needed)
|
||||
option(HTTPLIB_USE_OPENSSL_IF_AVAILABLE "Uses OpenSSL (if available) to enable HTTPS support." ON)
|
||||
option(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE "Uses wolfSSL (if available) to enable HTTPS support." OFF)
|
||||
option(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE "Uses MbedTLS (if available) to enable HTTPS support." OFF)
|
||||
option(HTTPLIB_USE_ZLIB_IF_AVAILABLE "Uses ZLIB (if available) to enable Zlib compression support." ON)
|
||||
# Lets you compile the program as a regular library instead of header-only
|
||||
option(HTTPLIB_COMPILE "If ON, uses a Python script to split the header into a compilable header & source file (requires Python v3)." OFF)
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(HTTPLIB_COMPILE)
|
||||
set(HTTPLIB_IS_COMPILED TRUE)
|
||||
endif()
|
||||
# Lets you disable the installation (useful when fetched from another CMake project)
|
||||
option(HTTPLIB_INSTALL "Enables the installation target" ON)
|
||||
option(HTTPLIB_TEST "Enables testing and builds tests" OFF)
|
||||
option(HTTPLIB_REQUIRE_BROTLI "Requires Brotli to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_BROTLI_IF_AVAILABLE "Uses Brotli (if available) to enable Brotli decompression support." ON)
|
||||
# Defaults to static library
|
||||
option(BUILD_SHARED_LIBS "Build the library as a shared library instead of static. Has no effect if using header-only." OFF)
|
||||
if (BUILD_SHARED_LIBS AND WIN32 AND HTTPLIB_COMPILE)
|
||||
# Necessary for Windows if building shared libs
|
||||
# See https://stackoverflow.com/a/40743080
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
option(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES "Disable loading system certs from the Apple Keychain on macOS." OFF)
|
||||
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
|
||||
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
|
||||
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
|
||||
# C++20 modules support requires CMake 3.28 or later
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
|
||||
option(HTTPLIB_BUILD_MODULES "Build httplib modules (requires HTTPLIB_COMPILE to be ON)." OFF)
|
||||
else()
|
||||
set(HTTPLIB_BUILD_MODULES OFF CACHE INTERNAL "Build httplib modules disabled (requires CMake 3.28+)" FORCE)
|
||||
if(DEFINED CACHE{HTTPLIB_BUILD_MODULES} AND HTTPLIB_BUILD_MODULES)
|
||||
message(WARNING "HTTPLIB_BUILD_MODULES requires CMake 3.28 or later. Current version is ${CMAKE_VERSION}. Modules support has been disabled.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Incompatibility between TLS libraries
|
||||
set(TLS_LIBRARIES_USED_TMP 0)
|
||||
|
||||
foreach(tls_library OPENSSL WOLFSSL MBEDTLS)
|
||||
set(TLS_REQUIRED ${HTTPLIB_REQUIRE_${tls_library}})
|
||||
set(TLS_IF_AVAILABLE ${HTTPLIB_USE_${tls_library}_IF_AVAILABLE})
|
||||
|
||||
if(TLS_REQUIRED OR TLS_IF_AVAILABLE)
|
||||
math(EXPR TLS_LIBRARIES_USED_TMP "${TLS_LIBRARIES_USED_TMP} + 1")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(TLS_LIBRARIES_USED_TMP GREATER 1)
|
||||
message(FATAL_ERROR "TLS libraries are mutually exclusive.")
|
||||
endif()
|
||||
|
||||
# Defaults to static library but respects standard BUILD_SHARED_LIBS if set
|
||||
include(CMakeDependentOption)
|
||||
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
|
||||
"${BUILD_SHARED_LIBS}" HTTPLIB_COMPILE OFF
|
||||
)
|
||||
if(HTTPLIB_SHARED)
|
||||
set(HTTPLIB_LIB_TYPE SHARED)
|
||||
if(WIN32)
|
||||
# Necessary for Windows if building shared libs
|
||||
# See https://stackoverflow.com/a/40743080
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
else()
|
||||
set(HTTPLIB_LIB_TYPE STATIC)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
|
||||
if(CMAKE_SYSTEM_VERSION)
|
||||
if(${CMAKE_SYSTEM_VERSION} VERSION_LESS "10.0.0")
|
||||
message(WARNING "Windows ${CMAKE_SYSTEM_VERSION} or lower is not supported. Please use Windows 10 or later.")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_SYSTEM_VERSION "10.0.19041.0")
|
||||
message(WARNING "The target is Windows but CMAKE_SYSTEM_VERSION is not set, the default system version is set to Windows 10.")
|
||||
endif()
|
||||
endif()
|
||||
if(CMAKE_SIZEOF_VOID_P LESS 8)
|
||||
message(WARNING "Pointer size ${CMAKE_SIZEOF_VOID_P} is not supported. Please use a 64-bit compiler.")
|
||||
endif()
|
||||
|
||||
# Set some variables that are used in-tree and while building based on our options
|
||||
set(HTTPLIB_IS_COMPILED ${HTTPLIB_COMPILE})
|
||||
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES TRUE)
|
||||
if(HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES)
|
||||
set(HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES FALSE)
|
||||
endif()
|
||||
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO ${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO})
|
||||
|
||||
# Threads needed for <thread> on some systems, and for <pthread.h> on Linux
|
||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
# Since Cmake v3.11, Crypto & SSL became optional when not specified as COMPONENTS.
|
||||
if(HTTPLIB_REQUIRE_OPENSSL)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL REQUIRED)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
elseif(HTTPLIB_USE_OPENSSL_IF_AVAILABLE)
|
||||
find_package(OpenSSL ${_HTTPLIB_OPENSSL_MIN_VER} COMPONENTS Crypto SSL QUIET)
|
||||
# Avoid a rare circumstance of not finding all components but the end-user did their
|
||||
# own call for OpenSSL, which might trick us into thinking we'd otherwise have what we wanted
|
||||
if (TARGET OpenSSL::SSL AND TARGET OpenSSL::Crypto)
|
||||
set(HTTPLIB_IS_USING_OPENSSL ${OPENSSL_FOUND})
|
||||
else()
|
||||
set(HTTPLIB_IS_USING_OPENSSL FALSE)
|
||||
endif()
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(OPENSSL_FOUND)
|
||||
set(HTTPLIB_IS_USING_OPENSSL TRUE)
|
||||
|
||||
if(HTTPLIB_REQUIRE_WOLFSSL)
|
||||
find_package(wolfssl REQUIRED)
|
||||
set(HTTPLIB_IS_USING_WOLFSSL TRUE)
|
||||
elseif(HTTPLIB_USE_WOLFSSL_IF_AVAILABLE)
|
||||
find_package(wolfssl QUIET)
|
||||
set(HTTPLIB_IS_USING_WOLFSSL ${wolfssl_FOUND})
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_MBEDTLS)
|
||||
find_package(MbedTLS REQUIRED)
|
||||
set(HTTPLIB_IS_USING_MBEDTLS TRUE)
|
||||
elseif(HTTPLIB_USE_MBEDTLS_IF_AVAILABLE)
|
||||
find_package(MbedTLS QUIET)
|
||||
set(HTTPLIB_IS_USING_MBEDTLS ${MbedTLS_FOUND})
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_REQUIRE_ZLIB)
|
||||
find_package(ZLIB REQUIRED)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
elseif(HTTPLIB_USE_ZLIB_IF_AVAILABLE)
|
||||
find_package(ZLIB QUIET)
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
# FindZLIB doesn't have a ZLIB_FOUND variable, so check the target.
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set(HTTPLIB_IS_USING_ZLIB TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Adds our cmake folder to the search path for find_package
|
||||
# This is so we can use our custom FindBrotli.cmake
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
if(HTTPLIB_REQUIRE_BROTLI)
|
||||
find_package(Brotli COMPONENTS encoder decoder common REQUIRED)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
elseif(HTTPLIB_USE_BROTLI_IF_AVAILABLE)
|
||||
find_package(Brotli COMPONENTS encoder decoder common QUIET)
|
||||
set(HTTPLIB_IS_USING_BROTLI ${Brotli_FOUND})
|
||||
endif()
|
||||
# Just setting this variable here for people building in-tree
|
||||
if(Brotli_FOUND)
|
||||
set(HTTPLIB_IS_USING_BROTLI TRUE)
|
||||
|
||||
# NOTE:
|
||||
# zstd < 1.5.6 does not provide the CMake imported target `zstd::libzstd`.
|
||||
# Older versions must be consumed via their pkg-config file.
|
||||
if(HTTPLIB_REQUIRE_ZSTD)
|
||||
find_package(zstd 1.5.6 CONFIG)
|
||||
if(NOT zstd_FOUND)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd)
|
||||
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
|
||||
endif()
|
||||
set(HTTPLIB_IS_USING_ZSTD TRUE)
|
||||
elseif(HTTPLIB_USE_ZSTD_IF_AVAILABLE)
|
||||
find_package(zstd 1.5.6 CONFIG QUIET)
|
||||
if(NOT zstd_FOUND)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(zstd QUIET IMPORTED_TARGET libzstd)
|
||||
|
||||
if(TARGET PkgConfig::zstd)
|
||||
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
# Both find_package and PkgConf set a XXX_FOUND var
|
||||
set(HTTPLIB_IS_USING_ZSTD ${zstd_FOUND})
|
||||
endif()
|
||||
|
||||
# Used for default, common dirs that the end-user can change (if needed)
|
||||
@@ -165,19 +296,42 @@ if(HTTPLIB_COMPILE)
|
||||
message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||
endif()
|
||||
|
||||
# If building modules, also generate the module file
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
# Put the generate_module script into the build dir
|
||||
configure_file(generate_module.py "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py"
|
||||
COPYONLY
|
||||
)
|
||||
# Generate the module file
|
||||
execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py"
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
ERROR_VARIABLE _httplib_module_error
|
||||
)
|
||||
if(_httplib_module_error)
|
||||
message(FATAL_ERROR "Failed when trying to generate cpp-httplib module with the Python script.\n${_httplib_module_error}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# split.py puts output in "out"
|
||||
set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out")
|
||||
# This will automatically be either static or shared based on the value of BUILD_SHARED_LIBS
|
||||
add_library(${PROJECT_NAME} "${_httplib_build_includedir}/httplib.cc")
|
||||
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
||||
)
|
||||
|
||||
# Add C++20 module support if requested
|
||||
# Include from separate file to prevent parse errors on older CMake versions
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
|
||||
include(cmake/modules.cmake)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT_NAME}
|
||||
PROPERTIES
|
||||
VERSION ${${PROJECT_NAME}_VERSION}
|
||||
SOVERSION "${${PROJECT_NAME}_VERSION_MAJOR}.${${PROJECT_NAME}_VERSION_MINOR}"
|
||||
OUTPUT_NAME cpp-httplib
|
||||
)
|
||||
else()
|
||||
# This is for header-only.
|
||||
@@ -189,39 +343,51 @@ endif()
|
||||
# Only useful if building in-tree, versus using it from an installation.
|
||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||
|
||||
# Require C++11
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
cxx_std_11
|
||||
)
|
||||
# Require C++11, or C++20 if modules are enabled
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_20)
|
||||
else()
|
||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
)
|
||||
|
||||
# Always require threads
|
||||
target_link_libraries(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
# Always require threads
|
||||
Threads::Threads
|
||||
# Needed for Windows libs on Mingw, as the pragma comment(lib, "xyz") aren't triggered.
|
||||
$<$<PLATFORM_ID:Windows>:ws2_32>
|
||||
$<$<PLATFORM_ID:Windows>:crypt32>
|
||||
$<$<PLATFORM_ID:Windows>:cryptui>
|
||||
# Needed for API from MacOS Security framework
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>>:-framework CoreFoundation -framework Security>"
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>,$<BOOL:${HTTPLIB_IS_USING_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:-framework CFNetwork -framework CoreFoundation -framework Security>"
|
||||
# Needed for non-blocking getaddrinfo on MacOS
|
||||
"$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>>:-framework CFNetwork -framework CoreFoundation>"
|
||||
# Can't put multiple targets in a single generator expression or it bugs out.
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::common>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::encoder>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:Brotli::decoder>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:ZLIB::ZLIB>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:zstd::libzstd>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::SSL>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:OpenSSL::Crypto>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_WOLFSSL}>:wolfssl::wolfssl>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_MBEDTLS}>:MbedTLS::mbedtls>
|
||||
)
|
||||
|
||||
# Set the definitions to enable optional features
|
||||
target_compile_definitions(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC}
|
||||
$<$<BOOL:${HTTPLIB_NO_EXCEPTIONS}>:CPPHTTPLIB_NO_EXCEPTIONS>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_BROTLI}>:CPPHTTPLIB_BROTLI_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZLIB}>:CPPHTTPLIB_ZLIB_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_ZSTD}>:CPPHTTPLIB_ZSTD_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_OPENSSL}>:CPPHTTPLIB_OPENSSL_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_WOLFSSL}>:CPPHTTPLIB_WOLFSSL_SUPPORT>
|
||||
$<$<BOOL:${HTTPLIB_IS_USING_MBEDTLS}>:CPPHTTPLIB_MBEDTLS_SUPPORT>
|
||||
$<$<AND:$<PLATFORM_ID:Darwin>,$<BOOL:${HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES}>>:CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES>
|
||||
$<$<BOOL:${HTTPLIB_USE_NON_BLOCKING_GETADDRINFO}>:CPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO>
|
||||
)
|
||||
|
||||
# CMake configuration files installation directory
|
||||
@@ -230,7 +396,7 @@ set(_TARGET_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
# Configures the meta-file httplibConfig.cmake.in to replace variables with paths/values/etc.
|
||||
configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
|
||||
configure_package_config_file("cmake/${PROJECT_NAME}Config.cmake.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
INSTALL_DESTINATION "${_TARGET_INSTALL_CMAKEDIR}"
|
||||
# Passes the includedir install path
|
||||
@@ -240,13 +406,13 @@ configure_package_config_file("${PROJECT_NAME}Config.cmake.in"
|
||||
if(HTTPLIB_COMPILE)
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5 and <= 1.0 is accepted
|
||||
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||
COMPATIBILITY SameMinorVersion
|
||||
)
|
||||
else()
|
||||
write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Example: if you find_package(httplib 0.5.4)
|
||||
# then anything >= 0.5 and <= 1.0 is accepted
|
||||
# then anything >= 0.5.4 and < 0.6 is accepted
|
||||
COMPATIBILITY SameMinorVersion
|
||||
# Tells Cmake that it's a header-only lib
|
||||
# Mildly useful for end-users :)
|
||||
@@ -254,33 +420,48 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
EXPORT httplibTargets
|
||||
)
|
||||
if(HTTPLIB_INSTALL)
|
||||
# Creates the export httplibTargets.cmake
|
||||
# This is strictly what holds compilation requirements
|
||||
# and linkage information (doesn't find deps though).
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/httplib/modules CXX_MODULES_BMI DESTINATION ${CMAKE_INSTALL_LIBDIR}/httplib/modules)
|
||||
else()
|
||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
|
||||
endif()
|
||||
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||
|
||||
install(FILES
|
||||
install(FILES
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
||||
# Install it so it can be used later by the httplibConfig.cmake file.
|
||||
# Put it in the same dir as our config file instead of a global path so we don't potentially stomp on other packages.
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindBrotli.cmake"
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
# NOTE: This path changes depending on if it's on Windows or Linux
|
||||
install(EXPORT httplibTargets
|
||||
# Puts the targets into the httplib namespace
|
||||
# So this makes httplib::httplib linkable after doing find_package(httplib)
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
# NOTE: This path changes depending on if it's on Windows or Linux
|
||||
install(EXPORT httplibTargets
|
||||
# Puts the targets into the httplib namespace
|
||||
# So this makes httplib::httplib linkable after doing find_package(httplib)
|
||||
NAMESPACE ${PROJECT_NAME}::
|
||||
DESTINATION ${_TARGET_INSTALL_CMAKEDIR}
|
||||
)
|
||||
|
||||
# Install documentation & license
|
||||
# ex: /usr/share/doc/httplib/README.md and /usr/share/licenses/httplib/LICENSE
|
||||
install(FILES "README.md" DESTINATION "${CMAKE_INSTALL_DOCDIR}")
|
||||
install(FILES "LICENSE" DESTINATION "${CMAKE_INSTALL_DATADIR}/licenses/${PROJECT_NAME}")
|
||||
|
||||
include(CPack)
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_BUILD_MODULES AND NOT HTTPLIB_COMPILE)
|
||||
message(FATAL_ERROR "HTTPLIB_BUILD_MODULES requires HTTPLIB_COMPILE to be ON.")
|
||||
endif()
|
||||
|
||||
if(HTTPLIB_TEST)
|
||||
include(CTest)
|
||||
add_subdirectory(test)
|
||||
include(CTest)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
FROM yhirose4dockerhub/ubuntu-builder AS builder
|
||||
WORKDIR /build
|
||||
COPY httplib.h .
|
||||
COPY docker/main.cc .
|
||||
RUN g++ -std=c++23 -static -o server -O2 -I. main.cc && strip server
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /build/server /server
|
||||
COPY docker/html/index.html /html/index.html
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/server"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "80", "--mount", "/:./html"]
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
# SSEClient - Server-Sent Events Client
|
||||
|
||||
A simple, EventSource-like SSE client for C++11.
|
||||
|
||||
## Features
|
||||
|
||||
- **Auto-reconnect**: Automatically reconnects on connection loss
|
||||
- **Last-Event-ID**: Sends last received ID on reconnect for resumption
|
||||
- **retry field**: Respects server's reconnect interval
|
||||
- **Event types**: Supports custom event types via `on_event()`
|
||||
- **Async support**: Run in background thread with `start_async()`
|
||||
- **C++11 compatible**: No C++14/17/20 features required
|
||||
|
||||
## Quick Start
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << "Event: " << msg.event << std::endl;
|
||||
std::cout << "Data: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start(); // Blocking, with auto-reconnect
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### SSEMessage
|
||||
|
||||
```cpp
|
||||
struct SSEMessage {
|
||||
std::string event; // Event type (default: "message")
|
||||
std::string data; // Event payload
|
||||
std::string id; // Event ID
|
||||
};
|
||||
```
|
||||
|
||||
### SSEClient
|
||||
|
||||
#### Constructor
|
||||
|
||||
```cpp
|
||||
// Basic
|
||||
SSEClient(Client &client, const std::string &path);
|
||||
|
||||
// With custom headers
|
||||
SSEClient(Client &client, const std::string &path, const Headers &headers);
|
||||
```
|
||||
|
||||
#### Event Handlers
|
||||
|
||||
```cpp
|
||||
// Called for all events (or events without a specific handler)
|
||||
sse.on_message([](const SSEMessage &msg) { });
|
||||
|
||||
// Called for specific event types
|
||||
sse.on_event("update", [](const SSEMessage &msg) { });
|
||||
sse.on_event("delete", [](const SSEMessage &msg) { });
|
||||
|
||||
// Called when connection is established
|
||||
sse.on_open([]() { });
|
||||
|
||||
// Called on connection errors
|
||||
sse.on_error([](httplib::Error err) { });
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
|
||||
```cpp
|
||||
// Set reconnect interval (default: 3000ms)
|
||||
sse.set_reconnect_interval(5000);
|
||||
|
||||
// Set max reconnect attempts (default: 0 = unlimited)
|
||||
sse.set_max_reconnect_attempts(10);
|
||||
```
|
||||
|
||||
#### Control
|
||||
|
||||
```cpp
|
||||
// Blocking start with auto-reconnect
|
||||
sse.start();
|
||||
|
||||
// Non-blocking start (runs in background thread)
|
||||
sse.start_async();
|
||||
|
||||
// Stop the client (thread-safe)
|
||||
sse.stop();
|
||||
```
|
||||
|
||||
#### State
|
||||
|
||||
```cpp
|
||||
bool connected = sse.is_connected();
|
||||
const std::string &id = sse.last_event_id();
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start();
|
||||
```
|
||||
|
||||
### With Custom Event Types
|
||||
|
||||
```cpp
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_event("notification", [](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << "Notification: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.on_event("update", [](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << "Update: " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start();
|
||||
```
|
||||
|
||||
### Async with Stop
|
||||
|
||||
```cpp
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start_async(); // Returns immediately
|
||||
|
||||
// ... do other work ...
|
||||
|
||||
sse.stop(); // Stop when done
|
||||
```
|
||||
|
||||
### With Custom Headers (e.g., Authentication)
|
||||
|
||||
```cpp
|
||||
httplib::Headers headers = {
|
||||
{"Authorization", "Bearer token123"}
|
||||
};
|
||||
|
||||
httplib::sse::SSEClient sse(cli, "/events", headers);
|
||||
sse.start();
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```cpp
|
||||
sse.on_error([](httplib::Error err) {
|
||||
std::cerr << "Error: " << httplib::to_string(err) << std::endl;
|
||||
});
|
||||
|
||||
sse.set_reconnect_interval(1000);
|
||||
sse.set_max_reconnect_attempts(5);
|
||||
|
||||
sse.start();
|
||||
```
|
||||
|
||||
## SSE Protocol
|
||||
|
||||
The client parses SSE format according to the [W3C specification](https://html.spec.whatwg.org/multipage/server-sent-events.html):
|
||||
|
||||
```
|
||||
event: custom-type
|
||||
id: 123
|
||||
data: {"message": "hello"}
|
||||
|
||||
data: simple message
|
||||
|
||||
: this is a comment (ignored)
|
||||
```
|
||||
@@ -0,0 +1,317 @@
|
||||
# cpp-httplib Streaming API
|
||||
|
||||
This document describes the streaming extensions for cpp-httplib, providing an iterator-style API for handling HTTP responses incrementally with **true socket-level streaming**.
|
||||
|
||||
> **Important Notes**:
|
||||
>
|
||||
> - **No Keep-Alive**: Each `stream::Get()` call uses a dedicated connection that is closed after the response is fully read. For connection reuse, use `Client::Get()`.
|
||||
> - **Single iteration only**: The `next()` method can only iterate through the body once.
|
||||
> - **Result is not thread-safe**: While `stream::Get()` can be called from multiple threads simultaneously, the returned `stream::Result` must be used from a single thread only.
|
||||
|
||||
## Overview
|
||||
|
||||
The streaming API allows you to process HTTP response bodies chunk by chunk using an iterator-style pattern. Data is read directly from the network socket, enabling low-memory processing of large responses. This is particularly useful for:
|
||||
|
||||
- **LLM/AI streaming responses** (e.g., ChatGPT, Claude, Ollama)
|
||||
- **Server-Sent Events (SSE)**
|
||||
- **Large file downloads** with progress tracking
|
||||
- **Reverse proxy implementations**
|
||||
|
||||
## Quick Start
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
// Get streaming response
|
||||
auto result = httplib::stream::Get(cli, "/stream");
|
||||
|
||||
if (result) {
|
||||
// Process response body in chunks
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## API Layers
|
||||
|
||||
cpp-httplib provides multiple API layers for different use cases:
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ SSEClient (planned) │ ← SSE-specific, parsed events
|
||||
│ - on_message(), on_event() │
|
||||
│ - Auto-reconnect, Last-Event-ID │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ stream::Get() / stream::Result │ ← Iterator-based streaming
|
||||
│ - while (result.next()) { ... } │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ open_stream() / StreamHandle │ ← General-purpose streaming
|
||||
│ - handle.read(buf, len) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ Client::Get() │ ← Traditional, full buffering
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Use Case | Recommended API |
|
||||
|----------|----------------|
|
||||
| SSE with auto-reconnect | SSEClient (planned) or `ssecli-stream.cc` example |
|
||||
| LLM streaming (JSON Lines) | `stream::Get()` |
|
||||
| Large file download | `stream::Get()` or `open_stream()` |
|
||||
| Reverse proxy | `open_stream()` |
|
||||
| Small responses with Keep-Alive | `Client::Get()` |
|
||||
|
||||
## API Reference
|
||||
|
||||
### Low-Level API: `StreamHandle`
|
||||
|
||||
The `StreamHandle` struct provides direct control over streaming responses. It takes ownership of the socket connection and reads data directly from the network.
|
||||
|
||||
> **Note:** When using `open_stream()`, the connection is dedicated to streaming and **Keep-Alive is not supported**. For Keep-Alive connections, use `client.Get()` instead.
|
||||
|
||||
```cpp
|
||||
// Open a stream (takes ownership of socket)
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
auto handle = cli.open_stream("GET", "/path");
|
||||
|
||||
// Check validity
|
||||
if (handle.is_valid()) {
|
||||
// Access response headers immediately
|
||||
int status = handle.response->status;
|
||||
auto content_type = handle.response->get_header_value("Content-Type");
|
||||
|
||||
// Read body incrementally
|
||||
char buf[4096];
|
||||
ssize_t n;
|
||||
while ((n = handle.read(buf, sizeof(buf))) > 0) {
|
||||
process(buf, n);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### StreamHandle Members
|
||||
|
||||
| Member | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `response` | `std::unique_ptr<Response>` | HTTP response with headers |
|
||||
| `error` | `Error` | Error code if request failed |
|
||||
| `is_valid()` | `bool` | Returns true if response is valid |
|
||||
| `read(buf, len)` | `ssize_t` | Read up to `len` bytes directly from socket |
|
||||
| `get_read_error()` | `Error` | Get the last read error |
|
||||
| `has_read_error()` | `bool` | Check if a read error occurred |
|
||||
|
||||
### High-Level API: `stream::Get()` and `stream::Result`
|
||||
|
||||
The `httplib.h` header provides a more ergonomic iterator-style API.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
cli.set_follow_location(true);
|
||||
...
|
||||
|
||||
// Simple GET
|
||||
auto result = httplib::stream::Get(cli, "/path");
|
||||
|
||||
// GET with custom headers
|
||||
httplib::Headers headers = {{"Authorization", "Bearer token"}};
|
||||
auto result = httplib::stream::Get(cli, "/path", headers);
|
||||
|
||||
// Process the response
|
||||
if (result) {
|
||||
while (result.next()) {
|
||||
process(result.data(), result.size());
|
||||
}
|
||||
}
|
||||
|
||||
// Or read entire body at once
|
||||
auto result2 = httplib::stream::Get(cli, "/path");
|
||||
if (result2) {
|
||||
std::string body = result2.read_all();
|
||||
}
|
||||
```
|
||||
|
||||
#### stream::Result Members
|
||||
|
||||
| Member | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `operator bool()` | `bool` | Returns true if response is valid |
|
||||
| `is_valid()` | `bool` | Same as `operator bool()` |
|
||||
| `status()` | `int` | HTTP status code |
|
||||
| `headers()` | `const Headers&` | Response headers |
|
||||
| `get_header_value(key, def)` | `std::string` | Get header value (with optional default) |
|
||||
| `has_header(key)` | `bool` | Check if header exists |
|
||||
| `next()` | `bool` | Read next chunk, returns false when done |
|
||||
| `data()` | `const char*` | Pointer to current chunk data |
|
||||
| `size()` | `size_t` | Size of current chunk |
|
||||
| `read_all()` | `std::string` | Read entire remaining body into string |
|
||||
| `error()` | `Error` | Get the connection/request error |
|
||||
| `read_error()` | `Error` | Get the last read error |
|
||||
| `has_read_error()` | `bool` | Check if a read error occurred |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: SSE (Server-Sent Events) Client
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:1234");
|
||||
|
||||
auto result = httplib::stream::Get(cli, "/events");
|
||||
if (!result) { return 1; }
|
||||
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
std::cout.flush();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
For a complete SSE client with auto-reconnection and event parsing, see `example/ssecli-stream.cc`.
|
||||
|
||||
### Example 2: LLM Streaming Response
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:11434"); // Ollama
|
||||
|
||||
auto result = httplib::stream::Get(cli, "/api/generate");
|
||||
|
||||
if (result && result.status() == 200) {
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
std::cout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Check for connection errors
|
||||
if (result.read_error() != httplib::Error::Success) {
|
||||
std::cerr << "Connection lost\n";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Large File Download with Progress
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://example.com");
|
||||
auto result = httplib::stream::Get(cli, "/large-file.zip");
|
||||
|
||||
if (!result || result.status() != 200) {
|
||||
std::cerr << "Download failed\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::ofstream file("download.zip", std::ios::binary);
|
||||
size_t total = 0;
|
||||
|
||||
while (result.next()) {
|
||||
file.write(result.data(), result.size());
|
||||
total += result.size();
|
||||
std::cout << "\rDownloaded: " << (total / 1024) << " KB" << std::flush;
|
||||
}
|
||||
|
||||
std::cout << "\nComplete!\n";
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Example 4: Reverse Proxy Streaming
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/proxy/(.*)", [](const httplib::Request& req, httplib::Response& res) {
|
||||
httplib::Client upstream("http://backend:8080");
|
||||
auto handle = upstream.open_stream("/" + req.matches[1].str());
|
||||
|
||||
if (!handle.is_valid()) {
|
||||
res.status = 502;
|
||||
return;
|
||||
}
|
||||
|
||||
res.status = handle.response->status;
|
||||
res.set_chunked_content_provider(
|
||||
handle.response->get_header_value("Content-Type"),
|
||||
[handle = std::move(handle)](size_t, httplib::DataSink& sink) mutable {
|
||||
char buf[8192];
|
||||
auto n = handle.read(buf, sizeof(buf));
|
||||
if (n > 0) {
|
||||
sink.write(buf, static_cast<size_t>(n));
|
||||
return true;
|
||||
}
|
||||
sink.done();
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 3000);
|
||||
```
|
||||
|
||||
## Comparison with Existing APIs
|
||||
|
||||
| Feature | `Client::Get()` | `open_stream()` | `stream::Get()` |
|
||||
|---------|----------------|-----------------|----------------|
|
||||
| Headers available | After complete | Immediately | Immediately |
|
||||
| Body reading | All at once | Direct from socket | Iterator-based |
|
||||
| Memory usage | Full body in RAM | Minimal (controlled) | Minimal (controlled) |
|
||||
| Keep-Alive support | ✅ Yes | ❌ No | ❌ No |
|
||||
| Compression | Auto-handled | Auto-handled | Auto-handled |
|
||||
| Best for | Small responses, Keep-Alive | Low-level streaming | Easy streaming |
|
||||
|
||||
## Features
|
||||
|
||||
- **True socket-level streaming**: Data is read directly from the network socket
|
||||
- **Low memory footprint**: Only the current chunk is held in memory
|
||||
- **Compression support**: Automatic decompression for gzip, brotli, and zstd
|
||||
- **Chunked transfer**: Full support for chunked transfer encoding
|
||||
- **SSL/TLS support**: Works with HTTPS connections
|
||||
|
||||
## Important Notes
|
||||
|
||||
### Keep-Alive Behavior
|
||||
|
||||
The streaming API (`stream::Get()` / `open_stream()`) takes ownership of the socket connection for the duration of the stream. This means:
|
||||
|
||||
- **Keep-Alive is not supported** for streaming connections
|
||||
- The socket is closed when `StreamHandle` is destroyed
|
||||
- For Keep-Alive scenarios, use the standard `client.Get()` API instead
|
||||
|
||||
```cpp
|
||||
// Use for streaming (no Keep-Alive)
|
||||
auto result = httplib::stream::Get(cli, "/large-stream");
|
||||
while (result.next()) { /* ... */ }
|
||||
|
||||
// Use for Keep-Alive connections
|
||||
auto res = cli.Get("/api/data"); // Connection can be reused
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Issue #2269](https://github.com/yhirose/cpp-httplib/issues/2269) - Original feature request
|
||||
- [example/ssecli-stream.cc](./example/ssecli-stream.cc) - SSE client with auto-reconnection
|
||||
@@ -0,0 +1,391 @@
|
||||
# WebSocket - RFC 6455 WebSocket Support
|
||||
|
||||
A simple, blocking WebSocket implementation for C++11.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This is a blocking I/O WebSocket implementation using a thread-per-connection model. If you need high-concurrency WebSocket support with non-blocking/async I/O (e.g., thousands of simultaneous connections), this is not the one that you want.
|
||||
|
||||
## Features
|
||||
|
||||
- **RFC 6455 compliant**: Full WebSocket protocol support
|
||||
- **Server and Client**: Both sides included
|
||||
- **SSL/TLS support**: `wss://` scheme for secure connections
|
||||
- **Text and Binary**: Both message types supported
|
||||
- **Automatic heartbeat**: Periodic Ping/Pong keeps connections alive
|
||||
- **Subprotocol negotiation**: `Sec-WebSocket-Protocol` support for GraphQL, MQTT, etc.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Server
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
});
|
||||
|
||||
svr.listen("localhost", 8080);
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("hello");
|
||||
|
||||
std::string msg;
|
||||
if (ws.read(msg)) {
|
||||
std::cout << msg << std::endl; // "echo: hello"
|
||||
}
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### ReadResult
|
||||
|
||||
```cpp
|
||||
enum ReadResult : int {
|
||||
Fail = 0, // Connection closed or error
|
||||
Text = 1, // UTF-8 text message
|
||||
Binary = 2, // Binary message
|
||||
};
|
||||
```
|
||||
|
||||
Returned by `read()`. Since `Fail` is `0`, the result works naturally in boolean contexts — `while (ws.read(msg))` continues until the connection closes. When you need to distinguish text from binary, check the return value directly.
|
||||
|
||||
### CloseStatus
|
||||
|
||||
```cpp
|
||||
enum class CloseStatus : uint16_t {
|
||||
Normal = 1000,
|
||||
GoingAway = 1001,
|
||||
ProtocolError = 1002,
|
||||
UnsupportedData = 1003,
|
||||
NoStatus = 1005,
|
||||
Abnormal = 1006,
|
||||
InvalidPayload = 1007,
|
||||
PolicyViolation = 1008,
|
||||
MessageTooBig = 1009,
|
||||
MandatoryExtension = 1010,
|
||||
InternalError = 1011,
|
||||
};
|
||||
```
|
||||
|
||||
### Server Registration
|
||||
|
||||
```cpp
|
||||
// Basic handler
|
||||
Server &WebSocket(const std::string &pattern, WebSocketHandler handler);
|
||||
|
||||
// With subprotocol negotiation
|
||||
Server &WebSocket(const std::string &pattern, WebSocketHandler handler,
|
||||
SubProtocolSelector sub_protocol_selector);
|
||||
```
|
||||
|
||||
**Type aliases:**
|
||||
|
||||
```cpp
|
||||
using WebSocketHandler =
|
||||
std::function<void(const Request &, ws::WebSocket &)>;
|
||||
using SubProtocolSelector =
|
||||
std::function<std::string(const std::vector<std::string> &protocols)>;
|
||||
```
|
||||
|
||||
The `SubProtocolSelector` receives the list of subprotocols proposed by the client (from the `Sec-WebSocket-Protocol` header) and returns the selected one. Return an empty string to decline all proposed subprotocols.
|
||||
|
||||
### WebSocket (Server-side)
|
||||
|
||||
Passed to the handler registered with `Server::WebSocket()`. The handler runs in a dedicated thread per connection.
|
||||
|
||||
```cpp
|
||||
// Read next message (blocks until received, returns Fail/Text/Binary)
|
||||
ReadResult read(std::string &msg);
|
||||
|
||||
// Send messages
|
||||
bool send(const std::string &data); // Text
|
||||
bool send(const char *data, size_t len); // Binary
|
||||
|
||||
// Close the connection
|
||||
void close(CloseStatus status = CloseStatus::Normal,
|
||||
const std::string &reason = "");
|
||||
|
||||
// Access the original HTTP upgrade request
|
||||
const Request &request() const;
|
||||
|
||||
// Check if the connection is still open
|
||||
bool is_open() const;
|
||||
```
|
||||
|
||||
### WebSocketClient
|
||||
|
||||
```cpp
|
||||
// Constructor - accepts ws:// or wss:// URL
|
||||
explicit WebSocketClient(const std::string &scheme_host_port_path,
|
||||
const Headers &headers = {});
|
||||
|
||||
// Check if the URL was parsed successfully
|
||||
bool is_valid() const;
|
||||
|
||||
// Connect (performs HTTP upgrade handshake)
|
||||
bool connect();
|
||||
|
||||
// Get the subprotocol selected by the server (empty if none)
|
||||
const std::string &subprotocol() const;
|
||||
|
||||
// Read/Send/Close (same as server-side WebSocket)
|
||||
ReadResult read(std::string &msg);
|
||||
bool send(const std::string &data);
|
||||
bool send(const char *data, size_t len);
|
||||
void close(CloseStatus status = CloseStatus::Normal,
|
||||
const std::string &reason = "");
|
||||
bool is_open() const;
|
||||
|
||||
// Timeouts
|
||||
void set_read_timeout(time_t sec, time_t usec = 0);
|
||||
void set_write_timeout(time_t sec, time_t usec = 0);
|
||||
|
||||
// SSL configuration (wss:// only, requires CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
void set_ca_cert_path(const std::string &path);
|
||||
void set_ca_cert_store(tls::ca_store_t store);
|
||||
void enable_server_certificate_verification(bool enabled);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Echo Server with Connection Logging
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::cout << "Connected from " << req.remote_addr << std::endl;
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
|
||||
std::cout << "Disconnected" << std::endl;
|
||||
});
|
||||
|
||||
svr.listen("localhost", 8080);
|
||||
```
|
||||
|
||||
### Client: Continuous Read Loop
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("hello");
|
||||
ws.send("world");
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) { // blocks until a message arrives
|
||||
std::cout << msg << std::endl; // "echo: hello", "echo: world"
|
||||
}
|
||||
// read() returns false when the server closes the connection
|
||||
}
|
||||
```
|
||||
|
||||
### Text and Binary Messages
|
||||
|
||||
Check the `ReadResult` return value to distinguish between text and binary:
|
||||
|
||||
```cpp
|
||||
// Server
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
httplib::ws::ReadResult ret;
|
||||
while ((ret = ws.read(msg))) {
|
||||
if (ret == httplib::ws::Text) {
|
||||
ws.send("echo: " + msg);
|
||||
} else {
|
||||
ws.send(msg.data(), msg.size()); // Binary echo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Client
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws");
|
||||
if (ws.connect()) {
|
||||
// Send binary data
|
||||
const char binary[] = {0x00, 0x01, 0x02, 0x03};
|
||||
ws.send(binary, sizeof(binary));
|
||||
|
||||
// Receive and check the type
|
||||
std::string msg;
|
||||
if (ws.read(msg) == httplib::ws::Binary) {
|
||||
// Process binary data in msg
|
||||
}
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
### SSL Client
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("wss://echo.example.com/ws");
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("hello over TLS");
|
||||
|
||||
std::string msg;
|
||||
if (ws.read(msg)) {
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
### Close with Status
|
||||
|
||||
```cpp
|
||||
// Client-side: close with a specific status code and reason
|
||||
ws.close(httplib::ws::CloseStatus::GoingAway, "shutting down");
|
||||
|
||||
// Server-side: close with a policy violation status
|
||||
ws.close(httplib::ws::CloseStatus::PolicyViolation, "forbidden");
|
||||
```
|
||||
|
||||
### Accessing the Upgrade Request
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
// Access headers from the original HTTP upgrade request
|
||||
auto auth = req.get_header_value("Authorization");
|
||||
if (auth.empty()) {
|
||||
ws.close(httplib::ws::CloseStatus::PolicyViolation, "unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Headers and Timeouts
|
||||
|
||||
```cpp
|
||||
httplib::Headers headers = {
|
||||
{"Authorization", "Bearer token123"}
|
||||
};
|
||||
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
|
||||
ws.set_read_timeout(30, 0); // 30 seconds
|
||||
ws.set_write_timeout(10, 0); // 10 seconds
|
||||
|
||||
if (ws.connect()) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
std::cout << msg << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Subprotocol Negotiation
|
||||
|
||||
The server can negotiate a subprotocol with the client using `Sec-WebSocket-Protocol`. This is required for protocols like GraphQL over WebSocket (`graphql-ws`) and MQTT.
|
||||
|
||||
```cpp
|
||||
// Server: register a handler with a subprotocol selector
|
||||
svr.WebSocket(
|
||||
"/ws",
|
||||
[](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
},
|
||||
[](const std::vector<std::string> &protocols) -> std::string {
|
||||
// The client proposed a list of subprotocols; pick one
|
||||
for (const auto &p : protocols) {
|
||||
if (p == "graphql-ws" || p == "graphql-transport-ws") {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return ""; // Decline all
|
||||
});
|
||||
|
||||
// Client: propose subprotocols via Sec-WebSocket-Protocol header
|
||||
httplib::Headers headers = {
|
||||
{"Sec-WebSocket-Protocol", "graphql-ws, graphql-transport-ws"}
|
||||
};
|
||||
httplib::ws::WebSocketClient ws("ws://localhost:8080/ws", headers);
|
||||
|
||||
if (ws.connect()) {
|
||||
// Check which subprotocol the server selected
|
||||
std::cout << "Subprotocol: " << ws.subprotocol() << std::endl;
|
||||
// => "graphql-ws"
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
### SSL Client with Certificate Configuration
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient ws("wss://example.com/ws");
|
||||
ws.set_ca_cert_path("/path/to/ca-bundle.crt");
|
||||
ws.enable_server_certificate_verification(true);
|
||||
|
||||
if (ws.connect()) {
|
||||
ws.send("secure message");
|
||||
ws.close();
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Macro | Default | Description |
|
||||
|---------------------------------------------|-------------------|----------------------------------------------------------|
|
||||
| `CPPHTTPLIB_WEBSOCKET_MAX_PAYLOAD_LENGTH` | `16777216` (16MB) | Maximum payload size per message |
|
||||
| `CPPHTTPLIB_WEBSOCKET_READ_TIMEOUT_SECOND` | `300` | Read timeout for WebSocket connections (seconds) |
|
||||
| `CPPHTTPLIB_WEBSOCKET_CLOSE_TIMEOUT_SECOND` | `5` | Timeout for waiting peer's Close response (seconds) |
|
||||
| `CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND` | `30` | Automatic Ping interval for heartbeat (seconds) |
|
||||
|
||||
## Threading Model
|
||||
|
||||
WebSocket connections share the same thread pool as HTTP requests. Each WebSocket connection occupies one thread for its entire lifetime.
|
||||
|
||||
The default thread pool uses dynamic scaling: it maintains a base thread count of `CPPHTTPLIB_THREAD_POOL_COUNT` (8 or `std::thread::hardware_concurrency() - 1`, whichever is greater) and can scale up to 4x that count under load (`CPPHTTPLIB_THREAD_POOL_MAX_COUNT`). When all base threads are busy, temporary threads are spawned automatically up to the maximum. These dynamic threads exit after an idle timeout (`CPPHTTPLIB_THREAD_POOL_IDLE_TIMEOUT`, default 3 seconds).
|
||||
|
||||
This dynamic scaling helps accommodate WebSocket connections alongside HTTP requests. However, if you expect many simultaneous WebSocket connections, you should configure the thread pool accordingly:
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.new_task_queue = [] {
|
||||
return new httplib::ThreadPool(/*base_threads=*/8, /*max_threads=*/128);
|
||||
};
|
||||
```
|
||||
|
||||
Choose sizes that account for both your expected HTTP load and the maximum number of simultaneous WebSocket connections.
|
||||
|
||||
## Protocol
|
||||
|
||||
The implementation follows [RFC 6455](https://tools.ietf.org/html/rfc6455):
|
||||
|
||||
- Handshake via HTTP Upgrade with `Sec-WebSocket-Key` / `Sec-WebSocket-Accept`
|
||||
- Subprotocol negotiation via `Sec-WebSocket-Protocol`
|
||||
- Frame masking (client-to-server)
|
||||
- Control frames: Close, Ping, Pong
|
||||
- Message fragmentation and reassembly
|
||||
- Close handshake with status codes
|
||||
|
||||
## Browser Test
|
||||
|
||||
Run the echo server example and open `http://localhost:8080` in a browser:
|
||||
|
||||
```bash
|
||||
cd example && make wsecho && ./wsecho
|
||||
```
|
||||
@@ -0,0 +1,50 @@
|
||||
CXXFLAGS = -O2 -I..
|
||||
|
||||
CPPHTTPLIB_CXXFLAGS = -std=c++11
|
||||
CROW_CXXFLAGS = -std=c++17
|
||||
|
||||
CPPHTTPLIB_FLAGS = -DCPPHTTPLIB_THREAD_POOL_COUNT=16
|
||||
|
||||
BENCH = bombardier -c 10 -d 5s localhost:8080
|
||||
MONITOR = ali http://localhost:8080
|
||||
|
||||
# cpp-httplib
|
||||
bench: server
|
||||
@echo "--------------------\n cpp-httplib latest\n--------------------\n"
|
||||
@./server & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor: server
|
||||
@./server & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run : server
|
||||
@./server
|
||||
|
||||
server : cpp-httplib/main.cpp ../httplib.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CPPHTTPLIB_CXXFLAGS) $(CPPHTTPLIB_FLAGS) cpp-httplib/main.cpp
|
||||
|
||||
# crow
|
||||
bench-crow: server-crow
|
||||
@echo "-------------\n Crow v1.3.1\n-------------\n"
|
||||
@./server-crow & export PID=$$!; $(BENCH); kill $${PID}
|
||||
@echo ""
|
||||
|
||||
monitor-crow: server-crow
|
||||
@./server-crow & export PID=$$!; $(MONITOR); kill $${PID}
|
||||
|
||||
run-crow : server-crow
|
||||
@./server-crow
|
||||
|
||||
server-crow : crow/main.cpp crow/crow_all.h
|
||||
@g++ -o $@ $(CXXFLAGS) $(CROW_CXXFLAGS) crow/main.cpp
|
||||
|
||||
# misc
|
||||
build: server server-crow
|
||||
|
||||
bench-all: bench-crow bench
|
||||
|
||||
issue:
|
||||
bombardier -c 10 -d 30s localhost:8080
|
||||
|
||||
clean:
|
||||
rm -rf server*
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "httplib.h"
|
||||
using namespace httplib;
|
||||
|
||||
int main() {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [](const Request &, Response &res) {
|
||||
res.set_content("Hello World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
#include "crow_all.h"
|
||||
|
||||
class CustomLogger : public crow::ILogHandler {
|
||||
public:
|
||||
void log(const std::string &, crow::LogLevel) {}
|
||||
};
|
||||
|
||||
int main() {
|
||||
CustomLogger logger;
|
||||
crow::logger::setHandler(&logger);
|
||||
|
||||
crow::SimpleApp app;
|
||||
|
||||
CROW_ROUTE(app, "/")([]() { return "Hello world!"; });
|
||||
|
||||
app.port(8080).multithreaded().run();
|
||||
}
|
||||
+25
-30
@@ -1,6 +1,6 @@
|
||||
# A simple FindBrotli package for Cmake's find_package function.
|
||||
# Note: This find package doesn't have version support, as the version file doesn't seem to be installed on most systems.
|
||||
#
|
||||
#
|
||||
# If you want to find the static packages instead of shared (the default), define BROTLI_USE_STATIC_LIBS as TRUE.
|
||||
# The targets will have the same names, but it will use the static libs.
|
||||
#
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
# If they asked for a specific version, warn/fail since we don't support it.
|
||||
# TODO: if they start distributing the version somewhere, implement finding it.
|
||||
# But currently there's a version header that doesn't seem to get installed.
|
||||
# See https://github.com/google/brotli/issues/773#issuecomment-579133187
|
||||
if(Brotli_FIND_VERSION)
|
||||
set(_brotli_version_error_msg "FindBrotli.cmake doesn't have version support!")
|
||||
# If the package is required, throw a fatal error
|
||||
@@ -68,30 +68,27 @@ if(BROTLI_USE_STATIC_LIBS)
|
||||
set(_brotli_stat_str "_STATIC")
|
||||
endif()
|
||||
|
||||
# Lets us know we are using the PkgConfig libraries
|
||||
# Will be set false if any non-pkgconf vars are used
|
||||
set(_brotli_using_pkgconf TRUE)
|
||||
|
||||
# Each string here is "ComponentName;LiteralName" (the semi-colon is a delimiter)
|
||||
foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
|
||||
# Split the component name and literal library name from the listvar
|
||||
list(GET _listvar 0 _component_name)
|
||||
list(GET _listvar 1 _libname)
|
||||
|
||||
if(PKG_CONFIG_FOUND)
|
||||
# NOTE: We can't rely on PkgConf for static libs since the upstream static lib support is broken
|
||||
# See https://github.com/google/brotli/issues/795
|
||||
# TODO: whenever their issue is fixed upstream, remove this "AND NOT BROTLI_USE_STATIC_LIBS" check
|
||||
if(PKG_CONFIG_FOUND AND NOT BROTLI_USE_STATIC_LIBS)
|
||||
# These need to be GLOBAL for MinGW when making ALIAS libraries against them.
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
# Have to use _STATIC to tell PkgConfig to find the static libs.
|
||||
pkg_check_modules(Brotli_${_component_name}_STATIC QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname})
|
||||
else()
|
||||
pkg_check_modules(Brotli_${_component_name} QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname})
|
||||
endif()
|
||||
# Have to postfix _STATIC on the name to tell PkgConfig to find the static libs.
|
||||
pkg_check_modules(Brotli_${_component_name}${_brotli_stat_str} QUIET GLOBAL IMPORTED_TARGET libbrotli${_libname})
|
||||
endif()
|
||||
|
||||
# Check if the target was already found by Pkgconf
|
||||
if(TARGET PkgConfig::Brotli_${_component_name} OR TARGET PkgConfig::Brotli_${_component_name}_STATIC)
|
||||
# Can't use generators for ALIAS targets, so you get this jank
|
||||
add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
# Check if the target was already found by Pkgconf
|
||||
if(TARGET PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
# ALIAS since we don't want the PkgConfig namespace on the Cmake library (for end-users)
|
||||
if (NOT TARGET Brotli::${_component_name})
|
||||
add_library(Brotli::${_component_name} ALIAS PkgConfig::Brotli_${_component_name}${_brotli_stat_str})
|
||||
endif()
|
||||
|
||||
# Tells HANDLE_COMPONENTS we found the component
|
||||
set(Brotli_${_component_name}_FOUND TRUE)
|
||||
@@ -109,23 +106,18 @@ foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
|
||||
continue()
|
||||
endif()
|
||||
|
||||
# Lets us know we aren't using the PkgConfig libraries
|
||||
set(_brotli_using_pkgconf FALSE)
|
||||
if(Brotli_FIND_REQUIRED_${_component_name})
|
||||
# If it's required, we can set the name used in find_library as a required var for FindPackageHandleStandardArgs
|
||||
list(APPEND _brotli_req_vars "Brotli_${_component_name}")
|
||||
endif()
|
||||
|
||||
list(APPEND _brotli_lib_names
|
||||
"brotli${_libname}"
|
||||
"libbrotli${_libname}"
|
||||
)
|
||||
if(BROTLI_USE_STATIC_LIBS)
|
||||
list(APPEND _brotli_lib_names
|
||||
"brotli${_libname}-static"
|
||||
"libbrotli${_libname}-static"
|
||||
)
|
||||
else()
|
||||
list(APPEND _brotli_lib_names
|
||||
"brotli${_libname}"
|
||||
"libbrotli${_libname}"
|
||||
)
|
||||
# Postfix "-static" to the libnames since we're looking for static libs
|
||||
list(TRANSFORM _brotli_lib_names APPEND "-static")
|
||||
endif()
|
||||
|
||||
find_library(Brotli_${_component_name}
|
||||
@@ -149,7 +141,10 @@ foreach(_listvar "common;common" "decoder;dec" "encoder;enc")
|
||||
# Tells HANDLE_COMPONENTS we found the component
|
||||
set(Brotli_${_component_name}_FOUND TRUE)
|
||||
|
||||
add_library("Brotli::${_component_name}" UNKNOWN IMPORTED)
|
||||
if (NOT TARGET Brotli::${_component_name})
|
||||
add_library("Brotli::${_component_name}" UNKNOWN IMPORTED)
|
||||
endif()
|
||||
|
||||
# Attach the literal library and include dir to the IMPORTED target for the end-user
|
||||
set_target_properties("Brotli::${_component_name}" PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Brotli_INCLUDE_DIR}"
|
||||
@@ -168,7 +163,7 @@ find_package_handle_standard_args(Brotli
|
||||
Brotli_FOUND
|
||||
REQUIRED_VARS
|
||||
Brotli_INCLUDE_DIR
|
||||
${_brotli_required_targets}
|
||||
${_brotli_req_vars}
|
||||
HANDLE_COMPONENTS
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
# Setting these here so they're accessible after install.
|
||||
# Might be useful for some users to check which settings were used.
|
||||
set(HTTPLIB_IS_USING_OPENSSL @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(HTTPLIB_IS_USING_WOLFSSL @HTTPLIB_IS_USING_WOLFSSL@)
|
||||
set(HTTPLIB_IS_USING_MBEDTLS @HTTPLIB_IS_USING_MBEDTLS@)
|
||||
set(HTTPLIB_IS_USING_ZLIB @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(HTTPLIB_IS_COMPILED @HTTPLIB_COMPILE@)
|
||||
set(HTTPLIB_IS_USING_BROTLI @HTTPLIB_IS_USING_BROTLI@)
|
||||
set(HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO @HTTPLIB_IS_USING_NON_BLOCKING_GETADDRINFO@)
|
||||
set(HTTPLIB_VERSION @PROJECT_VERSION@)
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
@@ -22,9 +25,22 @@ if(@HTTPLIB_IS_USING_OPENSSL@)
|
||||
# Since we use both, we need to search for both.
|
||||
find_dependency(OpenSSL @_HTTPLIB_OPENSSL_MIN_VER@ COMPONENTS Crypto SSL)
|
||||
endif()
|
||||
set(httplib_OpenSSL_FOUND ${OpenSSL_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_ZLIB@)
|
||||
find_dependency(ZLIB)
|
||||
set(httplib_ZLIB_FOUND ${ZLIB_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_WOLFSSL@)
|
||||
find_dependency(wolfssl)
|
||||
set(httplib_wolfssl_FOUND ${wolfssl_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_MBEDTLS@)
|
||||
find_dependency(MbedTLS)
|
||||
set(httplib_MbedTLS_FOUND ${MbedTLS_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
@@ -33,6 +49,30 @@ if(@HTTPLIB_IS_USING_BROTLI@)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(BROTLI_USE_STATIC_LIBS @BROTLI_USE_STATIC_LIBS@)
|
||||
find_dependency(Brotli COMPONENTS common encoder decoder)
|
||||
set(httplib_Brotli_FOUND ${Brotli_FOUND})
|
||||
endif()
|
||||
|
||||
if(@HTTPLIB_IS_USING_ZSTD@)
|
||||
set(httplib_fd_zstd_quiet_arg)
|
||||
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
|
||||
set(httplib_fd_zstd_quiet_arg QUIET)
|
||||
endif()
|
||||
set(httplib_fd_zstd_required_arg)
|
||||
if(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED)
|
||||
set(httplib_fd_zstd_required_arg REQUIRED)
|
||||
endif()
|
||||
find_package(zstd QUIET)
|
||||
if(NOT zstd_FOUND)
|
||||
find_package(PkgConfig ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg})
|
||||
if(PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(zstd ${httplib_fd_zstd_quiet_arg} ${httplib_fd_zstd_required_arg} IMPORTED_TARGET libzstd)
|
||||
|
||||
if(TARGET PkgConfig::zstd)
|
||||
add_library(zstd::libzstd ALIAS PkgConfig::zstd)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
set(httplib_zstd_FOUND ${zstd_FOUND})
|
||||
endif()
|
||||
|
||||
# Mildly useful for end-users
|
||||
@@ -42,11 +82,6 @@ set_and_check(HTTPLIB_INCLUDE_DIR "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@")
|
||||
# This is helpful if you're using Cmake's pre-compiled header feature
|
||||
set_and_check(HTTPLIB_HEADER_PATH "@PACKAGE_CMAKE_INSTALL_FULL_INCLUDEDIR@/httplib.h")
|
||||
|
||||
# Consider each library support as a "component"
|
||||
set(httplib_OpenSSL_FOUND @HTTPLIB_IS_USING_OPENSSL@)
|
||||
set(httplib_ZLIB_FOUND @HTTPLIB_IS_USING_ZLIB@)
|
||||
set(httplib_Brotli_FOUND @HTTPLIB_IS_USING_BROTLI@)
|
||||
|
||||
check_required_components(httplib)
|
||||
|
||||
# Brings in the target library, but only if all required components are found
|
||||
@@ -0,0 +1,16 @@
|
||||
# This file contains C++20 module support requiring CMake 3.28+
|
||||
# Included conditionally to prevent parse errors on older CMake versions
|
||||
|
||||
if(HTTPLIB_BUILD_MODULES)
|
||||
if(POLICY CMP0155)
|
||||
cmake_policy(SET CMP0155 NEW)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
|
||||
|
||||
target_sources(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
FILE_SET CXX_MODULES FILES
|
||||
"${_httplib_build_includedir}/httplib.cppm"
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
http:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:80"
|
||||
volumes:
|
||||
- ./docker/html:/html
|
||||
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Welcome to cpp-httplib!</title>
|
||||
<style>
|
||||
html { color-scheme: light dark; }
|
||||
body { width: 35em; margin: 0 auto;
|
||||
font-family: Tahoma, Verdana, Arial, sans-serif; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to cpp-httplib!</h1>
|
||||
<p>If you see this page, the cpp-httplib web server is successfully installed and
|
||||
working. Further configuration is required.</p>
|
||||
|
||||
<p>For online documentation and support please refer to
|
||||
<a href="https://github.com/yhirose/cpp-httplib">github.com/yhirose/cpp-httplib</a>.<br/>
|
||||
|
||||
<p><em>Thank you for using cpp-httplib.</em></p>
|
||||
</body>
|
||||
</html>
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
//
|
||||
// main.cc
|
||||
//
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <format>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <signal.h>
|
||||
#include <sstream>
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
const auto SERVER_NAME =
|
||||
std::format("cpp-httplib-server/{}", CPPHTTPLIB_VERSION);
|
||||
|
||||
Server svr;
|
||||
|
||||
void signal_handler(int signal) {
|
||||
if (signal == SIGINT || signal == SIGTERM) {
|
||||
std::cout << "\nReceived signal, shutting down gracefully...\n";
|
||||
svr.stop();
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_time_format() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%d/%b/%Y:%H:%M:%S %z");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string get_error_time_format() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(std::localtime(&time_t), "%Y/%m/%d %H:%M:%S");
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// NGINX Combined log format:
|
||||
// $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
|
||||
// "$http_referer" "$http_user_agent"
|
||||
void nginx_access_logger(const Request &req, const Response &res) {
|
||||
std::string remote_user =
|
||||
"-"; // cpp-httplib doesn't have built-in auth user tracking
|
||||
auto time_local = get_time_format();
|
||||
auto request = std::format("{} {} {}", req.method, req.path, req.version);
|
||||
auto status = res.status;
|
||||
auto body_bytes_sent = res.body.size();
|
||||
auto http_referer = req.get_header_value("Referer");
|
||||
if (http_referer.empty()) http_referer = "-";
|
||||
auto http_user_agent = req.get_header_value("User-Agent");
|
||||
if (http_user_agent.empty()) http_user_agent = "-";
|
||||
|
||||
std::cout << std::format("{} - {} [{}] \"{}\" {} {} \"{}\" \"{}\"",
|
||||
req.remote_addr, remote_user, time_local, request,
|
||||
status, body_bytes_sent, http_referer,
|
||||
http_user_agent)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
// NGINX Error log format:
|
||||
// YYYY/MM/DD HH:MM:SS [level] message, client: client_ip, request: "request",
|
||||
// host: "host"
|
||||
void nginx_error_logger(const Error &err, const Request *req) {
|
||||
auto time_local = get_error_time_format();
|
||||
std::string level = "error";
|
||||
|
||||
if (req) {
|
||||
auto request =
|
||||
std::format("{} {} {}", req->method, req->path, req->version);
|
||||
auto host = req->get_header_value("Host");
|
||||
if (host.empty()) host = "-";
|
||||
|
||||
std::cerr << std::format("{} [{}] {}, client: {}, request: "
|
||||
"\"{}\", host: \"{}\"",
|
||||
time_local, level, to_string(err),
|
||||
req->remote_addr, request, host)
|
||||
<< std::endl;
|
||||
} else {
|
||||
// If no request context, just log the error
|
||||
std::cerr << std::format("{} [{}] {}", time_local, level, to_string(err))
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void print_usage(const char *program_name) {
|
||||
std::cout << "Usage: " << program_name << " [OPTIONS]" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << " --host <hostname> Server hostname (default: localhost)"
|
||||
<< std::endl;
|
||||
std::cout << " --port <port> Server port (default: 8080)"
|
||||
<< std::endl;
|
||||
std::cout << " --mount <mount:path> Mount point and document root"
|
||||
<< std::endl;
|
||||
std::cout << " Format: mount_point:document_root"
|
||||
<< std::endl;
|
||||
std::cout << " (default: /:./html)" << std::endl;
|
||||
std::cout << " --trusted-proxy <ip> Add trusted proxy IP address"
|
||||
<< std::endl;
|
||||
std::cout << " (can be specified multiple times)"
|
||||
<< std::endl;
|
||||
std::cout << " --version Show version information"
|
||||
<< std::endl;
|
||||
std::cout << " --help Show this help message" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Examples:" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --host localhost --port 8080 --mount /:./html" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --host 0.0.0.0 --port 3000 --mount /api:./api" << std::endl;
|
||||
std::cout << " " << program_name
|
||||
<< " --trusted-proxy 192.168.1.100 --trusted-proxy 10.0.0.1"
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
struct ServerConfig {
|
||||
std::string hostname = "localhost";
|
||||
int port = 8080;
|
||||
std::string mount_point = "/";
|
||||
std::string document_root = "./html";
|
||||
std::vector<std::string> trusted_proxies;
|
||||
};
|
||||
|
||||
enum class ParseResult { SUCCESS, HELP_REQUESTED, VERSION_REQUESTED, ERROR };
|
||||
|
||||
ParseResult parse_command_line(int argc, char *argv[], ServerConfig &config) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::HELP_REQUESTED;
|
||||
} else if (strcmp(argv[i], "--host") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "Error: --host requires a hostname argument" << std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.hostname = argv[++i];
|
||||
} else if (strcmp(argv[i], "--port") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "Error: --port requires a port number argument"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.port = std::atoi(argv[++i]);
|
||||
if (config.port <= 0 || config.port > 65535) {
|
||||
std::cerr << "Error: Invalid port number. Must be between 1 and 65535"
|
||||
<< std::endl;
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--mount") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr
|
||||
<< "Error: --mount requires mount_point:document_root argument"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
std::string mount_arg = argv[++i];
|
||||
auto colon_pos = mount_arg.find(':');
|
||||
if (colon_pos == std::string::npos) {
|
||||
std::cerr << "Error: --mount argument must be in format "
|
||||
"mount_point:document_root"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.mount_point = mount_arg.substr(0, colon_pos);
|
||||
config.document_root = mount_arg.substr(colon_pos + 1);
|
||||
|
||||
if (config.mount_point.empty() || config.document_root.empty()) {
|
||||
std::cerr
|
||||
<< "Error: Both mount_point and document_root must be non-empty"
|
||||
<< std::endl;
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
} else if (strcmp(argv[i], "--version") == 0) {
|
||||
std::cout << CPPHTTPLIB_VERSION << std::endl;
|
||||
return ParseResult::VERSION_REQUESTED;
|
||||
} else if (strcmp(argv[i], "--trusted-proxy") == 0) {
|
||||
if (i + 1 >= argc) {
|
||||
std::cerr << "Error: --trusted-proxy requires an IP address argument"
|
||||
<< std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
config.trusted_proxies.push_back(argv[++i]);
|
||||
} else {
|
||||
std::cerr << "Error: Unknown option '" << argv[i] << "'" << std::endl;
|
||||
print_usage(argv[0]);
|
||||
return ParseResult::ERROR;
|
||||
}
|
||||
}
|
||||
return ParseResult::SUCCESS;
|
||||
}
|
||||
|
||||
bool setup_server(Server &svr, const ServerConfig &config) {
|
||||
svr.set_logger(nginx_access_logger);
|
||||
svr.set_error_logger(nginx_error_logger);
|
||||
|
||||
// Set trusted proxies if specified
|
||||
if (!config.trusted_proxies.empty()) {
|
||||
svr.set_trusted_proxies(config.trusted_proxies);
|
||||
}
|
||||
|
||||
auto ret = svr.set_mount_point(config.mount_point, config.document_root);
|
||||
if (!ret) {
|
||||
std::cerr
|
||||
<< std::format(
|
||||
"Error: Cannot mount '{}' to '{}'. Directory may not exist.",
|
||||
config.mount_point, config.document_root)
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
svr.set_file_extension_and_mimetype_mapping("html", "text/html");
|
||||
svr.set_file_extension_and_mimetype_mapping("htm", "text/html");
|
||||
svr.set_file_extension_and_mimetype_mapping("css", "text/css");
|
||||
svr.set_file_extension_and_mimetype_mapping("js", "text/javascript");
|
||||
svr.set_file_extension_and_mimetype_mapping("json", "application/json");
|
||||
svr.set_file_extension_and_mimetype_mapping("xml", "application/xml");
|
||||
svr.set_file_extension_and_mimetype_mapping("png", "image/png");
|
||||
svr.set_file_extension_and_mimetype_mapping("jpg", "image/jpeg");
|
||||
svr.set_file_extension_and_mimetype_mapping("jpeg", "image/jpeg");
|
||||
svr.set_file_extension_and_mimetype_mapping("gif", "image/gif");
|
||||
svr.set_file_extension_and_mimetype_mapping("svg", "image/svg+xml");
|
||||
svr.set_file_extension_and_mimetype_mapping("ico", "image/x-icon");
|
||||
svr.set_file_extension_and_mimetype_mapping("pdf", "application/pdf");
|
||||
svr.set_file_extension_and_mimetype_mapping("zip", "application/zip");
|
||||
svr.set_file_extension_and_mimetype_mapping("txt", "text/plain");
|
||||
|
||||
svr.set_error_handler([](const Request & /*req*/, Response &res) {
|
||||
if (res.status == 404) {
|
||||
res.set_content(
|
||||
std::format(
|
||||
"<html><head><title>404 Not Found</title></head>"
|
||||
"<body><h1>404 Not Found</h1>"
|
||||
"<p>The requested resource was not found on this server.</p>"
|
||||
"<hr><p>{}</p></body></html>",
|
||||
SERVER_NAME),
|
||||
"text/html");
|
||||
}
|
||||
});
|
||||
|
||||
svr.set_pre_routing_handler([](const Request & /*req*/, Response &res) {
|
||||
res.set_header("Server", SERVER_NAME);
|
||||
return Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
ServerConfig config;
|
||||
|
||||
auto result = parse_command_line(argc, argv, config);
|
||||
switch (result) {
|
||||
case ParseResult::HELP_REQUESTED:
|
||||
case ParseResult::VERSION_REQUESTED: return 0;
|
||||
case ParseResult::ERROR: return 1;
|
||||
case ParseResult::SUCCESS: break;
|
||||
}
|
||||
|
||||
if (!setup_server(svr, config)) { return 1; }
|
||||
|
||||
std::cout << "Serving HTTP on " << config.hostname << ":" << config.port
|
||||
<< std::endl;
|
||||
std::cout << "Mount point: " << config.mount_point << " -> "
|
||||
<< config.document_root << std::endl;
|
||||
|
||||
if (!config.trusted_proxies.empty()) {
|
||||
std::cout << "Trusted proxies: ";
|
||||
for (size_t i = 0; i < config.trusted_proxies.size(); ++i) {
|
||||
if (i > 0) std::cout << ", ";
|
||||
std::cout << config.trusted_proxies[i];
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "Press Ctrl+C to shutdown gracefully..." << std::endl;
|
||||
|
||||
auto ret = svr.listen(config.hostname, config.port);
|
||||
|
||||
std::cout << "Server has been shut down." << std::endl;
|
||||
|
||||
return ret ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[system]
|
||||
theme = "monotone"
|
||||
langs = ["en", "ja"]
|
||||
|
||||
[site]
|
||||
title = "cpp-httplib"
|
||||
version = "0.37.0"
|
||||
hostname = "https://yhirose.github.io"
|
||||
base_path = "/cpp-httplib"
|
||||
footer_message = "© 2026 Yuji Hirose. All rights reserved."
|
||||
|
||||
[[nav]]
|
||||
label = "Tour"
|
||||
path = "tour/"
|
||||
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></svg>'
|
||||
|
||||
[[nav]]
|
||||
label = "GitHub"
|
||||
url = "https://github.com/yhirose/cpp-httplib"
|
||||
icon_svg = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>'
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Cookbook"
|
||||
order: 1
|
||||
---
|
||||
|
||||
This section is under construction.
|
||||
|
||||
Check back soon for a collection of recipes organized by topic.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "cpp-httplib"
|
||||
order: 0
|
||||
---
|
||||
|
||||
[cpp-httplib](https://github.com/yhirose/cpp-httplib) is an HTTP/HTTPS library for C++. Just copy a single header file, [`httplib.h`](https://github.com/yhirose/cpp-httplib/raw/refs/tags/latest/httplib.h), and you're ready to go.
|
||||
|
||||
When you need a quick HTTP server or client in C++, you want something that just works. That's exactly why I built cpp-httplib. You can start writing both servers and clients in just a few lines of code.
|
||||
|
||||
The API uses a lambda-based design that feels natural. It runs anywhere you have a C++11 or later compiler. Windows, macOS, Linux — use whatever environment you already have.
|
||||
|
||||
HTTPS works too. Just link OpenSSL or mbedTLS, and both server and client gain TLS support. Content-Encoding (gzip, Brotli, etc.), file uploads, and other features you actually need in real-world development are all included. WebSocket is also supported.
|
||||
|
||||
Under the hood, it uses blocking I/O with a thread pool. It's not built for handling massive numbers of simultaneous connections. But for API servers, embedded HTTP in tools, mock servers for testing, and many other use cases, it delivers solid performance.
|
||||
|
||||
"Solve today's problem, today." That's the kind of simplicity cpp-httplib aims for.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [A Tour of cpp-httplib](tour/) — A step-by-step tutorial covering the basics. Start here if you're new
|
||||
- [Cookbook](cookbook/) — A collection of recipes organized by topic. Jump to whatever you need
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "Getting Started"
|
||||
order: 1
|
||||
---
|
||||
|
||||
All you need to get started with cpp-httplib is `httplib.h` and a C++ compiler. Let's download the file and get a Hello World server running.
|
||||
|
||||
## Getting httplib.h
|
||||
|
||||
You can download it directly from GitHub. Always use the latest version.
|
||||
|
||||
```sh
|
||||
curl -LO https://github.com/yhirose/cpp-httplib/raw/refs/tags/latest/httplib.h
|
||||
```
|
||||
|
||||
Place the downloaded `httplib.h` in your project directory and you're good to go.
|
||||
|
||||
## Setting Up Your Compiler
|
||||
|
||||
| OS | Development Environment | Setup |
|
||||
| -- | ----------------------- | ----- |
|
||||
| macOS | Apple Clang | Xcode Command Line Tools (`xcode-select --install`) |
|
||||
| Ubuntu | clang++ or g++ | `apt install clang` or `apt install g++` |
|
||||
| Windows | MSVC | Visual Studio 2022 or later (install with C++ components) |
|
||||
|
||||
## Hello World Server
|
||||
|
||||
Save the following code as `server.cpp`.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/", [](const httplib::Request&, httplib::Response& res) {
|
||||
res.set_content("Hello, World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
In just a few lines, you have a server that responds to HTTP requests.
|
||||
|
||||
## Compiling and Running
|
||||
|
||||
The sample code in this tutorial is written in C++17 for cleaner, more concise code. cpp-httplib itself can compile with C++11 as well.
|
||||
|
||||
```sh
|
||||
# macOS
|
||||
clang++ -std=c++17 -o server server.cpp
|
||||
|
||||
# Linux
|
||||
# `-pthread`: cpp-httplib uses threads internally
|
||||
clang++ -std=c++17 -pthread -o server server.cpp
|
||||
|
||||
# Windows (Developer Command Prompt)
|
||||
# `/EHsc`: Enable C++ exception handling
|
||||
cl /EHsc /std:c++17 server.cpp
|
||||
```
|
||||
|
||||
Once it compiles, run it.
|
||||
|
||||
```sh
|
||||
# macOS / Linux
|
||||
./server
|
||||
|
||||
# Windows
|
||||
server.exe
|
||||
```
|
||||
|
||||
Open `http://localhost:8080` in your browser. If you see "Hello, World!", you're all set.
|
||||
|
||||
You can also verify with `curl`.
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/
|
||||
# Hello, World!
|
||||
```
|
||||
|
||||
To stop the server, press `Ctrl+C` in your terminal.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now you know the basics of running a server. Next, let's look at the client side. cpp-httplib also comes with HTTP client functionality.
|
||||
|
||||
**Next:** [Basic Client](../02-basic-client)
|
||||
@@ -0,0 +1,266 @@
|
||||
---
|
||||
title: "Basic Client"
|
||||
order: 2
|
||||
---
|
||||
|
||||
cpp-httplib isn't just for servers -- it also comes with a full HTTP client. Let's use `httplib::Client` to send GET and POST requests.
|
||||
|
||||
## Preparing a Test Server
|
||||
|
||||
To try out the client, you need a server that accepts requests. Save the following code, then compile and run it the same way you did in the previous chapter. We'll cover the server details in the next chapter.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/hi", [](const auto &, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/search", [](const auto &req, auto &res) {
|
||||
auto q = req.get_param_value("q");
|
||||
res.set_content("Query: " + q, "text/plain");
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const auto &req, auto &res) {
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
|
||||
svr.Post("/submit", [](const auto &req, auto &res) {
|
||||
std::string result;
|
||||
for (auto &[key, val] : req.params) {
|
||||
result += key + " = " + val + "\n";
|
||||
}
|
||||
res.set_content(result, "text/plain");
|
||||
});
|
||||
|
||||
svr.Post("/upload", [](const auto &req, auto &res) {
|
||||
auto f = req.form.get_file("file");
|
||||
auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
|
||||
res.set_content(content, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/users/:id", [](const auto &req, auto &res) {
|
||||
auto id = req.path_params.at("id");
|
||||
res.set_content("User ID: " + id, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
|
||||
auto id = req.matches[1];
|
||||
res.set_content("File ID: " + std::string(id), "text/plain");
|
||||
});
|
||||
|
||||
std::cout << "Listening on port 8080..." << std::endl;
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
## GET Request
|
||||
|
||||
Once the server is running, open a separate terminal and give it a try. Let's start with the simplest GET request.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200
|
||||
std::cout << res->body << std::endl; // Hello!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pass the server address to the `httplib::Client` constructor, then call `Get()` to send a request. You can retrieve the status code and body from the returned `res`.
|
||||
|
||||
Here's the equivalent `curl` command.
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/hi
|
||||
# Hello!
|
||||
```
|
||||
|
||||
## Checking the Response
|
||||
|
||||
A response contains header information in addition to the status code and body.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/hi");
|
||||
if (res) {
|
||||
// Status code
|
||||
std::cout << res->status << std::endl; // 200
|
||||
|
||||
// Body
|
||||
std::cout << res->body << std::endl; // Hello!
|
||||
|
||||
// Headers
|
||||
std::cout << res->get_header_value("Content-Type") << std::endl; // text/plain
|
||||
}
|
||||
```
|
||||
|
||||
`res->body` is a `std::string`, so if you want to parse a JSON response, you can pass it directly to a JSON library like [nlohmann/json](https://github.com/nlohmann/json).
|
||||
|
||||
## Query Parameters
|
||||
|
||||
To add query parameters to a GET request, you can either write them directly in the URL or use `httplib::Params`.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/search", httplib::Params{{"q", "cpp-httplib"}});
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // Query: cpp-httplib
|
||||
}
|
||||
```
|
||||
|
||||
`httplib::Params` automatically URL-encodes special characters for you.
|
||||
|
||||
```sh
|
||||
curl "http://localhost:8080/search?q=cpp-httplib"
|
||||
# Query: cpp-httplib
|
||||
```
|
||||
|
||||
## Path Parameters
|
||||
|
||||
When values are embedded directly in the URL path, no special client API is needed. Just pass the path to `Get()` as-is.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/users/42");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // User ID: 42
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/users/42
|
||||
# User ID: 42
|
||||
```
|
||||
|
||||
The test server also has a `/files/(\d+)` route that uses a regex to accept numeric IDs only.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/files/42");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // File ID: 42
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/files/42
|
||||
# File ID: 42
|
||||
```
|
||||
|
||||
Pass a non-numeric ID like `/files/abc` and you'll get a 404. We'll cover how that works in the next chapter.
|
||||
|
||||
## Request Headers
|
||||
|
||||
To add custom HTTP headers, pass an `httplib::Headers` object. This works with both `Get()` and `Post()`.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/hi", httplib::Headers{
|
||||
{"Authorization", "Bearer my-token"}
|
||||
});
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -H "Authorization: Bearer my-token" http://localhost:8080/hi
|
||||
```
|
||||
|
||||
## POST Request
|
||||
|
||||
Let's POST some text data. Pass the body as the second argument to `Post()` and the Content-Type as the third.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Post("/post", "Hello, Server!", "text/plain");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200
|
||||
std::cout << res->body << std::endl; // Hello, Server!
|
||||
}
|
||||
```
|
||||
|
||||
The test server's `/post` endpoint echoes the body back, so you get the same string you sent.
|
||||
|
||||
```sh
|
||||
curl -X POST -H "Content-Type: text/plain" -d "Hello, Server!" http://localhost:8080/post
|
||||
# Hello, Server!
|
||||
```
|
||||
|
||||
## Sending Form Data
|
||||
|
||||
You can send key-value pairs just like an HTML form. Use `httplib::Params` for this.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Post("/submit", httplib::Params{
|
||||
{"name", "Alice"},
|
||||
{"age", "30"}
|
||||
});
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl;
|
||||
// age = 30
|
||||
// name = Alice
|
||||
}
|
||||
```
|
||||
|
||||
This sends the data in `application/x-www-form-urlencoded` format.
|
||||
|
||||
```sh
|
||||
curl -X POST -d "name=Alice&age=30" http://localhost:8080/submit
|
||||
```
|
||||
|
||||
## POSTing a File
|
||||
|
||||
To upload a file, use `httplib::UploadFormDataItems` to send it as multipart form data.
|
||||
|
||||
```cpp
|
||||
auto res = cli.Post("/upload", httplib::UploadFormDataItems{
|
||||
{"file", "Hello, File!", "hello.txt", "text/plain"}
|
||||
});
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // hello.txt (12 bytes)
|
||||
}
|
||||
```
|
||||
|
||||
Each element in `UploadFormDataItems` has four fields: `{name, content, filename, content_type}`.
|
||||
|
||||
```sh
|
||||
curl -F "file=Hello, File!;filename=hello.txt;type=text/plain" http://localhost:8080/upload
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Network communication can fail -- the server might not be reachable. Always check whether `res` is valid.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:9999"); // Non-existent port
|
||||
auto res = cli.Get("/hi");
|
||||
|
||||
if (!res) {
|
||||
// Connection error
|
||||
std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
|
||||
// Error: Connection
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If we reach here, we have a response
|
||||
if (res->status != 200) {
|
||||
std::cout << "HTTP Error: " << res->status << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << res->body << std::endl;
|
||||
```
|
||||
|
||||
There are two levels of errors.
|
||||
|
||||
- **Connection error**: The client couldn't reach the server. `res` evaluates to false, and you can call `res.error()` to find out what went wrong.
|
||||
- **HTTP error**: The server returned an error status (404, 500, etc.). `res` evaluates to true, but you need to check `res->status`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now you know how to send requests from a client. Next, let's take a closer look at the server side. We'll dig into routing, path parameters, and more.
|
||||
|
||||
**Next:** [Basic Server](../03-basic-server)
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
title: "Basic Server"
|
||||
order: 3
|
||||
---
|
||||
|
||||
In the previous chapter, you sent requests from a client to a test server. Now let's walk through how that server actually works.
|
||||
|
||||
## Starting the Server
|
||||
|
||||
Once you've registered your routes, call `svr.listen()` to start the server.
|
||||
|
||||
```cpp
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
The first argument is the host, and the second is the port. `"0.0.0.0"` listens on all network interfaces. Use `"127.0.0.1"` if you want to accept connections from your own machine only.
|
||||
|
||||
`listen()` is a blocking call. It won't return until the server stops. The server keeps running until you press `Ctrl+C` in your terminal or call `svr.stop()` from another thread.
|
||||
|
||||
## Routing
|
||||
|
||||
Routing is the heart of any server. It's how you tell cpp-httplib: when a request comes in for this URL with this HTTP method, run this code.
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/hi", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`svr.Get()` registers a handler for GET requests. The first argument is the path, the second is the handler function. When a GET request arrives at `/hi`, your lambda runs.
|
||||
|
||||
There's a method for each HTTP verb.
|
||||
|
||||
```cpp
|
||||
svr.Get("/path", handler); // GET
|
||||
svr.Post("/path", handler); // POST
|
||||
svr.Put("/path", handler); // PUT
|
||||
svr.Delete("/path", handler); // DELETE
|
||||
```
|
||||
|
||||
The handler signature is `(const httplib::Request &req, httplib::Response &res)`. You can use `auto` to keep it short.
|
||||
|
||||
```cpp
|
||||
svr.Get("/hi", [](const auto &req, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
The handler only runs when the path matches. Requests to unregistered paths automatically return 404.
|
||||
|
||||
## The Request Object
|
||||
|
||||
The first parameter `req` gives you everything the client sent.
|
||||
|
||||
### Body
|
||||
|
||||
`req.body` holds the request body as a `std::string`.
|
||||
|
||||
```cpp
|
||||
svr.Post("/post", [](const auto &req, auto &res) {
|
||||
// Echo the body back to the client
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### Headers
|
||||
|
||||
Use `req.get_header_value()` to read a request header.
|
||||
|
||||
```cpp
|
||||
svr.Get("/check", [](const auto &req, auto &res) {
|
||||
auto auth = req.get_header_value("Authorization");
|
||||
res.set_content("Auth: " + auth, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### Query Parameters and Form Data
|
||||
|
||||
`req.get_param_value()` retrieves a parameter by name. It works for both GET query parameters and POST form data.
|
||||
|
||||
```cpp
|
||||
svr.Get("/search", [](const auto &req, auto &res) {
|
||||
auto q = req.get_param_value("q");
|
||||
res.set_content("Query: " + q, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
A request to `/search?q=cpp-httplib` gives you `"cpp-httplib"` for `q`.
|
||||
|
||||
To loop over all parameters, use `req.params`.
|
||||
|
||||
```cpp
|
||||
svr.Post("/submit", [](const auto &req, auto &res) {
|
||||
std::string result;
|
||||
for (auto &[key, val] : req.params) {
|
||||
result += key + " = " + val + "\n";
|
||||
}
|
||||
res.set_content(result, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### File Uploads
|
||||
|
||||
Files uploaded via multipart form data are available through `req.form.get_file()`.
|
||||
|
||||
```cpp
|
||||
svr.Post("/upload", [](const auto &req, auto &res) {
|
||||
auto f = req.form.get_file("file");
|
||||
auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
|
||||
res.set_content(content, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`f.filename` gives you the filename, and `f.content` gives you the file data.
|
||||
|
||||
## Path Parameters
|
||||
|
||||
Sometimes you want to capture part of the URL as a variable -- for example, the `42` in `/users/42`. Use the `:param` syntax to do that.
|
||||
|
||||
```cpp
|
||||
svr.Get("/users/:id", [](const auto &req, auto &res) {
|
||||
auto id = req.path_params.at("id");
|
||||
res.set_content("User ID: " + id, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
A request to `/users/42` gives you `"42"` from `req.path_params.at("id")`. `/users/100` gives you `"100"`.
|
||||
|
||||
You can capture multiple segments at once.
|
||||
|
||||
```cpp
|
||||
svr.Get("/users/:user_id/posts/:post_id", [](const auto &req, auto &res) {
|
||||
auto user_id = req.path_params.at("user_id");
|
||||
auto post_id = req.path_params.at("post_id");
|
||||
res.set_content("User: " + user_id + ", Post: " + post_id, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### Regex Patterns
|
||||
|
||||
You can also write a regular expression directly in the path instead of `:param`. Capture group values are available via `req.matches`, which is a `std::smatch`.
|
||||
|
||||
```cpp
|
||||
// Only accept numeric IDs
|
||||
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
|
||||
auto id = req.matches[1]; // First capture group
|
||||
res.set_content("File ID: " + std::string(id), "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`/files/42` matches, but `/files/abc` doesn't. This is handy when you want to constrain what values are accepted.
|
||||
|
||||
## Building a Response
|
||||
|
||||
The second parameter `res` is how you send data back to the client.
|
||||
|
||||
### Body and Content-Type
|
||||
|
||||
`res.set_content()` sets the body and Content-Type. That's all you need for a 200 response.
|
||||
|
||||
```cpp
|
||||
svr.Get("/hi", [](const auto &req, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### Status Code
|
||||
|
||||
To return a different status code, assign to `res.status`.
|
||||
|
||||
```cpp
|
||||
svr.Get("/not-found", [](const auto &req, auto &res) {
|
||||
res.status = 404;
|
||||
res.set_content("Not found", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### Response Headers
|
||||
|
||||
Add response headers with `res.set_header()`.
|
||||
|
||||
```cpp
|
||||
svr.Get("/with-header", [](const auto &req, auto &res) {
|
||||
res.set_header("X-Custom", "my-value");
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
## Walking Through the Test Server
|
||||
|
||||
Now let's use what we've learned to read through the test server from the previous chapter.
|
||||
|
||||
### GET /hi
|
||||
|
||||
```cpp
|
||||
svr.Get("/hi", [](const auto &, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
The simplest possible handler. We don't need any information from the request, so the `req` parameter is left unnamed. It just returns `"Hello!"`.
|
||||
|
||||
### GET /search
|
||||
|
||||
```cpp
|
||||
svr.Get("/search", [](const auto &req, auto &res) {
|
||||
auto q = req.get_param_value("q");
|
||||
res.set_content("Query: " + q, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`req.get_param_value("q")` pulls out the query parameter `q`. A request to `/search?q=cpp-httplib` returns `"Query: cpp-httplib"`.
|
||||
|
||||
### POST /post
|
||||
|
||||
```cpp
|
||||
svr.Post("/post", [](const auto &req, auto &res) {
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
An echo server. Whatever body the client sends, `req.body` holds it, and we send it straight back.
|
||||
|
||||
### POST /submit
|
||||
|
||||
```cpp
|
||||
svr.Post("/submit", [](const auto &req, auto &res) {
|
||||
std::string result;
|
||||
for (auto &[key, val] : req.params) {
|
||||
result += key + " = " + val + "\n";
|
||||
}
|
||||
res.set_content(result, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
Loops over the form data in `req.params` using structured bindings (`auto &[key, val]`) to unpack each key-value pair.
|
||||
|
||||
### POST /upload
|
||||
|
||||
```cpp
|
||||
svr.Post("/upload", [](const auto &req, auto &res) {
|
||||
auto f = req.form.get_file("file");
|
||||
auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
|
||||
res.set_content(content, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
Receives a file uploaded via multipart form data. `req.form.get_file("file")` fetches the field named `"file"`, and we respond with the filename and size.
|
||||
|
||||
### GET /users/:id
|
||||
|
||||
```cpp
|
||||
svr.Get("/users/:id", [](const auto &req, auto &res) {
|
||||
auto id = req.path_params.at("id");
|
||||
res.set_content("User ID: " + id, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`:id` is the path parameter. `req.path_params.at("id")` retrieves its value. `/users/42` gives you `"42"`, `/users/alice` gives you `"alice"`.
|
||||
|
||||
### GET /files/(\d+)
|
||||
|
||||
```cpp
|
||||
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
|
||||
auto id = req.matches[1];
|
||||
res.set_content("File ID: " + std::string(id), "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
The regex `(\d+)` matches numeric IDs only. `/files/42` hits this handler, but `/files/abc` returns 404. `req.matches[1]` retrieves the first capture group.
|
||||
|
||||
## Next Steps
|
||||
|
||||
You now have the full picture of how a server works. Routing, reading requests, building responses -- that's enough to build a real API server.
|
||||
|
||||
Next, let's look at serving static files. We'll build a server that delivers HTML and CSS.
|
||||
|
||||
**Next:** [Static File Server](../04-static-file-server)
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
title: "Static File Server"
|
||||
order: 4
|
||||
---
|
||||
|
||||
cpp-httplib can serve static files too — HTML, CSS, images, you name it. No complicated configuration required. One call to `set_mount_point()` is all it takes.
|
||||
|
||||
## The basics of set_mount_point
|
||||
|
||||
Let's jump right in. `set_mount_point()` maps a URL path to a local directory.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.set_mount_point("/", "./html");
|
||||
|
||||
std::cout << "Listening on port 8080..." << std::endl;
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
The first argument is the URL mount point. The second is the local directory path. In this example, requests to `/` are served from the `./html` directory.
|
||||
|
||||
Let's try it out. First, create an `html` directory and add an `index.html` file.
|
||||
|
||||
```sh
|
||||
mkdir html
|
||||
```
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>My Page</title></head>
|
||||
<body>
|
||||
<h1>Hello from cpp-httplib!</h1>
|
||||
<p>This is a static file.</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Compile and start the server.
|
||||
|
||||
```sh
|
||||
g++ -std=c++17 -o server server.cpp -pthread
|
||||
./server
|
||||
```
|
||||
|
||||
Open `http://localhost:8080` in your browser. You should see the contents of `html/index.html`. Visiting `http://localhost:8080/index.html` returns the same page.
|
||||
|
||||
You can also access it with the client code from the previous chapter, or with `curl`.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // HTML is displayed
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080
|
||||
```
|
||||
|
||||
## Multiple mount points
|
||||
|
||||
You can call `set_mount_point()` as many times as you like. Each URL path gets its own directory.
|
||||
|
||||
```cpp
|
||||
svr.set_mount_point("/", "./public");
|
||||
svr.set_mount_point("/assets", "./static/assets");
|
||||
svr.set_mount_point("/docs", "./documentation");
|
||||
```
|
||||
|
||||
A request to `/assets/style.css` serves `./static/assets/style.css`. A request to `/docs/guide.html` serves `./documentation/guide.html`.
|
||||
|
||||
## Combining with handlers
|
||||
|
||||
Static file serving and routing handlers — the kind you learned about in the previous chapter — work side by side.
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
// API endpoint
|
||||
svr.Get("/api/hello", [](const auto &, auto &res) {
|
||||
res.set_content(R"({"message":"Hello!"})", "application/json");
|
||||
});
|
||||
|
||||
// Static file serving
|
||||
svr.set_mount_point("/", "./public");
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
Handlers take priority. The handler responds to `/api/hello`. For every other path, the server looks for a file in `./public`.
|
||||
|
||||
## Adding response headers
|
||||
|
||||
Pass headers as the third argument to `set_mount_point()` and they get attached to every static file response. This is great for cache control.
|
||||
|
||||
```cpp
|
||||
svr.set_mount_point("/", "./public", {
|
||||
{"Cache-Control", "max-age=3600"}
|
||||
});
|
||||
```
|
||||
|
||||
With this in place, the browser caches served files for one hour.
|
||||
|
||||
## A Dockerfile for your static file server
|
||||
|
||||
The cpp-httplib repository includes a `Dockerfile` built for static file serving. We also publish a pre-built image on Docker Hub, so you can get up and running with a single command.
|
||||
|
||||
```sh
|
||||
> docker run -p 8080:80 -v ./my-site:/html yhirose4dockerhub/cpp-httplib-server
|
||||
Serving HTTP on 0.0.0.0:80
|
||||
Mount point: / -> ./html
|
||||
Press Ctrl+C to shutdown gracefully...
|
||||
192.168.65.1 - - [22/Feb/2026:12:00:00 +0000] "GET / HTTP/1.1" 200 256 "-" "Mozilla/5.0 ..."
|
||||
192.168.65.1 - - [22/Feb/2026:12:00:00 +0000] "GET /style.css HTTP/1.1" 200 1024 "-" "Mozilla/5.0 ..."
|
||||
192.168.65.1 - - [22/Feb/2026:12:00:01 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
|
||||
```
|
||||
|
||||
Everything in your `./my-site` directory gets served on port 8080. The access log follows the same format as NGINX, so you can see exactly what's happening.
|
||||
|
||||
## What's next
|
||||
|
||||
You can now serve static files. A web server that delivers HTML, CSS, and JavaScript — built with this little code.
|
||||
|
||||
Next, let's encrypt your connections with HTTPS. We'll start by setting up a TLS library.
|
||||
|
||||
**Next:** [TLS Setup](../05-tls-setup)
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "TLS Setup"
|
||||
order: 5
|
||||
---
|
||||
|
||||
So far we've been using plain HTTP, but in the real world, HTTPS is the norm. To use HTTPS with cpp-httplib, you need a TLS library.
|
||||
|
||||
In this tour, we'll use OpenSSL. It's the most widely used option, and you'll find plenty of resources online.
|
||||
|
||||
## Installing OpenSSL
|
||||
|
||||
Install it for your OS.
|
||||
|
||||
| OS | How to install |
|
||||
| -- | -------------- |
|
||||
| macOS | [Homebrew](https://brew.sh/) (`brew install openssl`) |
|
||||
| Ubuntu / Debian | `sudo apt install libssl-dev` |
|
||||
| Windows | [vcpkg](https://vcpkg.io/) (`vcpkg install openssl`) |
|
||||
|
||||
## Compile Options
|
||||
|
||||
To enable TLS, define the `CPPHTTPLIB_OPENSSL_SUPPORT` macro when compiling. You'll need a few extra options compared to the previous chapters.
|
||||
|
||||
```sh
|
||||
# macOS (Homebrew)
|
||||
clang++ -std=c++17 -DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||
-I$(brew --prefix openssl)/include \
|
||||
-L$(brew --prefix openssl)/lib \
|
||||
-lssl -lcrypto \
|
||||
-framework CoreFoundation -framework Security \
|
||||
-o server server.cpp
|
||||
|
||||
# Linux
|
||||
clang++ -std=c++17 -pthread -DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||
-lssl -lcrypto \
|
||||
-o server server.cpp
|
||||
|
||||
# Windows (Developer Command Prompt)
|
||||
cl /EHsc /std:c++17 /DCPPHTTPLIB_OPENSSL_SUPPORT server.cpp libssl.lib libcrypto.lib
|
||||
```
|
||||
|
||||
Let's look at what each option does.
|
||||
|
||||
- **`-DCPPHTTPLIB_OPENSSL_SUPPORT`** — Defines the macro that enables TLS support
|
||||
- **`-lssl -lcrypto`** — Links the OpenSSL libraries
|
||||
- **`-I` / `-L`** (macOS only) — Points to the Homebrew OpenSSL paths
|
||||
- **`-framework CoreFoundation -framework Security`** (macOS only) — Needed to automatically load system certificates from the Keychain
|
||||
|
||||
## Verifying the Setup
|
||||
|
||||
Let's make sure everything works. Here's a simple program that passes an HTTPS URL to `httplib::Client`.
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://www.google.com");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << "Status: " << res->status << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Compile and run it. If you see `Status: 200`, your setup is complete.
|
||||
|
||||
## Other TLS Backends
|
||||
|
||||
cpp-httplib also supports Mbed TLS and wolfSSL in addition to OpenSSL. You can switch between them just by changing the macro definition and linked libraries.
|
||||
|
||||
| Backend | Macro | Libraries to link |
|
||||
| :--- | :--- | :--- |
|
||||
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` |
|
||||
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
|
||||
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
|
||||
|
||||
This tour assumes OpenSSL, but the API is the same regardless of which backend you choose.
|
||||
|
||||
## Next Step
|
||||
|
||||
You're all set with TLS. Next, let's send a request to an HTTPS site.
|
||||
|
||||
**Next:** [HTTPS Client](../06-https-client)
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: "HTTPS Client"
|
||||
order: 6
|
||||
---
|
||||
|
||||
In the previous chapter, you set up OpenSSL. Now let's put it to use with an HTTPS client. You can use the same `httplib::Client` from Chapter 2. Just pass a URL with the `https://` scheme to the constructor.
|
||||
|
||||
## GET Request
|
||||
|
||||
Let's try accessing a real HTTPS site.
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200
|
||||
std::cout << res->body.substr(0, 100) << std::endl; // First 100 chars of the HTML
|
||||
} else {
|
||||
std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In Chapter 2, you wrote `httplib::Client cli("http://localhost:8080")`. All you need to change is the scheme to `https://`. Every API you learned in Chapter 2 -- `Get()`, `Post()`, and so on -- works exactly the same way.
|
||||
|
||||
```sh
|
||||
curl https://nghttp2.org/
|
||||
```
|
||||
|
||||
## Specifying a Port
|
||||
|
||||
The default port for HTTPS is 443. If you need a different port, include it in the URL.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
```
|
||||
|
||||
## CA Certificate Verification
|
||||
|
||||
When connecting over HTTPS, `httplib::Client` verifies the server certificate by default. It only connects to servers whose certificate was issued by a trusted CA (Certificate Authority).
|
||||
|
||||
CA certificates are loaded automatically from the Keychain on macOS, the system CA certificate store on Linux, and the Windows certificate store on Windows. In most cases, no extra configuration is needed.
|
||||
|
||||
### Specifying a CA Certificate File
|
||||
|
||||
On some environments, the system CA certificates may not be found. In that case, use `set_ca_cert_path()` to specify the path directly.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
cli.set_ca_cert_path("/etc/ssl/certs/ca-certificates.crt");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
```sh
|
||||
curl --cacert /etc/ssl/certs/ca-certificates.crt https://nghttp2.org/
|
||||
```
|
||||
|
||||
### Disabling Certificate Verification
|
||||
|
||||
During development, you might want to connect to a server with a self-signed certificate. You can disable verification for that.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -k https://localhost:8443/
|
||||
```
|
||||
|
||||
Never disable this in production. It opens you up to man-in-the-middle attacks.
|
||||
|
||||
## Following Redirects
|
||||
|
||||
When accessing HTTPS sites, you'll often encounter redirects. For example, `http://` to `https://`, or a bare domain to `www`.
|
||||
|
||||
By default, redirects are not followed. You can check the redirect target in the `Location` header.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect/3");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 302
|
||||
std::cout << res->get_header_value("Location") << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl https://nghttp2.org/httpbin/redirect/3
|
||||
```
|
||||
|
||||
Call `set_follow_location(true)` to automatically follow redirects and get the final response.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect/3");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200 (the final response)
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -L https://nghttp2.org/httpbin/redirect/3
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now you know how to use the HTTPS client. Next, let's set up your own HTTPS server. We'll start with creating a self-signed certificate.
|
||||
|
||||
**Next:** [HTTPS Server](../07-https-server)
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: "HTTPS Server"
|
||||
order: 7
|
||||
---
|
||||
|
||||
In the previous chapter, you used an HTTPS client. Now let's set up your own HTTPS server. Just swap `httplib::Server` from Chapter 3 with `httplib::SSLServer`.
|
||||
|
||||
A TLS server needs a server certificate and a private key, though. Let's get those ready first.
|
||||
|
||||
## Creating a Self-Signed Certificate
|
||||
|
||||
For development and testing, a self-signed certificate works just fine. You can generate one quickly with an OpenSSL command.
|
||||
|
||||
```sh
|
||||
openssl req -x509 -noenc -keyout key.pem -out cert.pem -subj /CN=localhost
|
||||
```
|
||||
|
||||
This creates two files:
|
||||
|
||||
- **`cert.pem`** — Server certificate
|
||||
- **`key.pem`** — Private key
|
||||
|
||||
## A Minimal HTTPS Server
|
||||
|
||||
Once you have your certificate, let's write the server.
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::SSLServer svr("cert.pem", "key.pem");
|
||||
|
||||
svr.Get("/", [](const auto &, auto &res) {
|
||||
res.set_content("Hello, HTTPS!", "text/plain");
|
||||
});
|
||||
|
||||
std::cout << "Listening on https://localhost:8443" << std::endl;
|
||||
svr.listen("0.0.0.0", 8443);
|
||||
}
|
||||
```
|
||||
|
||||
Just pass the certificate and private key paths to the `httplib::SSLServer` constructor. The routing API is exactly the same as `httplib::Server` from Chapter 3.
|
||||
|
||||
Compile and start it up.
|
||||
|
||||
## Testing It Out
|
||||
|
||||
With the server running, try accessing it with `curl`. Since we're using a self-signed certificate, add the `-k` option to skip certificate verification.
|
||||
|
||||
```sh
|
||||
curl -k https://localhost:8443/
|
||||
# Hello, HTTPS!
|
||||
```
|
||||
|
||||
If you open `https://localhost:8443` in a browser, you'll see a "This connection is not secure" warning. That's expected with a self-signed certificate. Just proceed past it.
|
||||
|
||||
## Connecting from a Client
|
||||
|
||||
Let's connect using `httplib::Client` from the previous chapter. There are two ways to connect to a server with a self-signed certificate.
|
||||
|
||||
### Option 1: Disable Certificate Verification
|
||||
|
||||
This is the quick and easy approach for development.
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // Hello, HTTPS!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Option 2: Specify the Self-Signed Certificate as a CA Certificate
|
||||
|
||||
This is the safer approach. You tell the client to trust `cert.pem` as a CA certificate.
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
cli.set_ca_cert_path("cert.pem");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // Hello, HTTPS!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, only connections to the server with that specific certificate are allowed, preventing impersonation. Use this approach whenever possible, even in test environments.
|
||||
|
||||
## Comparing Server and SSLServer
|
||||
|
||||
The `httplib::Server` API you learned in Chapter 3 works exactly the same with `httplib::SSLServer`. The only difference is the constructor.
|
||||
|
||||
| | `httplib::Server` | `httplib::SSLServer` |
|
||||
| -- | ------------------ | -------------------- |
|
||||
| Constructor | No arguments | Certificate and private key paths |
|
||||
| Protocol | HTTP | HTTPS |
|
||||
| Port (convention) | 8080 | 8443 |
|
||||
| Routing | Same | Same |
|
||||
|
||||
To switch an HTTP server to HTTPS, just change the constructor.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Your HTTPS server is up and running. You now have the basics of both HTTP/HTTPS clients and servers covered.
|
||||
|
||||
Next, let's look at the WebSocket support that was recently added to cpp-httplib.
|
||||
|
||||
**Next:** [WebSocket](../08-websocket)
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: "WebSocket"
|
||||
order: 8
|
||||
---
|
||||
|
||||
cpp-httplib supports WebSocket as well. Unlike HTTP request/response, WebSocket lets the server and client exchange messages in both directions. It's great for chat apps and real-time notifications.
|
||||
|
||||
Let's build an echo server and client right away.
|
||||
|
||||
## Echo Server
|
||||
|
||||
Here's an echo server that sends back whatever message it receives.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send(msg); // Send back the received message as-is
|
||||
}
|
||||
});
|
||||
|
||||
std::cout << "Listening on port 8080..." << std::endl;
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
You register a WebSocket handler with `svr.WebSocket()`. It works just like `svr.Get()` and `svr.Post()` from Chapter 3.
|
||||
|
||||
Inside the handler, `ws.read(msg)` waits for a message. When the connection closes, `read()` returns `false`, so the loop exits. `ws.send(msg)` sends a message back.
|
||||
|
||||
## Connecting from a Client
|
||||
|
||||
Let's connect to the server using `httplib::ws::WebSocketClient`.
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::ws::WebSocketClient client("ws://localhost:8080/ws");
|
||||
|
||||
if (!client.connect()) {
|
||||
std::cout << "Connection failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Send a message
|
||||
client.send("Hello, WebSocket!");
|
||||
|
||||
// Receive a response from the server
|
||||
std::string msg;
|
||||
if (client.read(msg)) {
|
||||
std::cout << msg << std::endl; // Hello, WebSocket!
|
||||
}
|
||||
|
||||
client.close();
|
||||
}
|
||||
```
|
||||
|
||||
Pass a URL in `ws://host:port/path` format to the constructor. Call `connect()` to start the connection, then use `send()` and `read()` to exchange messages.
|
||||
|
||||
## Text and Binary
|
||||
|
||||
WebSocket has two types of messages: text and binary. You can tell them apart by the return value of `read()`.
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/ws", [](const httplib::Request &, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
httplib::ws::ReadResult ret;
|
||||
while ((ret = ws.read(msg))) {
|
||||
if (ret == httplib::ws::Binary) {
|
||||
ws.send(msg.data(), msg.size()); // Send as binary
|
||||
} else {
|
||||
ws.send(msg); // Send as text
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- `ws.send(const std::string &)` — sends as a text message
|
||||
- `ws.send(const char *, size_t)` — sends as a binary message
|
||||
|
||||
The client-side API is the same.
|
||||
|
||||
## Accessing Request Information
|
||||
|
||||
You can read HTTP request information from the handshake through the first argument `req` in the handler. This is handy for checking authentication tokens.
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
auto token = req.get_header_value("Authorization");
|
||||
if (token.empty()) {
|
||||
ws.close(httplib::ws::CloseStatus::PolicyViolation, "unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send(msg);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Using WSS
|
||||
|
||||
WebSocket over HTTPS (WSS) is also supported. On the server side, just register a WebSocket handler on `httplib::SSLServer`.
|
||||
|
||||
```cpp
|
||||
httplib::SSLServer svr("cert.pem", "key.pem");
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8443);
|
||||
```
|
||||
|
||||
On the client side, use the `wss://` scheme.
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient client("wss://localhost:8443/ws");
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now you know the basics of WebSocket. This wraps up the Tour.
|
||||
|
||||
The next page gives you a summary of features we didn't cover in the Tour.
|
||||
|
||||
**Next:** [What's Next](../09-whats-next)
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
title: "What's Next"
|
||||
order: 9
|
||||
---
|
||||
|
||||
Great job finishing the Tour! You now have a solid grasp of the cpp-httplib basics. But there's a lot more to explore. Here's a quick overview of features we didn't cover in the Tour, organized by category.
|
||||
|
||||
## Streaming API
|
||||
|
||||
When you're working with LLM streaming responses or downloading large files, you don't want to load the entire response into memory. Use `stream::Get()` to process data chunk by chunk.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:11434");
|
||||
|
||||
auto result = httplib::stream::Get(cli, "/api/generate");
|
||||
|
||||
if (result) {
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also pass a `content_receiver` callback to `Get()`. This approach works with Keep-Alive.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
cli.Get("/stream", [](const char *data, size_t len) {
|
||||
std::cout.write(data, len);
|
||||
return true;
|
||||
});
|
||||
```
|
||||
|
||||
On the server side, you have `set_content_provider()` and `set_chunked_content_provider()`. Use the former when you know the size, and the latter when you don't.
|
||||
|
||||
```cpp
|
||||
// With known size (sets Content-Length)
|
||||
svr.Get("/file", [](const auto &, auto &res) {
|
||||
auto size = get_file_size("large.bin");
|
||||
res.set_content_provider(size, "application/octet-stream",
|
||||
[](size_t offset, size_t length, httplib::DataSink &sink) {
|
||||
// Send 'length' bytes starting from 'offset'
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
// Unknown size (Chunked Transfer Encoding)
|
||||
svr.Get("/stream", [](const auto &, auto &res) {
|
||||
res.set_chunked_content_provider("text/plain",
|
||||
[](size_t offset, httplib::DataSink &sink) {
|
||||
sink.write("chunk\n", 6);
|
||||
return true; // Return false to finish
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
For uploading large files, `make_file_provider()` comes in handy. It streams the file instead of loading it all into memory.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
auto res = cli.Post("/upload", {}, {
|
||||
httplib::make_file_provider("file", "/path/to/large-file.zip")
|
||||
});
|
||||
```
|
||||
|
||||
## Server-Sent Events (SSE)
|
||||
|
||||
We provide an SSE client as well. It supports automatic reconnection and resuming via `Last-Event-ID`.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << msg.event << ": " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start(); // Blocking, with auto-reconnection
|
||||
```
|
||||
|
||||
You can also set separate handlers for each event type.
|
||||
|
||||
```cpp
|
||||
sse.on_event("update", [](const httplib::sse::SSEMessage &msg) {
|
||||
// Only handles "update" events
|
||||
});
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
The client has helpers for Basic auth, Bearer Token auth, and Digest auth.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.set_basic_auth("user", "password");
|
||||
cli.set_bearer_token_auth("my-token");
|
||||
```
|
||||
|
||||
## Compression
|
||||
|
||||
We support compression and decompression with gzip, Brotli, and Zstandard. Define the corresponding macro when you compile.
|
||||
|
||||
| Method | Macro |
|
||||
| -- | -- |
|
||||
| gzip | `CPPHTTPLIB_ZLIB_SUPPORT` |
|
||||
| Brotli | `CPPHTTPLIB_BROTLI_SUPPORT` |
|
||||
| Zstandard | `CPPHTTPLIB_ZSTD_SUPPORT` |
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_compress(true); // Compress request body
|
||||
cli.set_decompress(true); // Decompress response body
|
||||
```
|
||||
|
||||
## Proxy
|
||||
|
||||
You can connect through an HTTP proxy.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_proxy("proxy.example.com", 8080);
|
||||
cli.set_proxy_basic_auth("user", "password");
|
||||
```
|
||||
|
||||
## Timeouts
|
||||
|
||||
You can set connection, read, and write timeouts individually.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_connection_timeout(5, 0); // 5 seconds
|
||||
cli.set_read_timeout(10, 0); // 10 seconds
|
||||
cli.set_write_timeout(10, 0); // 10 seconds
|
||||
```
|
||||
|
||||
## Keep-Alive
|
||||
|
||||
If you're making multiple requests to the same server, enable Keep-Alive. It reuses the TCP connection, which is much more efficient.
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_keep_alive(true);
|
||||
```
|
||||
|
||||
## Server Middleware
|
||||
|
||||
You can hook into request processing before and after handlers run.
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto &req, auto &res) {
|
||||
// Runs before every request
|
||||
return httplib::Server::HandlerResponse::Unhandled; // Continue to normal routing
|
||||
});
|
||||
|
||||
svr.set_post_routing_handler([](const auto &req, auto &res) {
|
||||
// Runs after the response is sent
|
||||
res.set_header("X-Server", "cpp-httplib");
|
||||
});
|
||||
```
|
||||
|
||||
Use `req.user_data` to pass data from middleware to handlers. This is useful for sharing things like decoded auth tokens.
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto &req, auto &res) {
|
||||
req.user_data["auth_user"] = std::string("alice");
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
|
||||
svr.Get("/me", [](const auto &req, auto &res) {
|
||||
auto user = std::any_cast<std::string>(req.user_data.at("auth_user"));
|
||||
res.set_content("Hello, " + user, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
You can also customize error and exception handlers.
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const auto &req, auto &res) {
|
||||
res.set_content("Custom Error Page", "text/html");
|
||||
});
|
||||
|
||||
svr.set_exception_handler([](const auto &req, auto &res, std::exception_ptr ep) {
|
||||
res.status = 500;
|
||||
res.set_content("Internal Server Error", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
You can set a logger on both the server and the client.
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const auto &req, const auto &res) {
|
||||
std::cout << req.method << " " << req.path << " " << res.status << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
## Unix Domain Socket
|
||||
|
||||
In addition to TCP, we support Unix Domain Sockets. You can use them for inter-process communication on the same machine.
|
||||
|
||||
```cpp
|
||||
// Server
|
||||
httplib::Server svr;
|
||||
svr.set_address_family(AF_UNIX);
|
||||
svr.listen("/tmp/httplib.sock", 0);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// Client
|
||||
httplib::Client cli("http://localhost");
|
||||
cli.set_address_family(AF_UNIX);
|
||||
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
## Learn More
|
||||
|
||||
Want to dig deeper? Check out these resources.
|
||||
|
||||
- Cookbook — A collection of recipes for common use cases
|
||||
- [README](https://github.com/yhirose/cpp-httplib/blob/master/README.md) — Full API reference
|
||||
- [README-sse](https://github.com/yhirose/cpp-httplib/blob/master/README-sse.md) — How to use Server-Sent Events
|
||||
- [README-stream](https://github.com/yhirose/cpp-httplib/blob/master/README-stream.md) — How to use the Streaming API
|
||||
- [README-websocket](https://github.com/yhirose/cpp-httplib/blob/master/README-websocket.md) — How to use the WebSocket server
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "A Tour of cpp-httplib"
|
||||
order: 1
|
||||
---
|
||||
|
||||
This is a step-by-step tutorial that walks you through the basics of cpp-httplib. Each chapter builds on the previous one, so please read them in order starting from Chapter 1.
|
||||
|
||||
1. [Getting Started](01-getting-started) — Get httplib.h and build a Hello World server
|
||||
2. [Basic Client](02-basic-client) — Send GET/POST requests and use path parameters
|
||||
3. [Basic Server](03-basic-server) — Routing, path parameters, and building responses
|
||||
4. [Static File Server](04-static-file-server) — Serve static files
|
||||
5. [TLS Setup](05-tls-setup) — Set up OpenSSL / mbedTLS
|
||||
6. [HTTPS Client](06-https-client) — Make requests to HTTPS sites
|
||||
7. [HTTPS Server](07-https-server) — Build an HTTPS server
|
||||
8. [WebSocket](08-websocket) — Learn the basics of WebSocket communication
|
||||
9. [What's Next](09-whats-next) — Explore more features
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: "Cookbook"
|
||||
order: 1
|
||||
---
|
||||
|
||||
This section is under construction.
|
||||
|
||||
Check back soon for a collection of recipes organized by topic.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "cpp-httplib"
|
||||
order: 0
|
||||
---
|
||||
|
||||
[cpp-httplib](https://github.com/yhirose/cpp-httplib)は、C++用のHTTP/HTTPSライブラリです。[`httplib.h`](https://github.com/yhirose/cpp-httplib/raw/refs/tags/latest/httplib.h) というヘッダーファイルを1枚コピーするだけで使えます。
|
||||
|
||||
C++でちょっとしたHTTPサーバーやクライアントが必要になったとき、すぐに動くものが欲しいですよね。cpp-httplibはまさにそのために作られました。サーバーもクライアントも、数行のコードで書き始められます。
|
||||
|
||||
APIはラムダ式をベースにした直感的な設計で、C++11以降のコンパイラーがあればどこでも動きます。Windows、macOS、Linux — お使いの環境をそのまま使えます。
|
||||
|
||||
HTTPSも使えます。OpenSSLやmbedTLSをリンクするだけで、サーバー・クライアントの両方がTLSに対応します。Content-Encoding(gzip, brotli等)、ファイルアップロードなど、実際の開発で必要になる機能もひと通り揃っています。WebSocketもサポートしています。
|
||||
|
||||
内部的にはブロッキングI/Oとスレッドプールを使っています。大量の同時接続を捌くような用途には向きませんが、APIサーバーやツールの組み込みHTTP、テスト用のモックサーバーなど、多くのユースケースで十分な性能を発揮します。
|
||||
|
||||
「今日の課題を、今日中に解決する」— cpp-httplibが目指しているのは、そういうシンプルさです。
|
||||
|
||||
## ドキュメント
|
||||
|
||||
- [A Tour of cpp-httplib](tour/) — 基本を順を追って学べるチュートリアル。初めての方はここから
|
||||
- [Cookbook](cookbook/) — 目的別のレシピ集。必要なトピックから読めます
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "Getting Started"
|
||||
order: 1
|
||||
---
|
||||
|
||||
cpp-httplibを始めるのに必要なのは、`httplib.h`とC++コンパイラーだけです。ファイルをダウンロードして、Hello Worldサーバーを動かすところまでやってみましょう。
|
||||
|
||||
## httplib.h の入手
|
||||
|
||||
GitHubから直接ダウンロードできます。常に最新版を使ってください。
|
||||
|
||||
```sh
|
||||
curl -LO https://github.com/yhirose/cpp-httplib/raw/refs/tags/latest/httplib.h
|
||||
```
|
||||
|
||||
ダウンロードした `httplib.h` をプロジェクトのディレクトリに置けば、準備完了です。
|
||||
|
||||
## コンパイラーの準備
|
||||
|
||||
| OS | 開発環境 | セットアップ |
|
||||
| -- | -------- | ------------ |
|
||||
| macOS | Apple Clang | Xcode Command Line Tools (`xcode-select --install`) |
|
||||
| Ubuntu | clang++ または g++ | `apt install clang` または `apt install g++` |
|
||||
| Windows | MSVC | Visual Studio 2022 以降(C++ コンポーネントを含めてインストール) |
|
||||
|
||||
## Hello World サーバー
|
||||
|
||||
次のコードを `server.cpp` として保存しましょう。
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/", [](const httplib::Request&, httplib::Response& res) {
|
||||
res.set_content("Hello, World!", "text/plain");
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
たった数行で、HTTPリクエストに応答するサーバーが書けます。
|
||||
|
||||
## コンパイルと実行
|
||||
|
||||
このチュートリアルのサンプルコードは、コードを簡潔に書けるC++17で書いています。cpp-httplib自体はC++11でもコンパイルできます。
|
||||
|
||||
```sh
|
||||
# macOS
|
||||
clang++ -std=c++17 -o server server.cpp
|
||||
|
||||
# Linux
|
||||
# `-pthread`: cpp-httplibは内部でスレッドを使用
|
||||
clang++ -std=c++17 -pthread -o server server.cpp
|
||||
|
||||
# Windows (Developer Command Prompt)
|
||||
# `/EHsc`: C++例外処理を有効化
|
||||
cl /EHsc /std:c++17 server.cpp
|
||||
```
|
||||
|
||||
コンパイルできたら実行します。
|
||||
|
||||
```sh
|
||||
# macOS / Linux
|
||||
./server
|
||||
|
||||
# Windows
|
||||
server.exe
|
||||
```
|
||||
|
||||
ブラウザで `http://localhost:8080` を開いてください。"Hello, World!" と表示されれば成功です。
|
||||
|
||||
`curl` でも確認できます。
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/
|
||||
# Hello, World!
|
||||
```
|
||||
|
||||
サーバーを停止するには、ターミナルで `Ctrl+C` を押します。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
サーバーの基本がわかりましたね。次は、クライアント側を見てみましょう。cpp-httplibはHTTPクライアント機能も備えています。
|
||||
|
||||
**次:** [Basic Client](../02-basic-client)
|
||||
@@ -0,0 +1,266 @@
|
||||
---
|
||||
title: "Basic Client"
|
||||
order: 2
|
||||
---
|
||||
|
||||
cpp-httplibはサーバーだけでなく、HTTPクライアント機能も備えています。`httplib::Client` を使って、GETやPOSTリクエストを送ってみましょう。
|
||||
|
||||
## テスト用サーバーの準備
|
||||
|
||||
クライアントの動作を確認するために、リクエストを受け付けるサーバーを用意します。次のコードを保存し、前章と同じ手順でコンパイル・実行してください。サーバーの詳しい解説は次章で行います。
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/hi", [](const auto &, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/search", [](const auto &req, auto &res) {
|
||||
auto q = req.get_param_value("q");
|
||||
res.set_content("Query: " + q, "text/plain");
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const auto &req, auto &res) {
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
|
||||
svr.Post("/submit", [](const auto &req, auto &res) {
|
||||
std::string result;
|
||||
for (auto &[key, val] : req.params) {
|
||||
result += key + " = " + val + "\n";
|
||||
}
|
||||
res.set_content(result, "text/plain");
|
||||
});
|
||||
|
||||
svr.Post("/upload", [](const auto &req, auto &res) {
|
||||
auto f = req.form.get_file("file");
|
||||
auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
|
||||
res.set_content(content, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get("/users/:id", [](const auto &req, auto &res) {
|
||||
auto id = req.path_params.at("id");
|
||||
res.set_content("User ID: " + id, "text/plain");
|
||||
});
|
||||
|
||||
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
|
||||
auto id = req.matches[1];
|
||||
res.set_content("File ID: " + std::string(id), "text/plain");
|
||||
});
|
||||
|
||||
std::cout << "Listening on port 8080..." << std::endl;
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
## GETリクエスト
|
||||
|
||||
サーバーが起動したら、別のターミナルを開いて試してみましょう。まず、最もシンプルなGETリクエストです。
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
auto res = cli.Get("/hi");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200
|
||||
std::cout << res->body << std::endl; // Hello!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`httplib::Client` のコンストラクターにサーバーのアドレスを渡し、`Get()` でリクエストを送ります。戻り値の `res` からステータスコードやボディを取得できます。
|
||||
|
||||
対応する `curl` コマンドはこうなります。
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/hi
|
||||
# Hello!
|
||||
```
|
||||
|
||||
## レスポンスの確認
|
||||
|
||||
レスポンスには、ステータスコードとボディ以外にもヘッダー情報が含まれています。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/hi");
|
||||
if (res) {
|
||||
// ステータスコード
|
||||
std::cout << res->status << std::endl; // 200
|
||||
|
||||
// ボディ
|
||||
std::cout << res->body << std::endl; // Hello!
|
||||
|
||||
// ヘッダー
|
||||
std::cout << res->get_header_value("Content-Type") << std::endl; // text/plain
|
||||
}
|
||||
```
|
||||
|
||||
`res->body` は `std::string` なので、JSON レスポンスをパースしたい場合は [nlohmann/json](https://github.com/nlohmann/json) などの JSON ライブラリにそのまま渡せます。
|
||||
|
||||
## クエリパラメーター
|
||||
|
||||
GETリクエストにクエリパラメーターを付けるには、URLに直接書くか、`httplib::Params` を使います。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/search", httplib::Params{{"q", "cpp-httplib"}});
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // Query: cpp-httplib
|
||||
}
|
||||
```
|
||||
|
||||
`httplib::Params` を使うと、特殊文字のURLエンコードを自動で行ってくれます。
|
||||
|
||||
```sh
|
||||
curl "http://localhost:8080/search?q=cpp-httplib"
|
||||
# Query: cpp-httplib
|
||||
```
|
||||
|
||||
## パスパラメーター
|
||||
|
||||
URLのパスに値を直接埋め込む場合も、クライアント側は特別なAPIは不要です。パスをそのまま `Get()` に渡すだけです。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/users/42");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // User ID: 42
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/users/42
|
||||
# User ID: 42
|
||||
```
|
||||
|
||||
テスト用サーバーには、正規表現でIDを数字のみに絞った `/files/(\d+)` もあります。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/files/42");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // File ID: 42
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080/files/42
|
||||
# File ID: 42
|
||||
```
|
||||
|
||||
`/files/abc` のように数字以外を渡すと404が返ります。仕組みは次章で解説します。
|
||||
|
||||
## リクエストヘッダー
|
||||
|
||||
カスタムHTTPヘッダーを付けるには、`httplib::Headers` を渡します。`Get()` や `Post()` のどちらでも使えます。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Get("/hi", httplib::Headers{
|
||||
{"Authorization", "Bearer my-token"}
|
||||
});
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -H "Authorization: Bearer my-token" http://localhost:8080/hi
|
||||
```
|
||||
|
||||
## POSTリクエスト
|
||||
|
||||
テキストデータをPOSTしてみましょう。`Post()` の第2引数にボディ、第3引数にContent-Typeを指定します。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Post("/post", "Hello, Server!", "text/plain");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200
|
||||
std::cout << res->body << std::endl; // Hello, Server!
|
||||
}
|
||||
```
|
||||
|
||||
テスト用サーバーの `/post` はボディをそのまま返すので、送った文字列がそのまま返ってきます。
|
||||
|
||||
```sh
|
||||
curl -X POST -H "Content-Type: text/plain" -d "Hello, Server!" http://localhost:8080/post
|
||||
# Hello, Server!
|
||||
```
|
||||
|
||||
## フォームデータの送信
|
||||
|
||||
HTMLフォームのように、キーと値のペアを送ることもできます。`httplib::Params` を使います。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Post("/submit", httplib::Params{
|
||||
{"name", "Alice"},
|
||||
{"age", "30"}
|
||||
});
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl;
|
||||
// age = 30
|
||||
// name = Alice
|
||||
}
|
||||
```
|
||||
|
||||
これは `application/x-www-form-urlencoded` 形式で送信されます。
|
||||
|
||||
```sh
|
||||
curl -X POST -d "name=Alice&age=30" http://localhost:8080/submit
|
||||
```
|
||||
|
||||
## ファイルのPOST
|
||||
|
||||
ファイルをアップロードするには、`httplib::UploadFormDataItems` を使ってマルチパートフォームデータとして送信します。
|
||||
|
||||
```cpp
|
||||
auto res = cli.Post("/upload", httplib::UploadFormDataItems{
|
||||
{"file", "Hello, File!", "hello.txt", "text/plain"}
|
||||
});
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // hello.txt (12 bytes)
|
||||
}
|
||||
```
|
||||
|
||||
`UploadFormDataItems` の各要素は `{name, content, filename, content_type}` の4つのフィールドで構成されます。
|
||||
|
||||
```sh
|
||||
curl -F "file=Hello, File!;filename=hello.txt;type=text/plain" http://localhost:8080/upload
|
||||
```
|
||||
|
||||
## エラーハンドリング
|
||||
|
||||
ネットワーク通信では、サーバーに接続できない場合があります。`res` が有効かどうかを必ず確認しましょう。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:9999"); // 存在しないポート
|
||||
auto res = cli.Get("/hi");
|
||||
|
||||
if (!res) {
|
||||
// 接続エラー
|
||||
std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
|
||||
// Error: Connection
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ここに到達すればレスポンスを受信できている
|
||||
if (res->status != 200) {
|
||||
std::cout << "HTTP Error: " << res->status << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << res->body << std::endl;
|
||||
```
|
||||
|
||||
エラーには2つのレベルがあります。
|
||||
|
||||
- **接続エラー**: サーバーに到達できなかった場合。`res` が偽になり、`res.error()` でエラーの種類を取得できます
|
||||
- **HTTPエラー**: サーバーからエラーステータス(404、500など)が返ってきた場合。`res` は真ですが、`res->status` を確認する必要があります
|
||||
|
||||
## 次のステップ
|
||||
|
||||
クライアントからリクエストを送る方法がわかりました。次は、サーバー側をもっと詳しく見てみましょう。ルーティングやパスパラメータなど、サーバーの機能を掘り下げます。
|
||||
|
||||
**次:** [Basic Server](../03-basic-server)
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
title: "Basic Server"
|
||||
order: 3
|
||||
---
|
||||
|
||||
前章ではクライアントからリクエストを送りました。そのとき、テスト用サーバーを用意しましたね。この章では、あのサーバーの仕組みをひとつずつ紐解いていきます。
|
||||
|
||||
## サーバーの起動
|
||||
|
||||
ルーティングを登録したら、最後に `svr.listen()` を呼んでサーバーを起動します。
|
||||
|
||||
```cpp
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
第1引数はホスト、第2引数はポート番号です。`"0.0.0.0"` を指定すると、すべてのネットワークインターフェースでリクエストを受け付けます。自分のマシンからのアクセスだけに限定したいときは `"127.0.0.1"` を使います。
|
||||
|
||||
`listen()` はブロッキング呼び出しです。サーバーが停止するまで、この行から先には進みません。ターミナルで `Ctrl+C` を押すか、別スレッドから `svr.stop()` を呼ぶまでサーバーは動き続けます。
|
||||
|
||||
## ルーティング
|
||||
|
||||
サーバーの核になるのは「ルーティング」です。どのURLに、どのHTTPメソッドでアクセスされたら、何をするか。それを登録する仕組みです。
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
svr.Get("/hi", [](const httplib::Request &req, httplib::Response &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`svr.Get()` は、GETリクエストに対するハンドラーを登録します。第1引数がパス、第2引数がハンドラー関数です。`/hi` にGETリクエストが来たら、このラムダが呼ばれます。
|
||||
|
||||
HTTPメソッドごとにメソッドが用意されています。
|
||||
|
||||
```cpp
|
||||
svr.Get("/path", handler); // GET
|
||||
svr.Post("/path", handler); // POST
|
||||
svr.Put("/path", handler); // PUT
|
||||
svr.Delete("/path", handler); // DELETE
|
||||
```
|
||||
|
||||
ハンドラーのシグネチャは `(const httplib::Request &req, httplib::Response &res)` です。`auto` を使って短く書くこともできます。
|
||||
|
||||
```cpp
|
||||
svr.Get("/hi", [](const auto &req, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
パスが一致したときだけハンドラーが呼ばれます。登録されていないパスにアクセスすると、自動的に404が返ります。
|
||||
|
||||
## リクエストオブジェクト
|
||||
|
||||
ハンドラーの第1引数 `req` から、クライアントが送ってきた情報を読み取れます。
|
||||
|
||||
### ボディ
|
||||
|
||||
`req.body` でリクエストボディを取得できます。型は `std::string` です。
|
||||
|
||||
```cpp
|
||||
svr.Post("/post", [](const auto &req, auto &res) {
|
||||
// クライアントが送ったボディをそのまま返す
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### ヘッダー
|
||||
|
||||
`req.get_header_value()` でリクエストヘッダーの値を取得できます。
|
||||
|
||||
```cpp
|
||||
svr.Get("/check", [](const auto &req, auto &res) {
|
||||
auto auth = req.get_header_value("Authorization");
|
||||
res.set_content("Auth: " + auth, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### クエリパラメーターとフォームデータ
|
||||
|
||||
`req.get_param_value()` でパラメーターを取得できます。GETのクエリパラメーターと、POSTのフォームデータの両方に使えます。
|
||||
|
||||
```cpp
|
||||
svr.Get("/search", [](const auto &req, auto &res) {
|
||||
auto q = req.get_param_value("q");
|
||||
res.set_content("Query: " + q, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`/search?q=cpp-httplib` にアクセスすると、`q` の値は `"cpp-httplib"` になります。
|
||||
|
||||
すべてのパラメーターをループで処理したいときは、`req.params` を使います。
|
||||
|
||||
```cpp
|
||||
svr.Post("/submit", [](const auto &req, auto &res) {
|
||||
std::string result;
|
||||
for (auto &[key, val] : req.params) {
|
||||
result += key + " = " + val + "\n";
|
||||
}
|
||||
res.set_content(result, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### ファイルアップロード
|
||||
|
||||
マルチパートフォームでアップロードされたファイルは、`req.form.get_file()` で取得します。
|
||||
|
||||
```cpp
|
||||
svr.Post("/upload", [](const auto &req, auto &res) {
|
||||
auto f = req.form.get_file("file");
|
||||
auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
|
||||
res.set_content(content, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`f.filename` でファイル名、`f.content` でファイルの中身にアクセスできます。
|
||||
|
||||
## パスパラメーター
|
||||
|
||||
URLの一部を変数として受け取りたいことがあります。たとえば `/users/42` の `42` を取得したい場合です。`:param` 記法を使うと、URLの一部をキャプチャできます。
|
||||
|
||||
```cpp
|
||||
svr.Get("/users/:id", [](const auto &req, auto &res) {
|
||||
auto id = req.path_params.at("id");
|
||||
res.set_content("User ID: " + id, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`/users/42` にアクセスすると、`req.path_params.at("id")` は `"42"` を返します。`/users/100` なら `"100"` です。
|
||||
|
||||
複数のパスパラメーターも使えます。
|
||||
|
||||
```cpp
|
||||
svr.Get("/users/:user_id/posts/:post_id", [](const auto &req, auto &res) {
|
||||
auto user_id = req.path_params.at("user_id");
|
||||
auto post_id = req.path_params.at("post_id");
|
||||
res.set_content("User: " + user_id + ", Post: " + post_id, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### 正規表現パターン
|
||||
|
||||
`:param` の代わりに正規表現をパスに書くこともできます。キャプチャグループの値は `req.matches` で取得します。型は `std::smatch` です。
|
||||
|
||||
```cpp
|
||||
// 数字のみのIDを受け付ける
|
||||
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
|
||||
auto id = req.matches[1]; // 最初のキャプチャグループ
|
||||
res.set_content("File ID: " + std::string(id), "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`/files/42` にはマッチしますが、`/files/abc` にはマッチしません。入力値を絞り込みたいときに便利です。
|
||||
|
||||
## レスポンスの組み立て
|
||||
|
||||
ハンドラーの第2引数 `res` を使って、クライアントに返すレスポンスを組み立てます。
|
||||
|
||||
### ボディとContent-Type
|
||||
|
||||
`res.set_content()` でボディとContent-Typeを設定します。これだけでステータスコード200のレスポンスが返ります。
|
||||
|
||||
```cpp
|
||||
svr.Get("/hi", [](const auto &req, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### ステータスコード
|
||||
|
||||
ステータスコードを変えたいときは、`res.status` に代入します。
|
||||
|
||||
```cpp
|
||||
svr.Get("/not-found", [](const auto &req, auto &res) {
|
||||
res.status = 404;
|
||||
res.set_content("Not found", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
### レスポンスヘッダー
|
||||
|
||||
`res.set_header()` でレスポンスヘッダーを追加できます。
|
||||
|
||||
```cpp
|
||||
svr.Get("/with-header", [](const auto &req, auto &res) {
|
||||
res.set_header("X-Custom", "my-value");
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
## 前章のサーバーを読み解く
|
||||
|
||||
ここまでの知識を使って、前章で用意したテスト用サーバーを改めて見てみましょう。
|
||||
|
||||
### GET /hi
|
||||
|
||||
```cpp
|
||||
svr.Get("/hi", [](const auto &, auto &res) {
|
||||
res.set_content("Hello!", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
最もシンプルなハンドラーです。リクエストの情報は使わないので、`req` の変数名を省略しています。`"Hello!"` というテキストをそのまま返します。
|
||||
|
||||
### GET /search
|
||||
|
||||
```cpp
|
||||
svr.Get("/search", [](const auto &req, auto &res) {
|
||||
auto q = req.get_param_value("q");
|
||||
res.set_content("Query: " + q, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`req.get_param_value("q")` でクエリパラメーター `q` の値を取り出します。`/search?q=cpp-httplib` なら、レスポンスは `"Query: cpp-httplib"` になります。
|
||||
|
||||
### POST /post
|
||||
|
||||
```cpp
|
||||
svr.Post("/post", [](const auto &req, auto &res) {
|
||||
res.set_content(req.body, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
クライアントが送ったリクエストボディを、そのままレスポンスとして返すエコーサーバーです。`req.body` にボディが丸ごと入っています。
|
||||
|
||||
### POST /submit
|
||||
|
||||
```cpp
|
||||
svr.Post("/submit", [](const auto &req, auto &res) {
|
||||
std::string result;
|
||||
for (auto &[key, val] : req.params) {
|
||||
result += key + " = " + val + "\n";
|
||||
}
|
||||
res.set_content(result, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
フォームデータとして送られたキーと値のペアを、`req.params` でループ処理しています。構造化束縛 `auto &[key, val]` を使って、各ペアを取り出しています。
|
||||
|
||||
### POST /upload
|
||||
|
||||
```cpp
|
||||
svr.Post("/upload", [](const auto &req, auto &res) {
|
||||
auto f = req.form.get_file("file");
|
||||
auto content = f.filename + " (" + std::to_string(f.content.size()) + " bytes)";
|
||||
res.set_content(content, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
マルチパートフォームで送られたファイルを受け取ります。`req.form.get_file("file")` で `"file"` という名前のフィールドを取得し、`f.filename` と `f.content.size()` でファイル名とサイズを返しています。
|
||||
|
||||
### GET /users/:id
|
||||
|
||||
```cpp
|
||||
svr.Get("/users/:id", [](const auto &req, auto &res) {
|
||||
auto id = req.path_params.at("id");
|
||||
res.set_content("User ID: " + id, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
`:id` の部分がパスパラメーターです。`req.path_params.at("id")` で値を取り出しています。`/users/42` なら `"42"`、`/users/alice` なら `"alice"` が得られます。
|
||||
|
||||
### GET /files/(\d+)
|
||||
|
||||
```cpp
|
||||
svr.Get(R"(/files/(\d+))", [](const auto &req, auto &res) {
|
||||
auto id = req.matches[1];
|
||||
res.set_content("File ID: " + std::string(id), "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
正規表現 `(\d+)` で数字だけのIDにマッチします。`/files/42` にはマッチしますが、`/files/abc` は404になります。`req.matches[1]` で最初のキャプチャグループの値を取得しています。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
サーバーの基本がわかりましたね。ルーティング、リクエストの読み取り、レスポンスの組み立て。これだけで、十分に実用的なAPIサーバーが作れます。
|
||||
|
||||
次は、静的ファイルの配信を見てみましょう。HTMLやCSSを配信するサーバーを作ります。
|
||||
|
||||
**次:** [Static File Server](../04-static-file-server)
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
title: "Static File Server"
|
||||
order: 4
|
||||
---
|
||||
|
||||
cpp-httplibは、HTMLやCSS、画像ファイルなどの静的ファイルも配信できます。面倒な設定は要りません。`set_mount_point()` を1行呼ぶだけです。
|
||||
|
||||
## set_mount_point の基本
|
||||
|
||||
さっそくやってみましょう。`set_mount_point()` は、URLのパスとローカルディレクトリを紐づけます。
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.set_mount_point("/", "./html");
|
||||
|
||||
std::cout << "Listening on port 8080..." << std::endl;
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
第1引数がURLのマウントポイント、第2引数がローカルのディレクトリパスです。この例だと、`/` へのリクエストを `./html` ディレクトリから配信します。
|
||||
|
||||
試してみましょう。まず `html` ディレクトリを作って、`index.html` を置きます。
|
||||
|
||||
```sh
|
||||
mkdir html
|
||||
```
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>My Page</title></head>
|
||||
<body>
|
||||
<h1>Hello from cpp-httplib!</h1>
|
||||
<p>This is a static file.</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
コンパイルして起動します。
|
||||
|
||||
```sh
|
||||
g++ -std=c++17 -o server server.cpp -pthread
|
||||
./server
|
||||
```
|
||||
|
||||
ブラウザで `http://localhost:8080` を開いてみてください。`html/index.html` の内容が表示されるはずです。`http://localhost:8080/index.html` でも同じページが返ります。
|
||||
|
||||
もちろん、前章のクライアントコードや `curl` でもアクセスできますよ。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // HTMLが表示される
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl http://localhost:8080
|
||||
```
|
||||
|
||||
## 複数のマウントポイント
|
||||
|
||||
`set_mount_point()` は何回でも呼べます。URLのパスごとに、別々のディレクトリを割り当てられます。
|
||||
|
||||
```cpp
|
||||
svr.set_mount_point("/", "./public");
|
||||
svr.set_mount_point("/assets", "./static/assets");
|
||||
svr.set_mount_point("/docs", "./documentation");
|
||||
```
|
||||
|
||||
`/assets/style.css` なら `./static/assets/style.css` を、`/docs/guide.html` なら `./documentation/guide.html` を配信します。
|
||||
|
||||
## ハンドラーとの組み合わせ
|
||||
|
||||
静的ファイルの配信と、前章で学んだルーティングハンドラーは共存できます。
|
||||
|
||||
```cpp
|
||||
httplib::Server svr;
|
||||
|
||||
// APIエンドポイント
|
||||
svr.Get("/api/hello", [](const auto &, auto &res) {
|
||||
res.set_content(R"({"message":"Hello!"})", "application/json");
|
||||
});
|
||||
|
||||
// 静的ファイル配信
|
||||
svr.set_mount_point("/", "./public");
|
||||
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
```
|
||||
|
||||
ハンドラーが先に評価されます。`/api/hello` にはハンドラーが応答し、それ以外のパスは `./public` ディレクトリからファイルを探します。
|
||||
|
||||
## レスポンスヘッダーの追加
|
||||
|
||||
`set_mount_point()` の第3引数にヘッダーを渡すと、静的ファイルのレスポンスにカスタムヘッダーを付けられます。キャッシュ制御に便利です。
|
||||
|
||||
```cpp
|
||||
svr.set_mount_point("/", "./public", {
|
||||
{"Cache-Control", "max-age=3600"}
|
||||
});
|
||||
```
|
||||
|
||||
こうすると、ブラウザは配信されたファイルを1時間キャッシュします。
|
||||
|
||||
## 静的ファイルサーバー用のDockerファイル
|
||||
|
||||
cpp-httplibのリポジトリには、静的ファイルサーバー用の `Dockerfile` が含まれています。Docker Hubにビルド済みイメージも公開しているので、1コマンドで起動できます。
|
||||
|
||||
```sh
|
||||
> docker run -p 8080:80 -v ./my-site:/html yhirose4dockerhub/cpp-httplib-server
|
||||
Serving HTTP on 0.0.0.0:80
|
||||
Mount point: / -> ./html
|
||||
Press Ctrl+C to shutdown gracefully...
|
||||
192.168.65.1 - - [22/Feb/2026:12:00:00 +0000] "GET / HTTP/1.1" 200 256 "-" "Mozilla/5.0 ..."
|
||||
192.168.65.1 - - [22/Feb/2026:12:00:00 +0000] "GET /style.css HTTP/1.1" 200 1024 "-" "Mozilla/5.0 ..."
|
||||
192.168.65.1 - - [22/Feb/2026:12:00:01 +0000] "GET /favicon.ico HTTP/1.1" 404 152 "-" "Mozilla/5.0 ..."
|
||||
```
|
||||
|
||||
`./my-site` ディレクトリの中身が、そのままポート8080で配信されます。NGINXと同じログ形式で、アクセスの様子を確認できますよ。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
静的ファイルの配信ができるようになりましたね。HTMLやCSS、JavaScriptを配信するWebサーバーが、これだけのコードで作れます。
|
||||
|
||||
次は、HTTPSで暗号化通信をしてみましょう。まずはTLSライブラリのセットアップからです。
|
||||
|
||||
**次:** [TLS Setup](../05-tls-setup)
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
title: "TLS Setup"
|
||||
order: 5
|
||||
---
|
||||
|
||||
ここまではHTTP(平文)でやってきましたが、実際のWebではHTTPS(暗号化通信)が当たり前ですよね。cpp-httplibでHTTPSを使うには、TLSライブラリが必要です。
|
||||
|
||||
このTourではOpenSSLを使います。最も広く使われていて、情報も豊富です。
|
||||
|
||||
## OpenSSLのインストール
|
||||
|
||||
お使いのOSに合わせてインストールしましょう。
|
||||
|
||||
| OS | インストール方法 |
|
||||
| -- | ---------------- |
|
||||
| macOS | [Homebrew](https://brew.sh/) (`brew install openssl`) |
|
||||
| Ubuntu / Debian | `sudo apt install libssl-dev` |
|
||||
| Windows | [vcpkg](https://vcpkg.io/) (`vcpkg install openssl`) |
|
||||
|
||||
## コンパイルオプション
|
||||
|
||||
TLS機能を有効にするには、`CPPHTTPLIB_OPENSSL_SUPPORT` マクロを定義してコンパイルします。前章までのコンパイルコマンドに、いくつかオプションが増えます。
|
||||
|
||||
```sh
|
||||
# macOS (Homebrew)
|
||||
clang++ -std=c++17 -DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||
-I$(brew --prefix openssl)/include \
|
||||
-L$(brew --prefix openssl)/lib \
|
||||
-lssl -lcrypto \
|
||||
-framework CoreFoundation -framework Security \
|
||||
-o server server.cpp
|
||||
|
||||
# Linux
|
||||
clang++ -std=c++17 -pthread -DCPPHTTPLIB_OPENSSL_SUPPORT \
|
||||
-lssl -lcrypto \
|
||||
-o server server.cpp
|
||||
|
||||
# Windows (Developer Command Prompt)
|
||||
cl /EHsc /std:c++17 /DCPPHTTPLIB_OPENSSL_SUPPORT server.cpp libssl.lib libcrypto.lib
|
||||
```
|
||||
|
||||
それぞれのオプションの役割を見てみましょう。
|
||||
|
||||
- **`-DCPPHTTPLIB_OPENSSL_SUPPORT`** — TLS機能を有効にするマクロ定義
|
||||
- **`-lssl -lcrypto`** — OpenSSLのライブラリをリンク
|
||||
- **`-I` / `-L`**(macOSのみ)— Homebrew版OpenSSLのパスを指定
|
||||
- **`-framework CoreFoundation -framework Security`**(macOSのみ)— Keychainからシステム証明書を自動で読み込むために必要です
|
||||
|
||||
## 動作確認
|
||||
|
||||
ちゃんと動くか確認してみましょう。`httplib::Client` にHTTPSのURLを渡してアクセスするだけのプログラムです。
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://www.google.com");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << "Status: " << res->status << std::endl;
|
||||
} else {
|
||||
std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
コンパイルして実行してみてください。`Status: 200` と表示されれば、セットアップ完了です。
|
||||
|
||||
## 他のTLSバックエンド
|
||||
|
||||
cpp-httplibはOpenSSL以外にも、Mbed TLSとwolfSSLに対応しています。マクロ定義とリンクするライブラリを変えるだけで切り替えられます。
|
||||
|
||||
| バックエンド | マクロ定義 | リンクするライブラリ |
|
||||
| :--- | :--- | :--- |
|
||||
| OpenSSL | `CPPHTTPLIB_OPENSSL_SUPPORT` | `libssl`, `libcrypto` |
|
||||
| Mbed TLS | `CPPHTTPLIB_MBEDTLS_SUPPORT` | `libmbedtls`, `libmbedx509`, `libmbedcrypto` |
|
||||
| wolfSSL | `CPPHTTPLIB_WOLFSSL_SUPPORT` | `libwolfssl` |
|
||||
|
||||
このTourではOpenSSLを前提に進めますが、APIはどのバックエンドでも共通です。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
TLSの準備ができましたね。次は、HTTPSサイトにリクエストを送ってみましょう。
|
||||
|
||||
**次:** [HTTPS Client](../06-https-client)
|
||||
@@ -0,0 +1,122 @@
|
||||
---
|
||||
title: "HTTPS Client"
|
||||
order: 6
|
||||
---
|
||||
|
||||
前章でOpenSSLのセットアップが済んだので、さっそくHTTPSクライアントを使ってみましょう。2章で使った `httplib::Client` がそのまま使えます。コンストラクタに `https://` 付きのURLを渡すだけです。
|
||||
|
||||
## GETリクエスト
|
||||
|
||||
実在するHTTPSサイトにアクセスしてみましょう。
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200
|
||||
std::cout << res->body.substr(0, 100) << std::endl; // HTMLの先頭部分
|
||||
} else {
|
||||
std::cout << "Error: " << httplib::to_string(res.error()) << std::endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2章では `httplib::Client cli("http://localhost:8080")` と書きましたよね。スキームを `https://` に変えるだけです。`Get()` や `Post()` など、2章で学んだAPIはすべてそのまま使えます。
|
||||
|
||||
```sh
|
||||
curl https://nghttp2.org/
|
||||
```
|
||||
|
||||
## ポートの指定
|
||||
|
||||
HTTPSのデフォルトポートは443です。別のポートを使いたい場合は、URLにポートを含めます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
```
|
||||
|
||||
## CA証明書の検証
|
||||
|
||||
`httplib::Client` はHTTPS接続時、デフォルトでサーバー証明書を検証します。信頼できるCA(認証局)が発行した証明書を持つサーバーにしか接続しません。
|
||||
|
||||
CA証明書は、macOSならKeychain、LinuxならシステムのCA証明書ストア、WindowsならWindowsの証明書ストアから自動で読み込みます。ほとんどの場合、追加の設定は要りません。
|
||||
|
||||
### CA証明書ファイルの指定
|
||||
|
||||
環境によってはシステムのCA証明書が見つからないこともあります。そのときは `set_ca_cert_path()` でパスを直接指定してください。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
cli.set_ca_cert_path("/etc/ssl/certs/ca-certificates.crt");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
```sh
|
||||
curl --cacert /etc/ssl/certs/ca-certificates.crt https://nghttp2.org/
|
||||
```
|
||||
|
||||
### 証明書検証の無効化
|
||||
|
||||
開発中、自己署名証明書のサーバーに接続したいときは、検証を無効にできます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -k https://localhost:8443/
|
||||
```
|
||||
|
||||
本番では絶対に無効にしないでください。中間者攻撃のリスクがあります。
|
||||
|
||||
## リダイレクトの追跡
|
||||
|
||||
HTTPSサイトへのアクセスでは、リダイレクトに遭遇することがよくあります。たとえば `http://` から `https://` へ、あるいは `www` なしから `www` ありへ転送されるケースです。
|
||||
|
||||
デフォルトではリダイレクトを追跡しません。リダイレクト先は `Location` ヘッダーで確認できます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect/3");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 302
|
||||
std::cout << res->get_header_value("Location") << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl https://nghttp2.org/httpbin/redirect/3
|
||||
```
|
||||
|
||||
`set_follow_location(true)` を設定すると、リダイレクトを自動で追跡して、最終的なレスポンスを返してくれます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://nghttp2.org");
|
||||
cli.set_follow_location(true);
|
||||
|
||||
auto res = cli.Get("/httpbin/redirect/3");
|
||||
if (res) {
|
||||
std::cout << res->status << std::endl; // 200(最終的なレスポンス)
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -L https://nghttp2.org/httpbin/redirect/3
|
||||
```
|
||||
|
||||
## 次のステップ
|
||||
|
||||
HTTPSクライアントの使い方がわかりましたね。次は自分でHTTPSサーバーを立ててみましょう。自己署名証明書の作り方から始めます。
|
||||
|
||||
**次:** [HTTPS Server](../07-https-server)
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: "HTTPS Server"
|
||||
order: 7
|
||||
---
|
||||
|
||||
前章ではHTTPSクライアントを使いました。今度は自分でHTTPSサーバーを立ててみましょう。3章の `httplib::Server` を `httplib::SSLServer` に置き換えるだけです。
|
||||
|
||||
ただし、TLSサーバーにはサーバー証明書と秘密鍵が必要です。まずはそこから準備しましょう。
|
||||
|
||||
## 自己署名証明書の作成
|
||||
|
||||
開発やテスト用なら、自己署名証明書(いわゆるオレオレ証明書)で十分です。OpenSSLのコマンドでサクッと作れます。
|
||||
|
||||
```sh
|
||||
openssl req -x509 -noenc -keyout key.pem -out cert.pem -subj /CN=localhost
|
||||
```
|
||||
|
||||
これで2つのファイルができます。
|
||||
|
||||
- **`cert.pem`** — サーバー証明書
|
||||
- **`key.pem`** — 秘密鍵
|
||||
|
||||
## 最小のHTTPSサーバー
|
||||
|
||||
証明書ができたら、さっそくサーバーを書いてみましょう。
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::SSLServer svr("cert.pem", "key.pem");
|
||||
|
||||
svr.Get("/", [](const auto &, auto &res) {
|
||||
res.set_content("Hello, HTTPS!", "text/plain");
|
||||
});
|
||||
|
||||
std::cout << "Listening on https://localhost:8443" << std::endl;
|
||||
svr.listen("0.0.0.0", 8443);
|
||||
}
|
||||
```
|
||||
|
||||
`httplib::SSLServer` のコンストラクタに証明書と秘密鍵のパスを渡すだけです。ルーティングの書き方は3章の `httplib::Server` とまったく同じですよ。
|
||||
|
||||
コンパイルして起動しましょう。
|
||||
|
||||
## 動作確認
|
||||
|
||||
サーバーが起動したら、`curl` でアクセスしてみましょう。自己署名証明書なので、`-k` オプションで証明書検証をスキップします。
|
||||
|
||||
```sh
|
||||
curl -k https://localhost:8443/
|
||||
# Hello, HTTPS!
|
||||
```
|
||||
|
||||
ブラウザで `https://localhost:8443` を開くと、「この接続は安全ではありません」と警告が出ます。自己署名証明書なので正常です。気にせず進めてください。
|
||||
|
||||
## クライアントからの接続
|
||||
|
||||
前章の `httplib::Client` で接続してみましょう。自己署名証明書のサーバーに接続するには、2つの方法があります。
|
||||
|
||||
### 方法1: 証明書検証を無効にする
|
||||
|
||||
開発時の手軽な方法です。
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
cli.enable_server_certificate_verification(false);
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // Hello, HTTPS!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 方法2: 自己署名証明書をCA証明書として指定する
|
||||
|
||||
こちらのほうが安全です。`cert.pem` をCA証明書として信頼するよう指定します。
|
||||
|
||||
```cpp
|
||||
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Client cli("https://localhost:8443");
|
||||
cli.set_ca_cert_path("cert.pem");
|
||||
|
||||
auto res = cli.Get("/");
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl; // Hello, HTTPS!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
この方法なら、指定した証明書のサーバーにだけ接続を許可して、なりすましを防げます。テスト環境でもなるべくこちらを使いましょう。
|
||||
|
||||
## Server と SSLServer の比較
|
||||
|
||||
3章で学んだ `httplib::Server` のAPIは、`httplib::SSLServer` でもそのまま使えます。違いはコンストラクタだけです。
|
||||
|
||||
| | `httplib::Server` | `httplib::SSLServer` |
|
||||
| -- | ------------------ | -------------------- |
|
||||
| コンストラクタ | 引数なし | 証明書と秘密鍵のパス |
|
||||
| プロトコル | HTTP | HTTPS |
|
||||
| ポート(慣例) | 8080 | 8443 |
|
||||
| ルーティング | 共通 | 共通 |
|
||||
|
||||
HTTPサーバーをHTTPSに切り替えるには、コンストラクタを変えるだけです。
|
||||
|
||||
## 次のステップ
|
||||
|
||||
HTTPSサーバーが動きましたね。これでHTTP/HTTPSのクライアントとサーバー、両方の基本がそろいました。
|
||||
|
||||
次は、cpp-httplibに新しく加わったWebSocket機能を見てみましょう。
|
||||
|
||||
**次:** [WebSocket](../08-websocket)
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
title: "WebSocket"
|
||||
order: 8
|
||||
---
|
||||
|
||||
cpp-httplibはWebSocketにも対応しています。HTTPのリクエスト/レスポンスと違い、WebSocketはサーバーとクライアントが双方向にメッセージをやり取りできます。チャットやリアルタイム通知に便利です。
|
||||
|
||||
さっそく、エコーサーバーとクライアントを作ってみましょう。
|
||||
|
||||
## エコーサーバー
|
||||
|
||||
受け取ったメッセージをそのまま返すエコーサーバーです。
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::Server svr;
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send(msg); // 受け取ったメッセージをそのまま返す
|
||||
}
|
||||
});
|
||||
|
||||
std::cout << "Listening on port 8080..." << std::endl;
|
||||
svr.listen("0.0.0.0", 8080);
|
||||
}
|
||||
```
|
||||
|
||||
`svr.WebSocket()` でWebSocketハンドラーを登録します。3章の `svr.Get()` や `svr.Post()` と同じ感覚ですね。
|
||||
|
||||
ハンドラーの中では、`ws.read(msg)` でメッセージを待ちます。接続が閉じられると `read()` が `false` を返すので、ループを抜けます。`ws.send(msg)` でメッセージを送り返します。
|
||||
|
||||
## クライアントからの接続
|
||||
|
||||
`httplib::ws::WebSocketClient` を使ってサーバーに接続してみましょう。
|
||||
|
||||
```cpp
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
httplib::ws::WebSocketClient client("ws://localhost:8080/ws");
|
||||
|
||||
if (!client.connect()) {
|
||||
std::cout << "Connection failed" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// メッセージを送信
|
||||
client.send("Hello, WebSocket!");
|
||||
|
||||
// サーバーからの応答を受信
|
||||
std::string msg;
|
||||
if (client.read(msg)) {
|
||||
std::cout << msg << std::endl; // Hello, WebSocket!
|
||||
}
|
||||
|
||||
client.close();
|
||||
}
|
||||
```
|
||||
|
||||
コンストラクタには `ws://host:port/path` 形式のURLを渡します。`connect()` で接続を開始し、`send()` と `read()` でメッセージをやり取りします。
|
||||
|
||||
## テキストとバイナリ
|
||||
|
||||
WebSocketにはテキストとバイナリの2種類のメッセージがあります。`read()` の戻り値で区別できます。
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/ws", [](const httplib::Request &, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
httplib::ws::ReadResult ret;
|
||||
while ((ret = ws.read(msg))) {
|
||||
if (ret == httplib::ws::Binary) {
|
||||
ws.send(msg.data(), msg.size()); // バイナリとして送信
|
||||
} else {
|
||||
ws.send(msg); // テキストとして送信
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- `ws.send(const std::string &)` — テキストメッセージとして送信
|
||||
- `ws.send(const char *, size_t)` — バイナリメッセージとして送信
|
||||
|
||||
クライアント側も同じAPIです。
|
||||
|
||||
## リクエスト情報へのアクセス
|
||||
|
||||
ハンドラーの第1引数 `req` から、ハンドシェイク時のHTTPリクエスト情報を読み取れます。認証トークンの確認などに便利です。
|
||||
|
||||
```cpp
|
||||
svr.WebSocket("/ws", [](const httplib::Request &req, httplib::ws::WebSocket &ws) {
|
||||
auto token = req.get_header_value("Authorization");
|
||||
if (token.empty()) {
|
||||
ws.close(httplib::ws::CloseStatus::PolicyViolation, "unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send(msg);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## WSSで使う
|
||||
|
||||
HTTPS上のWebSocket(WSS)にも対応しています。サーバー側は `httplib::SSLServer` にWebSocketハンドラーを登録するだけです。
|
||||
|
||||
```cpp
|
||||
httplib::SSLServer svr("cert.pem", "key.pem");
|
||||
|
||||
svr.WebSocket("/ws", [](const httplib::Request &, httplib::ws::WebSocket &ws) {
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
ws.send(msg);
|
||||
}
|
||||
});
|
||||
|
||||
svr.listen("0.0.0.0", 8443);
|
||||
```
|
||||
|
||||
クライアント側は `wss://` スキームを使います。
|
||||
|
||||
```cpp
|
||||
httplib::ws::WebSocketClient client("wss://localhost:8443/ws");
|
||||
```
|
||||
|
||||
## 次のステップ
|
||||
|
||||
WebSocketの基本がわかりましたね。ここまでで Tourは終わりです。
|
||||
|
||||
次のページでは、Tourで取り上げなかった機能をまとめて紹介します。
|
||||
|
||||
**次:** [What's Next](../09-whats-next)
|
||||
@@ -0,0 +1,228 @@
|
||||
---
|
||||
title: "What's Next"
|
||||
order: 9
|
||||
---
|
||||
|
||||
Tourお疲れさまでした! cpp-httplibの基本はひと通り押さえましたね。でも、まだまだ便利な機能があります。Tourで取り上げなかった機能をカテゴリー別に紹介します。
|
||||
|
||||
## Streaming API
|
||||
|
||||
LLMのストリーミング応答や大きなファイルのダウンロードでは、レスポンス全体をメモリに載せたくないですよね。`stream::Get()` を使えば、データをチャンクごとに処理できます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:11434");
|
||||
|
||||
auto result = httplib::stream::Get(cli, "/api/generate");
|
||||
|
||||
if (result) {
|
||||
while (result.next()) {
|
||||
std::cout.write(result.data(), result.size());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Get()` に `content_receiver` コールバックを渡す方法もあります。こちらはKeep-Aliveと併用できます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
cli.Get("/stream", [](const char *data, size_t len) {
|
||||
std::cout.write(data, len);
|
||||
return true;
|
||||
});
|
||||
```
|
||||
|
||||
サーバー側には `set_content_provider()` と `set_chunked_content_provider()` があります。サイズがわかっているなら前者、不明なら後者を使ってください。
|
||||
|
||||
```cpp
|
||||
// サイズ指定あり(Content-Length が設定される)
|
||||
svr.Get("/file", [](const auto &, auto &res) {
|
||||
auto size = get_file_size("large.bin");
|
||||
res.set_content_provider(size, "application/octet-stream",
|
||||
[](size_t offset, size_t length, httplib::DataSink &sink) {
|
||||
// offset から length バイト分を送る
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
// サイズ不明(Chunked Transfer Encoding)
|
||||
svr.Get("/stream", [](const auto &, auto &res) {
|
||||
res.set_chunked_content_provider("text/plain",
|
||||
[](size_t offset, httplib::DataSink &sink) {
|
||||
sink.write("chunk\n", 6);
|
||||
return true; // falseを返すと終了
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
大きなファイルのアップロードには `make_file_provider()` が便利です。ファイルを全部メモリに読み込まず、ストリーミングで送れます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
|
||||
auto res = cli.Post("/upload", {}, {
|
||||
httplib::make_file_provider("file", "/path/to/large-file.zip")
|
||||
});
|
||||
```
|
||||
|
||||
## Server-Sent Events (SSE)
|
||||
|
||||
SSEクライアントも用意しています。自動再接続や `Last-Event-ID` による再開にも対応しています。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("http://localhost:8080");
|
||||
httplib::sse::SSEClient sse(cli, "/events");
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
std::cout << msg.event << ": " << msg.data << std::endl;
|
||||
});
|
||||
|
||||
sse.start(); // ブロッキング、自動再接続あり
|
||||
```
|
||||
|
||||
イベントタイプごとにハンドラーを分けることもできますよ。
|
||||
|
||||
```cpp
|
||||
sse.on_event("update", [](const httplib::sse::SSEMessage &msg) {
|
||||
// "update" イベントだけ処理
|
||||
});
|
||||
```
|
||||
|
||||
## 認証
|
||||
|
||||
クライアントにはBasic認証、Bearer Token認証、Digest認証のヘルパーを用意しています。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://api.example.com");
|
||||
cli.set_basic_auth("user", "password");
|
||||
cli.set_bearer_token_auth("my-token");
|
||||
```
|
||||
|
||||
## 圧縮
|
||||
|
||||
gzip、Brotli、Zstandardによる圧縮・展開に対応しています。使いたい方式のマクロを定義してコンパイルしましょう。
|
||||
|
||||
| 圧縮方式 | マクロ定義 |
|
||||
| -- | -- |
|
||||
| gzip | `CPPHTTPLIB_ZLIB_SUPPORT` |
|
||||
| Brotli | `CPPHTTPLIB_BROTLI_SUPPORT` |
|
||||
| Zstandard | `CPPHTTPLIB_ZSTD_SUPPORT` |
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_compress(true); // リクエストボディを圧縮
|
||||
cli.set_decompress(true); // レスポンスボディを展開
|
||||
```
|
||||
|
||||
## プロキシ
|
||||
|
||||
HTTPプロキシ経由で接続できます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_proxy("proxy.example.com", 8080);
|
||||
cli.set_proxy_basic_auth("user", "password");
|
||||
```
|
||||
|
||||
## タイムアウト
|
||||
|
||||
接続・読み取り・書き込みのタイムアウトを個別に設定できます。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_connection_timeout(5, 0); // 5秒
|
||||
cli.set_read_timeout(10, 0); // 10秒
|
||||
cli.set_write_timeout(10, 0); // 10秒
|
||||
```
|
||||
|
||||
## Keep-Alive
|
||||
|
||||
同じサーバーに何度もリクエストするなら、Keep-Aliveを有効にしましょう。TCP接続を再利用するので効率的です。
|
||||
|
||||
```cpp
|
||||
httplib::Client cli("https://example.com");
|
||||
cli.set_keep_alive(true);
|
||||
```
|
||||
|
||||
## サーバーのミドルウェア
|
||||
|
||||
リクエスト処理の前後にフックを挟めます。
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto &req, auto &res) {
|
||||
// すべてのリクエストの前に実行される
|
||||
return httplib::Server::HandlerResponse::Unhandled; // 通常のルーティングに進む
|
||||
});
|
||||
|
||||
svr.set_post_routing_handler([](const auto &req, auto &res) {
|
||||
// レスポンスが返された後に実行される
|
||||
res.set_header("X-Server", "cpp-httplib");
|
||||
});
|
||||
```
|
||||
|
||||
`req.user_data` を使うと、ミドルウェアからハンドラーにデータを渡せます。認証トークンのデコード結果を共有するときに便利です。
|
||||
|
||||
```cpp
|
||||
svr.set_pre_routing_handler([](const auto &req, auto &res) {
|
||||
req.user_data["auth_user"] = std::string("alice");
|
||||
return httplib::Server::HandlerResponse::Unhandled;
|
||||
});
|
||||
|
||||
svr.Get("/me", [](const auto &req, auto &res) {
|
||||
auto user = std::any_cast<std::string>(req.user_data.at("auth_user"));
|
||||
res.set_content("Hello, " + user, "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
エラーや例外のハンドラーもカスタマイズできますよ。
|
||||
|
||||
```cpp
|
||||
svr.set_error_handler([](const auto &req, auto &res) {
|
||||
res.set_content("Custom Error Page", "text/html");
|
||||
});
|
||||
|
||||
svr.set_exception_handler([](const auto &req, auto &res, std::exception_ptr ep) {
|
||||
res.status = 500;
|
||||
res.set_content("Internal Server Error", "text/plain");
|
||||
});
|
||||
```
|
||||
|
||||
## ロギング
|
||||
|
||||
サーバーでもクライアントでもロガーを設定できます。
|
||||
|
||||
```cpp
|
||||
svr.set_logger([](const auto &req, const auto &res) {
|
||||
std::cout << req.method << " " << req.path << " " << res.status << std::endl;
|
||||
});
|
||||
```
|
||||
|
||||
## Unix Domain Socket
|
||||
|
||||
TCP以外に、Unix Domain Socketでの通信にも対応しています。同じマシン上のプロセス間通信に使えます。
|
||||
|
||||
```cpp
|
||||
// サーバー
|
||||
httplib::Server svr;
|
||||
svr.set_address_family(AF_UNIX);
|
||||
svr.listen("/tmp/httplib.sock", 0);
|
||||
```
|
||||
|
||||
```cpp
|
||||
// クライアント
|
||||
httplib::Client cli("http://localhost");
|
||||
cli.set_address_family(AF_UNIX);
|
||||
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});
|
||||
|
||||
auto res = cli.Get("/");
|
||||
```
|
||||
|
||||
## さらに詳しく
|
||||
|
||||
もっと詳しく知りたいときは、以下を参照してください。
|
||||
|
||||
- Cookbook — よくあるユースケースのレシピ集
|
||||
- [README](https://github.com/yhirose/cpp-httplib/blob/master/README.md) — 全APIのリファレンス
|
||||
- [README-sse](https://github.com/yhirose/cpp-httplib/blob/master/README-sse.md) — Server-Sent Eventsの使い方
|
||||
- [README-stream](https://github.com/yhirose/cpp-httplib/blob/master/README-stream.md) — Streaming APIの使い方
|
||||
- [README-websocket](https://github.com/yhirose/cpp-httplib/blob/master/README-websocket.md) — WebSocketサーバーの使い方
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "A Tour of cpp-httplib"
|
||||
order: 1
|
||||
---
|
||||
|
||||
cpp-httplibの基本を、順番に学んでいくチュートリアルです。各章は前の章の内容を踏まえて進む構成なので、1章から順に読んでください。
|
||||
|
||||
1. [Getting Started](01-getting-started) — httplib.h の入手とHello Worldサーバー
|
||||
2. [Basic Client](02-basic-client) — GET/POST・パスパラメーターのリクエスト送信
|
||||
3. [Basic Server](03-basic-server) — ルーティング、パスパラメーター、レスポンスの組み立て
|
||||
4. [Static File Server](04-static-file-server) — 静的ファイルの配信
|
||||
5. [TLS Setup](05-tls-setup) — OpenSSL / mbedTLS のセットアップ
|
||||
6. [HTTPS Client](06-https-client) — HTTPSサイトへのリクエスト
|
||||
7. [HTTPS Server](07-https-server) — HTTPSサーバーの構築
|
||||
8. [WebSocket](08-websocket) — WebSocket通信の基本
|
||||
9. [What's Next](09-whats-next) — さらなる機能の紹介
|
||||
+16
-6
@@ -1,11 +1,9 @@
|
||||
#CXX = clang++
|
||||
CXXFLAGS = -O2 -std=c++11 -I.. -Wall -Wextra -pthread
|
||||
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
PREFIX ?= $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
@@ -20,7 +18,7 @@ ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark issue
|
||||
all: server client hello simplecli simplesvr upload redirect ssesvr ssecli wsecho benchmark one_time_request server_and_client accept_header
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CXX) -o server $(CXXFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
@@ -49,12 +47,24 @@ ssesvr : ssesvr.cc ../httplib.h Makefile
|
||||
ssecli : ssecli.cc ../httplib.h Makefile
|
||||
$(CXX) -o ssecli $(CXXFLAGS) ssecli.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
wsecho : wsecho.cc ../httplib.h Makefile
|
||||
$(CXX) -o wsecho $(CXXFLAGS) wsecho.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
benchmark : benchmark.cc ../httplib.h Makefile
|
||||
$(CXX) -o benchmark $(CXXFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
one_time_request : one_time_request.cc ../httplib.h Makefile
|
||||
$(CXX) -o one_time_request $(CXXFLAGS) one_time_request.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
server_and_client : server_and_client.cc ../httplib.h Makefile
|
||||
$(CXX) -o server_and_client $(CXXFLAGS) server_and_client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
accept_header : accept_header.cc ../httplib.h Makefile
|
||||
$(CXX) -o accept_header $(CXXFLAGS) accept_header.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT)
|
||||
|
||||
pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
|
||||
clean:
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli benchmark *.pem
|
||||
rm server client hello simplecli simplesvr upload redirect ssesvr ssecli wsecho benchmark one_time_request server_and_client accept_header *.pem
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "httplib.h"
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
using namespace httplib;
|
||||
|
||||
// Example usage of parse_accept_header function
|
||||
std::cout << "=== Accept Header Parser Example ===" << std::endl;
|
||||
|
||||
// Example 1: Simple Accept header
|
||||
std::string accept1 = "text/html,application/json,text/plain";
|
||||
std::vector<std::string> result1;
|
||||
if (detail::parse_accept_header(accept1, result1)) {
|
||||
std::cout << "\nExample 1: " << accept1 << std::endl;
|
||||
std::cout << "Parsed order:" << std::endl;
|
||||
for (size_t i = 0; i < result1.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << result1[i] << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\nExample 1: Failed to parse Accept header" << std::endl;
|
||||
}
|
||||
|
||||
// Example 2: Accept header with quality values
|
||||
std::string accept2 = "text/html;q=0.9,application/json;q=1.0,text/plain;q=0.8";
|
||||
std::vector<std::string> result2;
|
||||
if (detail::parse_accept_header(accept2, result2)) {
|
||||
std::cout << "\nExample 2: " << accept2 << std::endl;
|
||||
std::cout << "Parsed order (sorted by priority):" << std::endl;
|
||||
for (size_t i = 0; i < result2.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << result2[i] << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\nExample 2: Failed to parse Accept header" << std::endl;
|
||||
}
|
||||
|
||||
// Example 3: Browser-like Accept header
|
||||
std::string accept3 = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
|
||||
std::vector<std::string> result3;
|
||||
if (detail::parse_accept_header(accept3, result3)) {
|
||||
std::cout << "\nExample 3: " << accept3 << std::endl;
|
||||
std::cout << "Parsed order:" << std::endl;
|
||||
for (size_t i = 0; i < result3.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << result3[i] << std::endl;
|
||||
}
|
||||
} else {
|
||||
std::cout << "\nExample 3: Failed to parse Accept header" << std::endl;
|
||||
}
|
||||
|
||||
// Example 4: Invalid Accept header examples
|
||||
std::cout << "\n=== Invalid Accept Header Examples ===" << std::endl;
|
||||
|
||||
std::vector<std::string> invalid_examples = {
|
||||
"text/html;q=1.5,application/json", // q > 1.0
|
||||
"text/html;q=-0.1,application/json", // q < 0.0
|
||||
"text/html;q=invalid,application/json", // invalid q value
|
||||
"invalidtype,application/json", // invalid media type
|
||||
",application/json" // empty entry
|
||||
};
|
||||
|
||||
for (const auto& invalid_accept : invalid_examples) {
|
||||
std::vector<std::string> temp_result;
|
||||
std::cout << "\nTesting invalid: " << invalid_accept << std::endl;
|
||||
if (detail::parse_accept_header(invalid_accept, temp_result)) {
|
||||
std::cout << " Unexpectedly succeeded!" << std::endl;
|
||||
} else {
|
||||
std::cout << " Correctly rejected as invalid" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Example 4: Server usage example
|
||||
std::cout << "\n=== Server Usage Example ===" << std::endl;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/api/data", [](const Request& req, Response& res) {
|
||||
// Get Accept header
|
||||
auto accept_header = req.get_header_value("Accept");
|
||||
if (accept_header.empty()) {
|
||||
accept_header = "*/*"; // Default if no Accept header
|
||||
}
|
||||
|
||||
// Parse accept header to get preferred content types
|
||||
std::vector<std::string> preferred_types;
|
||||
if (!detail::parse_accept_header(accept_header, preferred_types)) {
|
||||
// Invalid Accept header
|
||||
res.status = 400; // Bad Request
|
||||
res.set_content("Invalid Accept header", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Client Accept header: " << accept_header << std::endl;
|
||||
std::cout << "Preferred types in order:" << std::endl;
|
||||
for (size_t i = 0; i < preferred_types.size(); ++i) {
|
||||
std::cout << " " << (i + 1) << ". " << preferred_types[i] << std::endl;
|
||||
}
|
||||
|
||||
// Choose response format based on client preference
|
||||
std::string response_content;
|
||||
std::string content_type;
|
||||
|
||||
for (const auto& type : preferred_types) {
|
||||
if (type == "application/json" || type == "application/*" || type == "*/*") {
|
||||
response_content = "{\"message\": \"Hello, World!\", \"data\": [1, 2, 3]}";
|
||||
content_type = "application/json";
|
||||
break;
|
||||
} else if (type == "text/html" || type == "text/*") {
|
||||
response_content = "<html><body><h1>Hello, World!</h1><p>Data: 1, 2, 3</p></body></html>";
|
||||
content_type = "text/html";
|
||||
break;
|
||||
} else if (type == "text/plain") {
|
||||
response_content = "Hello, World!\nData: 1, 2, 3";
|
||||
content_type = "text/plain";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (response_content.empty()) {
|
||||
// No supported content type found
|
||||
res.status = 406; // Not Acceptable
|
||||
res.set_content("No acceptable content type found", "text/plain");
|
||||
return;
|
||||
}
|
||||
|
||||
res.set_content(response_content, content_type);
|
||||
std::cout << "Responding with: " << content_type << std::endl;
|
||||
});
|
||||
|
||||
std::cout << "Server configured. You can test it with:" << std::endl;
|
||||
std::cout << " curl -H \"Accept: application/json\" http://localhost:8080/api/data" << std::endl;
|
||||
std::cout << " curl -H \"Accept: text/html\" http://localhost:8080/api/data" << std::endl;
|
||||
std::cout << " curl -H \"Accept: text/plain\" http://localhost:8080/api/data" << std::endl;
|
||||
std::cout << " curl -H \"Accept: text/html;q=0.9,application/json;q=1.0\" http://localhost:8080/api/data" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -21,12 +21,12 @@ struct StopWatch {
|
||||
int main(void) {
|
||||
string body(1024 * 5, 'a');
|
||||
|
||||
httplib::Client cli("httpbin.org", 80);
|
||||
httplib::Client cli("httpcan.org", 80);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
StopWatch sw(to_string(i).c_str());
|
||||
auto res = cli.Post("/post", body, "application/octet-stream");
|
||||
assert(res->status == 200);
|
||||
assert(res->status == httplib::StatusCode::OK_200);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// client.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
@@ -30,7 +30,7 @@ int main(void) {
|
||||
} else {
|
||||
cout << "error code: " << res.error() << std::endl;
|
||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||
auto result = cli.get_openssl_verify_result();
|
||||
auto result = cli.get_verify_result();
|
||||
if (result) {
|
||||
cout << "verify error: " << X509_verify_cert_error_string(result) << endl;
|
||||
}
|
||||
|
||||
@@ -22,34 +22,34 @@
|
||||
<ProjectGuid>{6DB1FC63-B153-4279-92B7-D8A11AF285D6}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>client</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// hello.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
const char *HOST = "localhost";
|
||||
const int PORT = 1234;
|
||||
|
||||
void one_time_request_server(const char *label) {
|
||||
std::thread th;
|
||||
Server svr;
|
||||
|
||||
svr.Get("/hi", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(std::string("Hello from ") + label, "text/plain");
|
||||
|
||||
// Stop server
|
||||
th = std::thread([&]() { svr.stop(); });
|
||||
});
|
||||
|
||||
svr.listen(HOST, PORT);
|
||||
th.join();
|
||||
|
||||
std::cout << label << " ended..." << std::endl;
|
||||
}
|
||||
|
||||
void send_request(const char *label) {
|
||||
Client cli(HOST, PORT);
|
||||
|
||||
std::cout << "Send " << label << " request" << std::endl;
|
||||
auto res = cli.Get("/hi");
|
||||
|
||||
if (res) {
|
||||
std::cout << res->body << std::endl;
|
||||
} else {
|
||||
std::cout << "Request error: " + to_string(res.error()) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
auto th1 = std::thread([&]() { one_time_request_server("Server #1"); });
|
||||
auto th2 = std::thread([&]() { one_time_request_server("Server #2"); });
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("1st");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("2nd");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
send_request("3rd");
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
th1.join();
|
||||
th2.join();
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// redirect.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// sample.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
|
||||
@@ -18,38 +18,41 @@
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="simplesvr.cc" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{864CD288-050A-4C8B-9BEF-3048BD876C5B}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>sample</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.15063.0</WindowsTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
@@ -151,9 +154,6 @@
|
||||
<AdditionalDependencies>Ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="server.cc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
//
|
||||
// server_and_client.cc
|
||||
//
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
std::string dump_headers(const Headers &headers) {
|
||||
std::string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
for (auto it = headers.begin(); it != headers.end(); ++it) {
|
||||
const auto &x = *it;
|
||||
snprintf(buf, sizeof(buf), "%s: %s\n", x.first.c_str(), x.second.c_str());
|
||||
s += buf;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void logger(const Request &req, const Response &res) {
|
||||
std::string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
s += "================================\n";
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s %s %s", req.method.c_str(),
|
||||
req.version.c_str(), req.path.c_str());
|
||||
s += buf;
|
||||
|
||||
std::string query;
|
||||
for (auto it = req.params.begin(); it != req.params.end(); ++it) {
|
||||
const auto &x = *it;
|
||||
snprintf(buf, sizeof(buf), "%c%s=%s",
|
||||
(it == req.params.begin()) ? '?' : '&', x.first.c_str(),
|
||||
x.second.c_str());
|
||||
query += buf;
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%s\n", query.c_str());
|
||||
s += buf;
|
||||
|
||||
s += dump_headers(req.headers);
|
||||
|
||||
s += "--------------------------------\n";
|
||||
|
||||
snprintf(buf, sizeof(buf), "%d %s\n", res.status, res.version.c_str());
|
||||
s += buf;
|
||||
s += dump_headers(res.headers);
|
||||
s += "\n";
|
||||
|
||||
if (!res.body.empty()) { s += res.body; }
|
||||
|
||||
s += "\n";
|
||||
|
||||
std::cout << s;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Server
|
||||
Server svr;
|
||||
svr.set_logger(logger);
|
||||
|
||||
svr.Post("/post", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content("POST", "text/plain");
|
||||
});
|
||||
|
||||
auto th = std::thread([&]() { svr.listen("localhost", 8080); });
|
||||
|
||||
auto se = detail::scope_exit([&] {
|
||||
svr.stop();
|
||||
th.join();
|
||||
});
|
||||
|
||||
svr.wait_until_ready();
|
||||
|
||||
// Client
|
||||
Client cli{"localhost", 8080};
|
||||
|
||||
std::string body = R"({"hello": "world"})";
|
||||
|
||||
auto res = cli.Post("/post", body, "application/json");
|
||||
std::cout << "--------------------------------" << std::endl;
|
||||
std::cout << to_string(res.error()) << std::endl;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// simplecli.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
|
||||
+18
-5
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// simplesvr.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
@@ -27,13 +27,26 @@ string dump_headers(const Headers &headers) {
|
||||
return s;
|
||||
}
|
||||
|
||||
string dump_multipart_files(const MultipartFormDataMap &files) {
|
||||
string dump_multipart_formdata(const MultipartFormData &form) {
|
||||
string s;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
s += "--------------------------------\n";
|
||||
|
||||
for (const auto &x : files) {
|
||||
for (const auto &x : form.fields) {
|
||||
const auto &name = x.first;
|
||||
const auto &field = x.second;
|
||||
|
||||
snprintf(buf, sizeof(buf), "name: %s\n", name.c_str());
|
||||
s += buf;
|
||||
|
||||
snprintf(buf, sizeof(buf), "text length: %zu\n", field.content.size());
|
||||
s += buf;
|
||||
|
||||
s += "----------------\n";
|
||||
}
|
||||
|
||||
for (const auto &x : form.files) {
|
||||
const auto &name = x.first;
|
||||
const auto &file = x.second;
|
||||
|
||||
@@ -77,7 +90,7 @@ string log(const Request &req, const Response &res) {
|
||||
s += buf;
|
||||
|
||||
s += dump_headers(req.headers);
|
||||
s += dump_multipart_files(req.files);
|
||||
s += dump_multipart_formdata(req.form);
|
||||
|
||||
s += "--------------------------------\n";
|
||||
|
||||
@@ -101,7 +114,7 @@ int main(int argc, const char **argv) {
|
||||
#endif
|
||||
|
||||
svr.Post("/multipart", [](const Request &req, Response &res) {
|
||||
auto body = dump_headers(req.headers) + dump_multipart_files(req.files);
|
||||
auto body = dump_headers(req.headers) + dump_multipart_formdata(req.form);
|
||||
|
||||
res.set_content(body, "text/plain");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
//
|
||||
// ssecli-stream.cc
|
||||
//
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
// SSE (Server-Sent Events) client example using Streaming API
|
||||
// with automatic reconnection support (similar to JavaScript's EventSource)
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// SSE Event Parser
|
||||
//------------------------------------------------------------------------------
|
||||
// Parses SSE events from the stream according to the SSE specification.
|
||||
// SSE format:
|
||||
// event: <event-type> (optional, defaults to "message")
|
||||
// data: <payload> (can have multiple lines)
|
||||
// id: <event-id> (optional, used for reconnection)
|
||||
// retry: <milliseconds> (optional, reconnection interval)
|
||||
// <blank line> (signals end of event)
|
||||
//
|
||||
struct SSEEvent {
|
||||
std::string event = "message"; // Event type (default: "message")
|
||||
std::string data; // Event payload
|
||||
std::string id; // Event ID for Last-Event-ID header
|
||||
|
||||
void clear() {
|
||||
event = "message";
|
||||
data.clear();
|
||||
id.clear();
|
||||
}
|
||||
};
|
||||
|
||||
// Parse a single SSE field line (e.g., "data: hello")
|
||||
// Returns true if this line ends an event (blank line)
|
||||
bool parse_sse_line(const std::string &line, SSEEvent &event, int &retry_ms) {
|
||||
// Blank line signals end of event
|
||||
if (line.empty() || line == "\r") { return true; }
|
||||
|
||||
// Find the colon separator
|
||||
auto colon_pos = line.find(':');
|
||||
if (colon_pos == std::string::npos) {
|
||||
// Line with no colon is treated as field name with empty value
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string field = line.substr(0, colon_pos);
|
||||
std::string value;
|
||||
|
||||
// Value starts after colon, skip optional single space
|
||||
if (colon_pos + 1 < line.size()) {
|
||||
size_t value_start = colon_pos + 1;
|
||||
if (line[value_start] == ' ') { value_start++; }
|
||||
value = line.substr(value_start);
|
||||
// Remove trailing \r if present
|
||||
if (!value.empty() && value.back() == '\r') { value.pop_back(); }
|
||||
}
|
||||
|
||||
// Handle known fields
|
||||
if (field == "event") {
|
||||
event.event = value;
|
||||
} else if (field == "data") {
|
||||
// Multiple data lines are concatenated with newlines
|
||||
if (!event.data.empty()) { event.data += "\n"; }
|
||||
event.data += value;
|
||||
} else if (field == "id") {
|
||||
// Empty id is valid (clears the last event ID)
|
||||
event.id = value;
|
||||
} else if (field == "retry") {
|
||||
// Parse retry interval in milliseconds
|
||||
try {
|
||||
retry_ms = std::stoi(value);
|
||||
} catch (...) {
|
||||
// Invalid retry value, ignore
|
||||
}
|
||||
}
|
||||
// Unknown fields are ignored per SSE spec
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Main - SSE Client with Auto-Reconnection
|
||||
//------------------------------------------------------------------------------
|
||||
int main(void) {
|
||||
// Configuration
|
||||
const std::string host = "http://localhost:1234";
|
||||
const std::string path = "/event1";
|
||||
|
||||
httplib::Client cli(host);
|
||||
|
||||
// State for reconnection (persists across connections)
|
||||
std::string last_event_id; // Sent as Last-Event-ID header on reconnect
|
||||
int retry_ms = 3000; // Reconnection delay (server can override via retry:)
|
||||
int connection_count = 0;
|
||||
|
||||
std::cout << "SSE Client starting...\n";
|
||||
std::cout << "Target: " << host << path << "\n";
|
||||
std::cout << "Press Ctrl+C to exit\n\n";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Main reconnection loop
|
||||
// This mimics JavaScript's EventSource behavior:
|
||||
// - Automatically reconnects on connection failure
|
||||
// - Sends Last-Event-ID header to resume from last received event
|
||||
// - Respects server's retry interval
|
||||
//----------------------------------------------------------------------------
|
||||
while (true) {
|
||||
connection_count++;
|
||||
std::cout << "[Connection #" << connection_count << "] Connecting...\n";
|
||||
|
||||
// Build headers, including Last-Event-ID if we have one
|
||||
httplib::Headers headers;
|
||||
if (!last_event_id.empty()) {
|
||||
headers.emplace("Last-Event-ID", last_event_id);
|
||||
std::cout << "[Connection #" << connection_count
|
||||
<< "] Resuming from event ID: " << last_event_id << "\n";
|
||||
}
|
||||
|
||||
// Open streaming connection
|
||||
auto result = httplib::stream::Get(cli, path, headers);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Connection error handling
|
||||
//--------------------------------------------------------------------------
|
||||
if (!result) {
|
||||
std::cerr << "[Connection #" << connection_count
|
||||
<< "] Failed: " << httplib::to_string(result.error()) << "\n";
|
||||
std::cerr << "Reconnecting in " << retry_ms << "ms...\n\n";
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retry_ms));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (result.status() != 200) {
|
||||
std::cerr << "[Connection #" << connection_count
|
||||
<< "] HTTP error: " << result.status() << "\n";
|
||||
|
||||
// For certain errors, don't reconnect
|
||||
if (result.status() == 204 || // No Content - server wants us to stop
|
||||
result.status() == 404 || // Not Found
|
||||
result.status() == 401 || // Unauthorized
|
||||
result.status() == 403) { // Forbidden
|
||||
std::cerr << "Permanent error, not reconnecting.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cerr << "Reconnecting in " << retry_ms << "ms...\n\n";
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retry_ms));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify Content-Type (optional but recommended)
|
||||
auto content_type = result.get_header_value("Content-Type");
|
||||
if (content_type.find("text/event-stream") == std::string::npos) {
|
||||
std::cerr << "[Warning] Content-Type is not text/event-stream: "
|
||||
<< content_type << "\n";
|
||||
}
|
||||
|
||||
std::cout << "[Connection #" << connection_count << "] Connected!\n\n";
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Event receiving loop
|
||||
// Reads chunks from the stream and parses SSE events
|
||||
//--------------------------------------------------------------------------
|
||||
std::string buffer;
|
||||
SSEEvent current_event;
|
||||
int event_count = 0;
|
||||
|
||||
// Read data from stream using httplib::stream API
|
||||
while (result.next()) {
|
||||
buffer.append(result.data(), result.size());
|
||||
|
||||
// Process complete lines in the buffer
|
||||
size_t line_start = 0;
|
||||
size_t newline_pos;
|
||||
|
||||
while ((newline_pos = buffer.find('\n', line_start)) !=
|
||||
std::string::npos) {
|
||||
std::string line = buffer.substr(line_start, newline_pos - line_start);
|
||||
line_start = newline_pos + 1;
|
||||
|
||||
// Parse the line and check if event is complete
|
||||
bool event_complete = parse_sse_line(line, current_event, retry_ms);
|
||||
|
||||
if (event_complete && !current_event.data.empty()) {
|
||||
// Event received - process it
|
||||
event_count++;
|
||||
|
||||
std::cout << "--- Event #" << event_count << " ---\n";
|
||||
std::cout << "Type: " << current_event.event << "\n";
|
||||
std::cout << "Data: " << current_event.data << "\n";
|
||||
if (!current_event.id.empty()) {
|
||||
std::cout << "ID: " << current_event.id << "\n";
|
||||
}
|
||||
std::cout << "\n";
|
||||
|
||||
// Update last_event_id for reconnection
|
||||
// Note: Empty id clears the last event ID per SSE spec
|
||||
if (!current_event.id.empty()) { last_event_id = current_event.id; }
|
||||
|
||||
current_event.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Keep unprocessed data in buffer
|
||||
buffer.erase(0, line_start);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Connection ended - check why
|
||||
//--------------------------------------------------------------------------
|
||||
if (result.read_error() != httplib::Error::Success) {
|
||||
std::cerr << "\n[Connection #" << connection_count
|
||||
<< "] Error: " << httplib::to_string(result.read_error())
|
||||
<< "\n";
|
||||
} else {
|
||||
std::cout << "\n[Connection #" << connection_count
|
||||
<< "] Stream ended normally\n";
|
||||
}
|
||||
|
||||
std::cout << "Received " << event_count << " events in this connection\n";
|
||||
std::cout << "Reconnecting in " << retry_ms << "ms...\n\n";
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(retry_ms));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+43
-7
@@ -1,21 +1,57 @@
|
||||
//
|
||||
// ssecli.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
#include <httplib.h>
|
||||
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main(void) {
|
||||
httplib::Client("http://localhost:1234")
|
||||
.Get("/event1", [&](const char *data, size_t data_length) {
|
||||
std::cout << string(data, data_length);
|
||||
return true;
|
||||
});
|
||||
// Global SSEClient pointer for signal handling
|
||||
httplib::sse::SSEClient *g_sse = nullptr;
|
||||
|
||||
void signal_handler(int) {
|
||||
if (g_sse) { g_sse->stop(); }
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
// Configuration
|
||||
const string host = "http://localhost:1234";
|
||||
const string path = "/event1";
|
||||
|
||||
cout << "SSE Client using httplib::sse::SSEClient\n";
|
||||
cout << "Connecting to: " << host << path << "\n";
|
||||
cout << "Press Ctrl+C to exit\n\n";
|
||||
|
||||
httplib::Client cli(host);
|
||||
httplib::sse::SSEClient sse(cli, path);
|
||||
|
||||
// Set up signal handler for graceful shutdown
|
||||
g_sse = &sse;
|
||||
signal(SIGINT, signal_handler);
|
||||
|
||||
// Event handlers
|
||||
sse.on_open([]() { cout << "[Connected]\n\n"; });
|
||||
|
||||
sse.on_message([](const httplib::sse::SSEMessage &msg) {
|
||||
cout << "Event: " << msg.event << "\n";
|
||||
cout << "Data: " << msg.data << "\n";
|
||||
if (!msg.id.empty()) { cout << "ID: " << msg.id << "\n"; }
|
||||
cout << "\n";
|
||||
});
|
||||
|
||||
sse.on_error([](httplib::Error err) {
|
||||
cerr << "[Error] " << httplib::to_string(err) << "\n";
|
||||
});
|
||||
|
||||
// Start with auto-reconnect (blocking)
|
||||
sse.start();
|
||||
|
||||
cout << "\n[Disconnected]\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
+12
-8
@@ -12,14 +12,20 @@ using namespace std;
|
||||
|
||||
class EventDispatcher {
|
||||
public:
|
||||
EventDispatcher() {
|
||||
}
|
||||
EventDispatcher() {}
|
||||
|
||||
void wait_event(DataSink *sink) {
|
||||
bool wait_event(DataSink *sink) {
|
||||
unique_lock<mutex> lk(m_);
|
||||
int id = id_;
|
||||
cv_.wait(lk, [&] { return cid_ == id; });
|
||||
|
||||
// Wait with timeout to prevent hanging if client disconnects
|
||||
if (!cv_.wait_for(lk, std::chrono::seconds(5),
|
||||
[&] { return cid_ == id; })) {
|
||||
return false; // Timeout occurred
|
||||
}
|
||||
|
||||
sink->write(message_.data(), message_.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void send_event(const string &message) {
|
||||
@@ -72,8 +78,7 @@ int main(void) {
|
||||
cout << "connected to event1..." << endl;
|
||||
res.set_chunked_content_provider("text/event-stream",
|
||||
[&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
return ed.wait_event(&sink);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,8 +86,7 @@ int main(void) {
|
||||
cout << "connected to event2..." << endl;
|
||||
res.set_chunked_content_provider("text/event-stream",
|
||||
[&](size_t /*offset*/, DataSink &sink) {
|
||||
ed.wait_event(&sink);
|
||||
return true;
|
||||
return ed.wait_event(&sink);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
//
|
||||
// upload.cc
|
||||
//
|
||||
// Copyright (c) 2019 Yuji Hirose. All rights reserved.
|
||||
// Copyright (c) 2026 Yuji Hirose. All rights reserved.
|
||||
// MIT License
|
||||
//
|
||||
|
||||
@@ -37,8 +37,8 @@ int main(void) {
|
||||
});
|
||||
|
||||
svr.Post("/post", [](const Request &req, Response &res) {
|
||||
auto image_file = req.get_file_value("image_file");
|
||||
auto text_file = req.get_file_value("text_file");
|
||||
const auto &image_file = req.form.get_file("image_file");
|
||||
const auto &text_file = req.form.get_file("text_file");
|
||||
|
||||
cout << "image file length: " << image_file.content.length() << endl
|
||||
<< "image file name: " << image_file.filename << endl
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#include <httplib.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace httplib;
|
||||
|
||||
const auto html = R"HTML(
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>WebSocket Demo</title>
|
||||
<style>
|
||||
body { font-family: monospace; margin: 2em; }
|
||||
#log { height: 300px; overflow-y: scroll; border: 1px solid #ccc; padding: 8px; }
|
||||
.controls { margin: 8px 0; }
|
||||
button { margin-right: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Demo</h1>
|
||||
<p>Server accepts subprotocols: <b>echo</b>, <b>chat</b> (or none)</p>
|
||||
|
||||
<div class="controls">
|
||||
<label>Subprotocols: </label>
|
||||
<input id="protos" type="text" value="echo, chat" placeholder="leave empty for none" />
|
||||
<button onclick="doConnect()">Connect</button>
|
||||
<button onclick="doDisconnect()">Disconnect</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<input id="msg" type="text" placeholder="Type a message..." />
|
||||
<button onclick="doSend()">Send</button>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startAuto()">Start Auto (1s)</button>
|
||||
<button onclick="stopAuto()">Stop Auto</button>
|
||||
<span id="auto-status"></span>
|
||||
</div>
|
||||
|
||||
<pre id="log"></pre>
|
||||
|
||||
<script>
|
||||
var sock = null;
|
||||
var logEl = document.getElementById("log");
|
||||
var statusEl = document.getElementById("auto-status");
|
||||
var timer = null;
|
||||
var seq = 0;
|
||||
|
||||
function appendLog(text) {
|
||||
logEl.textContent += text + "\n";
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
}
|
||||
|
||||
function doConnect() {
|
||||
if (sock && sock.readyState <= 1) { sock.close(); }
|
||||
var input = document.getElementById("protos").value.trim();
|
||||
var protocols = input ? input.split(/\s*,\s*/).filter(Boolean) : [];
|
||||
sock = new WebSocket("ws://" + location.host + "/ws", protocols);
|
||||
appendLog("[connecting] proposed: " + (protocols.length ? protocols.join(", ") : "(none)"));
|
||||
sock.onopen = function() { appendLog("[connected] subprotocol: " + (sock.protocol || "(none)")); };
|
||||
sock.onclose = function() { appendLog("[disconnected]"); stopAuto(); };
|
||||
sock.onmessage = function(e) { appendLog("< " + e.data); };
|
||||
}
|
||||
|
||||
function doDisconnect() {
|
||||
if (sock) { sock.close(); }
|
||||
}
|
||||
|
||||
function doSend() {
|
||||
var input = document.getElementById("msg");
|
||||
if (!sock || sock.readyState !== 1 || input.value === "") return;
|
||||
sock.send(input.value);
|
||||
appendLog("> " + input.value);
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
function startAuto() {
|
||||
if (timer || !sock || sock.readyState !== 1) return;
|
||||
seq = 0;
|
||||
statusEl.textContent = "running...";
|
||||
timer = setInterval(function() {
|
||||
if (!sock || sock.readyState !== 1) { stopAuto(); return; }
|
||||
var msg = "auto #" + seq++;
|
||||
sock.send(msg);
|
||||
appendLog("> " + msg);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopAuto() {
|
||||
if (timer) { clearInterval(timer); timer = null; }
|
||||
statusEl.textContent = "";
|
||||
}
|
||||
|
||||
document.getElementById("msg").addEventListener("keydown", function(e) {
|
||||
if (e.key === "Enter") doSend();
|
||||
});
|
||||
|
||||
doConnect();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)HTML";
|
||||
|
||||
int main(void) {
|
||||
Server svr;
|
||||
|
||||
svr.Get("/", [&](const Request & /*req*/, Response &res) {
|
||||
res.set_content(html, "text/html");
|
||||
});
|
||||
|
||||
svr.WebSocket(
|
||||
"/ws",
|
||||
[](const Request &req, ws::WebSocket &ws) {
|
||||
std::cout << "WebSocket connected from " << req.remote_addr
|
||||
<< std::endl;
|
||||
|
||||
std::string msg;
|
||||
while (ws.read(msg)) {
|
||||
std::cout << "Received: " << msg << std::endl;
|
||||
ws.send("echo: " + msg);
|
||||
}
|
||||
|
||||
std::cout << "WebSocket disconnected" << std::endl;
|
||||
},
|
||||
[](const std::vector<std::string> &protocols) -> std::string {
|
||||
for (const auto &p : protocols) {
|
||||
if (p == "echo" || p == "chat") { return p; }
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
std::cout << "Listening on http://localhost:8080" << std::endl;
|
||||
svr.listen("localhost", 8080);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""This script generates httplib.cppm module file from httplib.h."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from typing import List
|
||||
|
||||
def main() -> None:
|
||||
"""Main entry point for the script."""
|
||||
|
||||
args_parser: ArgumentParser = ArgumentParser(description=__doc__)
|
||||
args_parser.add_argument(
|
||||
"-o", "--out", help="where to write the files (default: out)", default="out"
|
||||
)
|
||||
args: Namespace = args_parser.parse_args()
|
||||
|
||||
cur_dir: str = os.path.dirname(sys.argv[0])
|
||||
if not cur_dir:
|
||||
cur_dir = '.'
|
||||
lib_name: str = "httplib"
|
||||
header_name: str = f"/{lib_name}.h"
|
||||
# get the input file
|
||||
in_file: str = f"{cur_dir}{header_name}"
|
||||
# get the output file
|
||||
cppm_out: str = f"{args.out}/{lib_name}.cppm"
|
||||
|
||||
# if the modification time of the out file is after the in file,
|
||||
# don't generate (as it is already finished)
|
||||
do_generate: bool = True
|
||||
|
||||
if os.path.exists(cppm_out):
|
||||
in_time: float = os.path.getmtime(in_file)
|
||||
out_time: float = os.path.getmtime(cppm_out)
|
||||
do_generate: bool = in_time > out_time
|
||||
|
||||
if do_generate:
|
||||
with open(in_file) as f:
|
||||
lines: List[str] = f.readlines()
|
||||
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
|
||||
# Find the Headers and Declaration comment markers
|
||||
headers_start: int = -1
|
||||
declaration_start: int = -1
|
||||
for i, line in enumerate(lines):
|
||||
if ' * Headers' in line:
|
||||
headers_start = i - 1 # Include the /* line
|
||||
elif ' * Declaration' in line:
|
||||
declaration_start = i - 1 # Stop before the /* line
|
||||
break
|
||||
|
||||
with open(cppm_out, 'w') as fm:
|
||||
# Write module file
|
||||
fm.write("module;\n\n")
|
||||
|
||||
# Write global module fragment (from Headers to Declaration comment)
|
||||
# Filter out 'using' declarations to avoid conflicts
|
||||
if headers_start >= 0 and declaration_start >= 0:
|
||||
for i in range(headers_start, declaration_start):
|
||||
line: str = lines[i]
|
||||
if 'using' not in line:
|
||||
fm.write(line)
|
||||
|
||||
fm.write("\nexport module httplib;\n\n")
|
||||
fm.write("export extern \"C++\" {\n")
|
||||
fm.write(f"{' ' * 4}#include \"httplib.h\"\n")
|
||||
fm.write("}\n")
|
||||
|
||||
print(f"Wrote {cppm_out}")
|
||||
else:
|
||||
print(f"{cppm_out} is up to date")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,55 @@
|
||||
set shell := ["bash", "-c"]
|
||||
|
||||
default: list
|
||||
|
||||
list:
|
||||
@just --list --unsorted
|
||||
|
||||
openssl:
|
||||
@(cd test && LSAN_OPTIONS=suppressions=lsan_suppressions.txt make)
|
||||
@(cd test && make proxy)
|
||||
|
||||
openssl_parallel:
|
||||
@(cd test && make test_openssl_parallel)
|
||||
|
||||
mbedtls:
|
||||
@(cd test && make test_mbedtls && LSAN_OPTIONS=suppressions=lsan_suppressions.txt ./test_mbedtls)
|
||||
@(cd test && make proxy_mbedtls)
|
||||
|
||||
mbedtls_parallel:
|
||||
@(cd test && make test_mbedtls_parallel)
|
||||
|
||||
wolfssl:
|
||||
@(cd test && make test_wolfssl && LSAN_OPTIONS=suppressions=lsan_suppressions.txt ./test_wolfssl)
|
||||
@(cd test && make proxy_wolfssl)
|
||||
|
||||
wolfssl_parallel:
|
||||
@(cd test && make test_wolfssl_parallel)
|
||||
|
||||
no_tls:
|
||||
@(cd test && make test_no_tls && ./test_no_tls)
|
||||
|
||||
no_tls_parallel:
|
||||
@(cd test && make test_no_tls_parallel)
|
||||
|
||||
others:
|
||||
@(cd test && make fuzz_test)
|
||||
@(cd test && make test_websocket_heartbeat && ./test_websocket_heartbeat)
|
||||
@(cd test && make test_thread_pool && ./test_thread_pool)
|
||||
|
||||
build:
|
||||
@(cd test && make test_split)
|
||||
@(cd test && make test_split_mbedtls)
|
||||
@(cd test && make test_split_wolfssl)
|
||||
|
||||
bench:
|
||||
@(cd benchmark && make bench-all)
|
||||
|
||||
docs:
|
||||
@cargo build --release --manifest-path docs-gen/Cargo.toml
|
||||
@./docs-gen/target/release/docs-gen build docs-src --out docs
|
||||
|
||||
docs-serve:
|
||||
@cargo build --release --manifest-path docs-gen/Cargo.toml
|
||||
-@./docs-gen/target/release/docs-gen serve docs-src --open
|
||||
|
||||
+72
-20
@@ -13,14 +13,23 @@ project(
|
||||
'b_lto=true',
|
||||
'warning_level=3'
|
||||
],
|
||||
meson_version: '>=0.47.0'
|
||||
meson_version: '>=0.63.0'
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
if cxx.sizeof('void *') != 8
|
||||
if host_machine.system() == 'windows'
|
||||
error('unsupported architecture: cpp-httplib doesn\'t support 32-bit Windows. Please use a 64-bit compiler.')
|
||||
else
|
||||
warning('cpp-httplib doesn\'t support 32-bit platforms. Please use a 64-bit compiler.')
|
||||
endif
|
||||
endif
|
||||
|
||||
# Check just in case downstream decides to edit the source
|
||||
# and add a project version
|
||||
version = meson.project_version()
|
||||
if version == 'undefined'
|
||||
cxx = meson.get_compiler('cpp')
|
||||
version = cxx.get_define('CPPHTTPLIB_VERSION',
|
||||
prefix: '#include <httplib.h>',
|
||||
include_directories: include_directories('.')).strip('"')
|
||||
@@ -30,24 +39,48 @@ endif
|
||||
deps = [dependency('threads')]
|
||||
args = []
|
||||
|
||||
openssl_dep = dependency('openssl', version: '>=1.1.1', required: get_option('cpp-httplib_openssl'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
if host_machine.system() == 'darwin'
|
||||
deps += dependency('appleframeworks', modules: ['CoreFoundation', 'Security'])
|
||||
if get_option('tls').allowed()
|
||||
tls_found = false
|
||||
if get_option('tls_backend') == 'openssl'
|
||||
openssl_dep = dependency('openssl', version: '>=3.0.0', required: get_option('tls'))
|
||||
if openssl_dep.found()
|
||||
deps += openssl_dep
|
||||
args += '-DCPPHTTPLIB_OPENSSL_SUPPORT'
|
||||
tls_found = true
|
||||
endif
|
||||
else
|
||||
mbedtls_dep = dependency('mbedtls', required: get_option('tls'))
|
||||
mbedtlsx509_dep = dependency('mbedx509', required: get_option('tls'))
|
||||
mbedtlscrypto_dep = dependency('mbedcrypto', required: get_option('tls'))
|
||||
if mbedtls_dep.found() and mbedtlsx509_dep.found() and mbedtlscrypto_dep.found()
|
||||
deps += mbedtls_dep
|
||||
deps += mbedtlsx509_dep
|
||||
deps += mbedtlscrypto_dep
|
||||
args += '-DCPPHTTPLIB_MBEDTLS_SUPPORT'
|
||||
tls_found = true
|
||||
endif
|
||||
endif
|
||||
|
||||
if tls_found and host_machine.system() == 'darwin'
|
||||
disable_macosx_certs = get_option('disable_macosx_automatic_root_certificates')
|
||||
if disable_macosx_certs
|
||||
args += '-DCPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES'
|
||||
else
|
||||
macosx_keychain_dep = dependency('appleframeworks', modules: ['CFNetwork', 'CoreFoundation', 'Security'], required: true)
|
||||
deps += macosx_keychain_dep
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
zlib_dep = dependency('zlib', required: get_option('cpp-httplib_zlib'))
|
||||
zlib_dep = dependency('zlib', required: get_option('zlib'))
|
||||
if zlib_dep.found()
|
||||
deps += zlib_dep
|
||||
args += '-DCPPHTTPLIB_ZLIB_SUPPORT'
|
||||
endif
|
||||
|
||||
brotli_deps = [dependency('libbrotlicommon', required: get_option('cpp-httplib_brotli'))]
|
||||
brotli_deps += dependency('libbrotlidec', required: get_option('cpp-httplib_brotli'))
|
||||
brotli_deps += dependency('libbrotlienc', required: get_option('cpp-httplib_brotli'))
|
||||
brotli_deps = [dependency('libbrotlicommon', required: get_option('brotli'))]
|
||||
brotli_deps += dependency('libbrotlidec', required: get_option('brotli'))
|
||||
brotli_deps += dependency('libbrotlienc', required: get_option('brotli'))
|
||||
|
||||
brotli_found_all = true
|
||||
foreach brotli_dep : brotli_deps
|
||||
@@ -61,9 +94,30 @@ if brotli_found_all
|
||||
args += '-DCPPHTTPLIB_BROTLI_SUPPORT'
|
||||
endif
|
||||
|
||||
zstd_dep = dependency('libzstd', required: get_option('zstd'))
|
||||
if zstd_dep.found()
|
||||
deps += zstd_dep
|
||||
args += '-DCPPHTTPLIB_ZSTD_SUPPORT'
|
||||
endif
|
||||
|
||||
async_ns_opt = get_option('non_blocking_getaddrinfo')
|
||||
|
||||
if host_machine.system() == 'windows'
|
||||
async_ns_dep = cxx.find_library('ws2_32', required: async_ns_opt)
|
||||
elif host_machine.system() == 'darwin'
|
||||
async_ns_dep = dependency('appleframeworks', modules: ['CFNetwork', 'CoreFoundation'], required: async_ns_opt)
|
||||
else
|
||||
async_ns_dep = cxx.find_library('anl', required: async_ns_opt)
|
||||
endif
|
||||
|
||||
if async_ns_dep.found()
|
||||
deps += async_ns_dep
|
||||
args += '-DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO'
|
||||
endif
|
||||
|
||||
cpp_httplib_dep = dependency('', required: false)
|
||||
|
||||
if get_option('cpp-httplib_compile')
|
||||
if get_option('compile')
|
||||
python3 = find_program('python3')
|
||||
|
||||
httplib_ch = custom_target(
|
||||
@@ -83,7 +137,7 @@ if get_option('cpp-httplib_compile')
|
||||
soversion: version.split('.')[0] + '.' + version.split('.')[1],
|
||||
install: true
|
||||
)
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1])
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, link_with: lib, sources: httplib_ch[1], version: version)
|
||||
|
||||
import('pkgconfig').generate(
|
||||
lib,
|
||||
@@ -94,21 +148,19 @@ if get_option('cpp-httplib_compile')
|
||||
)
|
||||
else
|
||||
install_headers('httplib.h')
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: include_directories('.'))
|
||||
cpp_httplib_dep = declare_dependency(compile_args: args, dependencies: deps, include_directories: '.', version: version)
|
||||
|
||||
import('pkgconfig').generate(
|
||||
name: 'cpp-httplib',
|
||||
description: 'A C++ HTTP/HTTPS server and client library',
|
||||
install_dir: join_paths(get_option('datadir'), 'pkgconfig'),
|
||||
install_dir: get_option('datadir')/'pkgconfig',
|
||||
url: 'https://github.com/yhirose/cpp-httplib',
|
||||
version: version
|
||||
)
|
||||
endif
|
||||
|
||||
if meson.version().version_compare('>=0.54.0')
|
||||
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
|
||||
endif
|
||||
meson.override_dependency('cpp-httplib', cpp_httplib_dep)
|
||||
|
||||
if get_option('cpp-httplib_test')
|
||||
if get_option('test')
|
||||
subdir('test')
|
||||
endif
|
||||
|
||||
+20
-5
@@ -2,8 +2,23 @@
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
option('cpp-httplib_openssl', type: 'feature', value: 'auto', description: 'Enable OpenSSL support')
|
||||
option('cpp-httplib_zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('cpp-httplib_brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
|
||||
option('cpp-httplib_test', type: 'boolean', value: false, description: 'Build tests')
|
||||
option('tls', type: 'feature', description: 'Enable TLS support')
|
||||
option('tls_backend', type: 'combo', choices: ['openssl', 'mbedtls'], value: 'openssl', description: 'Which TLS library to use')
|
||||
option('zlib', type: 'feature', value: 'auto', description: 'Enable zlib support')
|
||||
option('brotli', type: 'feature', value: 'auto', description: 'Enable Brotli support')
|
||||
option('zstd', type: 'feature', value: 'auto', description: 'Enable zstd support')
|
||||
option('disable_macosx_automatic_root_certificates', type: 'boolean', value: false, description: 'Disable loading system certs from the Apple Keychain on macOS')
|
||||
option('non_blocking_getaddrinfo', type: 'feature', value: 'auto', description: 'Enable asynchronous name lookup')
|
||||
option('compile', type: 'boolean', value: false, description: 'Split the header into a compilable header & source file (requires python3)')
|
||||
option('test', type: 'boolean', value: false, description: 'Build tests')
|
||||
|
||||
# Old option names
|
||||
option('openssl', type: 'feature', deprecated: 'tls')
|
||||
option('cpp-httplib_openssl', type: 'feature', deprecated: 'openssl')
|
||||
option('cpp-httplib_zlib', type: 'feature', deprecated: 'zlib')
|
||||
option('cpp-httplib_brotli', type: 'feature', deprecated: 'brotli')
|
||||
option('macosx_keychain', type: 'feature', deprecated: true, description: 'Deprecated: use disable_macosx_automatic_root_certificates instead')
|
||||
option('cpp-httplib_macosx_keychain', type: 'feature', deprecated: 'macosx_keychain')
|
||||
option('cpp-httplib_non_blocking_getaddrinfo', type: 'feature', deprecated: 'non_blocking_getaddrinfo')
|
||||
option('cpp-httplib_compile', type: 'boolean', value: false, deprecated: 'compile')
|
||||
option('cpp-httplib_test', type: 'boolean', value: false, deprecated: 'test')
|
||||
|
||||
@@ -2,66 +2,76 @@
|
||||
|
||||
"""This script splits httplib.h into .h and .cc parts."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from argparse import ArgumentParser, Namespace
|
||||
from typing import List
|
||||
|
||||
border = '// ----------------------------------------------------------------------------'
|
||||
BORDER: str = '// ----------------------------------------------------------------------------'
|
||||
|
||||
args_parser = argparse.ArgumentParser(description=__doc__)
|
||||
args_parser.add_argument(
|
||||
"-e", "--extension", help="extension of the implementation file (default: cc)",
|
||||
default="cc"
|
||||
)
|
||||
args_parser.add_argument(
|
||||
"-o", "--out", help="where to write the files (default: out)", default="out"
|
||||
)
|
||||
args = args_parser.parse_args()
|
||||
def main() -> None:
|
||||
"""Main entry point for the script."""
|
||||
|
||||
cur_dir = os.path.dirname(sys.argv[0])
|
||||
lib_name = 'httplib'
|
||||
header_name = '/' + lib_name + '.h'
|
||||
source_name = '/' + lib_name + '.' + args.extension
|
||||
# get the input file
|
||||
in_file = cur_dir + header_name
|
||||
# get the output file
|
||||
h_out = args.out + header_name
|
||||
cc_out = args.out + source_name
|
||||
args_parser: ArgumentParser = ArgumentParser(description=__doc__)
|
||||
args_parser.add_argument(
|
||||
"-e", "--extension", help="extension of the implementation file (default: cc)", default="cc"
|
||||
)
|
||||
args_parser.add_argument(
|
||||
"-o", "--out", help="where to write the files (default: out)", default="out"
|
||||
)
|
||||
args: Namespace = args_parser.parse_args()
|
||||
|
||||
# if the modification time of the out file is after the in file,
|
||||
# don't split (as it is already finished)
|
||||
do_split = True
|
||||
cur_dir: str = os.path.dirname(sys.argv[0])
|
||||
if not cur_dir:
|
||||
cur_dir = '.'
|
||||
lib_name: str = "httplib"
|
||||
header_name: str = f"/{lib_name}.h"
|
||||
source_name: str = f"/{lib_name}.{args.extension}"
|
||||
# get the input file
|
||||
in_file: str = f"{cur_dir}{header_name}"
|
||||
# get the output file
|
||||
h_out: str = f"{args.out}{header_name}"
|
||||
cc_out: str = f"{args.out}{source_name}"
|
||||
|
||||
if os.path.exists(h_out):
|
||||
in_time = os.path.getmtime(in_file)
|
||||
out_time = os.path.getmtime(h_out)
|
||||
do_split = in_time > out_time
|
||||
# if the modification time of the out file is after the in file,
|
||||
# don't split (as it is already finished)
|
||||
do_split: bool = True
|
||||
|
||||
if do_split:
|
||||
with open(in_file) as f:
|
||||
lines = f.readlines()
|
||||
if os.path.exists(h_out):
|
||||
in_time: float = os.path.getmtime(in_file)
|
||||
out_time: float = os.path.getmtime(h_out)
|
||||
do_split: bool = in_time > out_time
|
||||
|
||||
if do_split:
|
||||
with open(in_file) as f:
|
||||
lines: List[str] = f.readlines()
|
||||
|
||||
python_version = sys.version_info[0]
|
||||
if python_version < 3:
|
||||
os.makedirs(args.out)
|
||||
else:
|
||||
os.makedirs(args.out, exist_ok=True)
|
||||
|
||||
in_implementation = False
|
||||
cc_out = args.out + source_name
|
||||
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
|
||||
fc.write('#include "httplib.h"\n')
|
||||
fc.write('namespace httplib {\n')
|
||||
for line in lines:
|
||||
is_border_line = border in line
|
||||
if is_border_line:
|
||||
in_implementation = not in_implementation
|
||||
elif in_implementation:
|
||||
fc.write(line.replace('inline ', ''))
|
||||
else:
|
||||
fh.write(line)
|
||||
fc.write('} // namespace httplib\n')
|
||||
in_implementation: bool = False
|
||||
cc_out: str = args.out + source_name
|
||||
|
||||
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
|
||||
# Write source file
|
||||
fc.write("#include \"httplib.h\"\n")
|
||||
fc.write("namespace httplib {\n")
|
||||
|
||||
# Process lines for header and source split
|
||||
for line in lines:
|
||||
is_border_line: bool = BORDER in line
|
||||
if is_border_line:
|
||||
in_implementation: bool = not in_implementation
|
||||
elif in_implementation:
|
||||
fc.write(line.replace("inline ", ""))
|
||||
else:
|
||||
fh.write(line)
|
||||
|
||||
fc.write("} // namespace httplib\n")
|
||||
|
||||
print("Wrote {} and {}".format(h_out, cc_out))
|
||||
else:
|
||||
print("{} and {} are up to date".format(h_out, cc_out))
|
||||
print(f"Wrote {h_out} and {cc_out}")
|
||||
else:
|
||||
print(f"{h_out} and {cc_out} are up to date")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+30
-13
@@ -2,7 +2,7 @@ find_package(GTest)
|
||||
|
||||
if(GTest_FOUND)
|
||||
if(NOT TARGET GTest::gtest_main AND TARGET GTest::Main)
|
||||
# CMake <3.20
|
||||
# CMake <3.20
|
||||
add_library(GTest::gtest_main INTERFACE IMPORTED)
|
||||
target_link_libraries(GTest::gtest_main INTERFACE GTest::Main)
|
||||
endif()
|
||||
@@ -24,22 +24,24 @@ else()
|
||||
FetchContent_MakeAvailable(gtest)
|
||||
endif()
|
||||
|
||||
add_executable(httplib-test test.cc)
|
||||
find_package(CURL REQUIRED)
|
||||
|
||||
add_executable(httplib-test test.cc include_httplib.cc $<$<BOOL:${WIN32}>:include_windows_h.cc>)
|
||||
target_compile_options(httplib-test PRIVATE "$<$<CXX_COMPILER_ID:MSVC>:/utf-8;/bigobj>")
|
||||
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main)
|
||||
target_link_libraries(httplib-test PRIVATE httplib GTest::gtest_main CURL::libcurl)
|
||||
gtest_discover_tests(httplib-test)
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www www
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www2 www2
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_LIST_DIR}/www3 www3
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/ca-bundle.crt ca-bundle.crt
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_LIST_DIR}/image.jpg image.jpg
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
file(
|
||||
COPY www www2 www3 ca-bundle.crt image.jpg
|
||||
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
if(HTTPLIB_IS_USING_OPENSSL)
|
||||
if (OPENSSL_VERSION VERSION_LESS "3.2.0")
|
||||
set(OPENSSL_X509_FLAG "-x509")
|
||||
else()
|
||||
set(OPENSSL_X509_FLAG "-x509v1")
|
||||
endif()
|
||||
find_program(OPENSSL_COMMAND
|
||||
NAMES openssl
|
||||
PATHS ${OPENSSL_INCLUDE_DIR}/../bin
|
||||
@@ -59,7 +61,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
@@ -70,7 +72,7 @@ if(HTTPLIB_IS_USING_OPENSSL)
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -x509 -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
|
||||
COMMAND ${OPENSSL_COMMAND} req ${OPENSSL_X509_FLAG} -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.rootCA.conf -key rootCA.key.pem -days 1024
|
||||
OUTPUT_FILE rootCA.cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
@@ -101,4 +103,19 @@ if(HTTPLIB_IS_USING_OPENSSL)
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} genrsa -aes256 -passout pass:test012! 2048
|
||||
OUTPUT_FILE client_encrypted.key.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
execute_process(
|
||||
COMMAND ${OPENSSL_COMMAND} req -new -batch -config ${CMAKE_CURRENT_LIST_DIR}/test.conf -key client_encrypted.key.pem -passin pass:test012!
|
||||
COMMAND ${OPENSSL_COMMAND} x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial
|
||||
OUTPUT_FILE client_encrypted.cert.pem
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMAND_ERROR_IS_FATAL ANY
|
||||
)
|
||||
endif()
|
||||
|
||||
add_subdirectory(fuzzing)
|
||||
|
||||
+203
-25
@@ -1,26 +1,49 @@
|
||||
CXX = clang++
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS -fsanitize=address
|
||||
|
||||
PREFIX = /usr/local
|
||||
#PREFIX = $(shell brew --prefix)
|
||||
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@1.1
|
||||
#OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
CXXFLAGS = -g -std=c++11 -I. -Wall -Wextra -Wtype-limits -Wconversion -Wshadow $(EXTRA_CXXFLAGS) -DCPPHTTPLIB_USE_NON_BLOCKING_GETADDRINFO -fsanitize=address # -fno-exceptions -DCPPHTTPLIB_NO_EXCEPTIONS
|
||||
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
PREFIX ?= $(shell brew --prefix)
|
||||
OPENSSL_DIR = $(PREFIX)/opt/openssl@3
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I$(OPENSSL_DIR)/include -L$(OPENSSL_DIR)/lib -lssl -lcrypto
|
||||
OPENSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
MBEDTLS_DIR ?= $(shell brew --prefix mbedtls@3)
|
||||
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -I$(MBEDTLS_DIR)/include -L$(MBEDTLS_DIR)/lib -lmbedtls -lmbedx509 -lmbedcrypto
|
||||
MBEDTLS_SUPPORT += -framework CoreFoundation -framework Security
|
||||
WOLFSSL_DIR ?= $(shell brew --prefix wolfssl)
|
||||
WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -I$(WOLFSSL_DIR)/include -I$(WOLFSSL_DIR)/include/wolfssl -L$(WOLFSSL_DIR)/lib -lwolfssl
|
||||
WOLFSSL_SUPPORT += -framework CoreFoundation -framework Security
|
||||
else
|
||||
OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -lssl -lcrypto
|
||||
MBEDTLS_SUPPORT = -DCPPHTTPLIB_MBEDTLS_SUPPORT -lmbedtls -lmbedx509 -lmbedcrypto
|
||||
WOLFSSL_SUPPORT = -DCPPHTTPLIB_WOLFSSL_SUPPORT -lwolfssl
|
||||
endif
|
||||
endif
|
||||
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
ifneq ($(OS), Windows_NT)
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S), Darwin)
|
||||
# macOS: use Homebrew paths for brotli and zstd
|
||||
BROTLI_DIR = $(PREFIX)/opt/brotli
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -I$(BROTLI_DIR)/include -L$(BROTLI_DIR)/lib -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
ZSTD_DIR = $(PREFIX)/opt/zstd
|
||||
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -I$(ZSTD_DIR)/include -L$(ZSTD_DIR)/lib -lzstd
|
||||
LIBS = -lpthread -lcurl -framework CoreFoundation -framework CFNetwork
|
||||
else
|
||||
# Linux: use system paths
|
||||
BROTLI_SUPPORT = -DCPPHTTPLIB_BROTLI_SUPPORT -lbrotlicommon -lbrotlienc -lbrotlidec
|
||||
ZSTD_SUPPORT = -DCPPHTTPLIB_ZSTD_SUPPORT -lzstd
|
||||
LIBS = -lpthread -lcurl -lanl
|
||||
endif
|
||||
endif
|
||||
|
||||
TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) -pthread
|
||||
TEST_ARGS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
||||
TEST_ARGS_MBEDTLS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(MBEDTLS_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
||||
TEST_ARGS_WOLFSSL = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(WOLFSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
||||
TEST_ARGS_NO_TLS = gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(ZSTD_SUPPORT) $(LIBS)
|
||||
|
||||
# By default, use standalone_fuzz_target_runner.
|
||||
# This runner does no fuzzing, but simply executes the inputs
|
||||
@@ -30,23 +53,185 @@ TEST_ARGS = gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUP
|
||||
# OSS-Fuzz will define its own value for LIB_FUZZING_ENGINE.
|
||||
LIB_FUZZING_ENGINE ?= standalone_fuzz_target_runner.o
|
||||
|
||||
CLANG_FORMAT = clang-format
|
||||
REALPATH = $(shell which grealpath 2>/dev/null || which realpath 2>/dev/null)
|
||||
STYLE_CHECK_FILES = $(filter-out httplib.h httplib.cc, \
|
||||
$(wildcard example/*.h example/*.cc fuzzing/*.h fuzzing/*.cc *.h *.cc ../httplib.h))
|
||||
|
||||
all : test test_split
|
||||
./test
|
||||
LSAN_OPTIONS=suppressions=lsan_suppressions.txt ./test
|
||||
|
||||
SHARDS ?= 4
|
||||
|
||||
define run_parallel
|
||||
@echo "Running $(1) with $(SHARDS) shards in parallel..."
|
||||
@fail=0; \
|
||||
for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
|
||||
GTEST_TOTAL_SHARDS=$(SHARDS) GTEST_SHARD_INDEX=$$i \
|
||||
LSAN_OPTIONS=suppressions=lsan_suppressions.txt \
|
||||
./$(1) --gtest_color=yes > $(1)_shard_$$i.log 2>&1 & \
|
||||
done; \
|
||||
wait; \
|
||||
for i in $$(seq 0 $$(($(SHARDS) - 1))); do \
|
||||
if ! grep -q "\[ PASSED \]" $(1)_shard_$$i.log; then \
|
||||
echo "=== Shard $$i FAILED ==="; \
|
||||
cat $(1)_shard_$$i.log; \
|
||||
fail=1; \
|
||||
else \
|
||||
passed=$$(grep "\[ PASSED \]" $(1)_shard_$$i.log); \
|
||||
echo "Shard $$i: $$passed"; \
|
||||
fi; \
|
||||
done; \
|
||||
if [ $$fail -ne 0 ]; then exit 1; fi; \
|
||||
echo "All shards passed."
|
||||
endef
|
||||
|
||||
.PHONY: test_openssl_parallel test_mbedtls_parallel test_wolfssl_parallel test_no_tls_parallel
|
||||
|
||||
test_openssl_parallel : test
|
||||
$(call run_parallel,test)
|
||||
|
||||
test_mbedtls_parallel : test_mbedtls
|
||||
$(call run_parallel,test_mbedtls)
|
||||
|
||||
test_wolfssl_parallel : test_wolfssl
|
||||
$(call run_parallel,test_wolfssl)
|
||||
|
||||
test_no_tls_parallel : test_no_tls
|
||||
$(call run_parallel,test_no_tls)
|
||||
|
||||
proxy : test_proxy
|
||||
./test_proxy
|
||||
@echo "Starting proxy server..."
|
||||
cd proxy && \
|
||||
docker compose up -d
|
||||
@echo "Waiting for proxy to be ready..."
|
||||
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
|
||||
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
|
||||
@sleep 5
|
||||
@echo "Checking proxy server status..."
|
||||
@cd proxy && docker compose ps
|
||||
@echo "Checking proxy server logs..."
|
||||
@cd proxy && docker compose logs --tail=20
|
||||
@echo "Running proxy tests..."
|
||||
./test_proxy; \
|
||||
exit_code=$$?; \
|
||||
echo "Stopping proxy server..."; \
|
||||
cd proxy && docker compose down; \
|
||||
exit $$exit_code
|
||||
|
||||
proxy_mbedtls : test_proxy_mbedtls
|
||||
@echo "Starting proxy server..."
|
||||
cd proxy && \
|
||||
docker compose up -d
|
||||
@echo "Waiting for proxy to be ready..."
|
||||
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
|
||||
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
|
||||
@sleep 5
|
||||
@echo "Checking proxy server status..."
|
||||
@cd proxy && docker compose ps
|
||||
@echo "Checking proxy server logs..."
|
||||
@cd proxy && docker compose logs --tail=20
|
||||
@echo "Running proxy tests (Mbed TLS)..."
|
||||
./test_proxy_mbedtls; \
|
||||
exit_code=$$?; \
|
||||
echo "Stopping proxy server..."; \
|
||||
cd proxy && docker compose down; \
|
||||
exit $$exit_code
|
||||
|
||||
proxy_wolfssl : test_proxy_wolfssl
|
||||
@echo "Starting proxy server..."
|
||||
cd proxy && \
|
||||
docker compose up -d
|
||||
@echo "Waiting for proxy to be ready..."
|
||||
@until nc -z localhost 3128 && nc -z localhost 3129; do sleep 1; done
|
||||
@echo "Proxy servers are ready, waiting additional 5 seconds for full startup..."
|
||||
@sleep 5
|
||||
@echo "Checking proxy server status..."
|
||||
@cd proxy && docker compose ps
|
||||
@echo "Checking proxy server logs..."
|
||||
@cd proxy && docker compose logs --tail=20
|
||||
@echo "Running proxy tests (wolfSSL)..."
|
||||
./test_proxy_wolfssl; \
|
||||
exit_code=$$?; \
|
||||
echo "Stopping proxy server..."; \
|
||||
cd proxy && docker compose down; \
|
||||
exit $$exit_code
|
||||
|
||||
test : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS)
|
||||
@file $@
|
||||
|
||||
# Note: The intention of test_split is to verify that it works to compile and
|
||||
# link the split httplib.h, so there is normally no need to execute it.
|
||||
test_split : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
||||
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS)
|
||||
|
||||
# Mbed TLS backend targets
|
||||
test_mbedtls : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_MBEDTLS)
|
||||
@file $@
|
||||
|
||||
test_split_mbedtls : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
||||
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_MBEDTLS)
|
||||
|
||||
# wolfSSL backend targets
|
||||
test_wolfssl : test.cc include_httplib.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_WOLFSSL)
|
||||
@file $@
|
||||
|
||||
test_split_wolfssl : test.cc ../httplib.h httplib.cc Makefile cert.pem
|
||||
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_WOLFSSL)
|
||||
|
||||
# No TLS
|
||||
test_no_tls : test.cc include_httplib.cc ../httplib.h Makefile
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test.cc include_httplib.cc $(TEST_ARGS_NO_TLS)
|
||||
@file $@
|
||||
|
||||
test_split_no_tls : test.cc ../httplib.h httplib.cc Makefile
|
||||
$(CXX) -o $@ $(CXXFLAGS) test.cc httplib.cc $(TEST_ARGS_NO_TLS)
|
||||
|
||||
# ThreadPool unit tests (no TLS, no compression needed)
|
||||
test_thread_pool : test_thread_pool.cc ../httplib.h Makefile
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_thread_pool.cc gtest/src/gtest-all.cc gtest/src/gtest_main.cc -Igtest -Igtest/include -lpthread
|
||||
|
||||
check_abi:
|
||||
@./check-shared-library-abi-compatibility.sh
|
||||
|
||||
.PHONY: style_check
|
||||
style_check: $(STYLE_CHECK_FILES)
|
||||
@for file in $(STYLE_CHECK_FILES); do \
|
||||
$(CLANG_FORMAT) $$file > $$file.formatted; \
|
||||
if ! diff -u $$file $$file.formatted; then \
|
||||
file2=$$($(REALPATH) --relative-to=.. $$file); \
|
||||
printf "\n%*s\n" 80 | tr ' ' '#'; \
|
||||
printf "##%*s##\n" 76; \
|
||||
printf "## %-70s ##\n" "$$file2 not properly formatted. Please run clang-format."; \
|
||||
printf "##%*s##\n" 76; \
|
||||
printf "%*s\n\n" 80 | tr ' ' '#'; \
|
||||
failed=1; \
|
||||
fi; \
|
||||
rm -f $$file.formatted; \
|
||||
done; \
|
||||
if [ -n "$$failed" ]; then \
|
||||
echo "Style check failed for one or more files. See above for details."; \
|
||||
false; \
|
||||
else \
|
||||
echo "All files are properly formatted."; \
|
||||
fi
|
||||
|
||||
test_websocket_heartbeat : test_websocket_heartbeat.cc ../httplib.h Makefile
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_websocket_heartbeat.cc $(TEST_ARGS)
|
||||
@file $@
|
||||
|
||||
test_proxy : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS)
|
||||
|
||||
test_proxy_mbedtls : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS_MBEDTLS)
|
||||
|
||||
test_proxy_wolfssl : test_proxy.cc ../httplib.h Makefile cert.pem
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) test_proxy.cc $(TEST_ARGS_WOLFSSL)
|
||||
|
||||
# Runs server_fuzzer.cc based on value of $(LIB_FUZZING_ENGINE).
|
||||
# Usage: make fuzz_test LIB_FUZZING_ENGINE=/path/to/libFuzzer
|
||||
fuzz_test: server_fuzzer
|
||||
@@ -54,7 +239,8 @@ fuzz_test: server_fuzzer
|
||||
|
||||
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
|
||||
server_fuzzer : fuzzing/server_fuzzer.cc ../httplib.h standalone_fuzz_target_runner.o
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
|
||||
$(CXX) -o $@ -I.. $(CXXFLAGS) $< $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT) $(BROTLI_SUPPORT) $(LIB_FUZZING_ENGINE) $(ZSTD_SUPPORT) $(LIBS)
|
||||
@file $@
|
||||
|
||||
# Standalone fuzz runner, which just reads inputs from fuzzing/corpus/ dir and
|
||||
# feeds it to server_fuzzer.
|
||||
@@ -65,16 +251,8 @@ httplib.cc : ../httplib.h
|
||||
python3 ../split.py -o .
|
||||
|
||||
cert.pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
openssl genrsa 2048 > rootCA.key.pem
|
||||
openssl req -x509 -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
|
||||
openssl genrsa 2048 > client.key.pem
|
||||
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
|
||||
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
|
||||
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
|
||||
#c_rehash .
|
||||
./gen-certs.sh
|
||||
|
||||
clean:
|
||||
rm -f test test_split test_proxy server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc
|
||||
rm -rf test test_split test_mbedtls test_split_mbedtls test_wolfssl test_split_wolfssl test_no_tls, test_split_no_tls test_proxy test_proxy_mbedtls test_proxy_wolfssl server_fuzzer *.pem *.0 *.o *.1 *.srl httplib.h httplib.cc _build* *.dSYM *_shard_*.log cpp-httplib
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
file(GLOB HTTPLIB_CORPUS corpus/*)
|
||||
add_executable(httplib-test-fuzz
|
||||
server_fuzzer.cc
|
||||
standalone_fuzz_target_runner.cpp
|
||||
)
|
||||
target_link_libraries(httplib-test-fuzz PRIVATE httplib)
|
||||
add_test(
|
||||
NAME httplib-test-fuzz
|
||||
COMMAND httplib-test-fuzz ${HTTPLIB_CORPUS}
|
||||
)
|
||||
@@ -20,7 +20,7 @@ all : server_fuzzer
|
||||
# Fuzz target, so that you can choose which $(LIB_FUZZING_ENGINE) to use.
|
||||
server_fuzzer : server_fuzzer.cc ../../httplib.h
|
||||
# $(CXX) $(CXXFLAGS) -o $@ $< -Wl,-Bstatic $(OPENSSL_SUPPORT) -Wl,-Bdynamic -ldl $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
|
||||
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread
|
||||
$(CXX) $(CXXFLAGS) -o $@ $< $(ZLIB_SUPPORT) $(LIB_FUZZING_ENGINE) -pthread -lanl
|
||||
zip -q -r server_fuzzer_seed_corpus.zip corpus
|
||||
|
||||
clean:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include <httplib.h>
|
||||
#include <memory>
|
||||
|
||||
class FuzzedStream : public httplib::Stream {
|
||||
public:
|
||||
@@ -24,7 +25,9 @@ public:
|
||||
|
||||
bool is_readable() const override { return true; }
|
||||
|
||||
bool is_writable() const override { return true; }
|
||||
bool wait_readable() const override { return true; }
|
||||
|
||||
bool wait_writable() const override { return true; }
|
||||
|
||||
void get_remote_ip_and_port(std::string &ip, int &port) const override {
|
||||
ip = "127.0.0.1";
|
||||
@@ -38,6 +41,8 @@ public:
|
||||
|
||||
socket_t socket() const override { return 0; }
|
||||
|
||||
time_t duration() const override { return 0; };
|
||||
|
||||
private:
|
||||
const uint8_t *data_;
|
||||
size_t size_;
|
||||
@@ -49,8 +54,12 @@ class FuzzableServer : public httplib::Server {
|
||||
public:
|
||||
void ProcessFuzzedRequest(FuzzedStream &stream) {
|
||||
bool connection_close = false;
|
||||
process_request(stream, /*last_connection=*/false, connection_close,
|
||||
nullptr);
|
||||
process_request(stream,
|
||||
/*remote_addr=*/"",
|
||||
/*remote_port =*/0,
|
||||
/*local_addr=*/"",
|
||||
/*local_port =*/0,
|
||||
/*last_connection=*/false, connection_close, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
// on the test corpus or on a single file,
|
||||
// e.g. the one that comes from a bug report.
|
||||
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
// Forward declare the "fuzz target" interface.
|
||||
@@ -21,7 +21,7 @@ int main(int argc, char **argv) {
|
||||
std::ifstream in(argv[i]);
|
||||
in.seekg(0, in.end);
|
||||
size_t length = static_cast<size_t>(in.tellg());
|
||||
in.seekg (0, in.beg);
|
||||
in.seekg(0, in.beg);
|
||||
std::cout << "Reading " << length << " bytes from " << argv[i] << std::endl;
|
||||
// Allocate exactly length bytes so that we reliably catch buffer overflows.
|
||||
std::vector<char> bytes(length);
|
||||
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
if [[ $(openssl version) =~ 3\.[2-9]\.[0-9]+ ]]; then
|
||||
OPENSSL_X509_FLAG='-x509v1'
|
||||
else
|
||||
OPENSSL_X509_FLAG='-x509'
|
||||
fi
|
||||
|
||||
openssl genrsa 2048 > key.pem
|
||||
openssl req -new -batch -config test.conf -key key.pem | openssl x509 -days 3650 -req -signkey key.pem > cert.pem
|
||||
openssl req -x509 -config test.conf -key key.pem -sha256 -days 3650 -nodes -out cert2.pem -extensions SAN
|
||||
openssl genrsa 2048 > rootCA.key.pem
|
||||
openssl req $OPENSSL_X509_FLAG -new -batch -config test.rootCA.conf -key rootCA.key.pem -days 1024 > rootCA.cert.pem
|
||||
openssl genrsa 2048 > client.key.pem
|
||||
openssl req -new -batch -config test.conf -key client.key.pem | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client.cert.pem
|
||||
openssl genrsa -passout pass:test123! 2048 > key_encrypted.pem
|
||||
openssl req -new -batch -config test.conf -key key_encrypted.pem | openssl x509 -days 3650 -req -signkey key_encrypted.pem > cert_encrypted.pem
|
||||
openssl genrsa 2048 | openssl pkcs8 -topk8 -v1 PBE-SHA1-3DES -passout pass:test012! -out client_encrypted.key.pem
|
||||
openssl req -new -batch -config test.conf -key client_encrypted.key.pem -passin pass:test012! | openssl x509 -days 370 -req -CA rootCA.cert.pem -CAkey rootCA.key.pem -CAcreateserial > client_encrypted.cert.pem
|
||||
-12501
File diff suppressed because it is too large
Load Diff
-12377
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,237 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This file implements the AssertionResult type.
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_ASSERTION_RESULT_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_ASSERTION_RESULT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "gtest/gtest-message.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// A class for indicating whether an assertion was successful. When
|
||||
// the assertion wasn't successful, the AssertionResult object
|
||||
// remembers a non-empty message that describes how it failed.
|
||||
//
|
||||
// To create an instance of this class, use one of the factory functions
|
||||
// (AssertionSuccess() and AssertionFailure()).
|
||||
//
|
||||
// This class is useful for two purposes:
|
||||
// 1. Defining predicate functions to be used with Boolean test assertions
|
||||
// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts
|
||||
// 2. Defining predicate-format functions to be
|
||||
// used with predicate assertions (ASSERT_PRED_FORMAT*, etc).
|
||||
//
|
||||
// For example, if you define IsEven predicate:
|
||||
//
|
||||
// testing::AssertionResult IsEven(int n) {
|
||||
// if ((n % 2) == 0)
|
||||
// return testing::AssertionSuccess();
|
||||
// else
|
||||
// return testing::AssertionFailure() << n << " is odd";
|
||||
// }
|
||||
//
|
||||
// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5)))
|
||||
// will print the message
|
||||
//
|
||||
// Value of: IsEven(Fib(5))
|
||||
// Actual: false (5 is odd)
|
||||
// Expected: true
|
||||
//
|
||||
// instead of a more opaque
|
||||
//
|
||||
// Value of: IsEven(Fib(5))
|
||||
// Actual: false
|
||||
// Expected: true
|
||||
//
|
||||
// in case IsEven is a simple Boolean predicate.
|
||||
//
|
||||
// If you expect your predicate to be reused and want to support informative
|
||||
// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up
|
||||
// about half as often as positive ones in our tests), supply messages for
|
||||
// both success and failure cases:
|
||||
//
|
||||
// testing::AssertionResult IsEven(int n) {
|
||||
// if ((n % 2) == 0)
|
||||
// return testing::AssertionSuccess() << n << " is even";
|
||||
// else
|
||||
// return testing::AssertionFailure() << n << " is odd";
|
||||
// }
|
||||
//
|
||||
// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print
|
||||
//
|
||||
// Value of: IsEven(Fib(6))
|
||||
// Actual: true (8 is even)
|
||||
// Expected: false
|
||||
//
|
||||
// NB: Predicates that support negative Boolean assertions have reduced
|
||||
// performance in positive ones so be careful not to use them in tests
|
||||
// that have lots (tens of thousands) of positive Boolean assertions.
|
||||
//
|
||||
// To use this class with EXPECT_PRED_FORMAT assertions such as:
|
||||
//
|
||||
// // Verifies that Foo() returns an even number.
|
||||
// EXPECT_PRED_FORMAT1(IsEven, Foo());
|
||||
//
|
||||
// you need to define:
|
||||
//
|
||||
// testing::AssertionResult IsEven(const char* expr, int n) {
|
||||
// if ((n % 2) == 0)
|
||||
// return testing::AssertionSuccess();
|
||||
// else
|
||||
// return testing::AssertionFailure()
|
||||
// << "Expected: " << expr << " is even\n Actual: it's " << n;
|
||||
// }
|
||||
//
|
||||
// If Foo() returns 5, you will see the following message:
|
||||
//
|
||||
// Expected: Foo() is even
|
||||
// Actual: it's 5
|
||||
//
|
||||
class GTEST_API_ AssertionResult {
|
||||
public:
|
||||
// Copy constructor.
|
||||
// Used in EXPECT_TRUE/FALSE(assertion_result).
|
||||
AssertionResult(const AssertionResult& other);
|
||||
|
||||
// C4800 is a level 3 warning in Visual Studio 2015 and earlier.
|
||||
// This warning is not emitted in Visual Studio 2017.
|
||||
// This warning is off by default starting in Visual Studio 2019 but can be
|
||||
// enabled with command-line options.
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920)
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4800 /* forcing value to bool */)
|
||||
#endif
|
||||
|
||||
// Used in the EXPECT_TRUE/FALSE(bool_expression).
|
||||
//
|
||||
// T must be contextually convertible to bool.
|
||||
//
|
||||
// The second parameter prevents this overload from being considered if
|
||||
// the argument is implicitly convertible to AssertionResult. In that case
|
||||
// we want AssertionResult's copy constructor to be used.
|
||||
template <typename T>
|
||||
explicit AssertionResult(
|
||||
const T& success,
|
||||
typename std::enable_if<
|
||||
!std::is_convertible<T, AssertionResult>::value>::type*
|
||||
/*enabler*/
|
||||
= nullptr)
|
||||
: success_(success) {}
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER < 1910 || _MSC_VER >= 1920)
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_()
|
||||
#endif
|
||||
|
||||
// Assignment operator.
|
||||
AssertionResult& operator=(AssertionResult other) {
|
||||
swap(other);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns true if and only if the assertion succeeded.
|
||||
operator bool() const { return success_; } // NOLINT
|
||||
|
||||
// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE.
|
||||
AssertionResult operator!() const;
|
||||
|
||||
// Returns the text streamed into this AssertionResult. Test assertions
|
||||
// use it when they fail (i.e., the predicate's outcome doesn't match the
|
||||
// assertion's expectation). When nothing has been streamed into the
|
||||
// object, returns an empty string.
|
||||
const char* message() const {
|
||||
return message_.get() != nullptr ? message_->c_str() : "";
|
||||
}
|
||||
// Deprecated; please use message() instead.
|
||||
const char* failure_message() const { return message(); }
|
||||
|
||||
// Streams a custom failure message into this object.
|
||||
template <typename T>
|
||||
AssertionResult& operator<<(const T& value) {
|
||||
AppendMessage(Message() << value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Allows streaming basic output manipulators such as endl or flush into
|
||||
// this object.
|
||||
AssertionResult& operator<<(
|
||||
::std::ostream& (*basic_manipulator)(::std::ostream& stream)) {
|
||||
AppendMessage(Message() << basic_manipulator);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
// Appends the contents of message to message_.
|
||||
void AppendMessage(const Message& a_message) {
|
||||
if (message_.get() == nullptr) message_.reset(new ::std::string);
|
||||
message_->append(a_message.GetString().c_str());
|
||||
}
|
||||
|
||||
// Swap the contents of this AssertionResult with other.
|
||||
void swap(AssertionResult& other);
|
||||
|
||||
// Stores result of the assertion predicate.
|
||||
bool success_;
|
||||
// Stores the message describing the condition in case the expectation
|
||||
// construct is not satisfied with the predicate's outcome.
|
||||
// Referenced via a pointer to avoid taking too much stack frame space
|
||||
// with test assertions.
|
||||
std::unique_ptr< ::std::string> message_;
|
||||
};
|
||||
|
||||
// Makes a successful assertion result.
|
||||
GTEST_API_ AssertionResult AssertionSuccess();
|
||||
|
||||
// Makes a failed assertion result.
|
||||
GTEST_API_ AssertionResult AssertionFailure();
|
||||
|
||||
// Makes a failed assertion result with the given failure message.
|
||||
// Deprecated; use AssertionFailure() << msg.
|
||||
GTEST_API_ AssertionResult AssertionFailure(const Message& msg);
|
||||
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_ASSERTION_RESULT_H_
|
||||
@@ -0,0 +1,345 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This header file defines the public API for death tests. It is
|
||||
// #included by gtest.h so a user doesn't need to include this
|
||||
// directly.
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
|
||||
|
||||
#include "gtest/internal/gtest-death-test-internal.h"
|
||||
|
||||
// This flag controls the style of death tests. Valid values are "threadsafe",
|
||||
// meaning that the death test child process will re-execute the test binary
|
||||
// from the start, running only a single death test, or "fast",
|
||||
// meaning that the child process will execute the test logic immediately
|
||||
// after forking.
|
||||
GTEST_DECLARE_string_(death_test_style);
|
||||
|
||||
namespace testing {
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Returns a Boolean value indicating whether the caller is currently
|
||||
// executing in the context of the death test child process. Tools such as
|
||||
// Valgrind heap checkers may need this to modify their behavior in death
|
||||
// tests. IMPORTANT: This is an internal utility. Using it may break the
|
||||
// implementation of death tests. User code MUST NOT use it.
|
||||
GTEST_API_ bool InDeathTestChild();
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// The following macros are useful for writing death tests.
|
||||
|
||||
// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is
|
||||
// executed:
|
||||
//
|
||||
// 1. It generates a warning if there is more than one active
|
||||
// thread. This is because it's safe to fork() or clone() only
|
||||
// when there is a single thread.
|
||||
//
|
||||
// 2. The parent process clone()s a sub-process and runs the death
|
||||
// test in it; the sub-process exits with code 0 at the end of the
|
||||
// death test, if it hasn't exited already.
|
||||
//
|
||||
// 3. The parent process waits for the sub-process to terminate.
|
||||
//
|
||||
// 4. The parent process checks the exit code and error message of
|
||||
// the sub-process.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number");
|
||||
// for (int i = 0; i < 5; i++) {
|
||||
// EXPECT_DEATH(server.ProcessRequest(i),
|
||||
// "Invalid request .* in ProcessRequest()")
|
||||
// << "Failed to die on request " << i;
|
||||
// }
|
||||
//
|
||||
// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting");
|
||||
//
|
||||
// bool KilledBySIGHUP(int exit_code) {
|
||||
// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP;
|
||||
// }
|
||||
//
|
||||
// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!");
|
||||
//
|
||||
// The final parameter to each of these macros is a matcher applied to any data
|
||||
// the sub-process wrote to stderr. For compatibility with existing tests, a
|
||||
// bare string is interpreted as a regular expression matcher.
|
||||
//
|
||||
// On the regular expressions used in death tests:
|
||||
//
|
||||
// On POSIX-compliant systems (*nix), we use the <regex.h> library,
|
||||
// which uses the POSIX extended regex syntax.
|
||||
//
|
||||
// On other platforms (e.g. Windows or Mac), we only support a simple regex
|
||||
// syntax implemented as part of Google Test. This limited
|
||||
// implementation should be enough most of the time when writing
|
||||
// death tests; though it lacks many features you can find in PCRE
|
||||
// or POSIX extended regex syntax. For example, we don't support
|
||||
// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and
|
||||
// repetition count ("x{5,7}"), among others.
|
||||
//
|
||||
// Below is the syntax that we do support. We chose it to be a
|
||||
// subset of both PCRE and POSIX extended regex, so it's easy to
|
||||
// learn wherever you come from. In the following: 'A' denotes a
|
||||
// literal character, period (.), or a single \\ escape sequence;
|
||||
// 'x' and 'y' denote regular expressions; 'm' and 'n' are for
|
||||
// natural numbers.
|
||||
//
|
||||
// c matches any literal character c
|
||||
// \\d matches any decimal digit
|
||||
// \\D matches any character that's not a decimal digit
|
||||
// \\f matches \f
|
||||
// \\n matches \n
|
||||
// \\r matches \r
|
||||
// \\s matches any ASCII whitespace, including \n
|
||||
// \\S matches any character that's not a whitespace
|
||||
// \\t matches \t
|
||||
// \\v matches \v
|
||||
// \\w matches any letter, _, or decimal digit
|
||||
// \\W matches any character that \\w doesn't match
|
||||
// \\c matches any literal character c, which must be a punctuation
|
||||
// . matches any single character except \n
|
||||
// A? matches 0 or 1 occurrences of A
|
||||
// A* matches 0 or many occurrences of A
|
||||
// A+ matches 1 or many occurrences of A
|
||||
// ^ matches the beginning of a string (not that of each line)
|
||||
// $ matches the end of a string (not that of each line)
|
||||
// xy matches x followed by y
|
||||
//
|
||||
// If you accidentally use PCRE or POSIX extended regex features
|
||||
// not implemented by us, you will get a run-time failure. In that
|
||||
// case, please try to rewrite your regular expression within the
|
||||
// above syntax.
|
||||
//
|
||||
// This implementation is *not* meant to be as highly tuned or robust
|
||||
// as a compiled regex library, but should perform well enough for a
|
||||
// death test, which already incurs significant overhead by launching
|
||||
// a child process.
|
||||
//
|
||||
// Known caveats:
|
||||
//
|
||||
// A "threadsafe" style death test obtains the path to the test
|
||||
// program from argv[0] and re-executes it in the sub-process. For
|
||||
// simplicity, the current implementation doesn't search the PATH
|
||||
// when launching the sub-process. This means that the user must
|
||||
// invoke the test program via a path that contains at least one
|
||||
// path separator (e.g. path/to/foo_test and
|
||||
// /absolute/path/to/bar_test are fine, but foo_test is not). This
|
||||
// is rarely a problem as people usually don't put the test binary
|
||||
// directory in PATH.
|
||||
//
|
||||
|
||||
// Asserts that a given `statement` causes the program to exit, with an
|
||||
// integer exit status that satisfies `predicate`, and emitting error output
|
||||
// that matches `matcher`.
|
||||
#define ASSERT_EXIT(statement, predicate, matcher) \
|
||||
GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_FATAL_FAILURE_)
|
||||
|
||||
// Like `ASSERT_EXIT`, but continues on to successive tests in the
|
||||
// test suite, if any:
|
||||
#define EXPECT_EXIT(statement, predicate, matcher) \
|
||||
GTEST_DEATH_TEST_(statement, predicate, matcher, GTEST_NONFATAL_FAILURE_)
|
||||
|
||||
// Asserts that a given `statement` causes the program to exit, either by
|
||||
// explicitly exiting with a nonzero exit code or being killed by a
|
||||
// signal, and emitting error output that matches `matcher`.
|
||||
#define ASSERT_DEATH(statement, matcher) \
|
||||
ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, matcher)
|
||||
|
||||
// Like `ASSERT_DEATH`, but continues on to successive tests in the
|
||||
// test suite, if any:
|
||||
#define EXPECT_DEATH(statement, matcher) \
|
||||
EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, matcher)
|
||||
|
||||
// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*:
|
||||
|
||||
// Tests that an exit code describes a normal exit with a given exit code.
|
||||
class GTEST_API_ ExitedWithCode {
|
||||
public:
|
||||
explicit ExitedWithCode(int exit_code);
|
||||
ExitedWithCode(const ExitedWithCode&) = default;
|
||||
void operator=(const ExitedWithCode& other) = delete;
|
||||
bool operator()(int exit_status) const;
|
||||
|
||||
private:
|
||||
const int exit_code_;
|
||||
};
|
||||
|
||||
#if !GTEST_OS_WINDOWS && !GTEST_OS_FUCHSIA
|
||||
// Tests that an exit code describes an exit due to termination by a
|
||||
// given signal.
|
||||
class GTEST_API_ KilledBySignal {
|
||||
public:
|
||||
explicit KilledBySignal(int signum);
|
||||
bool operator()(int exit_status) const;
|
||||
|
||||
private:
|
||||
const int signum_;
|
||||
};
|
||||
#endif // !GTEST_OS_WINDOWS
|
||||
|
||||
// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode.
|
||||
// The death testing framework causes this to have interesting semantics,
|
||||
// since the sideeffects of the call are only visible in opt mode, and not
|
||||
// in debug mode.
|
||||
//
|
||||
// In practice, this can be used to test functions that utilize the
|
||||
// LOG(DFATAL) macro using the following style:
|
||||
//
|
||||
// int DieInDebugOr12(int* sideeffect) {
|
||||
// if (sideeffect) {
|
||||
// *sideeffect = 12;
|
||||
// }
|
||||
// LOG(DFATAL) << "death";
|
||||
// return 12;
|
||||
// }
|
||||
//
|
||||
// TEST(TestSuite, TestDieOr12WorksInDgbAndOpt) {
|
||||
// int sideeffect = 0;
|
||||
// // Only asserts in dbg.
|
||||
// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death");
|
||||
//
|
||||
// #ifdef NDEBUG
|
||||
// // opt-mode has sideeffect visible.
|
||||
// EXPECT_EQ(12, sideeffect);
|
||||
// #else
|
||||
// // dbg-mode no visible sideeffect.
|
||||
// EXPECT_EQ(0, sideeffect);
|
||||
// #endif
|
||||
// }
|
||||
//
|
||||
// This will assert that DieInDebugReturn12InOpt() crashes in debug
|
||||
// mode, usually due to a DCHECK or LOG(DFATAL), but returns the
|
||||
// appropriate fallback value (12 in this case) in opt mode. If you
|
||||
// need to test that a function has appropriate side-effects in opt
|
||||
// mode, include assertions against the side-effects. A general
|
||||
// pattern for this is:
|
||||
//
|
||||
// EXPECT_DEBUG_DEATH({
|
||||
// // Side-effects here will have an effect after this statement in
|
||||
// // opt mode, but none in debug mode.
|
||||
// EXPECT_EQ(12, DieInDebugOr12(&sideeffect));
|
||||
// }, "death");
|
||||
//
|
||||
#ifdef NDEBUG
|
||||
|
||||
#define EXPECT_DEBUG_DEATH(statement, regex) \
|
||||
GTEST_EXECUTE_STATEMENT_(statement, regex)
|
||||
|
||||
#define ASSERT_DEBUG_DEATH(statement, regex) \
|
||||
GTEST_EXECUTE_STATEMENT_(statement, regex)
|
||||
|
||||
#else
|
||||
|
||||
#define EXPECT_DEBUG_DEATH(statement, regex) EXPECT_DEATH(statement, regex)
|
||||
|
||||
#define ASSERT_DEBUG_DEATH(statement, regex) ASSERT_DEATH(statement, regex)
|
||||
|
||||
#endif // NDEBUG for EXPECT_DEBUG_DEATH
|
||||
#endif // GTEST_HAS_DEATH_TEST
|
||||
|
||||
// This macro is used for implementing macros such as
|
||||
// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where
|
||||
// death tests are not supported. Those macros must compile on such systems
|
||||
// if and only if EXPECT_DEATH and ASSERT_DEATH compile with the same parameters
|
||||
// on systems that support death tests. This allows one to write such a macro on
|
||||
// a system that does not support death tests and be sure that it will compile
|
||||
// on a death-test supporting system. It is exposed publicly so that systems
|
||||
// that have death-tests with stricter requirements than GTEST_HAS_DEATH_TEST
|
||||
// can write their own equivalent of EXPECT_DEATH_IF_SUPPORTED and
|
||||
// ASSERT_DEATH_IF_SUPPORTED.
|
||||
//
|
||||
// Parameters:
|
||||
// statement - A statement that a macro such as EXPECT_DEATH would test
|
||||
// for program termination. This macro has to make sure this
|
||||
// statement is compiled but not executed, to ensure that
|
||||
// EXPECT_DEATH_IF_SUPPORTED compiles with a certain
|
||||
// parameter if and only if EXPECT_DEATH compiles with it.
|
||||
// regex - A regex that a macro such as EXPECT_DEATH would use to test
|
||||
// the output of statement. This parameter has to be
|
||||
// compiled but not evaluated by this macro, to ensure that
|
||||
// this macro only accepts expressions that a macro such as
|
||||
// EXPECT_DEATH would accept.
|
||||
// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED
|
||||
// and a return statement for ASSERT_DEATH_IF_SUPPORTED.
|
||||
// This ensures that ASSERT_DEATH_IF_SUPPORTED will not
|
||||
// compile inside functions where ASSERT_DEATH doesn't
|
||||
// compile.
|
||||
//
|
||||
// The branch that has an always false condition is used to ensure that
|
||||
// statement and regex are compiled (and thus syntactically correct) but
|
||||
// never executed. The unreachable code macro protects the terminator
|
||||
// statement from generating an 'unreachable code' warning in case
|
||||
// statement unconditionally returns or throws. The Message constructor at
|
||||
// the end allows the syntax of streaming additional messages into the
|
||||
// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH.
|
||||
#define GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, terminator) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
if (::testing::internal::AlwaysTrue()) { \
|
||||
GTEST_LOG_(WARNING) << "Death tests are not supported on this platform.\n" \
|
||||
<< "Statement '" #statement "' cannot be verified."; \
|
||||
} else if (::testing::internal::AlwaysFalse()) { \
|
||||
::testing::internal::RE::PartialMatch(".*", (regex)); \
|
||||
GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
|
||||
terminator; \
|
||||
} else \
|
||||
::testing::Message()
|
||||
|
||||
// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and
|
||||
// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if
|
||||
// death tests are supported; otherwise they just issue a warning. This is
|
||||
// useful when you are combining death test assertions with normal test
|
||||
// assertions in one test.
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
EXPECT_DEATH(statement, regex)
|
||||
#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
ASSERT_DEATH(statement, regex)
|
||||
#else
|
||||
#define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, )
|
||||
#define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \
|
||||
GTEST_UNSUPPORTED_DEATH_TEST(statement, regex, return)
|
||||
#endif
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_
|
||||
@@ -0,0 +1,956 @@
|
||||
// Copyright 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This file implements just enough of the matcher interface to allow
|
||||
// EXPECT_DEATH and friends to accept a matcher argument.
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "gtest/gtest-printers.h"
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
// MSVC warning C5046 is new as of VS2017 version 15.8.
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1915
|
||||
#define GTEST_MAYBE_5046_ 5046
|
||||
#else
|
||||
#define GTEST_MAYBE_5046_
|
||||
#endif
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(
|
||||
4251 GTEST_MAYBE_5046_ /* class A needs to have dll-interface to be used by
|
||||
clients of class B */
|
||||
/* Symbol involving type with internal linkage not defined */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// To implement a matcher Foo for type T, define:
|
||||
// 1. a class FooMatcherMatcher that implements the matcher interface:
|
||||
// using is_gtest_matcher = void;
|
||||
// bool MatchAndExplain(const T&, std::ostream*);
|
||||
// (MatchResultListener* can also be used instead of std::ostream*)
|
||||
// void DescribeTo(std::ostream*);
|
||||
// void DescribeNegationTo(std::ostream*);
|
||||
//
|
||||
// 2. a factory function that creates a Matcher<T> object from a
|
||||
// FooMatcherMatcher.
|
||||
|
||||
class MatchResultListener {
|
||||
public:
|
||||
// Creates a listener object with the given underlying ostream. The
|
||||
// listener does not own the ostream, and does not dereference it
|
||||
// in the constructor or destructor.
|
||||
explicit MatchResultListener(::std::ostream* os) : stream_(os) {}
|
||||
virtual ~MatchResultListener() = 0; // Makes this class abstract.
|
||||
|
||||
// Streams x to the underlying ostream; does nothing if the ostream
|
||||
// is NULL.
|
||||
template <typename T>
|
||||
MatchResultListener& operator<<(const T& x) {
|
||||
if (stream_ != nullptr) *stream_ << x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Returns the underlying ostream.
|
||||
::std::ostream* stream() { return stream_; }
|
||||
|
||||
// Returns true if and only if the listener is interested in an explanation
|
||||
// of the match result. A matcher's MatchAndExplain() method can use
|
||||
// this information to avoid generating the explanation when no one
|
||||
// intends to hear it.
|
||||
bool IsInterested() const { return stream_ != nullptr; }
|
||||
|
||||
private:
|
||||
::std::ostream* const stream_;
|
||||
|
||||
MatchResultListener(const MatchResultListener&) = delete;
|
||||
MatchResultListener& operator=(const MatchResultListener&) = delete;
|
||||
};
|
||||
|
||||
inline MatchResultListener::~MatchResultListener() {}
|
||||
|
||||
// An instance of a subclass of this knows how to describe itself as a
|
||||
// matcher.
|
||||
class GTEST_API_ MatcherDescriberInterface {
|
||||
public:
|
||||
virtual ~MatcherDescriberInterface() {}
|
||||
|
||||
// Describes this matcher to an ostream. The function should print
|
||||
// a verb phrase that describes the property a value matching this
|
||||
// matcher should have. The subject of the verb phrase is the value
|
||||
// being matched. For example, the DescribeTo() method of the Gt(7)
|
||||
// matcher prints "is greater than 7".
|
||||
virtual void DescribeTo(::std::ostream* os) const = 0;
|
||||
|
||||
// Describes the negation of this matcher to an ostream. For
|
||||
// example, if the description of this matcher is "is greater than
|
||||
// 7", the negated description could be "is not greater than 7".
|
||||
// You are not required to override this when implementing
|
||||
// MatcherInterface, but it is highly advised so that your matcher
|
||||
// can produce good error messages.
|
||||
virtual void DescribeNegationTo(::std::ostream* os) const {
|
||||
*os << "not (";
|
||||
DescribeTo(os);
|
||||
*os << ")";
|
||||
}
|
||||
};
|
||||
|
||||
// The implementation of a matcher.
|
||||
template <typename T>
|
||||
class MatcherInterface : public MatcherDescriberInterface {
|
||||
public:
|
||||
// Returns true if and only if the matcher matches x; also explains the
|
||||
// match result to 'listener' if necessary (see the next paragraph), in
|
||||
// the form of a non-restrictive relative clause ("which ...",
|
||||
// "whose ...", etc) that describes x. For example, the
|
||||
// MatchAndExplain() method of the Pointee(...) matcher should
|
||||
// generate an explanation like "which points to ...".
|
||||
//
|
||||
// Implementations of MatchAndExplain() should add an explanation of
|
||||
// the match result *if and only if* they can provide additional
|
||||
// information that's not already present (or not obvious) in the
|
||||
// print-out of x and the matcher's description. Whether the match
|
||||
// succeeds is not a factor in deciding whether an explanation is
|
||||
// needed, as sometimes the caller needs to print a failure message
|
||||
// when the match succeeds (e.g. when the matcher is used inside
|
||||
// Not()).
|
||||
//
|
||||
// For example, a "has at least 10 elements" matcher should explain
|
||||
// what the actual element count is, regardless of the match result,
|
||||
// as it is useful information to the reader; on the other hand, an
|
||||
// "is empty" matcher probably only needs to explain what the actual
|
||||
// size is when the match fails, as it's redundant to say that the
|
||||
// size is 0 when the value is already known to be empty.
|
||||
//
|
||||
// You should override this method when defining a new matcher.
|
||||
//
|
||||
// It's the responsibility of the caller (Google Test) to guarantee
|
||||
// that 'listener' is not NULL. This helps to simplify a matcher's
|
||||
// implementation when it doesn't care about the performance, as it
|
||||
// can talk to 'listener' without checking its validity first.
|
||||
// However, in order to implement dummy listeners efficiently,
|
||||
// listener->stream() may be NULL.
|
||||
virtual bool MatchAndExplain(T x, MatchResultListener* listener) const = 0;
|
||||
|
||||
// Inherits these methods from MatcherDescriberInterface:
|
||||
// virtual void DescribeTo(::std::ostream* os) const = 0;
|
||||
// virtual void DescribeNegationTo(::std::ostream* os) const;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct AnyEq {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const {
|
||||
return a == b;
|
||||
}
|
||||
};
|
||||
struct AnyNe {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const {
|
||||
return a != b;
|
||||
}
|
||||
};
|
||||
struct AnyLt {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const {
|
||||
return a < b;
|
||||
}
|
||||
};
|
||||
struct AnyGt {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const {
|
||||
return a > b;
|
||||
}
|
||||
};
|
||||
struct AnyLe {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const {
|
||||
return a <= b;
|
||||
}
|
||||
};
|
||||
struct AnyGe {
|
||||
template <typename A, typename B>
|
||||
bool operator()(const A& a, const B& b) const {
|
||||
return a >= b;
|
||||
}
|
||||
};
|
||||
|
||||
// A match result listener that ignores the explanation.
|
||||
class DummyMatchResultListener : public MatchResultListener {
|
||||
public:
|
||||
DummyMatchResultListener() : MatchResultListener(nullptr) {}
|
||||
|
||||
private:
|
||||
DummyMatchResultListener(const DummyMatchResultListener&) = delete;
|
||||
DummyMatchResultListener& operator=(const DummyMatchResultListener&) = delete;
|
||||
};
|
||||
|
||||
// A match result listener that forwards the explanation to a given
|
||||
// ostream. The difference between this and MatchResultListener is
|
||||
// that the former is concrete.
|
||||
class StreamMatchResultListener : public MatchResultListener {
|
||||
public:
|
||||
explicit StreamMatchResultListener(::std::ostream* os)
|
||||
: MatchResultListener(os) {}
|
||||
|
||||
private:
|
||||
StreamMatchResultListener(const StreamMatchResultListener&) = delete;
|
||||
StreamMatchResultListener& operator=(const StreamMatchResultListener&) =
|
||||
delete;
|
||||
};
|
||||
|
||||
struct SharedPayloadBase {
|
||||
std::atomic<int> ref{1};
|
||||
void Ref() { ref.fetch_add(1, std::memory_order_relaxed); }
|
||||
bool Unref() { return ref.fetch_sub(1, std::memory_order_acq_rel) == 1; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct SharedPayload : SharedPayloadBase {
|
||||
explicit SharedPayload(const T& v) : value(v) {}
|
||||
explicit SharedPayload(T&& v) : value(std::move(v)) {}
|
||||
|
||||
static void Destroy(SharedPayloadBase* shared) {
|
||||
delete static_cast<SharedPayload*>(shared);
|
||||
}
|
||||
|
||||
T value;
|
||||
};
|
||||
|
||||
// An internal class for implementing Matcher<T>, which will derive
|
||||
// from it. We put functionalities common to all Matcher<T>
|
||||
// specializations here to avoid code duplication.
|
||||
template <typename T>
|
||||
class MatcherBase : private MatcherDescriberInterface {
|
||||
public:
|
||||
// Returns true if and only if the matcher matches x; also explains the
|
||||
// match result to 'listener'.
|
||||
bool MatchAndExplain(const T& x, MatchResultListener* listener) const {
|
||||
GTEST_CHECK_(vtable_ != nullptr);
|
||||
return vtable_->match_and_explain(*this, x, listener);
|
||||
}
|
||||
|
||||
// Returns true if and only if this matcher matches x.
|
||||
bool Matches(const T& x) const {
|
||||
DummyMatchResultListener dummy;
|
||||
return MatchAndExplain(x, &dummy);
|
||||
}
|
||||
|
||||
// Describes this matcher to an ostream.
|
||||
void DescribeTo(::std::ostream* os) const final {
|
||||
GTEST_CHECK_(vtable_ != nullptr);
|
||||
vtable_->describe(*this, os, false);
|
||||
}
|
||||
|
||||
// Describes the negation of this matcher to an ostream.
|
||||
void DescribeNegationTo(::std::ostream* os) const final {
|
||||
GTEST_CHECK_(vtable_ != nullptr);
|
||||
vtable_->describe(*this, os, true);
|
||||
}
|
||||
|
||||
// Explains why x matches, or doesn't match, the matcher.
|
||||
void ExplainMatchResultTo(const T& x, ::std::ostream* os) const {
|
||||
StreamMatchResultListener listener(os);
|
||||
MatchAndExplain(x, &listener);
|
||||
}
|
||||
|
||||
// Returns the describer for this matcher object; retains ownership
|
||||
// of the describer, which is only guaranteed to be alive when
|
||||
// this matcher object is alive.
|
||||
const MatcherDescriberInterface* GetDescriber() const {
|
||||
if (vtable_ == nullptr) return nullptr;
|
||||
return vtable_->get_describer(*this);
|
||||
}
|
||||
|
||||
protected:
|
||||
MatcherBase() : vtable_(nullptr), buffer_() {}
|
||||
|
||||
// Constructs a matcher from its implementation.
|
||||
template <typename U>
|
||||
explicit MatcherBase(const MatcherInterface<U>* impl)
|
||||
: vtable_(nullptr), buffer_() {
|
||||
Init(impl);
|
||||
}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
MatcherBase(M&& m) : vtable_(nullptr), buffer_() { // NOLINT
|
||||
Init(std::forward<M>(m));
|
||||
}
|
||||
|
||||
MatcherBase(const MatcherBase& other)
|
||||
: vtable_(other.vtable_), buffer_(other.buffer_) {
|
||||
if (IsShared()) buffer_.shared->Ref();
|
||||
}
|
||||
|
||||
MatcherBase& operator=(const MatcherBase& other) {
|
||||
if (this == &other) return *this;
|
||||
Destroy();
|
||||
vtable_ = other.vtable_;
|
||||
buffer_ = other.buffer_;
|
||||
if (IsShared()) buffer_.shared->Ref();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatcherBase(MatcherBase&& other)
|
||||
: vtable_(other.vtable_), buffer_(other.buffer_) {
|
||||
other.vtable_ = nullptr;
|
||||
}
|
||||
|
||||
MatcherBase& operator=(MatcherBase&& other) {
|
||||
if (this == &other) return *this;
|
||||
Destroy();
|
||||
vtable_ = other.vtable_;
|
||||
buffer_ = other.buffer_;
|
||||
other.vtable_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~MatcherBase() override { Destroy(); }
|
||||
|
||||
private:
|
||||
struct VTable {
|
||||
bool (*match_and_explain)(const MatcherBase&, const T&,
|
||||
MatchResultListener*);
|
||||
void (*describe)(const MatcherBase&, std::ostream*, bool negation);
|
||||
// Returns the captured object if it implements the interface, otherwise
|
||||
// returns the MatcherBase itself.
|
||||
const MatcherDescriberInterface* (*get_describer)(const MatcherBase&);
|
||||
// Called on shared instances when the reference count reaches 0.
|
||||
void (*shared_destroy)(SharedPayloadBase*);
|
||||
};
|
||||
|
||||
bool IsShared() const {
|
||||
return vtable_ != nullptr && vtable_->shared_destroy != nullptr;
|
||||
}
|
||||
|
||||
// If the implementation uses a listener, call that.
|
||||
template <typename P>
|
||||
static auto MatchAndExplainImpl(const MatcherBase& m, const T& value,
|
||||
MatchResultListener* listener)
|
||||
-> decltype(P::Get(m).MatchAndExplain(value, listener->stream())) {
|
||||
return P::Get(m).MatchAndExplain(value, listener->stream());
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
static auto MatchAndExplainImpl(const MatcherBase& m, const T& value,
|
||||
MatchResultListener* listener)
|
||||
-> decltype(P::Get(m).MatchAndExplain(value, listener)) {
|
||||
return P::Get(m).MatchAndExplain(value, listener);
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
static void DescribeImpl(const MatcherBase& m, std::ostream* os,
|
||||
bool negation) {
|
||||
if (negation) {
|
||||
P::Get(m).DescribeNegationTo(os);
|
||||
} else {
|
||||
P::Get(m).DescribeTo(os);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
static const MatcherDescriberInterface* GetDescriberImpl(
|
||||
const MatcherBase& m) {
|
||||
// If the impl is a MatcherDescriberInterface, then return it.
|
||||
// Otherwise use MatcherBase itself.
|
||||
// This allows us to implement the GetDescriber() function without support
|
||||
// from the impl, but some users really want to get their impl back when
|
||||
// they call GetDescriber().
|
||||
// We use std::get on a tuple as a workaround of not having `if constexpr`.
|
||||
return std::get<(
|
||||
std::is_convertible<decltype(&P::Get(m)),
|
||||
const MatcherDescriberInterface*>::value
|
||||
? 1
|
||||
: 0)>(std::make_tuple(&m, &P::Get(m)));
|
||||
}
|
||||
|
||||
template <typename P>
|
||||
const VTable* GetVTable() {
|
||||
static constexpr VTable kVTable = {&MatchAndExplainImpl<P>,
|
||||
&DescribeImpl<P>, &GetDescriberImpl<P>,
|
||||
P::shared_destroy};
|
||||
return &kVTable;
|
||||
}
|
||||
|
||||
union Buffer {
|
||||
// Add some types to give Buffer some common alignment/size use cases.
|
||||
void* ptr;
|
||||
double d;
|
||||
int64_t i;
|
||||
// And add one for the out-of-line cases.
|
||||
SharedPayloadBase* shared;
|
||||
};
|
||||
|
||||
void Destroy() {
|
||||
if (IsShared() && buffer_.shared->Unref()) {
|
||||
vtable_->shared_destroy(buffer_.shared);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename M>
|
||||
static constexpr bool IsInlined() {
|
||||
return sizeof(M) <= sizeof(Buffer) && alignof(M) <= alignof(Buffer) &&
|
||||
std::is_trivially_copy_constructible<M>::value &&
|
||||
std::is_trivially_destructible<M>::value;
|
||||
}
|
||||
|
||||
template <typename M, bool = MatcherBase::IsInlined<M>()>
|
||||
struct ValuePolicy {
|
||||
static const M& Get(const MatcherBase& m) {
|
||||
// When inlined along with Init, need to be explicit to avoid violating
|
||||
// strict aliasing rules.
|
||||
const M* ptr =
|
||||
static_cast<const M*>(static_cast<const void*>(&m.buffer_));
|
||||
return *ptr;
|
||||
}
|
||||
static void Init(MatcherBase& m, M impl) {
|
||||
::new (static_cast<void*>(&m.buffer_)) M(impl);
|
||||
}
|
||||
static constexpr auto shared_destroy = nullptr;
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
struct ValuePolicy<M, false> {
|
||||
using Shared = SharedPayload<M>;
|
||||
static const M& Get(const MatcherBase& m) {
|
||||
return static_cast<Shared*>(m.buffer_.shared)->value;
|
||||
}
|
||||
template <typename Arg>
|
||||
static void Init(MatcherBase& m, Arg&& arg) {
|
||||
m.buffer_.shared = new Shared(std::forward<Arg>(arg));
|
||||
}
|
||||
static constexpr auto shared_destroy = &Shared::Destroy;
|
||||
};
|
||||
|
||||
template <typename U, bool B>
|
||||
struct ValuePolicy<const MatcherInterface<U>*, B> {
|
||||
using M = const MatcherInterface<U>;
|
||||
using Shared = SharedPayload<std::unique_ptr<M>>;
|
||||
static const M& Get(const MatcherBase& m) {
|
||||
return *static_cast<Shared*>(m.buffer_.shared)->value;
|
||||
}
|
||||
static void Init(MatcherBase& m, M* impl) {
|
||||
m.buffer_.shared = new Shared(std::unique_ptr<M>(impl));
|
||||
}
|
||||
|
||||
static constexpr auto shared_destroy = &Shared::Destroy;
|
||||
};
|
||||
|
||||
template <typename M>
|
||||
void Init(M&& m) {
|
||||
using MM = typename std::decay<M>::type;
|
||||
using Policy = ValuePolicy<MM>;
|
||||
vtable_ = GetVTable<Policy>();
|
||||
Policy::Init(*this, std::forward<M>(m));
|
||||
}
|
||||
|
||||
const VTable* vtable_;
|
||||
Buffer buffer_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
// A Matcher<T> is a copyable and IMMUTABLE (except by assignment)
|
||||
// object that can check whether a value of type T matches. The
|
||||
// implementation of Matcher<T> is just a std::shared_ptr to const
|
||||
// MatcherInterface<T>. Don't inherit from Matcher!
|
||||
template <typename T>
|
||||
class Matcher : public internal::MatcherBase<T> {
|
||||
public:
|
||||
// Constructs a null matcher. Needed for storing Matcher objects in STL
|
||||
// containers. A default-constructed matcher is not yet initialized. You
|
||||
// cannot use it until a valid value has been assigned to it.
|
||||
explicit Matcher() {} // NOLINT
|
||||
|
||||
// Constructs a matcher from its implementation.
|
||||
explicit Matcher(const MatcherInterface<const T&>* impl)
|
||||
: internal::MatcherBase<T>(impl) {}
|
||||
|
||||
template <typename U>
|
||||
explicit Matcher(
|
||||
const MatcherInterface<U>* impl,
|
||||
typename std::enable_if<!std::is_same<U, const U&>::value>::type* =
|
||||
nullptr)
|
||||
: internal::MatcherBase<T>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) : internal::MatcherBase<T>(std::forward<M>(m)) {} // NOLINT
|
||||
|
||||
// Implicit constructor here allows people to write
|
||||
// EXPECT_CALL(foo, Bar(5)) instead of EXPECT_CALL(foo, Bar(Eq(5))) sometimes
|
||||
Matcher(T value); // NOLINT
|
||||
};
|
||||
|
||||
// The following two specializations allow the user to write str
|
||||
// instead of Eq(str) and "foo" instead of Eq("foo") when a std::string
|
||||
// matcher is expected.
|
||||
template <>
|
||||
class GTEST_API_ Matcher<const std::string&>
|
||||
: public internal::MatcherBase<const std::string&> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const std::string&>* impl)
|
||||
: internal::MatcherBase<const std::string&>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<const std::string&>(std::forward<M>(m)) {}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a std::string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
};
|
||||
|
||||
template <>
|
||||
class GTEST_API_ Matcher<std::string>
|
||||
: public internal::MatcherBase<std::string> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const std::string&>* impl)
|
||||
: internal::MatcherBase<std::string>(impl) {}
|
||||
explicit Matcher(const MatcherInterface<std::string>* impl)
|
||||
: internal::MatcherBase<std::string>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<std::string>(std::forward<M>(m)) {}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
};
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
// The following two specializations allow the user to write str
|
||||
// instead of Eq(str) and "foo" instead of Eq("foo") when a absl::string_view
|
||||
// matcher is expected.
|
||||
template <>
|
||||
class GTEST_API_ Matcher<const internal::StringView&>
|
||||
: public internal::MatcherBase<const internal::StringView&> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const internal::StringView&>* impl)
|
||||
: internal::MatcherBase<const internal::StringView&>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<const internal::StringView&>(std::forward<M>(m)) {
|
||||
}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a std::string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
|
||||
// Allows the user to pass absl::string_views or std::string_views directly.
|
||||
Matcher(internal::StringView s); // NOLINT
|
||||
};
|
||||
|
||||
template <>
|
||||
class GTEST_API_ Matcher<internal::StringView>
|
||||
: public internal::MatcherBase<internal::StringView> {
|
||||
public:
|
||||
Matcher() {}
|
||||
|
||||
explicit Matcher(const MatcherInterface<const internal::StringView&>* impl)
|
||||
: internal::MatcherBase<internal::StringView>(impl) {}
|
||||
explicit Matcher(const MatcherInterface<internal::StringView>* impl)
|
||||
: internal::MatcherBase<internal::StringView>(impl) {}
|
||||
|
||||
template <typename M, typename = typename std::remove_reference<
|
||||
M>::type::is_gtest_matcher>
|
||||
Matcher(M&& m) // NOLINT
|
||||
: internal::MatcherBase<internal::StringView>(std::forward<M>(m)) {}
|
||||
|
||||
// Allows the user to write str instead of Eq(str) sometimes, where
|
||||
// str is a std::string object.
|
||||
Matcher(const std::string& s); // NOLINT
|
||||
|
||||
// Allows the user to write "foo" instead of Eq("foo") sometimes.
|
||||
Matcher(const char* s); // NOLINT
|
||||
|
||||
// Allows the user to pass absl::string_views or std::string_views directly.
|
||||
Matcher(internal::StringView s); // NOLINT
|
||||
};
|
||||
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
// Prints a matcher in a human-readable format.
|
||||
template <typename T>
|
||||
std::ostream& operator<<(std::ostream& os, const Matcher<T>& matcher) {
|
||||
matcher.DescribeTo(&os);
|
||||
return os;
|
||||
}
|
||||
|
||||
// The PolymorphicMatcher class template makes it easy to implement a
|
||||
// polymorphic matcher (i.e. a matcher that can match values of more
|
||||
// than one type, e.g. Eq(n) and NotNull()).
|
||||
//
|
||||
// To define a polymorphic matcher, a user should provide an Impl
|
||||
// class that has a DescribeTo() method and a DescribeNegationTo()
|
||||
// method, and define a member function (or member function template)
|
||||
//
|
||||
// bool MatchAndExplain(const Value& value,
|
||||
// MatchResultListener* listener) const;
|
||||
//
|
||||
// See the definition of NotNull() for a complete example.
|
||||
template <class Impl>
|
||||
class PolymorphicMatcher {
|
||||
public:
|
||||
explicit PolymorphicMatcher(const Impl& an_impl) : impl_(an_impl) {}
|
||||
|
||||
// Returns a mutable reference to the underlying matcher
|
||||
// implementation object.
|
||||
Impl& mutable_impl() { return impl_; }
|
||||
|
||||
// Returns an immutable reference to the underlying matcher
|
||||
// implementation object.
|
||||
const Impl& impl() const { return impl_; }
|
||||
|
||||
template <typename T>
|
||||
operator Matcher<T>() const {
|
||||
return Matcher<T>(new MonomorphicImpl<const T&>(impl_));
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
class MonomorphicImpl : public MatcherInterface<T> {
|
||||
public:
|
||||
explicit MonomorphicImpl(const Impl& impl) : impl_(impl) {}
|
||||
|
||||
void DescribeTo(::std::ostream* os) const override { impl_.DescribeTo(os); }
|
||||
|
||||
void DescribeNegationTo(::std::ostream* os) const override {
|
||||
impl_.DescribeNegationTo(os);
|
||||
}
|
||||
|
||||
bool MatchAndExplain(T x, MatchResultListener* listener) const override {
|
||||
return impl_.MatchAndExplain(x, listener);
|
||||
}
|
||||
|
||||
private:
|
||||
const Impl impl_;
|
||||
};
|
||||
|
||||
Impl impl_;
|
||||
};
|
||||
|
||||
// Creates a matcher from its implementation.
|
||||
// DEPRECATED: Especially in the generic code, prefer:
|
||||
// Matcher<T>(new MyMatcherImpl<const T&>(...));
|
||||
//
|
||||
// MakeMatcher may create a Matcher that accepts its argument by value, which
|
||||
// leads to unnecessary copies & lack of support for non-copyable types.
|
||||
template <typename T>
|
||||
inline Matcher<T> MakeMatcher(const MatcherInterface<T>* impl) {
|
||||
return Matcher<T>(impl);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher from its implementation. This is
|
||||
// easier to use than the PolymorphicMatcher<Impl> constructor as it
|
||||
// doesn't require you to explicitly write the template argument, e.g.
|
||||
//
|
||||
// MakePolymorphicMatcher(foo);
|
||||
// vs
|
||||
// PolymorphicMatcher<TypeOfFoo>(foo);
|
||||
template <class Impl>
|
||||
inline PolymorphicMatcher<Impl> MakePolymorphicMatcher(const Impl& impl) {
|
||||
return PolymorphicMatcher<Impl>(impl);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
// Implements a matcher that compares a given value with a
|
||||
// pre-supplied value using one of the ==, <=, <, etc, operators. The
|
||||
// two values being compared don't have to have the same type.
|
||||
//
|
||||
// The matcher defined here is polymorphic (for example, Eq(5) can be
|
||||
// used to match an int, a short, a double, etc). Therefore we use
|
||||
// a template type conversion operator in the implementation.
|
||||
//
|
||||
// The following template definition assumes that the Rhs parameter is
|
||||
// a "bare" type (i.e. neither 'const T' nor 'T&').
|
||||
template <typename D, typename Rhs, typename Op>
|
||||
class ComparisonBase {
|
||||
public:
|
||||
explicit ComparisonBase(const Rhs& rhs) : rhs_(rhs) {}
|
||||
|
||||
using is_gtest_matcher = void;
|
||||
|
||||
template <typename Lhs>
|
||||
bool MatchAndExplain(const Lhs& lhs, std::ostream*) const {
|
||||
return Op()(lhs, Unwrap(rhs_));
|
||||
}
|
||||
void DescribeTo(std::ostream* os) const {
|
||||
*os << D::Desc() << " ";
|
||||
UniversalPrint(Unwrap(rhs_), os);
|
||||
}
|
||||
void DescribeNegationTo(std::ostream* os) const {
|
||||
*os << D::NegatedDesc() << " ";
|
||||
UniversalPrint(Unwrap(rhs_), os);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
static const T& Unwrap(const T& v) {
|
||||
return v;
|
||||
}
|
||||
template <typename T>
|
||||
static const T& Unwrap(std::reference_wrapper<T> v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
Rhs rhs_;
|
||||
};
|
||||
|
||||
template <typename Rhs>
|
||||
class EqMatcher : public ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq> {
|
||||
public:
|
||||
explicit EqMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<EqMatcher<Rhs>, Rhs, AnyEq>(rhs) {}
|
||||
static const char* Desc() { return "is equal to"; }
|
||||
static const char* NegatedDesc() { return "isn't equal to"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class NeMatcher : public ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe> {
|
||||
public:
|
||||
explicit NeMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<NeMatcher<Rhs>, Rhs, AnyNe>(rhs) {}
|
||||
static const char* Desc() { return "isn't equal to"; }
|
||||
static const char* NegatedDesc() { return "is equal to"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class LtMatcher : public ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt> {
|
||||
public:
|
||||
explicit LtMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<LtMatcher<Rhs>, Rhs, AnyLt>(rhs) {}
|
||||
static const char* Desc() { return "is <"; }
|
||||
static const char* NegatedDesc() { return "isn't <"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class GtMatcher : public ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt> {
|
||||
public:
|
||||
explicit GtMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<GtMatcher<Rhs>, Rhs, AnyGt>(rhs) {}
|
||||
static const char* Desc() { return "is >"; }
|
||||
static const char* NegatedDesc() { return "isn't >"; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class LeMatcher : public ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe> {
|
||||
public:
|
||||
explicit LeMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<LeMatcher<Rhs>, Rhs, AnyLe>(rhs) {}
|
||||
static const char* Desc() { return "is <="; }
|
||||
static const char* NegatedDesc() { return "isn't <="; }
|
||||
};
|
||||
template <typename Rhs>
|
||||
class GeMatcher : public ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe> {
|
||||
public:
|
||||
explicit GeMatcher(const Rhs& rhs)
|
||||
: ComparisonBase<GeMatcher<Rhs>, Rhs, AnyGe>(rhs) {}
|
||||
static const char* Desc() { return "is >="; }
|
||||
static const char* NegatedDesc() { return "isn't >="; }
|
||||
};
|
||||
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_constructible<std::string, T>::value>::type>
|
||||
using StringLike = T;
|
||||
|
||||
// Implements polymorphic matchers MatchesRegex(regex) and
|
||||
// ContainsRegex(regex), which can be used as a Matcher<T> as long as
|
||||
// T can be converted to a string.
|
||||
class MatchesRegexMatcher {
|
||||
public:
|
||||
MatchesRegexMatcher(const RE* regex, bool full_match)
|
||||
: regex_(regex), full_match_(full_match) {}
|
||||
|
||||
#if GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
bool MatchAndExplain(const internal::StringView& s,
|
||||
MatchResultListener* listener) const {
|
||||
return MatchAndExplain(std::string(s), listener);
|
||||
}
|
||||
#endif // GTEST_INTERNAL_HAS_STRING_VIEW
|
||||
|
||||
// Accepts pointer types, particularly:
|
||||
// const char*
|
||||
// char*
|
||||
// const wchar_t*
|
||||
// wchar_t*
|
||||
template <typename CharType>
|
||||
bool MatchAndExplain(CharType* s, MatchResultListener* listener) const {
|
||||
return s != nullptr && MatchAndExplain(std::string(s), listener);
|
||||
}
|
||||
|
||||
// Matches anything that can convert to std::string.
|
||||
//
|
||||
// This is a template, not just a plain function with const std::string&,
|
||||
// because absl::string_view has some interfering non-explicit constructors.
|
||||
template <class MatcheeStringType>
|
||||
bool MatchAndExplain(const MatcheeStringType& s,
|
||||
MatchResultListener* /* listener */) const {
|
||||
const std::string& s2(s);
|
||||
return full_match_ ? RE::FullMatch(s2, *regex_)
|
||||
: RE::PartialMatch(s2, *regex_);
|
||||
}
|
||||
|
||||
void DescribeTo(::std::ostream* os) const {
|
||||
*os << (full_match_ ? "matches" : "contains") << " regular expression ";
|
||||
UniversalPrinter<std::string>::Print(regex_->pattern(), os);
|
||||
}
|
||||
|
||||
void DescribeNegationTo(::std::ostream* os) const {
|
||||
*os << "doesn't " << (full_match_ ? "match" : "contain")
|
||||
<< " regular expression ";
|
||||
UniversalPrinter<std::string>::Print(regex_->pattern(), os);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::shared_ptr<const RE> regex_;
|
||||
const bool full_match_;
|
||||
};
|
||||
} // namespace internal
|
||||
|
||||
// Matches a string that fully matches regular expression 'regex'.
|
||||
// The matcher takes ownership of 'regex'.
|
||||
inline PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex(
|
||||
const internal::RE* regex) {
|
||||
return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, true));
|
||||
}
|
||||
template <typename T = std::string>
|
||||
PolymorphicMatcher<internal::MatchesRegexMatcher> MatchesRegex(
|
||||
const internal::StringLike<T>& regex) {
|
||||
return MatchesRegex(new internal::RE(std::string(regex)));
|
||||
}
|
||||
|
||||
// Matches a string that contains regular expression 'regex'.
|
||||
// The matcher takes ownership of 'regex'.
|
||||
inline PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex(
|
||||
const internal::RE* regex) {
|
||||
return MakePolymorphicMatcher(internal::MatchesRegexMatcher(regex, false));
|
||||
}
|
||||
template <typename T = std::string>
|
||||
PolymorphicMatcher<internal::MatchesRegexMatcher> ContainsRegex(
|
||||
const internal::StringLike<T>& regex) {
|
||||
return ContainsRegex(new internal::RE(std::string(regex)));
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything equal to x.
|
||||
// Note: if the parameter of Eq() were declared as const T&, Eq("foo")
|
||||
// wouldn't compile.
|
||||
template <typename T>
|
||||
inline internal::EqMatcher<T> Eq(T x) {
|
||||
return internal::EqMatcher<T>(x);
|
||||
}
|
||||
|
||||
// Constructs a Matcher<T> from a 'value' of type T. The constructed
|
||||
// matcher matches any value that's equal to 'value'.
|
||||
template <typename T>
|
||||
Matcher<T>::Matcher(T value) {
|
||||
*this = Eq(value);
|
||||
}
|
||||
|
||||
// Creates a monomorphic matcher that matches anything with type Lhs
|
||||
// and equal to rhs. A user may need to use this instead of Eq(...)
|
||||
// in order to resolve an overloading ambiguity.
|
||||
//
|
||||
// TypedEq<T>(x) is just a convenient short-hand for Matcher<T>(Eq(x))
|
||||
// or Matcher<T>(x), but more readable than the latter.
|
||||
//
|
||||
// We could define similar monomorphic matchers for other comparison
|
||||
// operations (e.g. TypedLt, TypedGe, and etc), but decided not to do
|
||||
// it yet as those are used much less than Eq() in practice. A user
|
||||
// can always write Matcher<T>(Lt(5)) to be explicit about the type,
|
||||
// for example.
|
||||
template <typename Lhs, typename Rhs>
|
||||
inline Matcher<Lhs> TypedEq(const Rhs& rhs) {
|
||||
return Eq(rhs);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything >= x.
|
||||
template <typename Rhs>
|
||||
inline internal::GeMatcher<Rhs> Ge(Rhs x) {
|
||||
return internal::GeMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything > x.
|
||||
template <typename Rhs>
|
||||
inline internal::GtMatcher<Rhs> Gt(Rhs x) {
|
||||
return internal::GtMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything <= x.
|
||||
template <typename Rhs>
|
||||
inline internal::LeMatcher<Rhs> Le(Rhs x) {
|
||||
return internal::LeMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything < x.
|
||||
template <typename Rhs>
|
||||
inline internal::LtMatcher<Rhs> Lt(Rhs x) {
|
||||
return internal::LtMatcher<Rhs>(x);
|
||||
}
|
||||
|
||||
// Creates a polymorphic matcher that matches anything != x.
|
||||
template <typename Rhs>
|
||||
inline internal::NeMatcher<Rhs> Ne(Rhs x) {
|
||||
return internal::NeMatcher<Rhs>(x);
|
||||
}
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251 5046
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MATCHERS_H_
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// The Google C++ Testing and Mocking Framework (Google Test)
|
||||
//
|
||||
// This header file defines the Message class.
|
||||
//
|
||||
// IMPORTANT NOTE: Due to limitation of the C++ language, we have to
|
||||
// leave some internal implementation details in this header file.
|
||||
// They are clearly marked by comments like this:
|
||||
//
|
||||
// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
|
||||
//
|
||||
// Such code is NOT meant to be used by a user directly, and is subject
|
||||
// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user
|
||||
// program!
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
|
||||
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
// Ensures that there is at least one operator<< in the global namespace.
|
||||
// See Message& operator<<(...) below for why.
|
||||
void operator<<(const testing::internal::Secret&, int);
|
||||
|
||||
namespace testing {
|
||||
|
||||
// The Message class works like an ostream repeater.
|
||||
//
|
||||
// Typical usage:
|
||||
//
|
||||
// 1. You stream a bunch of values to a Message object.
|
||||
// It will remember the text in a stringstream.
|
||||
// 2. Then you stream the Message object to an ostream.
|
||||
// This causes the text in the Message to be streamed
|
||||
// to the ostream.
|
||||
//
|
||||
// For example;
|
||||
//
|
||||
// testing::Message foo;
|
||||
// foo << 1 << " != " << 2;
|
||||
// std::cout << foo;
|
||||
//
|
||||
// will print "1 != 2".
|
||||
//
|
||||
// Message is not intended to be inherited from. In particular, its
|
||||
// destructor is not virtual.
|
||||
//
|
||||
// Note that stringstream behaves differently in gcc and in MSVC. You
|
||||
// can stream a NULL char pointer to it in the former, but not in the
|
||||
// latter (it causes an access violation if you do). The Message
|
||||
// class hides this difference by treating a NULL char pointer as
|
||||
// "(null)".
|
||||
class GTEST_API_ Message {
|
||||
private:
|
||||
// The type of basic IO manipulators (endl, ends, and flush) for
|
||||
// narrow streams.
|
||||
typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&);
|
||||
|
||||
public:
|
||||
// Constructs an empty Message.
|
||||
Message();
|
||||
|
||||
// Copy constructor.
|
||||
Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT
|
||||
*ss_ << msg.GetString();
|
||||
}
|
||||
|
||||
// Constructs a Message from a C-string.
|
||||
explicit Message(const char* str) : ss_(new ::std::stringstream) {
|
||||
*ss_ << str;
|
||||
}
|
||||
|
||||
// Streams a non-pointer value to this object.
|
||||
template <typename T>
|
||||
inline Message& operator<<(const T& val) {
|
||||
// Some libraries overload << for STL containers. These
|
||||
// overloads are defined in the global namespace instead of ::std.
|
||||
//
|
||||
// C++'s symbol lookup rule (i.e. Koenig lookup) says that these
|
||||
// overloads are visible in either the std namespace or the global
|
||||
// namespace, but not other namespaces, including the testing
|
||||
// namespace which Google Test's Message class is in.
|
||||
//
|
||||
// To allow STL containers (and other types that has a << operator
|
||||
// defined in the global namespace) to be used in Google Test
|
||||
// assertions, testing::Message must access the custom << operator
|
||||
// from the global namespace. With this using declaration,
|
||||
// overloads of << defined in the global namespace and those
|
||||
// visible via Koenig lookup are both exposed in this function.
|
||||
using ::operator<<;
|
||||
*ss_ << val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Streams a pointer value to this object.
|
||||
//
|
||||
// This function is an overload of the previous one. When you
|
||||
// stream a pointer to a Message, this definition will be used as it
|
||||
// is more specialized. (The C++ Standard, section
|
||||
// [temp.func.order].) If you stream a non-pointer, then the
|
||||
// previous definition will be used.
|
||||
//
|
||||
// The reason for this overload is that streaming a NULL pointer to
|
||||
// ostream is undefined behavior. Depending on the compiler, you
|
||||
// may get "0", "(nil)", "(null)", or an access violation. To
|
||||
// ensure consistent result across compilers, we always treat NULL
|
||||
// as "(null)".
|
||||
template <typename T>
|
||||
inline Message& operator<<(T* const& pointer) { // NOLINT
|
||||
if (pointer == nullptr) {
|
||||
*ss_ << "(null)";
|
||||
} else {
|
||||
*ss_ << pointer;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Since the basic IO manipulators are overloaded for both narrow
|
||||
// and wide streams, we have to provide this specialized definition
|
||||
// of operator <<, even though its body is the same as the
|
||||
// templatized version above. Without this definition, streaming
|
||||
// endl or other basic IO manipulators to Message will confuse the
|
||||
// compiler.
|
||||
Message& operator<<(BasicNarrowIoManip val) {
|
||||
*ss_ << val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Instead of 1/0, we want to see true/false for bool values.
|
||||
Message& operator<<(bool b) { return *this << (b ? "true" : "false"); }
|
||||
|
||||
// These two overloads allow streaming a wide C string to a Message
|
||||
// using the UTF-8 encoding.
|
||||
Message& operator<<(const wchar_t* wide_c_str);
|
||||
Message& operator<<(wchar_t* wide_c_str);
|
||||
|
||||
#if GTEST_HAS_STD_WSTRING
|
||||
// Converts the given wide string to a narrow string using the UTF-8
|
||||
// encoding, and streams the result to this Message object.
|
||||
Message& operator<<(const ::std::wstring& wstr);
|
||||
#endif // GTEST_HAS_STD_WSTRING
|
||||
|
||||
// Gets the text streamed to this object so far as an std::string.
|
||||
// Each '\0' character in the buffer is replaced with "\\0".
|
||||
//
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
|
||||
std::string GetString() const;
|
||||
|
||||
private:
|
||||
// We'll hold the text streamed to this object here.
|
||||
const std::unique_ptr< ::std::stringstream> ss_;
|
||||
|
||||
// We declare (but don't implement) this to prevent the compiler
|
||||
// from implementing the assignment operator.
|
||||
void operator=(const Message&);
|
||||
};
|
||||
|
||||
// Streams a Message to an ostream.
|
||||
inline std::ostream& operator<<(std::ostream& os, const Message& sb) {
|
||||
return os << sb.GetString();
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Converts a streamable value to an std::string. A NULL pointer is
|
||||
// converted to "(null)". When the input value is a ::string,
|
||||
// ::std::string, ::wstring, or ::std::wstring object, each NUL
|
||||
// character in it is replaced with "\\0".
|
||||
template <typename T>
|
||||
std::string StreamableToString(const T& streamable) {
|
||||
return (Message() << streamable).GetString();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_MESSAGE_H_
|
||||
@@ -0,0 +1,510 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Macros and functions for implementing parameterized tests
|
||||
// in Google C++ Testing and Mocking Framework (Google Test)
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
|
||||
|
||||
// Value-parameterized tests allow you to test your code with different
|
||||
// parameters without writing multiple copies of the same test.
|
||||
//
|
||||
// Here is how you use value-parameterized tests:
|
||||
|
||||
#if 0
|
||||
|
||||
// To write value-parameterized tests, first you should define a fixture
|
||||
// class. It is usually derived from testing::TestWithParam<T> (see below for
|
||||
// another inheritance scheme that's sometimes useful in more complicated
|
||||
// class hierarchies), where the type of your parameter values.
|
||||
// TestWithParam<T> is itself derived from testing::Test. T can be any
|
||||
// copyable type. If it's a raw pointer, you are responsible for managing the
|
||||
// lifespan of the pointed values.
|
||||
|
||||
class FooTest : public ::testing::TestWithParam<const char*> {
|
||||
// You can implement all the usual class fixture members here.
|
||||
};
|
||||
|
||||
// Then, use the TEST_P macro to define as many parameterized tests
|
||||
// for this fixture as you want. The _P suffix is for "parameterized"
|
||||
// or "pattern", whichever you prefer to think.
|
||||
|
||||
TEST_P(FooTest, DoesBlah) {
|
||||
// Inside a test, access the test parameter with the GetParam() method
|
||||
// of the TestWithParam<T> class:
|
||||
EXPECT_TRUE(foo.Blah(GetParam()));
|
||||
...
|
||||
}
|
||||
|
||||
TEST_P(FooTest, HasBlahBlah) {
|
||||
...
|
||||
}
|
||||
|
||||
// Finally, you can use INSTANTIATE_TEST_SUITE_P to instantiate the test
|
||||
// case with any set of parameters you want. Google Test defines a number
|
||||
// of functions for generating test parameters. They return what we call
|
||||
// (surprise!) parameter generators. Here is a summary of them, which
|
||||
// are all in the testing namespace:
|
||||
//
|
||||
//
|
||||
// Range(begin, end [, step]) - Yields values {begin, begin+step,
|
||||
// begin+step+step, ...}. The values do not
|
||||
// include end. step defaults to 1.
|
||||
// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}.
|
||||
// ValuesIn(container) - Yields values from a C-style array, an STL
|
||||
// ValuesIn(begin,end) container, or an iterator range [begin, end).
|
||||
// Bool() - Yields sequence {false, true}.
|
||||
// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product
|
||||
// for the math savvy) of the values generated
|
||||
// by the N generators.
|
||||
//
|
||||
// For more details, see comments at the definitions of these functions below
|
||||
// in this file.
|
||||
//
|
||||
// The following statement will instantiate tests from the FooTest test suite
|
||||
// each with parameter values "meeny", "miny", and "moe".
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(InstantiationName,
|
||||
FooTest,
|
||||
Values("meeny", "miny", "moe"));
|
||||
|
||||
// To distinguish different instances of the pattern, (yes, you
|
||||
// can instantiate it more than once) the first argument to the
|
||||
// INSTANTIATE_TEST_SUITE_P macro is a prefix that will be added to the
|
||||
// actual test suite name. Remember to pick unique prefixes for different
|
||||
// instantiations. The tests from the instantiation above will have
|
||||
// these names:
|
||||
//
|
||||
// * InstantiationName/FooTest.DoesBlah/0 for "meeny"
|
||||
// * InstantiationName/FooTest.DoesBlah/1 for "miny"
|
||||
// * InstantiationName/FooTest.DoesBlah/2 for "moe"
|
||||
// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny"
|
||||
// * InstantiationName/FooTest.HasBlahBlah/1 for "miny"
|
||||
// * InstantiationName/FooTest.HasBlahBlah/2 for "moe"
|
||||
//
|
||||
// You can use these names in --gtest_filter.
|
||||
//
|
||||
// This statement will instantiate all tests from FooTest again, each
|
||||
// with parameter values "cat" and "dog":
|
||||
|
||||
const char* pets[] = {"cat", "dog"};
|
||||
INSTANTIATE_TEST_SUITE_P(AnotherInstantiationName, FooTest, ValuesIn(pets));
|
||||
|
||||
// The tests from the instantiation above will have these names:
|
||||
//
|
||||
// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat"
|
||||
// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog"
|
||||
// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat"
|
||||
// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog"
|
||||
//
|
||||
// Please note that INSTANTIATE_TEST_SUITE_P will instantiate all tests
|
||||
// in the given test suite, whether their definitions come before or
|
||||
// AFTER the INSTANTIATE_TEST_SUITE_P statement.
|
||||
//
|
||||
// Please also note that generator expressions (including parameters to the
|
||||
// generators) are evaluated in InitGoogleTest(), after main() has started.
|
||||
// This allows the user on one hand, to adjust generator parameters in order
|
||||
// to dynamically determine a set of tests to run and on the other hand,
|
||||
// give the user a chance to inspect the generated tests with Google Test
|
||||
// reflection API before RUN_ALL_TESTS() is executed.
|
||||
//
|
||||
// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc
|
||||
// for more examples.
|
||||
//
|
||||
// In the future, we plan to publish the API for defining new parameter
|
||||
// generators. But for now this interface remains part of the internal
|
||||
// implementation and is subject to change.
|
||||
//
|
||||
//
|
||||
// A parameterized test fixture must be derived from testing::Test and from
|
||||
// testing::WithParamInterface<T>, where T is the type of the parameter
|
||||
// values. Inheriting from TestWithParam<T> satisfies that requirement because
|
||||
// TestWithParam<T> inherits from both Test and WithParamInterface. In more
|
||||
// complicated hierarchies, however, it is occasionally useful to inherit
|
||||
// separately from Test and WithParamInterface. For example:
|
||||
|
||||
class BaseTest : public ::testing::Test {
|
||||
// You can inherit all the usual members for a non-parameterized test
|
||||
// fixture here.
|
||||
};
|
||||
|
||||
class DerivedTest : public BaseTest, public ::testing::WithParamInterface<int> {
|
||||
// The usual test fixture members go here too.
|
||||
};
|
||||
|
||||
TEST_F(BaseTest, HasFoo) {
|
||||
// This is an ordinary non-parameterized test.
|
||||
}
|
||||
|
||||
TEST_P(DerivedTest, DoesBlah) {
|
||||
// GetParam works just the same here as if you inherit from TestWithParam.
|
||||
EXPECT_TRUE(foo.Blah(GetParam()));
|
||||
}
|
||||
|
||||
#endif // 0
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-param-util.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
// Functions producing parameter generators.
|
||||
//
|
||||
// Google Test uses these generators to produce parameters for value-
|
||||
// parameterized tests. When a parameterized test suite is instantiated
|
||||
// with a particular generator, Google Test creates and runs tests
|
||||
// for each element in the sequence produced by the generator.
|
||||
//
|
||||
// In the following sample, tests from test suite FooTest are instantiated
|
||||
// each three times with parameter values 3, 5, and 8:
|
||||
//
|
||||
// class FooTest : public TestWithParam<int> { ... };
|
||||
//
|
||||
// TEST_P(FooTest, TestThis) {
|
||||
// }
|
||||
// TEST_P(FooTest, TestThat) {
|
||||
// }
|
||||
// INSTANTIATE_TEST_SUITE_P(TestSequence, FooTest, Values(3, 5, 8));
|
||||
//
|
||||
|
||||
// Range() returns generators providing sequences of values in a range.
|
||||
//
|
||||
// Synopsis:
|
||||
// Range(start, end)
|
||||
// - returns a generator producing a sequence of values {start, start+1,
|
||||
// start+2, ..., }.
|
||||
// Range(start, end, step)
|
||||
// - returns a generator producing a sequence of values {start, start+step,
|
||||
// start+step+step, ..., }.
|
||||
// Notes:
|
||||
// * The generated sequences never include end. For example, Range(1, 5)
|
||||
// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2)
|
||||
// returns a generator producing {1, 3, 5, 7}.
|
||||
// * start and end must have the same type. That type may be any integral or
|
||||
// floating-point type or a user defined type satisfying these conditions:
|
||||
// * It must be assignable (have operator=() defined).
|
||||
// * It must have operator+() (operator+(int-compatible type) for
|
||||
// two-operand version).
|
||||
// * It must have operator<() defined.
|
||||
// Elements in the resulting sequences will also have that type.
|
||||
// * Condition start < end must be satisfied in order for resulting sequences
|
||||
// to contain any elements.
|
||||
//
|
||||
template <typename T, typename IncrementT>
|
||||
internal::ParamGenerator<T> Range(T start, T end, IncrementT step) {
|
||||
return internal::ParamGenerator<T>(
|
||||
new internal::RangeGenerator<T, IncrementT>(start, end, step));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
internal::ParamGenerator<T> Range(T start, T end) {
|
||||
return Range(start, end, 1);
|
||||
}
|
||||
|
||||
// ValuesIn() function allows generation of tests with parameters coming from
|
||||
// a container.
|
||||
//
|
||||
// Synopsis:
|
||||
// ValuesIn(const T (&array)[N])
|
||||
// - returns a generator producing sequences with elements from
|
||||
// a C-style array.
|
||||
// ValuesIn(const Container& container)
|
||||
// - returns a generator producing sequences with elements from
|
||||
// an STL-style container.
|
||||
// ValuesIn(Iterator begin, Iterator end)
|
||||
// - returns a generator producing sequences with elements from
|
||||
// a range [begin, end) defined by a pair of STL-style iterators. These
|
||||
// iterators can also be plain C pointers.
|
||||
//
|
||||
// Please note that ValuesIn copies the values from the containers
|
||||
// passed in and keeps them to generate tests in RUN_ALL_TESTS().
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// This instantiates tests from test suite StringTest
|
||||
// each with C-string values of "foo", "bar", and "baz":
|
||||
//
|
||||
// const char* strings[] = {"foo", "bar", "baz"};
|
||||
// INSTANTIATE_TEST_SUITE_P(StringSequence, StringTest, ValuesIn(strings));
|
||||
//
|
||||
// This instantiates tests from test suite StlStringTest
|
||||
// each with STL strings with values "a" and "b":
|
||||
//
|
||||
// ::std::vector< ::std::string> GetParameterStrings() {
|
||||
// ::std::vector< ::std::string> v;
|
||||
// v.push_back("a");
|
||||
// v.push_back("b");
|
||||
// return v;
|
||||
// }
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(CharSequence,
|
||||
// StlStringTest,
|
||||
// ValuesIn(GetParameterStrings()));
|
||||
//
|
||||
//
|
||||
// This will also instantiate tests from CharTest
|
||||
// each with parameter values 'a' and 'b':
|
||||
//
|
||||
// ::std::list<char> GetParameterChars() {
|
||||
// ::std::list<char> list;
|
||||
// list.push_back('a');
|
||||
// list.push_back('b');
|
||||
// return list;
|
||||
// }
|
||||
// ::std::list<char> l = GetParameterChars();
|
||||
// INSTANTIATE_TEST_SUITE_P(CharSequence2,
|
||||
// CharTest,
|
||||
// ValuesIn(l.begin(), l.end()));
|
||||
//
|
||||
template <typename ForwardIterator>
|
||||
internal::ParamGenerator<
|
||||
typename std::iterator_traits<ForwardIterator>::value_type>
|
||||
ValuesIn(ForwardIterator begin, ForwardIterator end) {
|
||||
typedef typename std::iterator_traits<ForwardIterator>::value_type ParamType;
|
||||
return internal::ParamGenerator<ParamType>(
|
||||
new internal::ValuesInIteratorRangeGenerator<ParamType>(begin, end));
|
||||
}
|
||||
|
||||
template <typename T, size_t N>
|
||||
internal::ParamGenerator<T> ValuesIn(const T (&array)[N]) {
|
||||
return ValuesIn(array, array + N);
|
||||
}
|
||||
|
||||
template <class Container>
|
||||
internal::ParamGenerator<typename Container::value_type> ValuesIn(
|
||||
const Container& container) {
|
||||
return ValuesIn(container.begin(), container.end());
|
||||
}
|
||||
|
||||
// Values() allows generating tests from explicitly specified list of
|
||||
// parameters.
|
||||
//
|
||||
// Synopsis:
|
||||
// Values(T v1, T v2, ..., T vN)
|
||||
// - returns a generator producing sequences with elements v1, v2, ..., vN.
|
||||
//
|
||||
// For example, this instantiates tests from test suite BarTest each
|
||||
// with values "one", "two", and "three":
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(NumSequence,
|
||||
// BarTest,
|
||||
// Values("one", "two", "three"));
|
||||
//
|
||||
// This instantiates tests from test suite BazTest each with values 1, 2, 3.5.
|
||||
// The exact type of values will depend on the type of parameter in BazTest.
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5));
|
||||
//
|
||||
//
|
||||
template <typename... T>
|
||||
internal::ValueArray<T...> Values(T... v) {
|
||||
return internal::ValueArray<T...>(std::move(v)...);
|
||||
}
|
||||
|
||||
// Bool() allows generating tests with parameters in a set of (false, true).
|
||||
//
|
||||
// Synopsis:
|
||||
// Bool()
|
||||
// - returns a generator producing sequences with elements {false, true}.
|
||||
//
|
||||
// It is useful when testing code that depends on Boolean flags. Combinations
|
||||
// of multiple flags can be tested when several Bool()'s are combined using
|
||||
// Combine() function.
|
||||
//
|
||||
// In the following example all tests in the test suite FlagDependentTest
|
||||
// will be instantiated twice with parameters false and true.
|
||||
//
|
||||
// class FlagDependentTest : public testing::TestWithParam<bool> {
|
||||
// virtual void SetUp() {
|
||||
// external_flag = GetParam();
|
||||
// }
|
||||
// }
|
||||
// INSTANTIATE_TEST_SUITE_P(BoolSequence, FlagDependentTest, Bool());
|
||||
//
|
||||
inline internal::ParamGenerator<bool> Bool() { return Values(false, true); }
|
||||
|
||||
// Combine() allows the user to combine two or more sequences to produce
|
||||
// values of a Cartesian product of those sequences' elements.
|
||||
//
|
||||
// Synopsis:
|
||||
// Combine(gen1, gen2, ..., genN)
|
||||
// - returns a generator producing sequences with elements coming from
|
||||
// the Cartesian product of elements from the sequences generated by
|
||||
// gen1, gen2, ..., genN. The sequence elements will have a type of
|
||||
// std::tuple<T1, T2, ..., TN> where T1, T2, ..., TN are the types
|
||||
// of elements from sequences produces by gen1, gen2, ..., genN.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// This will instantiate tests in test suite AnimalTest each one with
|
||||
// the parameter values tuple("cat", BLACK), tuple("cat", WHITE),
|
||||
// tuple("dog", BLACK), and tuple("dog", WHITE):
|
||||
//
|
||||
// enum Color { BLACK, GRAY, WHITE };
|
||||
// class AnimalTest
|
||||
// : public testing::TestWithParam<std::tuple<const char*, Color> > {...};
|
||||
//
|
||||
// TEST_P(AnimalTest, AnimalLooksNice) {...}
|
||||
//
|
||||
// INSTANTIATE_TEST_SUITE_P(AnimalVariations, AnimalTest,
|
||||
// Combine(Values("cat", "dog"),
|
||||
// Values(BLACK, WHITE)));
|
||||
//
|
||||
// This will instantiate tests in FlagDependentTest with all variations of two
|
||||
// Boolean flags:
|
||||
//
|
||||
// class FlagDependentTest
|
||||
// : public testing::TestWithParam<std::tuple<bool, bool> > {
|
||||
// virtual void SetUp() {
|
||||
// // Assigns external_flag_1 and external_flag_2 values from the tuple.
|
||||
// std::tie(external_flag_1, external_flag_2) = GetParam();
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// TEST_P(FlagDependentTest, TestFeature1) {
|
||||
// // Test your code using external_flag_1 and external_flag_2 here.
|
||||
// }
|
||||
// INSTANTIATE_TEST_SUITE_P(TwoBoolSequence, FlagDependentTest,
|
||||
// Combine(Bool(), Bool()));
|
||||
//
|
||||
template <typename... Generator>
|
||||
internal::CartesianProductHolder<Generator...> Combine(const Generator&... g) {
|
||||
return internal::CartesianProductHolder<Generator...>(g...);
|
||||
}
|
||||
|
||||
#define TEST_P(test_suite_name, test_name) \
|
||||
class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
|
||||
: public test_suite_name { \
|
||||
public: \
|
||||
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() {} \
|
||||
void TestBody() override; \
|
||||
\
|
||||
private: \
|
||||
static int AddToRegistry() { \
|
||||
::testing::UnitTest::GetInstance() \
|
||||
->parameterized_test_registry() \
|
||||
.GetTestSuitePatternHolder<test_suite_name>( \
|
||||
GTEST_STRINGIFY_(test_suite_name), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__)) \
|
||||
->AddTestPattern( \
|
||||
GTEST_STRINGIFY_(test_suite_name), GTEST_STRINGIFY_(test_name), \
|
||||
new ::testing::internal::TestMetaFactory<GTEST_TEST_CLASS_NAME_( \
|
||||
test_suite_name, test_name)>(), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__)); \
|
||||
return 0; \
|
||||
} \
|
||||
static int gtest_registering_dummy_ GTEST_ATTRIBUTE_UNUSED_; \
|
||||
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
|
||||
(const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete; \
|
||||
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=( \
|
||||
const GTEST_TEST_CLASS_NAME_(test_suite_name, \
|
||||
test_name) &) = delete; /* NOLINT */ \
|
||||
}; \
|
||||
int GTEST_TEST_CLASS_NAME_(test_suite_name, \
|
||||
test_name)::gtest_registering_dummy_ = \
|
||||
GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::AddToRegistry(); \
|
||||
void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()
|
||||
|
||||
// The last argument to INSTANTIATE_TEST_SUITE_P allows the user to specify
|
||||
// generator and an optional function or functor that generates custom test name
|
||||
// suffixes based on the test parameters. Such a function or functor should
|
||||
// accept one argument of type testing::TestParamInfo<class ParamType>, and
|
||||
// return std::string.
|
||||
//
|
||||
// testing::PrintToStringParamName is a builtin test suffix generator that
|
||||
// returns the value of testing::PrintToString(GetParam()).
|
||||
//
|
||||
// Note: test names must be non-empty, unique, and may only contain ASCII
|
||||
// alphanumeric characters or underscore. Because PrintToString adds quotes
|
||||
// to std::string and C strings, it won't work for these types.
|
||||
|
||||
#define GTEST_EXPAND_(arg) arg
|
||||
#define GTEST_GET_FIRST_(first, ...) first
|
||||
#define GTEST_GET_SECOND_(first, second, ...) second
|
||||
|
||||
#define INSTANTIATE_TEST_SUITE_P(prefix, test_suite_name, ...) \
|
||||
static ::testing::internal::ParamGenerator<test_suite_name::ParamType> \
|
||||
gtest_##prefix##test_suite_name##_EvalGenerator_() { \
|
||||
return GTEST_EXPAND_(GTEST_GET_FIRST_(__VA_ARGS__, DUMMY_PARAM_)); \
|
||||
} \
|
||||
static ::std::string gtest_##prefix##test_suite_name##_EvalGenerateName_( \
|
||||
const ::testing::TestParamInfo<test_suite_name::ParamType>& info) { \
|
||||
if (::testing::internal::AlwaysFalse()) { \
|
||||
::testing::internal::TestNotEmpty(GTEST_EXPAND_(GTEST_GET_SECOND_( \
|
||||
__VA_ARGS__, \
|
||||
::testing::internal::DefaultParamName<test_suite_name::ParamType>, \
|
||||
DUMMY_PARAM_))); \
|
||||
auto t = std::make_tuple(__VA_ARGS__); \
|
||||
static_assert(std::tuple_size<decltype(t)>::value <= 2, \
|
||||
"Too Many Args!"); \
|
||||
} \
|
||||
return ((GTEST_EXPAND_(GTEST_GET_SECOND_( \
|
||||
__VA_ARGS__, \
|
||||
::testing::internal::DefaultParamName<test_suite_name::ParamType>, \
|
||||
DUMMY_PARAM_))))(info); \
|
||||
} \
|
||||
static int gtest_##prefix##test_suite_name##_dummy_ \
|
||||
GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
::testing::UnitTest::GetInstance() \
|
||||
->parameterized_test_registry() \
|
||||
.GetTestSuitePatternHolder<test_suite_name>( \
|
||||
GTEST_STRINGIFY_(test_suite_name), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__)) \
|
||||
->AddTestSuiteInstantiation( \
|
||||
GTEST_STRINGIFY_(prefix), \
|
||||
>est_##prefix##test_suite_name##_EvalGenerator_, \
|
||||
>est_##prefix##test_suite_name##_EvalGenerateName_, \
|
||||
__FILE__, __LINE__)
|
||||
|
||||
// Allow Marking a Parameterized test class as not needing to be instantiated.
|
||||
#define GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(T) \
|
||||
namespace gtest_do_not_use_outside_namespace_scope {} \
|
||||
static const ::testing::internal::MarkAsIgnored gtest_allow_ignore_##T( \
|
||||
GTEST_STRINGIFY_(T))
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define INSTANTIATE_TEST_CASE_P \
|
||||
static_assert(::testing::internal::InstantiateTestCase_P_IsDeprecated(), \
|
||||
""); \
|
||||
INSTANTIATE_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,248 @@
|
||||
// Copyright 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Utilities for testing Google Test itself and code that uses Google Test
|
||||
// (e.g. frameworks built on top of Google Test).
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// This helper class can be used to mock out Google Test failure reporting
|
||||
// so that we can test Google Test or code that builds on Google Test.
|
||||
//
|
||||
// An object of this class appends a TestPartResult object to the
|
||||
// TestPartResultArray object given in the constructor whenever a Google Test
|
||||
// failure is reported. It can either intercept only failures that are
|
||||
// generated in the same thread that created this object or it can intercept
|
||||
// all generated failures. The scope of this mock object can be controlled with
|
||||
// the second argument to the two arguments constructor.
|
||||
class GTEST_API_ ScopedFakeTestPartResultReporter
|
||||
: public TestPartResultReporterInterface {
|
||||
public:
|
||||
// The two possible mocking modes of this object.
|
||||
enum InterceptMode {
|
||||
INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures.
|
||||
INTERCEPT_ALL_THREADS // Intercepts all failures.
|
||||
};
|
||||
|
||||
// The c'tor sets this object as the test part result reporter used
|
||||
// by Google Test. The 'result' parameter specifies where to report the
|
||||
// results. This reporter will only catch failures generated in the current
|
||||
// thread. DEPRECATED
|
||||
explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result);
|
||||
|
||||
// Same as above, but you can choose the interception scope of this object.
|
||||
ScopedFakeTestPartResultReporter(InterceptMode intercept_mode,
|
||||
TestPartResultArray* result);
|
||||
|
||||
// The d'tor restores the previous test part result reporter.
|
||||
~ScopedFakeTestPartResultReporter() override;
|
||||
|
||||
// Appends the TestPartResult object to the TestPartResultArray
|
||||
// received in the constructor.
|
||||
//
|
||||
// This method is from the TestPartResultReporterInterface
|
||||
// interface.
|
||||
void ReportTestPartResult(const TestPartResult& result) override;
|
||||
|
||||
private:
|
||||
void Init();
|
||||
|
||||
const InterceptMode intercept_mode_;
|
||||
TestPartResultReporterInterface* old_reporter_;
|
||||
TestPartResultArray* const result_;
|
||||
|
||||
ScopedFakeTestPartResultReporter(const ScopedFakeTestPartResultReporter&) =
|
||||
delete;
|
||||
ScopedFakeTestPartResultReporter& operator=(
|
||||
const ScopedFakeTestPartResultReporter&) = delete;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
// A helper class for implementing EXPECT_FATAL_FAILURE() and
|
||||
// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given
|
||||
// TestPartResultArray contains exactly one failure that has the given
|
||||
// type and contains the given substring. If that's not the case, a
|
||||
// non-fatal failure will be generated.
|
||||
class GTEST_API_ SingleFailureChecker {
|
||||
public:
|
||||
// The constructor remembers the arguments.
|
||||
SingleFailureChecker(const TestPartResultArray* results,
|
||||
TestPartResult::Type type, const std::string& substr);
|
||||
~SingleFailureChecker();
|
||||
|
||||
private:
|
||||
const TestPartResultArray* const results_;
|
||||
const TestPartResult::Type type_;
|
||||
const std::string substr_;
|
||||
|
||||
SingleFailureChecker(const SingleFailureChecker&) = delete;
|
||||
SingleFailureChecker& operator=(const SingleFailureChecker&) = delete;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
// A set of macros for testing Google Test assertions or code that's expected
|
||||
// to generate Google Test fatal failures (e.g. a failure from an ASSERT_EQ, but
|
||||
// not a non-fatal failure, as from EXPECT_EQ). It verifies that the given
|
||||
// statement will cause exactly one fatal Google Test failure with 'substr'
|
||||
// being part of the failure message.
|
||||
//
|
||||
// There are two different versions of this macro. EXPECT_FATAL_FAILURE only
|
||||
// affects and considers failures generated in the current thread and
|
||||
// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
|
||||
//
|
||||
// The verification of the assertion is done correctly even when the statement
|
||||
// throws an exception or aborts the current function.
|
||||
//
|
||||
// Known restrictions:
|
||||
// - 'statement' cannot reference local non-static variables or
|
||||
// non-static members of the current object.
|
||||
// - 'statement' cannot return a value.
|
||||
// - You cannot stream a failure message to this macro.
|
||||
//
|
||||
// Note that even though the implementations of the following two
|
||||
// macros are much alike, we cannot refactor them to use a common
|
||||
// helper macro, due to some peculiarity in how the preprocessor
|
||||
// works. The AcceptsMacroThatExpandsToUnprotectedComma test in
|
||||
// gtest_unittest.cc will fail to compile if we do that.
|
||||
#define EXPECT_FATAL_FAILURE(statement, substr) \
|
||||
do { \
|
||||
class GTestExpectFatalFailureHelper { \
|
||||
public: \
|
||||
static void Execute() { statement; } \
|
||||
}; \
|
||||
::testing::TestPartResultArray gtest_failures; \
|
||||
::testing::internal::SingleFailureChecker gtest_checker( \
|
||||
>est_failures, ::testing::TestPartResult::kFatalFailure, (substr)); \
|
||||
{ \
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter( \
|
||||
::testing::ScopedFakeTestPartResultReporter:: \
|
||||
INTERCEPT_ONLY_CURRENT_THREAD, \
|
||||
>est_failures); \
|
||||
GTestExpectFatalFailureHelper::Execute(); \
|
||||
} \
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
|
||||
do { \
|
||||
class GTestExpectFatalFailureHelper { \
|
||||
public: \
|
||||
static void Execute() { statement; } \
|
||||
}; \
|
||||
::testing::TestPartResultArray gtest_failures; \
|
||||
::testing::internal::SingleFailureChecker gtest_checker( \
|
||||
>est_failures, ::testing::TestPartResult::kFatalFailure, (substr)); \
|
||||
{ \
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter( \
|
||||
::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \
|
||||
>est_failures); \
|
||||
GTestExpectFatalFailureHelper::Execute(); \
|
||||
} \
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
// A macro for testing Google Test assertions or code that's expected to
|
||||
// generate Google Test non-fatal failures (e.g. a failure from an EXPECT_EQ,
|
||||
// but not from an ASSERT_EQ). It asserts that the given statement will cause
|
||||
// exactly one non-fatal Google Test failure with 'substr' being part of the
|
||||
// failure message.
|
||||
//
|
||||
// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only
|
||||
// affects and considers failures generated in the current thread and
|
||||
// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads.
|
||||
//
|
||||
// 'statement' is allowed to reference local variables and members of
|
||||
// the current object.
|
||||
//
|
||||
// The verification of the assertion is done correctly even when the statement
|
||||
// throws an exception or aborts the current function.
|
||||
//
|
||||
// Known restrictions:
|
||||
// - You cannot stream a failure message to this macro.
|
||||
//
|
||||
// Note that even though the implementations of the following two
|
||||
// macros are much alike, we cannot refactor them to use a common
|
||||
// helper macro, due to some peculiarity in how the preprocessor
|
||||
// works. If we do that, the code won't compile when the user gives
|
||||
// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that
|
||||
// expands to code containing an unprotected comma. The
|
||||
// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc
|
||||
// catches that.
|
||||
//
|
||||
// For the same reason, we have to write
|
||||
// if (::testing::internal::AlwaysTrue()) { statement; }
|
||||
// instead of
|
||||
// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement)
|
||||
// to avoid an MSVC warning on unreachable code.
|
||||
#define EXPECT_NONFATAL_FAILURE(statement, substr) \
|
||||
do { \
|
||||
::testing::TestPartResultArray gtest_failures; \
|
||||
::testing::internal::SingleFailureChecker gtest_checker( \
|
||||
>est_failures, ::testing::TestPartResult::kNonFatalFailure, \
|
||||
(substr)); \
|
||||
{ \
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter( \
|
||||
::testing::ScopedFakeTestPartResultReporter:: \
|
||||
INTERCEPT_ONLY_CURRENT_THREAD, \
|
||||
>est_failures); \
|
||||
if (::testing::internal::AlwaysTrue()) { \
|
||||
statement; \
|
||||
} \
|
||||
} \
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \
|
||||
do { \
|
||||
::testing::TestPartResultArray gtest_failures; \
|
||||
::testing::internal::SingleFailureChecker gtest_checker( \
|
||||
>est_failures, ::testing::TestPartResult::kNonFatalFailure, \
|
||||
(substr)); \
|
||||
{ \
|
||||
::testing::ScopedFakeTestPartResultReporter gtest_reporter( \
|
||||
::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS, \
|
||||
>est_failures); \
|
||||
if (::testing::internal::AlwaysTrue()) { \
|
||||
statement; \
|
||||
} \
|
||||
} \
|
||||
} while (::testing::internal::AlwaysFalse())
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_SPI_H_
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2008, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <vector>
|
||||
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-string.h"
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_PUSH_(4251 \
|
||||
/* class A needs to have dll-interface to be used by clients of class B */)
|
||||
|
||||
namespace testing {
|
||||
|
||||
// A copyable object representing the result of a test part (i.e. an
|
||||
// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()).
|
||||
//
|
||||
// Don't inherit from TestPartResult as its destructor is not virtual.
|
||||
class GTEST_API_ TestPartResult {
|
||||
public:
|
||||
// The possible outcomes of a test part (i.e. an assertion or an
|
||||
// explicit SUCCEED(), FAIL(), or ADD_FAILURE()).
|
||||
enum Type {
|
||||
kSuccess, // Succeeded.
|
||||
kNonFatalFailure, // Failed but the test can continue.
|
||||
kFatalFailure, // Failed and the test should be terminated.
|
||||
kSkip // Skipped.
|
||||
};
|
||||
|
||||
// C'tor. TestPartResult does NOT have a default constructor.
|
||||
// Always use this constructor (with parameters) to create a
|
||||
// TestPartResult object.
|
||||
TestPartResult(Type a_type, const char* a_file_name, int a_line_number,
|
||||
const char* a_message)
|
||||
: type_(a_type),
|
||||
file_name_(a_file_name == nullptr ? "" : a_file_name),
|
||||
line_number_(a_line_number),
|
||||
summary_(ExtractSummary(a_message)),
|
||||
message_(a_message) {}
|
||||
|
||||
// Gets the outcome of the test part.
|
||||
Type type() const { return type_; }
|
||||
|
||||
// Gets the name of the source file where the test part took place, or
|
||||
// NULL if it's unknown.
|
||||
const char* file_name() const {
|
||||
return file_name_.empty() ? nullptr : file_name_.c_str();
|
||||
}
|
||||
|
||||
// Gets the line in the source file where the test part took place,
|
||||
// or -1 if it's unknown.
|
||||
int line_number() const { return line_number_; }
|
||||
|
||||
// Gets the summary of the failure message.
|
||||
const char* summary() const { return summary_.c_str(); }
|
||||
|
||||
// Gets the message associated with the test part.
|
||||
const char* message() const { return message_.c_str(); }
|
||||
|
||||
// Returns true if and only if the test part was skipped.
|
||||
bool skipped() const { return type_ == kSkip; }
|
||||
|
||||
// Returns true if and only if the test part passed.
|
||||
bool passed() const { return type_ == kSuccess; }
|
||||
|
||||
// Returns true if and only if the test part non-fatally failed.
|
||||
bool nonfatally_failed() const { return type_ == kNonFatalFailure; }
|
||||
|
||||
// Returns true if and only if the test part fatally failed.
|
||||
bool fatally_failed() const { return type_ == kFatalFailure; }
|
||||
|
||||
// Returns true if and only if the test part failed.
|
||||
bool failed() const { return fatally_failed() || nonfatally_failed(); }
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
|
||||
// Gets the summary of the failure message by omitting the stack
|
||||
// trace in it.
|
||||
static std::string ExtractSummary(const char* message);
|
||||
|
||||
// The name of the source file where the test part took place, or
|
||||
// "" if the source file is unknown.
|
||||
std::string file_name_;
|
||||
// The line in the source file where the test part took place, or -1
|
||||
// if the line number is unknown.
|
||||
int line_number_;
|
||||
std::string summary_; // The test failure summary.
|
||||
std::string message_; // The test failure message.
|
||||
};
|
||||
|
||||
// Prints a TestPartResult object.
|
||||
std::ostream& operator<<(std::ostream& os, const TestPartResult& result);
|
||||
|
||||
// An array of TestPartResult objects.
|
||||
//
|
||||
// Don't inherit from TestPartResultArray as its destructor is not
|
||||
// virtual.
|
||||
class GTEST_API_ TestPartResultArray {
|
||||
public:
|
||||
TestPartResultArray() {}
|
||||
|
||||
// Appends the given TestPartResult to the array.
|
||||
void Append(const TestPartResult& result);
|
||||
|
||||
// Returns the TestPartResult at the given index (0-based).
|
||||
const TestPartResult& GetTestPartResult(int index) const;
|
||||
|
||||
// Returns the number of TestPartResult objects in the array.
|
||||
int size() const;
|
||||
|
||||
private:
|
||||
std::vector<TestPartResult> array_;
|
||||
|
||||
TestPartResultArray(const TestPartResultArray&) = delete;
|
||||
TestPartResultArray& operator=(const TestPartResultArray&) = delete;
|
||||
};
|
||||
|
||||
// This interface knows how to report a test part result.
|
||||
class GTEST_API_ TestPartResultReporterInterface {
|
||||
public:
|
||||
virtual ~TestPartResultReporterInterface() {}
|
||||
|
||||
virtual void ReportTestPartResult(const TestPartResult& result) = 0;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
|
||||
// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a
|
||||
// statement generates new fatal failures. To do so it registers itself as the
|
||||
// current test part result reporter. Besides checking if fatal failures were
|
||||
// reported, it only delegates the reporting to the former result reporter.
|
||||
// The original result reporter is restored in the destructor.
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM.
|
||||
class GTEST_API_ HasNewFatalFailureHelper
|
||||
: public TestPartResultReporterInterface {
|
||||
public:
|
||||
HasNewFatalFailureHelper();
|
||||
~HasNewFatalFailureHelper() override;
|
||||
void ReportTestPartResult(const TestPartResult& result) override;
|
||||
bool has_new_fatal_failure() const { return has_new_fatal_failure_; }
|
||||
|
||||
private:
|
||||
bool has_new_fatal_failure_;
|
||||
TestPartResultReporterInterface* original_reporter_;
|
||||
|
||||
HasNewFatalFailureHelper(const HasNewFatalFailureHelper&) = delete;
|
||||
HasNewFatalFailureHelper& operator=(const HasNewFatalFailureHelper&) = delete;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace testing
|
||||
|
||||
GTEST_DISABLE_MSC_WARNINGS_POP_() // 4251
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TEST_PART_H_
|
||||
@@ -0,0 +1,331 @@
|
||||
// Copyright 2008 Google Inc.
|
||||
// All Rights Reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
|
||||
|
||||
// This header implements typed tests and type-parameterized tests.
|
||||
|
||||
// Typed (aka type-driven) tests repeat the same test for types in a
|
||||
// list. You must know which types you want to test with when writing
|
||||
// typed tests. Here's how you do it:
|
||||
|
||||
#if 0
|
||||
|
||||
// First, define a fixture class template. It should be parameterized
|
||||
// by a type. Remember to derive it from testing::Test.
|
||||
template <typename T>
|
||||
class FooTest : public testing::Test {
|
||||
public:
|
||||
...
|
||||
typedef std::list<T> List;
|
||||
static T shared_;
|
||||
T value_;
|
||||
};
|
||||
|
||||
// Next, associate a list of types with the test suite, which will be
|
||||
// repeated for each type in the list. The typedef is necessary for
|
||||
// the macro to parse correctly.
|
||||
typedef testing::Types<char, int, unsigned int> MyTypes;
|
||||
TYPED_TEST_SUITE(FooTest, MyTypes);
|
||||
|
||||
// If the type list contains only one type, you can write that type
|
||||
// directly without Types<...>:
|
||||
// TYPED_TEST_SUITE(FooTest, int);
|
||||
|
||||
// Then, use TYPED_TEST() instead of TEST_F() to define as many typed
|
||||
// tests for this test suite as you want.
|
||||
TYPED_TEST(FooTest, DoesBlah) {
|
||||
// Inside a test, refer to the special name TypeParam to get the type
|
||||
// parameter. Since we are inside a derived class template, C++ requires
|
||||
// us to visit the members of FooTest via 'this'.
|
||||
TypeParam n = this->value_;
|
||||
|
||||
// To visit static members of the fixture, add the TestFixture::
|
||||
// prefix.
|
||||
n += TestFixture::shared_;
|
||||
|
||||
// To refer to typedefs in the fixture, add the "typename
|
||||
// TestFixture::" prefix.
|
||||
typename TestFixture::List values;
|
||||
values.push_back(n);
|
||||
...
|
||||
}
|
||||
|
||||
TYPED_TEST(FooTest, HasPropertyA) { ... }
|
||||
|
||||
// TYPED_TEST_SUITE takes an optional third argument which allows to specify a
|
||||
// class that generates custom test name suffixes based on the type. This should
|
||||
// be a class which has a static template function GetName(int index) returning
|
||||
// a string for each type. The provided integer index equals the index of the
|
||||
// type in the provided type list. In many cases the index can be ignored.
|
||||
//
|
||||
// For example:
|
||||
// class MyTypeNames {
|
||||
// public:
|
||||
// template <typename T>
|
||||
// static std::string GetName(int) {
|
||||
// if (std::is_same<T, char>()) return "char";
|
||||
// if (std::is_same<T, int>()) return "int";
|
||||
// if (std::is_same<T, unsigned int>()) return "unsignedInt";
|
||||
// }
|
||||
// };
|
||||
// TYPED_TEST_SUITE(FooTest, MyTypes, MyTypeNames);
|
||||
|
||||
#endif // 0
|
||||
|
||||
// Type-parameterized tests are abstract test patterns parameterized
|
||||
// by a type. Compared with typed tests, type-parameterized tests
|
||||
// allow you to define the test pattern without knowing what the type
|
||||
// parameters are. The defined pattern can be instantiated with
|
||||
// different types any number of times, in any number of translation
|
||||
// units.
|
||||
//
|
||||
// If you are designing an interface or concept, you can define a
|
||||
// suite of type-parameterized tests to verify properties that any
|
||||
// valid implementation of the interface/concept should have. Then,
|
||||
// each implementation can easily instantiate the test suite to verify
|
||||
// that it conforms to the requirements, without having to write
|
||||
// similar tests repeatedly. Here's an example:
|
||||
|
||||
#if 0
|
||||
|
||||
// First, define a fixture class template. It should be parameterized
|
||||
// by a type. Remember to derive it from testing::Test.
|
||||
template <typename T>
|
||||
class FooTest : public testing::Test {
|
||||
...
|
||||
};
|
||||
|
||||
// Next, declare that you will define a type-parameterized test suite
|
||||
// (the _P suffix is for "parameterized" or "pattern", whichever you
|
||||
// prefer):
|
||||
TYPED_TEST_SUITE_P(FooTest);
|
||||
|
||||
// Then, use TYPED_TEST_P() to define as many type-parameterized tests
|
||||
// for this type-parameterized test suite as you want.
|
||||
TYPED_TEST_P(FooTest, DoesBlah) {
|
||||
// Inside a test, refer to TypeParam to get the type parameter.
|
||||
TypeParam n = 0;
|
||||
...
|
||||
}
|
||||
|
||||
TYPED_TEST_P(FooTest, HasPropertyA) { ... }
|
||||
|
||||
// Now the tricky part: you need to register all test patterns before
|
||||
// you can instantiate them. The first argument of the macro is the
|
||||
// test suite name; the rest are the names of the tests in this test
|
||||
// case.
|
||||
REGISTER_TYPED_TEST_SUITE_P(FooTest,
|
||||
DoesBlah, HasPropertyA);
|
||||
|
||||
// Finally, you are free to instantiate the pattern with the types you
|
||||
// want. If you put the above code in a header file, you can #include
|
||||
// it in multiple C++ source files and instantiate it multiple times.
|
||||
//
|
||||
// To distinguish different instances of the pattern, the first
|
||||
// argument to the INSTANTIATE_* macro is a prefix that will be added
|
||||
// to the actual test suite name. Remember to pick unique prefixes for
|
||||
// different instances.
|
||||
typedef testing::Types<char, int, unsigned int> MyTypes;
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes);
|
||||
|
||||
// If the type list contains only one type, you can write that type
|
||||
// directly without Types<...>:
|
||||
// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, int);
|
||||
//
|
||||
// Similar to the optional argument of TYPED_TEST_SUITE above,
|
||||
// INSTANTIATE_TEST_SUITE_P takes an optional fourth argument which allows to
|
||||
// generate custom names.
|
||||
// INSTANTIATE_TYPED_TEST_SUITE_P(My, FooTest, MyTypes, MyTypeNames);
|
||||
|
||||
#endif // 0
|
||||
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
#include "gtest/internal/gtest-type-util.h"
|
||||
|
||||
// Implements typed tests.
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Expands to the name of the typedef for the type parameters of the
|
||||
// given test suite.
|
||||
#define GTEST_TYPE_PARAMS_(TestSuiteName) gtest_type_params_##TestSuiteName##_
|
||||
|
||||
// Expands to the name of the typedef for the NameGenerator, responsible for
|
||||
// creating the suffixes of the name.
|
||||
#define GTEST_NAME_GENERATOR_(TestSuiteName) \
|
||||
gtest_type_params_##TestSuiteName##_NameGenerator
|
||||
|
||||
#define TYPED_TEST_SUITE(CaseName, Types, ...) \
|
||||
typedef ::testing::internal::GenerateTypeList<Types>::type \
|
||||
GTEST_TYPE_PARAMS_(CaseName); \
|
||||
typedef ::testing::internal::NameGeneratorSelector<__VA_ARGS__>::type \
|
||||
GTEST_NAME_GENERATOR_(CaseName)
|
||||
|
||||
#define TYPED_TEST(CaseName, TestName) \
|
||||
static_assert(sizeof(GTEST_STRINGIFY_(TestName)) > 1, \
|
||||
"test-name must not be empty"); \
|
||||
template <typename gtest_TypeParam_> \
|
||||
class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \
|
||||
: public CaseName<gtest_TypeParam_> { \
|
||||
private: \
|
||||
typedef CaseName<gtest_TypeParam_> TestFixture; \
|
||||
typedef gtest_TypeParam_ TypeParam; \
|
||||
void TestBody() override; \
|
||||
}; \
|
||||
static bool gtest_##CaseName##_##TestName##_registered_ \
|
||||
GTEST_ATTRIBUTE_UNUSED_ = ::testing::internal::TypeParameterizedTest< \
|
||||
CaseName, \
|
||||
::testing::internal::TemplateSel<GTEST_TEST_CLASS_NAME_(CaseName, \
|
||||
TestName)>, \
|
||||
GTEST_TYPE_PARAMS_( \
|
||||
CaseName)>::Register("", \
|
||||
::testing::internal::CodeLocation( \
|
||||
__FILE__, __LINE__), \
|
||||
GTEST_STRINGIFY_(CaseName), \
|
||||
GTEST_STRINGIFY_(TestName), 0, \
|
||||
::testing::internal::GenerateNames< \
|
||||
GTEST_NAME_GENERATOR_(CaseName), \
|
||||
GTEST_TYPE_PARAMS_(CaseName)>()); \
|
||||
template <typename gtest_TypeParam_> \
|
||||
void GTEST_TEST_CLASS_NAME_(CaseName, \
|
||||
TestName)<gtest_TypeParam_>::TestBody()
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define TYPED_TEST_CASE \
|
||||
static_assert(::testing::internal::TypedTestCaseIsDeprecated(), ""); \
|
||||
TYPED_TEST_SUITE
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
// Implements type-parameterized tests.
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Expands to the namespace name that the type-parameterized tests for
|
||||
// the given type-parameterized test suite are defined in. The exact
|
||||
// name of the namespace is subject to change without notice.
|
||||
#define GTEST_SUITE_NAMESPACE_(TestSuiteName) gtest_suite_##TestSuiteName##_
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE.
|
||||
//
|
||||
// Expands to the name of the variable used to remember the names of
|
||||
// the defined tests in the given test suite.
|
||||
#define GTEST_TYPED_TEST_SUITE_P_STATE_(TestSuiteName) \
|
||||
gtest_typed_test_suite_p_state_##TestSuiteName##_
|
||||
|
||||
// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY.
|
||||
//
|
||||
// Expands to the name of the variable used to remember the names of
|
||||
// the registered tests in the given test suite.
|
||||
#define GTEST_REGISTERED_TEST_NAMES_(TestSuiteName) \
|
||||
gtest_registered_test_names_##TestSuiteName##_
|
||||
|
||||
// The variables defined in the type-parameterized test macros are
|
||||
// static as typically these macros are used in a .h file that can be
|
||||
// #included in multiple translation units linked together.
|
||||
#define TYPED_TEST_SUITE_P(SuiteName) \
|
||||
static ::testing::internal::TypedTestSuitePState \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName)
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define TYPED_TEST_CASE_P \
|
||||
static_assert(::testing::internal::TypedTestCase_P_IsDeprecated(), ""); \
|
||||
TYPED_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
#define TYPED_TEST_P(SuiteName, TestName) \
|
||||
namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \
|
||||
template <typename gtest_TypeParam_> \
|
||||
class TestName : public SuiteName<gtest_TypeParam_> { \
|
||||
private: \
|
||||
typedef SuiteName<gtest_TypeParam_> TestFixture; \
|
||||
typedef gtest_TypeParam_ TypeParam; \
|
||||
void TestBody() override; \
|
||||
}; \
|
||||
static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).AddTestName( \
|
||||
__FILE__, __LINE__, GTEST_STRINGIFY_(SuiteName), \
|
||||
GTEST_STRINGIFY_(TestName)); \
|
||||
} \
|
||||
template <typename gtest_TypeParam_> \
|
||||
void GTEST_SUITE_NAMESPACE_( \
|
||||
SuiteName)::TestName<gtest_TypeParam_>::TestBody()
|
||||
|
||||
// Note: this won't work correctly if the trailing arguments are macros.
|
||||
#define REGISTER_TYPED_TEST_SUITE_P(SuiteName, ...) \
|
||||
namespace GTEST_SUITE_NAMESPACE_(SuiteName) { \
|
||||
typedef ::testing::internal::Templates<__VA_ARGS__> gtest_AllTests_; \
|
||||
} \
|
||||
static const char* const GTEST_REGISTERED_TEST_NAMES_( \
|
||||
SuiteName) GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
GTEST_TYPED_TEST_SUITE_P_STATE_(SuiteName).VerifyRegisteredTestNames( \
|
||||
GTEST_STRINGIFY_(SuiteName), __FILE__, __LINE__, #__VA_ARGS__)
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define REGISTER_TYPED_TEST_CASE_P \
|
||||
static_assert(::testing::internal::RegisterTypedTestCase_P_IsDeprecated(), \
|
||||
""); \
|
||||
REGISTER_TYPED_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
#define INSTANTIATE_TYPED_TEST_SUITE_P(Prefix, SuiteName, Types, ...) \
|
||||
static_assert(sizeof(GTEST_STRINGIFY_(Prefix)) > 1, \
|
||||
"test-suit-prefix must not be empty"); \
|
||||
static bool gtest_##Prefix##_##SuiteName GTEST_ATTRIBUTE_UNUSED_ = \
|
||||
::testing::internal::TypeParameterizedTestSuite< \
|
||||
SuiteName, GTEST_SUITE_NAMESPACE_(SuiteName)::gtest_AllTests_, \
|
||||
::testing::internal::GenerateTypeList<Types>::type>:: \
|
||||
Register(GTEST_STRINGIFY_(Prefix), \
|
||||
::testing::internal::CodeLocation(__FILE__, __LINE__), \
|
||||
>EST_TYPED_TEST_SUITE_P_STATE_(SuiteName), \
|
||||
GTEST_STRINGIFY_(SuiteName), \
|
||||
GTEST_REGISTERED_TEST_NAMES_(SuiteName), \
|
||||
::testing::internal::GenerateNames< \
|
||||
::testing::internal::NameGeneratorSelector< \
|
||||
__VA_ARGS__>::type, \
|
||||
::testing::internal::GenerateTypeList<Types>::type>())
|
||||
|
||||
// Legacy API is deprecated but still available
|
||||
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
#define INSTANTIATE_TYPED_TEST_CASE_P \
|
||||
static_assert( \
|
||||
::testing::internal::InstantiateTypedTestCase_P_IsDeprecated(), ""); \
|
||||
INSTANTIATE_TYPED_TEST_SUITE_P
|
||||
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,279 @@
|
||||
// Copyright 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Implements a family of generic predicate assertion macros.
|
||||
|
||||
// IWYU pragma: private, include "gtest/gtest.h"
|
||||
// IWYU pragma: friend gtest/.*
|
||||
// IWYU pragma: friend gmock/.*
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
|
||||
|
||||
#include "gtest/gtest-assertion-result.h"
|
||||
#include "gtest/internal/gtest-internal.h"
|
||||
#include "gtest/internal/gtest-port.h"
|
||||
|
||||
namespace testing {
|
||||
|
||||
// This header implements a family of generic predicate assertion
|
||||
// macros:
|
||||
//
|
||||
// ASSERT_PRED_FORMAT1(pred_format, v1)
|
||||
// ASSERT_PRED_FORMAT2(pred_format, v1, v2)
|
||||
// ...
|
||||
//
|
||||
// where pred_format is a function or functor that takes n (in the
|
||||
// case of ASSERT_PRED_FORMATn) values and their source expression
|
||||
// text, and returns a testing::AssertionResult. See the definition
|
||||
// of ASSERT_EQ in gtest.h for an example.
|
||||
//
|
||||
// If you don't care about formatting, you can use the more
|
||||
// restrictive version:
|
||||
//
|
||||
// ASSERT_PRED1(pred, v1)
|
||||
// ASSERT_PRED2(pred, v1, v2)
|
||||
// ...
|
||||
//
|
||||
// where pred is an n-ary function or functor that returns bool,
|
||||
// and the values v1, v2, ..., must support the << operator for
|
||||
// streaming to std::ostream.
|
||||
//
|
||||
// We also define the EXPECT_* variations.
|
||||
//
|
||||
// For now we only support predicates whose arity is at most 5.
|
||||
// Please email googletestframework@googlegroups.com if you need
|
||||
// support for higher arities.
|
||||
|
||||
// GTEST_ASSERT_ is the basic statement to which all of the assertions
|
||||
// in this file reduce. Don't use this in your code.
|
||||
|
||||
#define GTEST_ASSERT_(expression, on_failure) \
|
||||
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
|
||||
if (const ::testing::AssertionResult gtest_ar = (expression)) \
|
||||
; \
|
||||
else \
|
||||
on_failure(gtest_ar.failure_message())
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred, typename T1>
|
||||
AssertionResult AssertPred1Helper(const char* pred_text, const char* e1,
|
||||
Pred pred, const T1& v1) {
|
||||
if (pred(v1)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure) \
|
||||
GTEST_ASSERT_(pred_format(#v1, v1), on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED1_(pred, v1, on_failure) \
|
||||
GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, #v1, pred, v1), on_failure)
|
||||
|
||||
// Unary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT1(pred_format, v1) \
|
||||
GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED1(pred, v1) GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT1(pred_format, v1) \
|
||||
GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED1(pred, v1) GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_)
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred, typename T1, typename T2>
|
||||
AssertionResult AssertPred2Helper(const char* pred_text, const char* e1,
|
||||
const char* e2, Pred pred, const T1& v1,
|
||||
const T2& v2) {
|
||||
if (pred(v1, v2)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2
|
||||
<< ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure) \
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2), on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED2_(pred, v1, v2, on_failure) \
|
||||
GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, #v1, #v2, pred, v1, v2), \
|
||||
on_failure)
|
||||
|
||||
// Binary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \
|
||||
GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED2(pred, v1, v2) \
|
||||
GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \
|
||||
GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED2(pred, v1, v2) \
|
||||
GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_)
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred, typename T1, typename T2, typename T3>
|
||||
AssertionResult AssertPred3Helper(const char* pred_text, const char* e1,
|
||||
const char* e2, const char* e3, Pred pred,
|
||||
const T1& v1, const T2& v2, const T3& v3) {
|
||||
if (pred(v1, v2, v3)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2 << ", " << e3
|
||||
<< ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
|
||||
<< e3 << " evaluates to " << ::testing::PrintToString(v3);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure) \
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3), on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED3_(pred, v1, v2, v3, on_failure) \
|
||||
GTEST_ASSERT_( \
|
||||
::testing::AssertPred3Helper(#pred, #v1, #v2, #v3, pred, v1, v2, v3), \
|
||||
on_failure)
|
||||
|
||||
// Ternary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \
|
||||
GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED3(pred, v1, v2, v3) \
|
||||
GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \
|
||||
GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED3(pred, v1, v2, v3) \
|
||||
GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_)
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred, typename T1, typename T2, typename T3, typename T4>
|
||||
AssertionResult AssertPred4Helper(const char* pred_text, const char* e1,
|
||||
const char* e2, const char* e3,
|
||||
const char* e4, Pred pred, const T1& v1,
|
||||
const T2& v2, const T3& v3, const T4& v4) {
|
||||
if (pred(v1, v2, v3, v4)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4
|
||||
<< ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
|
||||
<< e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n"
|
||||
<< e4 << " evaluates to " << ::testing::PrintToString(v4);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure) \
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4), on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure) \
|
||||
GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, #v1, #v2, #v3, #v4, pred, \
|
||||
v1, v2, v3, v4), \
|
||||
on_failure)
|
||||
|
||||
// 4-ary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
|
||||
GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED4(pred, v1, v2, v3, v4) \
|
||||
GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \
|
||||
GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED4(pred, v1, v2, v3, v4) \
|
||||
GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_)
|
||||
|
||||
// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use
|
||||
// this in your code.
|
||||
template <typename Pred, typename T1, typename T2, typename T3, typename T4,
|
||||
typename T5>
|
||||
AssertionResult AssertPred5Helper(const char* pred_text, const char* e1,
|
||||
const char* e2, const char* e3,
|
||||
const char* e4, const char* e5, Pred pred,
|
||||
const T1& v1, const T2& v2, const T3& v3,
|
||||
const T4& v4, const T5& v5) {
|
||||
if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess();
|
||||
|
||||
return AssertionFailure()
|
||||
<< pred_text << "(" << e1 << ", " << e2 << ", " << e3 << ", " << e4
|
||||
<< ", " << e5 << ") evaluates to false, where"
|
||||
<< "\n"
|
||||
<< e1 << " evaluates to " << ::testing::PrintToString(v1) << "\n"
|
||||
<< e2 << " evaluates to " << ::testing::PrintToString(v2) << "\n"
|
||||
<< e3 << " evaluates to " << ::testing::PrintToString(v3) << "\n"
|
||||
<< e4 << " evaluates to " << ::testing::PrintToString(v4) << "\n"
|
||||
<< e5 << " evaluates to " << ::testing::PrintToString(v5);
|
||||
}
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5.
|
||||
// Don't use this in your code.
|
||||
#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure) \
|
||||
GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5), \
|
||||
on_failure)
|
||||
|
||||
// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use
|
||||
// this in your code.
|
||||
#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure) \
|
||||
GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, #v1, #v2, #v3, #v4, #v5, \
|
||||
pred, v1, v2, v3, v4, v5), \
|
||||
on_failure)
|
||||
|
||||
// 5-ary predicate assertion macros.
|
||||
#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
|
||||
#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_)
|
||||
#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
|
||||
#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \
|
||||
GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_)
|
||||
|
||||
} // namespace testing
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2006, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Google C++ Testing and Mocking Framework definitions useful in production
|
||||
// code.
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
|
||||
|
||||
// When you need to test the private or protected members of a class,
|
||||
// use the FRIEND_TEST macro to declare your tests as friends of the
|
||||
// class. For example:
|
||||
//
|
||||
// class MyClass {
|
||||
// private:
|
||||
// void PrivateMethod();
|
||||
// FRIEND_TEST(MyClassTest, PrivateMethodWorks);
|
||||
// };
|
||||
//
|
||||
// class MyClassTest : public testing::Test {
|
||||
// // ...
|
||||
// };
|
||||
//
|
||||
// TEST_F(MyClassTest, PrivateMethodWorks) {
|
||||
// // Can call MyClass::PrivateMethod() here.
|
||||
// }
|
||||
//
|
||||
// Note: The test class must be in the same namespace as the class being tested.
|
||||
// For example, putting MyClassTest in an anonymous namespace will not work.
|
||||
|
||||
#define FRIEND_TEST(test_case_name, test_name) \
|
||||
friend class test_case_name##_##test_name##_Test
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
|
||||
@@ -0,0 +1,44 @@
|
||||
# Customization Points
|
||||
|
||||
The custom directory is an injection point for custom user configurations.
|
||||
|
||||
## Header `gtest.h`
|
||||
|
||||
### The following macros can be defined:
|
||||
|
||||
* `GTEST_OS_STACK_TRACE_GETTER_` - The name of an implementation of
|
||||
`OsStackTraceGetterInterface`.
|
||||
* `GTEST_CUSTOM_TEMPDIR_FUNCTION_` - An override for `testing::TempDir()`. See
|
||||
`testing::TempDir` for semantics and signature.
|
||||
|
||||
## Header `gtest-port.h`
|
||||
|
||||
The following macros can be defined:
|
||||
|
||||
### Logging:
|
||||
|
||||
* `GTEST_LOG_(severity)`
|
||||
* `GTEST_CHECK_(condition)`
|
||||
* Functions `LogToStderr()` and `FlushInfoLog()` have to be provided too.
|
||||
|
||||
### Threading:
|
||||
|
||||
* `GTEST_HAS_NOTIFICATION_` - Enabled if Notification is already provided.
|
||||
* `GTEST_HAS_MUTEX_AND_THREAD_LOCAL_` - Enabled if `Mutex` and `ThreadLocal`
|
||||
are already provided. Must also provide `GTEST_DECLARE_STATIC_MUTEX_(mutex)`
|
||||
and `GTEST_DEFINE_STATIC_MUTEX_(mutex)`
|
||||
* `GTEST_EXCLUSIVE_LOCK_REQUIRED_(locks)`
|
||||
* `GTEST_LOCK_EXCLUDED_(locks)`
|
||||
|
||||
### Underlying library support features
|
||||
|
||||
* `GTEST_HAS_CXXABI_H_`
|
||||
|
||||
### Exporting API symbols:
|
||||
|
||||
* `GTEST_API_` - Specifier for exported symbols.
|
||||
|
||||
## Header `gtest-printers.h`
|
||||
|
||||
* See documentation at `gtest/gtest-printers.h` for details on how to define a
|
||||
custom printer.
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Injection point for custom user configurations. See README for details
|
||||
//
|
||||
// ** Custom implementation starts here **
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PORT_H_
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// This file provides an injection point for custom printers in a local
|
||||
// installation of gTest.
|
||||
// It will be included from gtest-printers.h and the overrides in this file
|
||||
// will be visible to everyone.
|
||||
//
|
||||
// Injection point for custom user configurations. See README for details
|
||||
//
|
||||
// ** Custom implementation starts here **
|
||||
|
||||
#ifndef GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
|
||||
#define GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
|
||||
|
||||
#endif // GOOGLETEST_INCLUDE_GTEST_INTERNAL_CUSTOM_GTEST_PRINTERS_H_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user