diff --git a/.github/workflows/spa-client-cd.yml b/.github/workflows/spa-client-cd.yml index d596ee9..d2068f7 100644 --- a/.github/workflows/spa-client-cd.yml +++ b/.github/workflows/spa-client-cd.yml @@ -3,9 +3,6 @@ name: spa-client release on: - push: - tags: - - 'v*' workflow_dispatch: jobs: @@ -44,4 +41,12 @@ jobs: with: path: ./release/* name: ${{ matrix.settings.name }} - if-no-files-found: error \ No newline at end of file + if-no-files-found: error + + - name: Create Release + uses: ncipollo/release-action@v1 + if: ${{ ! startsWith(github.ref, 'refs/tags/') }} + with: + artifacts: spa-client* + token: ${{ secrets.YOUR_GITHUB_TOKEN }} + draft: true \ No newline at end of file diff --git a/.github/workflows/spa-server-cd.yml b/.github/workflows/spa-server-cd.yml index f81e97e..e4e4e1d 100644 --- a/.github/workflows/spa-server-cd.yml +++ b/.github/workflows/spa-server-cd.yml @@ -1,4 +1,4 @@ -name: spa-server-docker release +name: spa-server release on: push: @@ -8,6 +8,42 @@ env: REGISTRY: ghcr.io jobs: + client: + strategy: + fail-fast: false + matrix: + settings: + - os: ubuntu-latest + name: linux-spa-client + bash: make release-linux-client-musl + - os: macos-latest + name: mac-spa-client + bash: make release-client-mac + - os: windows-latest + name: windows-spa-client + bash: make release-client-win + runs-on: ${{ matrix.settings.os }} + steps: + - name: Get version + uses: actions/checkout@v4 + with: + submodules: true + - uses: Swatinem/rust-cache@v2 + - name: Setup MUSL + if: matrix.settings.os == 'ubuntu-latest' + run: | + rustup target add x86_64-unknown-linux-musl + sudo apt-get -qq install musl-tools + + - name: Build Release + run: ${{ matrix.settings.bash }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + path: ./release/* + name: ${{ matrix.settings.name }} + if-no-files-found: error docker: runs-on: ubuntu-latest steps: @@ -59,5 +95,12 @@ jobs: uses: actions/upload-artifact@v4 with: path: ./release/* - name: spa-server-linux - if-no-files-found: error \ No newline at end of file + name: spa-server-linux-musl + if-no-files-found: error + - name: Create Release + uses: ncipollo/release-action@v1 + if: ${{ ! startsWith(github.ref, 'refs/tags/') }} + with: + artifacts: spa-server-* + token: ${{ secrets.YOUR_GITHUB_TOKEN }} + draft: true \ No newline at end of file diff --git a/.github/workflows/spa-server-ci.yml b/.github/workflows/spa-server-ci.yml index 64878bc..a123b37 100644 --- a/.github/workflows/spa-server-ci.yml +++ b/.github/workflows/spa-server-ci.yml @@ -8,7 +8,31 @@ on: workflow_dispatch: jobs: + changes: + runs-on: ubuntu-latest + # Required permissions + permissions: + pull-requests: read + # Set job outputs to values from filter step + outputs: + check_server: ${{ steps.filter.outputs.check_server }} + check_js_client: ${{ steps.filter.outputs.check_js_client }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + check_server: + - 'client/**' + - 'server/**' + - 'Cargo.lock' + check_js_sdk: + - 'jsclient/**' + check_server: + needs: changes + if: ${{ needs.changes.outputs.check_server == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -26,4 +50,17 @@ jobs: - name: run spa-client test run: cargo test -p spa-client -j 1 -- --test-threads 1 - name: run spa-server test - run: cargo test -p spa-server \ No newline at end of file + run: cargo test -p spa-server + check_js_client: + needs: changes + if: ${{ needs.changes.outputs.check_js_client == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Setup .npmrc file to publish to npm + - uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + cache-dependency-path: './jsclient/package-lock.json' + - run: npm ci && npm run build \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 57340b9..303ba05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,19 +25,10 @@ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", - "version_check 0.9.4", + "version_check", "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - [[package]] name = "aho-corasick" version = "1.1.3" @@ -141,6 +132,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "asn1-rs" version = "0.6.1" @@ -150,7 +147,7 @@ dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", - "nom 7.1.3", + "nom", "num-traits", "rusticata-macros", "thiserror", @@ -410,8 +407,8 @@ dependencies = [ "bytes", "futures-util", "http 0.2.12", - "http-body", - "hyper", + "http-body 0.4.6", + "hyper 0.14.29", "itoa", "matchit", "memchr", @@ -420,7 +417,7 @@ dependencies = [ "pin-project-lite", "rustversion", "serde", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower", "tower-layer", "tower-service", @@ -436,7 +433,7 @@ dependencies = [ "bytes", "futures-util", "http 0.2.12", - "http-body", + "http-body 0.4.6", "mime", "rustversion", "tower-layer", @@ -727,7 +724,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a8699d8ed16e3db689f8ae04d8dc3c6666a4ba7e724e5a157884b7cc385d16b" dependencies = [ "chrono", - "nom 7.1.3", + "nom", "once_cell", ] @@ -808,7 +805,7 @@ checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ "asn1-rs", "displaydoc", - "nom 7.1.3", + "nom", "num-bigint", "num-traits", "rusticata-macros", @@ -844,6 +841,20 @@ dependencies = [ "syn", ] +[[package]] +name = "duration-str" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709d653e7c92498eb29fb86a2a6f0f3502b97530f33aedb32ef848d4d28b31a3" +dependencies = [ + "chrono", + "rust_decimal", + "serde", + "thiserror", + "time", + "winnow", +] + [[package]] name = "either" version = "1.13.0" @@ -856,70 +867,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" -[[package]] -name = "encoding" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" -dependencies = [ - "encoding-index-japanese", - "encoding-index-korean", - "encoding-index-simpchinese", - "encoding-index-singlebyte", - "encoding-index-tradchinese", -] - -[[package]] -name = "encoding-index-japanese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-korean" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-simpchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-singlebyte" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-tradchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding_index_tests" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" - [[package]] name = "encoding_rs" version = "0.8.34" @@ -929,6 +876,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "entity" +version = "2.4.0" +dependencies = [ + "chrono", + "duration-str", + "serde", + "serde_json", + "serde_repr", +] + [[package]] name = "env_filter" version = "0.1.0" @@ -1196,7 +1154,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", - "version_check 0.9.4", + "version_check", ] [[package]] @@ -1216,6 +1174,12 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "h2" version = "0.3.26" @@ -1235,6 +1199,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "handlebars" version = "5.1.2" @@ -1274,7 +1257,7 @@ dependencies = [ "base64 0.21.7", "byteorder", "flate2", - "nom 7.1.3", + "nom", "num-traits", ] @@ -1320,25 +1303,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" -[[package]] -name = "hocon" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dbf0338dac82c09762a1283d8bb117f2d0dddb97ee4eec711dd1b78f89975ee" -dependencies = [ - "aho-corasick 0.7.20", - "java-properties", - "lazy_static", - "linked-hash-map", - "memchr", - "nom 4.2.3", - "reqwest", - "serde", - "serde_path_to_error", - "thiserror", - "uuid", -] - [[package]] name = "http" version = "0.2.12" @@ -1372,6 +1336,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.9.4" @@ -1400,9 +1387,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1414,18 +1401,42 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", - "http 0.2.12", - "hyper", - "rustls 0.21.12", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.23.10", + "rustls-pki-types", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots", ] [[package]] @@ -1434,7 +1445,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1442,15 +1453,38 @@ dependencies = [ [[package]] name = "hyper-tls" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", - "hyper", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1583,17 +1617,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "java-properties" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1904d8654a1ef51034d02d5a9411b50bf91bea15b0ab644ae179d1325976263" -dependencies = [ - "encoding", - "lazy_static", - "regex", -] - [[package]] name = "js-sys" version = "0.3.69" @@ -1615,12 +1638,6 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1757,7 +1774,7 @@ dependencies = [ "memchr", "mime", "spin", - "version_check 0.9.4", + "version_check", ] [[package]] @@ -1777,16 +1794,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" -dependencies = [ - "memchr", - "version_check 0.1.5", -] - [[package]] name = "nom" version = "7.1.3" @@ -1925,6 +1932,105 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b69a91d4893e713e06f724597ad630f1fa76057a5e1026c0ca67054a9032a76" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94c69209c05319cdf7460c6d4c055ed102be242a0a6245835d7bc42c6ec7f54" +dependencies = [ + "async-trait", + "futures-core", + "http 0.2.12", + "opentelemetry", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "thiserror", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984806e6cf27f2b49282e2a05e288f30594f3dbc74eb7a6e99422bc48ed78162" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", + "tonic", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1869fb4bb9b35c5ba8a1e40c9b128a7b4c010d07091e864a29da19e4fe2ca4d7" + +[[package]] +name = "opentelemetry-stdout" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6d080bf06af02b738feb2e6830cf72c30b76ca18b40f555cdf1b53e7b491bfe" +dependencies = [ + "async-trait", + "chrono", + "futures-util", + "opentelemetry", + "opentelemetry_sdk", + "ordered-float", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae312d58eaa90a82d2e627fd86e075cf5230b3f11794e2ed74199ebbe572d4fd" +dependencies = [ + "async-trait", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "lazy_static", + "once_cell", + "opentelemetry", + "ordered-float", + "percent-encoding", + "rand", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-float" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ff2cf528c6c03d9ed653d6c4ce1dc0582dc4af309790ad92f07c1cd551b0be" +dependencies = [ + "num-traits", +] + [[package]] name = "overload" version = "0.1.1" @@ -2170,6 +2276,53 @@ dependencies = [ "prost", ] +[[package]] +name = "quinn" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.10", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +dependencies = [ + "bytes", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.10", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +dependencies = [ + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" version = "1.0.36" @@ -2236,7 +2389,7 @@ version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ - "aho-corasick 1.1.3", + "aho-corasick", "memchr", "regex-automata 0.4.7", "regex-syntax 0.8.4", @@ -2257,7 +2410,7 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ - "aho-corasick 1.1.3", + "aho-corasick", "memchr", "regex-syntax 0.8.4", ] @@ -2276,21 +2429,24 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2", - "http 0.2.12", - "http-body", - "hyper", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -2300,17 +2456,18 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-native-certs", - "rustls-pemfile 1.0.4", + "quinn", + "rustls 0.23.10", + "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "system-configuration", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.0", "tokio-util", "tower-service", "url", @@ -2318,7 +2475,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.4", + "webpki-roots", "winreg", ] @@ -2343,12 +2500,28 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e60ef3b82994702bbe4e134d98aadca4b49ed04440148985678d415c68127666" +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "num-traits", +] + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2364,7 +2537,7 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "nom 7.1.3", + "nom", ] [[package]] @@ -2394,18 +2567,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.22.4" @@ -2415,30 +2576,23 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki", "subtle", "zeroize", ] [[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustls" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ - "base64 0.21.7", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] @@ -2457,16 +2611,6 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.102.4" @@ -2520,16 +2664,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.11.0" @@ -2599,16 +2733,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_path_to_error" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" -dependencies = [ - "itoa", - "serde", -] - [[package]] name = "serde_repr" version = "0.1.19" @@ -2753,20 +2877,19 @@ dependencies = [ [[package]] name = "spa-client" -version = "2.3.0" +version = "2.4.0" dependencies = [ "anyhow", "clap", "console", + "entity", "futures", - "hocon", "if_chain", "indicatif", "md-5", "reqwest", "serde", "serde_json", - "spa-server", "tokio", "toml", "toml_edit", @@ -2777,7 +2900,7 @@ dependencies = [ [[package]] name = "spa-server" -version = "2.3.0" +version = "2.4.0" dependencies = [ "anyhow", "base64 0.22.1", @@ -2785,22 +2908,27 @@ dependencies = [ "chrono", "dashmap", "delay_timer", + "duration-str", + "entity", "flate2", "futures", "futures-util", "headers", - "hocon", - "hyper", + "hyper 0.14.29", "if_chain", "lazy_static", "md-5", "mime", "mime_guess", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", "percent-encoding", "rcgen", "regex", "rustls 0.22.4", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "serde", "serde_json", "serde_repr", @@ -2810,6 +2938,8 @@ dependencies = [ "tokio-rustls 0.25.0", "toml", "tracing", + "tracing-core", + "tracing-opentelemetry", "tracing-subscriber", "ureq", "walkdir", @@ -2852,6 +2982,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "synstructure" version = "0.13.1" @@ -2911,11 +3047,15 @@ version = "0.1.0" dependencies = [ "anyhow", "console-subscriber", + "opentelemetry", + "opentelemetry-stdout", + "opentelemetry_sdk", "reqwest", "spa-client", "spa-server", "tokio", "tracing", + "tracing-opentelemetry", "tracing-subscriber", ] @@ -3048,21 +3188,22 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.21.12", + "rustls 0.22.4", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.22.4", + "rustls 0.23.10", "rustls-pki-types", "tokio", ] @@ -3148,10 +3289,10 @@ dependencies = [ "axum", "base64 0.21.7", "bytes", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", - "hyper", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -3240,6 +3381,24 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f68803492bf28ab40aeccaecc7021096bd256baf7ca77c3d425d89b35a7be4e4" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -3301,7 +3460,7 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ - "version_check 0.9.4", + "version_check", ] [[package]] @@ -3348,11 +3507,11 @@ dependencies = [ "once_cell", "rustls 0.22.4", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki", "serde", "serde_json", "url", - "webpki-roots 0.26.3", + "webpki-roots", ] [[package]] @@ -3383,9 +3542,6 @@ name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" -dependencies = [ - "getrandom", -] [[package]] name = "valuable" @@ -3399,12 +3555,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.4" @@ -3447,7 +3597,7 @@ dependencies = [ "handlebars", "headers", "http 0.2.12", - "hyper", + "hyper 0.14.29", "listenfd", "log", "mime", @@ -3456,7 +3606,7 @@ dependencies = [ "percent-encoding", "pin-project", "pretty_env_logger", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "scoped-tls", "serde", "serde_derive", @@ -3569,10 +3719,14 @@ dependencies = [ ] [[package]] -name = "webpki-roots" -version = "0.25.4" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] [[package]] name = "webpki-roots" @@ -3773,9 +3927,9 @@ dependencies = [ [[package]] name = "winreg" -version = "0.50.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", @@ -3791,7 +3945,7 @@ dependencies = [ "data-encoding", "der-parser", "lazy_static", - "nom 7.1.3", + "nom", "oid-registry", "rusticata-macros", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 85c3f67..118d9a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "server", "client", - "tests" + "tests", + "entity", ] resolver = "2" diff --git a/README.md b/README.md index 37fdcb4..d7dff73 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # SPA-SERVER +[![GitHub Release](https://img.shields.io/github/release/ForNetCode/spa-server?color=brightgreen)](https://github.com/ForNetCode/spa-server/releases) [![Build status](https://github.com/ForNetCode/spa-server/actions/workflows/spa-server-ci.yml/badge.svg)](https://github.com/ForNetCode/spa-server/actions/workflows/spa-server-ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-green)](LICENSE) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/ForNetCode/spa-server/graphs/commit-activity) @@ -21,6 +22,7 @@ It provides a static web http server with cache and hot reload. - Provide command line/npm package to deploy spa. - Multiple configs for different domain. - support Let's Encrypt +- support OpenTelemetry Trace - provide JS SDK and command line client to interact with Server ## Document diff --git a/README_CN.md b/README_CN.md index 3e73f0b..767db64 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,4 +1,5 @@ # SPA-SERVER +[![GitHub Release](https://img.shields.io/github/release/ForNetCode/spa-server?color=brightgreen)](https://github.com/ForNetCode/spa-server/releases) [![Build status](https://github.com/ForNetCode/spa-server/actions/workflows/spa-server-ci.yml/badge.svg)](https://github.com/ForNetCode/spa-server/actions/workflows/spa-server-ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-green)](LICENSE) [![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/ForNetCode/spa-server/graphs/commit-activity) @@ -20,6 +21,7 @@ - 提供 命令行/npm包 客户端,一行命令部署 - 每个域名可拥有独立的配置 - 支持 Let's Encrypt +- 支持 OpenTelemetry - 提供JS SDK、命令行客户端与服务器进行交互。 ## 文档 @@ -67,4 +69,4 @@ docker run -d -p 80 -v $HOST_VOLUME:/data -v $CONFIG:/config.conf ghcr.io/forne 本项目用了很多 warp/src/filters/fs.rs 的私有API。 ## 项目起源 -请跳转至 [SPA 发布辅助工具](https://github.com/timzaak/blog/issues/80) 浏览。 \ No newline at end of file +请跳转至 [SPA 发布辅助工具](https://github.com/timzaak/blog/issues/80) 浏览。 diff --git a/client/Cargo.toml b/client/Cargo.toml index 514f9ea..5e2d191 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spa-client" -version = "2.3.0" +version = "2.4.0" edition = "2021" authors = ["timzaak"] license = "MIT" @@ -18,10 +18,10 @@ path = "src/bin/main.rs" [dependencies] -spa-server = { path = "../server" } +entity = {path = "../entity"} # web request -reqwest = { version = "0.11.27", features = ["json", "blocking", "multipart", "stream", "rustls-tls"], default-features = false } +reqwest = { version = "0.12", features = ["json", "blocking", "multipart", "stream", "rustls-tls"], default-features = false } tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "io-std", "sync", "time", "tokio-macros", "test-util"] } futures = "0.3" @@ -35,7 +35,6 @@ tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } #config -hocon = "0.9" toml = "0.8.14" toml_edit = "0.22.14" serde = { version = "1.0", features = ["derive"] } diff --git a/client/client_config_default.conf b/client/client_config_default.conf deleted file mode 100644 index 82be235..0000000 --- a/client/client_config_default.conf +++ /dev/null @@ -1,10 +0,0 @@ -# admin server, if set --config-dir -server { - address: "http://127.0.0.1:9000" - auth_token: "token" -} - -upload { - # default value is:3 - parallel: 3 -} \ No newline at end of file diff --git a/client/src/api.rs b/client/src/api.rs index c59af76..0231a24 100644 --- a/client/src/api.rs +++ b/client/src/api.rs @@ -1,8 +1,8 @@ use crate::Config; use anyhow::anyhow; use reqwest::{header, multipart, StatusCode}; -use spa_server::admin_server::request::{DeleteDomainVersionOption, DomainWithOptVersionOption, DomainWithVersionOption, GetDomainOption, UpdateUploadingStatusOption}; -use spa_server::domain_storage::{CertInfo, DomainInfo, ShortMetaData, UploadDomainPosition}; +use entity::request::{DeleteDomainVersionOption, DomainWithOptVersionOption, DomainWithVersionOption, GetDomainOption, UpdateUploadingStatusOption}; +use entity::storage::{CertInfo, DomainInfo, ShortMetaData, UploadDomainPosition}; use std::borrow::Cow; use std::path::PathBuf; @@ -205,8 +205,8 @@ impl API { #[cfg(test)] mod test { use crate::api::API; - use spa_server::admin_server::request::UpdateUploadingStatusOption; - use spa_server::domain_storage::UploadingStatus; + use entity::request::UpdateUploadingStatusOption; + use entity::storage::UploadingStatus; use crate::LOCAL_HOST; fn get_api() -> API { diff --git a/client/src/config.rs b/client/src/config.rs index 8ad62ac..a0e7449 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -1,12 +1,8 @@ use anyhow::anyhow; -use hocon::HoconLoader; use serde::Deserialize; use std::fs; use std::path::PathBuf; use toml_edit::DocumentMut; -use tracing::warn; - -//const ENV_CONFIG: &str = include_str!("../client_config_env.conf"); #[derive(Debug, Deserialize, Clone, PartialEq)] pub struct Config { @@ -30,58 +26,7 @@ fn env_opt(key: &str) -> Option { impl Config { pub fn load(config_dir: Option) -> anyhow::Result { - if config_dir.as_ref().is_some_and(|f| f.display().to_string().ends_with(".conf")) { - Self::load_hocon(config_dir) - } else { - Self::load_toml(config_dir) - } - } - - fn load_hocon(config_dir: Option) -> anyhow::Result { - warn!("config format: conf would not support in future, please use toml"); - let mut conf_loader = HoconLoader::new(); - let config_dir = config_dir.or_else(|| { - std::env::current_exe().ok().and_then(|p| { - p.parent().and_then(|p| { - let p = p.join("config.conf"); - if p.exists() { - Some(p) - } else { - None - } - }) - }) - }); - - if let Some(config_dir) = config_dir { - conf_loader = conf_loader.load_file(config_dir)?; - } - - let conf = conf_loader.hocon()?; - // Hocon has a problem, new will override old even new is error. - // and environment variable will be empty string if not exists. - let admin_server = AdminServerConfig { - address: conf["server"]["address"] - .as_string() - .or(env_opt("SPA_SERVER_ADDRESS")) - .ok_or(anyhow!("server.address could not get"))?, - auth_token: conf["server"]["auth_token"] - .as_string() - .or(env_opt("SPA_SERVER_AUTH_TOKEN")) - .ok_or(anyhow!("server.auth_token could not get"))?, - }; - let uploading_config = UploadConfig { - parallel: conf["upload"]["parallel"] - .as_string() - .or(env_opt("SPA_UPLOAD_PARALLEL")) - .and_then(|x| x.parse::().ok()) - .unwrap_or(3), - }; - let config: Config = Config { - server: admin_server, - upload: uploading_config, - }; - Ok(config) + Self::load_toml(config_dir) } fn load_toml(config_dir: Option) -> anyhow::Result { let mut conf = DocumentMut::new(); @@ -156,26 +101,5 @@ pub(crate) mod test { let c = Config::load(None); assert!(c.is_err()); } - #[test] - fn load_env_equal() { - init_env(); - assert_eq!( - Config::load_hocon(None).unwrap(), - Config::load_toml(None).unwrap() - ); - } - fn get_project_path() -> PathBuf { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let path = path.parent().unwrap(); - path.to_owned() - } - #[test] - fn load_file_equal() { - remove_env(); - let path = get_project_path().join("client"); - assert_eq!( - Config::load_hocon(Some(path.join("client_config_default.conf"))).unwrap(), - Config::load_toml(Some(path.join("client_config_default.toml"))).unwrap() - ); - } + } diff --git a/client/src/upload_files.rs b/client/src/upload_files.rs index 9ca7493..978f8e1 100644 --- a/client/src/upload_files.rs +++ b/client/src/upload_files.rs @@ -4,17 +4,20 @@ use console::style; use futures::future::Either; use futures::StreamExt; use if_chain::if_chain; -use spa_server::admin_server::request::UpdateUploadingStatusOption; -use spa_server::domain_storage::{ - md5_file, GetDomainPositionStatus, ShortMetaData, UploadingStatus, +use entity::request::UpdateUploadingStatusOption; +use entity::storage::{ + GetDomainPositionStatus, ShortMetaData, UploadingStatus, }; use std::borrow::Cow; use std::collections::HashMap; use std::fs; -use std::path::PathBuf; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::time::Duration; +use md5::{Digest, Md5}; use tracing::warn; use walkdir::WalkDir; @@ -204,3 +207,23 @@ async fn get_upload_version(api: &API, domain: &str, version: Option) -> an Ok(resp.version) } } + +fn md5_file(path: impl AsRef, byte_buffer: &mut Vec) -> Option { + File::open(path) + .ok() + .map(|mut f| { + let mut hasher = Md5::new(); + //if file_size > 1024 * 1024 { + //1Mb + loop { + let n = f.read(byte_buffer).ok()?; + let valid_buf_slice = &byte_buffer[..n]; + if n == 0 { + break; + } + hasher.update(valid_buf_slice); + } + Some(format!("{:x}", hasher.finalize())) + }) + .flatten() +} diff --git a/config.release.conf b/config.release.conf deleted file mode 100644 index 59ddab6..0000000 --- a/config.release.conf +++ /dev/null @@ -1,111 +0,0 @@ -# http bind, if set port <= 0 or remove http, will disable http server(need set https config) -http { - port = 80 - addr = "0.0.0.0" -} - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "/data" - -# enable cors, default is false, its implementation is simple now. -# Access-Control-Allow-Origin: $ORIGIN -# Access-Control-Allow-Methods: OPTION,GET,HEAD -# Access-Control-Max-Age: 3600 -// cors = true -# https config, optional -//https { -// # default value for https ssl -// ssl { -// # private ssl key -// private = "private.key path", -// # public ssl cert -// public = "public.cert path" -// } -// # acme config, it doest not support run with https.ssl config. -// acme { -// # emails to Let's Encrypt needs to interact. -// emails = ["mailto:email@example.com"] -// # directory to store account and certificate -// # optional, default is ${file_dir}/acme -// // dir = "/data/acme" -// # ci / stage / prod, default is prod, ci is just for CI test with Pebble, don't use it. -// //type = prod -// } - -// # https bind address -// port = 443 -// addr = "0.0.0.0" - -// # if set true, http server(80) will send client -// # status code:301(Moved Permanently) to tell client redirect to https -// # optional, default is null -// http_redirect_to_https = 443 -//} - - - -# default cache config -cache { -// # if file size > max_size, it will not be cached. default is (10MB). - max_size = 10MB -// # http header Cache-Control config, -// # optional, if not set, won't sender this header to client -// client_cache = [{ -// expire = 30d -// extension_names = [icon,gif,jpg,jpeg,png,js] -// }, { -// // set 0, would set Cache-Control: no-cache -// expire = 0 -// extension_names = [html] -// }] -// # gzip compression for js/json/icon/json, default is false, -// # only support gzip algo, and only compress cached files, -// # be careful to set it true -// compression = false - -} - -//# admin server config -//# admin server don't support hot reload. the config should not change. -//# optional, and it's disabled by default. -//# if you use spa-client to upload files, control version. Need to open it -//admin_config { -//# bind host -// port = 9000 -// addr = "127.0.0.1" -// # this is used to check client request -// # put it in http header, Authorization: Bearer $token -// token = "token" -// # max file size allowed to be uploaded, -// # default is 30MB(30*1000*1000) -// max_upload_size = 30*1000*1000 -// # delete deprecated version by cron -// deprecated_version_delete { -// # default value: every day at 3am. -// cron: "0 0 3 * * *", -// # default value is 2 -// max_preserve: 2, -// } -//} - - -# optional, domains specfic config, it will use the default config if not set -//domains = [{ -// # domain name -// domain: "www.example.com", -// # optional, `example.com` would redirect to `www.example.com` -// alias = ["example.com"] - -// // optional, same with cache config, if not set, will use default cache config. -// cache: { -// client_cache:${cache.client_cache} -// max_size: ${cache.max_size} -// }, -// # cors -// cors: ${cors}, -// # domain https config, if not set, will use default https config. -// https: { -// ssl: ${https.ssl} -// http_redirect_to_https: ${https.http_redirect_to_https} -// } -//}] \ No newline at end of file diff --git a/config.release.toml b/config.release.toml index b9a5813..d1d9684 100644 --- a/config.release.toml +++ b/config.release.toml @@ -104,3 +104,8 @@ addr = "0.0.0.0" # [[domains.cache.client_cache]] # expire = '30d' # 30day # extension_names = ['icon', 'gif', 'jpg', 'jpeg', 'png', 'js'] + +[openTelemetry] +endpoint = "http://localhost:4317" + + diff --git a/docs/develop/change-log.md b/docs/develop/change-log.md index 2838448..25e3a09 100644 --- a/docs/develop/change-log.md +++ b/docs/develop/change-log.md @@ -1,12 +1,19 @@ # Change Log +### Version 2.4.0 + +- improve: extract client and server common entity +- conf: **break change** remove hocon config +- ci: add js client build ci, improve release cd +- improve: clean deps, update claps +- feat: add openTelemetry trace + ### Version 2.3.0 - feat: support toml config format, deprecated hocon config format. - feat: support host alias. add config `http.external_port`, `https.external_port` - conf: **break change** `https.http_redirect_to_https` move to `http.redirect_https`, and value is bool. - improve: improve change_status response text style (release JS SDK 2.3.0) -- improve: clean deps, update clap ### Version 2.2.4 diff --git a/docs/develop/develop-tips.md b/docs/develop/develop-tips.md index af252e0..363fe76 100644 --- a/docs/develop/develop-tips.md +++ b/docs/develop/develop-tips.md @@ -37,4 +37,10 @@ https.acme { ## reqwest -when reqwest use rustls, redirect would have problems: it would not redirect event with Policy::default(). \ No newline at end of file +when reqwest use rustls, redirect would have problems: it would not redirect event with Policy::default(). + +## OpenTelemetry Test + +```shell +docker run --rm -p4317:4317 -p16686:16686 jaegertracing/all-in-one:latest +``` \ No newline at end of file diff --git a/docs/develop/roadmap.md b/docs/develop/roadmap.md index 9b27be6..1631514 100644 --- a/docs/develop/roadmap.md +++ b/docs/develop/roadmap.md @@ -3,4 +3,5 @@ ### Version 2.x - bench: add benchmark -- admin server support HTTPS \ No newline at end of file +- admin server support HTTPS +- openTelemetry \ No newline at end of file diff --git a/docs/guide/break-changes.md b/docs/guide/break-changes.md index 55a0104..6a44439 100644 --- a/docs/guide/break-changes.md +++ b/docs/guide/break-changes.md @@ -1,5 +1,7 @@ # Break Changes -## V2.3.0 +## V2.4.0 +* remove hocon config format, change to toml. +## V2.3.0(2024-07-01) * spa-server: `https.http_redirect_to_https` move to `http.redirect_https`, and value is bool. ## V2.2.2(2024-06-18) * spa-server: `http_redirect_to_https` convert from bool to u32. diff --git a/docs/guide/spa-client-command-line.md b/docs/guide/spa-client-command-line.md index b507c3d..b7eabd9 100644 --- a/docs/guide/spa-client-command-line.md +++ b/docs/guide/spa-client-command-line.md @@ -47,22 +47,15 @@ spa-client -c $CONFIG_PATH delete $OPT_DOMAIN $OPT_MAX_RESERVE ### Config -the config file format is hocon: - -```hocon -# admin server address and auth -server { - # required - address: "http://127.0.0.1:9000" - # required - auth_token: "token" -} - -# uploading file thread number. -upload { - # optional, default value is 3. - parallel: 3 -} +the config file format is toml: + +```toml +[server] +address = "http://127.0.0.1:9000" +auth_token = "token" +# [upload] +## default value is:3 +# parallel = 3 ``` the config default file name is `client.conf`. diff --git a/docs/guide/spa-client-configuration.md b/docs/guide/spa-client-configuration.md index 70cdec5..092ce20 100644 --- a/docs/guide/spa-client-configuration.md +++ b/docs/guide/spa-client-configuration.md @@ -18,24 +18,4 @@ auth_token = "token" # [upload] ## default value is:3 # parallel = 3 -``` - -## Hocon Format Config - -**Attention: hocon format would not support in the future.** - -```hocon -# admin server address and auth -server { - # required - address: "http://127.0.0.1:9000" - # required - auth_token: "token" -} - -# uploading file thread number. -upload { - # optional, default value is 3. - parallel: 3 -} -``` +``` \ No newline at end of file diff --git a/docs/guide/spa-client-npm-package.md b/docs/guide/spa-client-npm-package.md index 1891568..b8e407e 100644 --- a/docs/guide/spa-client-npm-package.md +++ b/docs/guide/spa-client-npm-package.md @@ -1,12 +1,13 @@ # NPM Package -## Overview - ## Install in new project -there is more info at [getting started](./getting-started.md#run-spa-client-in-npm-package) +```shell +npm install --save-dev spa-client +``` +there has more info at [getting started](./getting-started.md#run-spa-client-in-npm-package) -There has an example project for npm package users: +There id an example project for npm package users, you can view the package.json: [js-app-example](https://github.com/fornetcode/spa-server/tree/master/example/js-app-example). ## Build Source Code @@ -14,4 +15,4 @@ There has an example project for npm package users: ```shell git clone --recursive https://github.com/fornetcode/spa-server cd jsclient && npm install && npm build -``` \ No newline at end of file +``` diff --git a/docs/guide/spa-server-configuration.md b/docs/guide/spa-server-configuration.md index 81f6220..1afead6 100644 --- a/docs/guide/spa-server-configuration.md +++ b/docs/guide/spa-server-configuration.md @@ -2,8 +2,7 @@ ## Overview -The config format -is [HOCON(Human-Optimized Config Object Notation)](https://github.com/lightbend/config/blob/main/HOCON.md). +The config format toml. The config default path is './config.conf', you can change it by environment `SPA_CONFIG`. @@ -119,9 +118,7 @@ addr = "0.0.0.0" # expire = '30d' # 30day # extension_names = ['icon', 'gif', 'jpg', 'jpeg', 'png', 'js'] -``` - -## Hocon Format Config -**Attention: hocon format would not support in the future.** -the format is like Toml. you could check it from toml format. +# [openTelemetry] +# endpoint = "http://localhost:4317" +``` diff --git a/entity/Cargo.toml b/entity/Cargo.toml new file mode 100644 index 0000000..fbecac7 --- /dev/null +++ b/entity/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "entity" +version = "2.4.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_repr = "0.1" +serde_json = "1" +duration-str = "0.11" +chrono = { version = "0.4", features = ["serde"] } \ No newline at end of file diff --git a/entity/src/lib.rs b/entity/src/lib.rs new file mode 100644 index 0000000..52b1512 --- /dev/null +++ b/entity/src/lib.rs @@ -0,0 +1,2 @@ +pub mod request; +pub mod storage; \ No newline at end of file diff --git a/entity/src/request.rs b/entity/src/request.rs new file mode 100644 index 0000000..3f16f34 --- /dev/null +++ b/entity/src/request.rs @@ -0,0 +1,54 @@ +use serde::{Deserialize, Serialize}; +use crate::storage::UploadingStatus; + +#[derive(Deserialize, Serialize)] +pub struct GetDomainOption { + pub domain: Option, +} + +#[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Default)] +pub enum GetDomainPositionFormat { + #[default] + Path, + Json, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct GetDomainPositionOption { + pub domain: String, + //#[serde(default="crate::admin_server::request::GetDomainPositionFormat::Path")] + #[serde(default)] + pub format: GetDomainPositionFormat, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct UploadFileOption { + pub domain: String, + pub version: u32, + pub path: String, +} + +#[derive(Deserialize, Serialize)] +pub struct DomainWithVersionOption { + pub domain: String, + pub version: u32, +} + +#[derive(Deserialize, Serialize)] +pub struct DomainWithOptVersionOption { + pub domain: String, + pub version: Option, +} + +#[derive(Deserialize, Serialize)] +pub struct UpdateUploadingStatusOption { + pub domain: String, + pub version: u32, + pub status: UploadingStatus, +} + +#[derive(Deserialize, Serialize)] +pub struct DeleteDomainVersionOption { + pub domain: Option, + pub max_reserve: Option, +} diff --git a/entity/src/storage.rs b/entity/src/storage.rs new file mode 100644 index 0000000..f919041 --- /dev/null +++ b/entity/src/storage.rs @@ -0,0 +1,49 @@ +use std::path::PathBuf; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +#[derive(Deserialize, Serialize, Debug)] +pub struct DomainInfo { + pub domain: String, // www.example.com|www.example.com/a/b + pub current_version: Option, + pub versions: Vec, + //pub uploading_version: Vec, //TODO: add uploading_versions + //pub web_path: Vec, // [www.example.com/index.html|www.example.com/a/b/index.html,...] +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ShortMetaData { + pub path: String, + pub md5: String, + pub length: u64, +} + +#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)] +#[repr(u8)] +pub enum UploadingStatus { + Uploading = 0, + Finish = 1, +} + +#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug)] +#[repr(u8)] +pub enum GetDomainPositionStatus { + NewDomain = 0, + NewVersion = 1, + InUploading = 2, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct UploadDomainPosition { + pub path: PathBuf, + pub version: u32, + pub status: GetDomainPositionStatus, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct CertInfo { + pub begin: DateTime, + pub end: DateTime, + pub host: String, +} diff --git a/jsclient/package.json b/jsclient/package.json index bdb5781..f4cd9d5 100644 --- a/jsclient/package.json +++ b/jsclient/package.json @@ -1,7 +1,7 @@ { "name": "spa-client", - "version": "2.3.0", - "description": "js sdk for spa-server", + "version": "2.4.0", + "description": "js client for spa-server", "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { diff --git a/package.json b/package.json index 33eb39f..df33f61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spa-server-doc", - "version": "2.3.0", + "version": "2.4.0", "description": "This is for docs powered by VitePress", "private": true, "type": "module", diff --git a/server/Cargo.toml b/server/Cargo.toml index 7eecfac..9572e51 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spa-server" -version = "2.3.0" +version = "2.4.0" edition = "2021" authors = ["timzaak"] license = "MIT" @@ -15,9 +15,10 @@ include = ["src/**/*", "Cargo.toml"] [[bin]] name = "spa-server" -path = "src/bin/main.rs" +path = "src/main.rs" [dependencies] +entity = { path = "../entity" } # web hyper = { version = "0.14", features = ["stream", "server", "http1", "http2", "tcp"] } # sync with warp tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "io-std", "sync", "time", "tokio-macros"] } # sync with warp @@ -45,15 +46,23 @@ flate2 = "1.0" # tokio trace and log tracing = "0.1.21"# sync with warp tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +tracing-opentelemetry = "0.24" +opentelemetry = { version = "0.23", features = ["trace"] } +opentelemetry_sdk = { version = "0.23", features = ["rt-tokio"] } +opentelemetry-otlp = { version = "0.16", features = ["default"] } +#opentelemetry-resource-detectors = { version = "0.2" } +opentelemetry-semantic-conventions = { version = "0.15" } + # tokio cron delay_timer = "0.11.6" # dashmap is same #config -hocon = "0.9" toml = { version = "0.8.14" } serde = { version = "1.0", features = ["derive"] } serde_repr = "0.1" serde_json = "1" +duration-str = "0.11" base64 = "0.22" #cache @@ -80,4 +89,5 @@ walkdir = "2.5" # time chrono = { version = "0.4", features = ["serde"] } #make if let more easy -if_chain = "1" \ No newline at end of file +if_chain = "1" +tracing-core = "0.1.32" \ No newline at end of file diff --git a/server/examples/try_hocon_with_environment.rs b/server/examples/try_hocon_with_environment.rs deleted file mode 100644 index 49c7e0c..0000000 --- a/server/examples/try_hocon_with_environment.rs +++ /dev/null @@ -1,10 +0,0 @@ -use hocon::HoconLoader; - -fn main() -> anyhow::Result<()> { - let str_config = r" - a:${ABC} - "; - let c = HoconLoader::new().load_str(str_config)?.hocon()?; - println!("{:?}", c["a"].as_string()); - Ok(()) -} diff --git a/server/examples/try_opentelemetry.rs b/server/examples/try_opentelemetry.rs new file mode 100644 index 0000000..de6d6ef --- /dev/null +++ b/server/examples/try_opentelemetry.rs @@ -0,0 +1,319 @@ +/* +use opentelemetry::{global, Key, KeyValue}; +use opentelemetry_otlp::{ExportConfig, Protocol, WithExportConfig}; +use opentelemetry_sdk::{ + metrics::{ + reader::{DefaultAggregationSelector, DefaultTemporalitySelector}, + Aggregation, Instrument, MeterProviderBuilder, PeriodicReader, SdkMeterProvider, Stream, + }, + runtime, + trace::{BatchConfig, RandomIdGenerator, Sampler, Tracer}, + Resource, +}; +use opentelemetry_semantic_conventions::{ + resource::{DEPLOYMENT_ENVIRONMENT, SERVICE_NAME, SERVICE_VERSION}, + SCHEMA_URL, +}; +use std::time::Duration; +use tracing_core::Level; +use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +// Create a Resource that captures information about the entity for which telemetry is recorded. +fn resource() -> Resource { + Resource::from_schema_url( + [ + KeyValue::new(SERVICE_NAME, env!("CARGO_PKG_NAME")), + KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")), + KeyValue::new(DEPLOYMENT_ENVIRONMENT, "develop"), + ], + SCHEMA_URL, + ) +} + +// Construct MeterProvider for MetricsLayer +fn init_meter_provider() -> SdkMeterProvider { + let export_config = ExportConfig { + endpoint: "http://localhost:4317".to_string(), + timeout: Duration::from_secs(3), + protocol: Protocol::Grpc, + }; + let exporter = opentelemetry_otlp::new_exporter() + .tonic() + .with_export_config(export_config) + .build_metrics_exporter( + Box::new(DefaultAggregationSelector::new()), + Box::new(DefaultTemporalitySelector::new()), + ) + .unwrap(); + + let reader = PeriodicReader::builder(exporter, runtime::Tokio) + .with_interval(std::time::Duration::from_secs(30)) + .build(); + + // For debugging in development + let stdout_reader = PeriodicReader::builder( + opentelemetry_stdout::MetricsExporter::default(), + runtime::Tokio, + ) + .build(); + + // Rename foo metrics to foo_named and drop key_2 attribute + let view_foo = |instrument: &Instrument| -> Option { + if instrument.name == "foo" { + Some( + Stream::new() + .name("foo_named") + .allowed_attribute_keys([Key::from("key_1")]), + ) + } else { + None + } + }; + + // Set Custom histogram boundaries for baz metrics + let view_baz = |instrument: &Instrument| -> Option { + if instrument.name == "baz" { + Some( + Stream::new() + .name("baz") + .aggregation(Aggregation::ExplicitBucketHistogram { + boundaries: vec![0.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0], + record_min_max: true, + }), + ) + } else { + None + } + }; + + let meter_provider = MeterProviderBuilder::default() + .with_resource(resource()) + .with_reader(reader) + .with_reader(stdout_reader) + .with_view(view_foo) + .with_view(view_baz) + .build(); + + global::set_meter_provider(meter_provider.clone()); + + meter_provider +} + +// Construct Tracer for OpenTelemetryLayer +fn init_tracer() -> Tracer { + let export_config = ExportConfig { + endpoint: "http://localhost:4317".to_string(), + timeout: Duration::from_secs(3), + protocol: Protocol::Grpc, + }; + opentelemetry_otlp::new_pipeline() + .tracing() + .with_trace_config( + opentelemetry_sdk::trace::Config::default() + // Customize sampling strategy + .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased( + 1.0, + )))) + // If export trace to AWS X-Ray, you can use XrayIdGenerator + .with_id_generator(RandomIdGenerator::default()) + .with_resource(resource()), + ) + .with_batch_config(BatchConfig::default()) + .with_exporter( + opentelemetry_otlp::new_exporter() + .tonic() + .with_export_config(export_config), + ) + .install_batch(runtime::Tokio) + .unwrap() +} + +// Initialize tracing-subscriber and return OtelGuard for opentelemetry-related termination processing +fn init_tracing_subscriber() -> OtelGuard { + let meter_provider = init_meter_provider(); + tracing_subscriber::registry() + .with(tracing_subscriber::filter::LevelFilter::from_level( + Level::DEBUG, + )) + .with(tracing_subscriber::fmt::layer()) + .with(MetricsLayer::new(meter_provider.clone())) + .with(OpenTelemetryLayer::new(init_tracer())) + .init(); + + OtelGuard { meter_provider } +} + +struct OtelGuard { + meter_provider: SdkMeterProvider, +} + +impl Drop for OtelGuard { + fn drop(&mut self) { + if let Err(err) = self.meter_provider.shutdown() { + eprintln!("{err:?}"); + } + opentelemetry::global::shutdown_tracer_provider(); + } +} + +#[tokio::main] +async fn main() { + let _guard = init_tracing_subscriber(); + + foo().await; +} + +#[tracing::instrument] +async fn foo() { + tracing::info!( + monotonic_counter.foo = 1_u64, + key_1 = "bar", + key_2 = 10, + "handle foo", + ); + + tracing::info!(histogram.baz = 10, "histogram example",); +} + + */ +/* +use opentelemetry::{global, Key, KeyValue}; +use opentelemetry_otlp::{ExportConfig, Protocol, TonicExporterBuilder, WithExportConfig}; +use opentelemetry_resource_detectors::{OsResourceDetector, ProcessResourceDetector}; +use opentelemetry_sdk::metrics::reader::DefaultAggregationSelector; +use opentelemetry_sdk::resource::{ + ResourceDetector, SdkProvidedResourceDetector, TelemetryResourceDetector, +}; +use opentelemetry_sdk::trace::config; +use opentelemetry_sdk::{Resource, runtime}; +use opentelemetry_semantic_conventions::resource; +use std::time::Duration; +use opentelemetry_sdk::metrics::{Aggregation, Instrument, MeterProviderBuilder, PeriodicReader, SdkMeterProvider, Stream}; +use opentelemetry_stdout::MetricsExporter; +use tracing::{debug, error, info, instrument, warn, Level}; +use tracing_opentelemetry::OpenTelemetryLayer; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::EnvFilter; + +fn resource() -> Resource { + let os_resource = OsResourceDetector.detect(Duration::from_secs(0)); + let process_resource = ProcessResourceDetector.detect(Duration::from_secs(0)); + let telemetry_resource = TelemetryResourceDetector.detect(Duration::from_secs(0)); + let sdk_resource = SdkProvidedResourceDetector.detect(Duration::from_secs(0)); + + let provided = Resource::new(vec![ + KeyValue::new(resource::SERVICE_NAME, "spa-server"), + KeyValue::new(resource::SERVICE_VERSION, env!("CARGO_PKG_VERSION")), + ]); + + sdk_resource + .merge(&provided) + .merge(&telemetry_resource) + .merge(&os_resource) + .merge(&process_resource) +} +fn otlp_exporter() -> TonicExporterBuilder { + let export_config = ExportConfig { + endpoint: "http://localhost:4317".to_string(), + timeout: Duration::from_secs(3), + protocol: Protocol::Grpc, + }; + opentelemetry_otlp::new_exporter().tonic() + .with_export_config(export_config) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let tracer = opentelemetry_otlp::new_pipeline() + .tracing() + .with_exporter(otlp_exporter()) + .with_trace_config(config().with_resource(resource())) + .install_batch(runtime::Tokio)?; + + + + + // does not work for metrics + // let metrics = opentelemetry_otlp::new_pipeline() + // .metrics(runtime::Tokio) + // .with_exporter(otlp_exporter()) + // .with_period(Duration::from_secs(3)) + // .with_resource(resource()) + // .with_aggregation_selector(DefaultAggregationSelector::new()) + // .build()?; + + + + let stdout_reader = PeriodicReader::builder( + MetricsExporter::default(), + runtime::Tokio, + ) + .build(); + + let metrics = { + let reader = + PeriodicReader::builder(MetricsExporter::default(), runtime::Tokio) + .with_interval(Duration::from_secs(3)) + .build(); + SdkMeterProvider::builder() + .with_resource(resource()) + .with_reader(reader) + .with_reader(stdout_reader) + .build() + }; + + + global::set_meter_provider(metrics.clone()); + + + + + let filter = EnvFilter::builder() + .with_default_directive(Level::INFO.into()) + .from_env_lossy(); + + tracing_subscriber::registry() + .with(filter) + .with(tracing_subscriber::fmt::layer().compact()) // stdout_layer + .with(tracing_opentelemetry::MetricsLayer::new(metrics)) + .with(OpenTelemetryLayer::new(tracer)) // traces_layer + .init(); + + for _ in 1..10 { + test_instrument2().await; + } + + let _ = tokio::time::sleep(Duration::from_secs(2)).await; + println!("finish"); + Ok(()) +} + +#[instrument] +async fn test_instrument2() { + tokio::time::sleep(Duration::from_secs(1)).await; + info!(monotonic_counter.baz = 1,"info log"); + +} + +#[instrument] +async fn test_instrument() { + debug!("test debug"); + info!("test info"); + warn!("test warn"); + error!("test error"); + tokio::time::sleep(Duration::from_secs(1)).await; + info!( + monotonic_counter.foo = 1_u64, + key_1 = "bar", + key_2 = 10, + "handle foo", + ); + + info!(histogram.baz = 10, "histogram example",); +} + +*/ + +fn main() {} diff --git a/server/src/acme.rs b/server/src/acme.rs index 1130ad8..6ec4d78 100644 --- a/server/src/acme.rs +++ b/server/src/acme.rs @@ -27,7 +27,8 @@ use tracing::{debug, error, info, warn}; use walkdir::WalkDir; use crate::config::{get_host_path_from_domain, ACMEConfig, ACMEType, Config}; -use crate::domain_storage::{CertInfo, DomainStorage}; +use entity::storage::{CertInfo}; +use crate::domain_storage::DomainStorage; use crate::tls::load_ssl_file; const ACME_DIR: &str = "acme"; diff --git a/server/src/admin_server.rs b/server/src/admin_server.rs index 642f6a0..9ba2271 100644 --- a/server/src/admin_server.rs +++ b/server/src/admin_server.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; use crate::acme::ACMEManager; -use crate::admin_server::request::{ +use entity::request::{ DeleteDomainVersionOption, DomainWithOptVersionOption, DomainWithVersionOption, GetDomainOption, GetDomainPositionOption, UpdateUploadingStatusOption, UploadFileOption, }; @@ -225,12 +225,12 @@ impl AdminServer { pub mod service { use std::collections::HashMap; use crate::acme::ACMEManager; - use crate::admin_server::request::{ + use entity::request::{ DeleteDomainVersionOption, DomainWithOptVersionOption, DomainWithVersionOption, GetDomainOption, GetDomainPositionFormat, GetDomainPositionOption, UpdateUploadingStatusOption, UploadFileOption, }; - use crate::domain_storage::{DomainInfo, DomainStorage, URI_REGEX}; + use crate::domain_storage::{DomainStorage, URI_REGEX}; use crate::{AdminConfig, HotReloadManager}; use anyhow::{anyhow, Context}; use bytes::Buf; @@ -239,6 +239,7 @@ pub mod service { use std::convert::Infallible; use std::sync::Arc; use tracing::error; + use entity::storage::DomainInfo; use warp::http::StatusCode; use warp::multipart::FormData; use warp::reply::Response; @@ -507,63 +508,6 @@ fn bad_resp(text:String) -> Response { resp } -pub mod request { - use crate::domain_storage::UploadingStatus; - use serde::{Deserialize, Serialize}; - - #[derive(Deserialize, Serialize)] - pub struct GetDomainOption { - pub domain: Option, - } - - #[derive(Deserialize, Serialize, Debug, Eq, PartialEq, Default)] - pub enum GetDomainPositionFormat { - #[default] - Path, - Json, - } - - #[derive(Deserialize, Serialize, Debug)] - pub struct GetDomainPositionOption { - pub domain: String, - //#[serde(default="crate::admin_server::request::GetDomainPositionFormat::Path")] - #[serde(default)] - pub format: GetDomainPositionFormat, - } - - #[derive(Deserialize, Serialize, Debug)] - pub struct UploadFileOption { - pub domain: String, - pub version: u32, - pub path: String, - } - - #[derive(Deserialize, Serialize)] - pub struct DomainWithVersionOption { - pub domain: String, - pub version: u32, - } - - #[derive(Deserialize, Serialize)] - pub struct DomainWithOptVersionOption { - pub domain: String, - pub version: Option, - } - - #[derive(Deserialize, Serialize)] - pub struct UpdateUploadingStatusOption { - pub domain: String, - pub version: u32, - pub status: UploadingStatus, - } - - #[derive(Deserialize, Serialize)] - pub struct DeleteDomainVersionOption { - pub domain: Option, - pub max_reserve: Option, - } -} - fn build_async_job( domain_storage: Arc, max_reserve: Option, diff --git a/server/src/bin/main.rs b/server/src/bin/main.rs deleted file mode 100644 index d2dba15..0000000 --- a/server/src/bin/main.rs +++ /dev/null @@ -1,15 +0,0 @@ -use anyhow::Result; -use tracing::Level; -use tracing_subscriber::EnvFilter; - -#[tokio::main] -async fn main() -> Result<()> { - tracing_subscriber::fmt() - .with_env_filter( - EnvFilter::builder() - .with_default_directive(Level::INFO.into()) - .from_env_lossy(), - ) - .init(); - spa_server::run_server().await -} diff --git a/server/src/config.rs b/server/src/config.rs index 1ded1e8..92ca277 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -1,5 +1,5 @@ use anyhow::{bail, Context}; -use hocon::de::wrappers::Serde; +use duration_str::deserialize_duration; use serde::Deserialize; use small_acme::LetsEncrypt; use std::time::Duration; @@ -20,6 +20,12 @@ pub struct Config { pub cache: CacheConfig, #[serde(default)] pub domains: Vec, + pub open_telemetry: Option, +} + +#[derive(Deserialize, Debug, Clone, PartialEq)] +pub struct OpenTelemetry { + pub endpoint: String, } //TODO: create config with lots of default value @@ -27,11 +33,7 @@ impl Config { pub fn load() -> anyhow::Result { let config_path = env::var("SPA_CONFIG").unwrap_or(CONFIG_PATH.to_string()); - let config = if config_path.ends_with(".conf") { - Self::load_hocon(&config_path) - } else { - Self::load_toml(&config_path) - }?; + let config = Self::load_toml(&config_path)?; if config.http.is_none() && config.https.is_none() { bail!("should set http or https server config") } @@ -68,15 +70,6 @@ impl Config { Ok(config) } - fn load_hocon(path: &str) -> anyhow::Result { - warn!("config format: conf would not support in future, please use toml"); - let load_file = hocon::HoconLoader::new() - .load_file(path) - .with_context(|| format!("can not read config file: {path}"))?; - load_file - .resolve::() - .with_context(|| "parse config file error") - } fn load_toml(path: &str) -> anyhow::Result { let config = fs::read_to_string(path) .with_context(|| format!("can not read config file: {path}"))?; @@ -198,7 +191,7 @@ fn default_max_size() -> u64 { #[derive(Deserialize, Debug, Clone, PartialEq)] pub struct ClientCacheItem { - #[serde(deserialize_with = "Serde::::with")] + #[serde(deserialize_with = "deserialize_duration")] pub expire: Duration, pub extension_names: Vec, } @@ -235,7 +228,6 @@ pub fn get_host_path_from_domain(domain: &str) -> (&str, &str) { #[cfg(test)] mod test { - use crate::config::Config; use std::env; use std::path::PathBuf; @@ -244,20 +236,4 @@ mod test { let path = path.parent().unwrap(); path.to_owned() } - #[test] - fn test_hocon_toml_is_same() { - let path = get_project_path(); - env::set_var( - "SPA_CONFIG", - path.join("config.release.conf").display().to_string(), - ); - - let hocon_config = Config::load().unwrap(); - env::set_var( - "SPA_CONFIG", - path.join("config.release.toml").display().to_string(), - ); - let toml_config = Config::load().unwrap(); - assert_eq!(hocon_config, toml_config); - } } diff --git a/server/src/domain_storage.rs b/server/src/domain_storage.rs index ce443ad..5034a47 100644 --- a/server/src/domain_storage.rs +++ b/server/src/domain_storage.rs @@ -2,13 +2,10 @@ use crate::acme::ACMEManager; use crate::config::get_host_path_from_domain; use crate::file_cache::{CacheItem, FileCache}; use anyhow::{anyhow, bail, Context}; -use chrono::{DateTime, Utc}; use dashmap::DashMap; use lazy_static::lazy_static; use md5::{Digest, Md5}; use regex::Regex; -use serde::{Deserialize, Serialize}; -use serde_repr::{Deserialize_repr, Serialize_repr}; use std::fs; use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; @@ -17,6 +14,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use tracing::{debug, info}; use walkdir::{DirEntry, WalkDir}; +use entity::storage::{DomainInfo, GetDomainPositionStatus, ShortMetaData, UploadDomainPosition, UploadingStatus}; use warp::fs::sanitize_path; pub(crate) const URI_REGEX_STR: &str = @@ -729,7 +727,7 @@ pub fn md5_file(path: impl AsRef, byte_buffer: &mut Vec) -> Option, delay_timer: DelayTimer, - host_alias: Arc> + host_alias: Arc>, ) -> anyhow::Result<()> { let admin_server = AdminServer::new( config, @@ -95,7 +95,8 @@ pub async fn reload_server( } let challenge_path = acme_manager.challenge_dir.clone(); let _sender = acme_manager.sender.clone(); - let tls_server_config = load_ssl_server_config(&config, acme_manager, server.get_host_alias())?; + let tls_server_config = + load_ssl_server_config(&config, acme_manager, server.get_host_alias())?; tokio::task::spawn(async move { join( server @@ -159,8 +160,8 @@ pub async fn run_server_with_config(config: Config) -> anyhow::Result<()> { let host_alias = server.get_host_alias(); - - let tls_server_config = load_ssl_server_config(&config, acme_manager.clone(), host_alias.clone())?; + let tls_server_config = + load_ssl_server_config(&config, acme_manager.clone(), host_alias.clone())?; let _ = tokio::join!( server .init_https_server(https_rx, tls_server_config) @@ -201,7 +202,8 @@ pub async fn run_server_with_config(config: Config) -> anyhow::Result<()> { &delay_timer, )?); let challenge_path = acme_manager.challenge_dir.clone(); - let tls_server_config = load_ssl_server_config(&config, acme_manager.clone(), server.get_host_alias())?; + let tls_server_config = + load_ssl_server_config(&config, acme_manager.clone(), server.get_host_alias())?; let _ = tokio::join!( server .init_https_server(None, tls_server_config) diff --git a/server/src/main.rs b/server/src/main.rs new file mode 100644 index 0000000..0e68a5e --- /dev/null +++ b/server/src/main.rs @@ -0,0 +1,27 @@ +mod otlp; + +use tracing_core::Level; +use tracing_subscriber::EnvFilter; +use spa_server::config::Config; + + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let config = Config::load()?; + let open_telemetry = &config.open_telemetry; + if let Some(open_telemetry) = open_telemetry.as_ref() { + otlp::init_otlp(open_telemetry.endpoint.clone())?; + } else { + tracing_subscriber::fmt() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(Level::INFO.into()) + .from_env_lossy(), + ) + .init(); + } + + + tracing::debug!("config load:{:?}", &config); + spa_server::run_server_with_config(config).await +} diff --git a/server/src/otlp.rs b/server/src/otlp.rs new file mode 100644 index 0000000..0b13ef3 --- /dev/null +++ b/server/src/otlp.rs @@ -0,0 +1,38 @@ +use opentelemetry::KeyValue; +use opentelemetry_otlp::{ExportConfig, WithExportConfig}; +use opentelemetry_sdk::{Resource, runtime}; +use opentelemetry_sdk::trace::config; +use opentelemetry_semantic_conventions::resource::SERVICE_NAME; +use opentelemetry_semantic_conventions::trace::SERVICE_VERSION; +use tracing_core::Level; +use tracing_opentelemetry::OpenTelemetryLayer; +use tracing_subscriber::EnvFilter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + +pub fn init_otlp(endpoint:String) -> anyhow::Result<()>{ + let resource = Resource::new(vec![ + KeyValue::new(SERVICE_NAME, "spa-server"), + KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")) + ]); + let mut export_config = ExportConfig::default(); + export_config.endpoint = endpoint; + let exporter = opentelemetry_otlp::new_exporter().tonic() + .with_export_config(export_config); + + let tracer = opentelemetry_otlp::new_pipeline() + .tracing().with_exporter(exporter) + .with_trace_config(config().with_resource(resource)) + .install_batch(runtime::Tokio)?; + + let filter = EnvFilter::builder() + .with_default_directive(Level::INFO.into()) + .from_env_lossy(); + tracing_subscriber::registry() + .with(filter) + .with(tracing_subscriber::fmt::layer().compact()) + .with(OpenTelemetryLayer::new(tracer)) + .init(); + Ok(()) + +} \ No newline at end of file diff --git a/server/src/service.rs b/server/src/service.rs index 9d35db3..c2de294 100644 --- a/server/src/service.rs +++ b/server/src/service.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; use std::convert::Infallible; use std::str::FromStr; use std::sync::Arc; -use tracing::warn; +use tracing::{instrument, warn}; use warp::fs::{ArcPath, Conditionals}; use warp::http::Uri; use warp::Reply; @@ -55,6 +55,7 @@ fn alias_redirect(uri: &Uri, https: bool, host:&str, external_port:u16) -> warp: } // static file reply +#[instrument(skip(uri,host,domain_storage,origin_opt))] async fn file_resp(req: &Request,uri:&Uri, host:&str, domain_storage: Arc, origin_opt: Option) -> Result, Infallible> { let path = uri.path(); let mut resp = match get_cache_file(path, host, domain_storage.clone()).await { @@ -109,7 +110,7 @@ fn get_authority(req:&Request) -> Option { }) } - +#[instrument(skip(service_config, domain_storage,challenge_path, external_port))] pub async fn create_http_service( req: Request, service_config: Arc, @@ -174,7 +175,7 @@ pub async fn create_http_service( } - +#[instrument(skip(service_config, domain_storage, external_port))] pub async fn create_https_service( req: Request, service_config: Arc, diff --git a/server/src/static_file_filter.rs b/server/src/static_file_filter.rs index 2a26c70..f9770fe 100644 --- a/server/src/static_file_filter.rs +++ b/server/src/static_file_filter.rs @@ -14,7 +14,7 @@ use std::path::Path; use std::sync::Arc; use tokio::fs::File; use tokio::io; -use tracing::debug; +use tracing::{debug, trace}; use warp::fs::{file_stream, optimal_buf_size, Cond, Conditionals}; use warp::http::{Response, StatusCode}; @@ -202,15 +202,15 @@ pub async fn cache_or_file_reply( //false,false => cache without content-encoding //false, true => file if !client_accept_gzip && compressed { - debug!("{} hit disk", key); + trace!("{} hit disk", key); Ok(file_reply(&item, path.as_ref(), range, modified).await) } else { let mut resp = cache_reply(item.as_ref(), bytes, range, modified); if client_accept_gzip && compressed { - debug!("{} hit cache, compressed", key); + trace!("{} hit cache, compressed", key); resp.headers_mut().typed_insert(ContentEncoding::gzip()); } else { - debug!("{} hit cache", key); + trace!("{} hit cache", key); } Ok(resp) } @@ -255,7 +255,7 @@ pub async fn get_cache_file( ) -> Option<(String, Arc)> { let _key = sanitize_path(tail)?; let key = _key[1..].to_owned(); - debug!("get file: {host}, tail:{_key}, fixed: {key}"); + //debug!("get file: {host}, tail:{_key}, fixed: {key}"); if let Some(cache_item) = domain_storage.get_file(host, &key) { Some((key, cache_item)) } else { diff --git a/server/tests/config_test.rs b/server/tests/config_test.rs deleted file mode 100644 index 27b7fad..0000000 --- a/server/tests/config_test.rs +++ /dev/null @@ -1,13 +0,0 @@ - -use spa_server::config::Config; -use std::env; - -pub fn get_test1_config() -> Config { - env::set_var("SPA_CONFIG", "tests/data/test1.conf"); - Config::load().unwrap() -} -#[test] -fn test1_config() { - let config = get_test1_config(); - assert!(!config.domains.is_empty()); -} diff --git a/server/tests/data/test1.conf b/server/tests/data/test1.conf deleted file mode 100644 index b57d8d9..0000000 --- a/server/tests/data/test1.conf +++ /dev/null @@ -1,28 +0,0 @@ -# http bind, if set port <= 0, will disable http server(need set https config) -port = 80 -addr = "0.0.0.0" - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "/data" - -https { - ssl: { - private: "tests/data/noti.link.key", - public: "tests/data/noti.link.pem" - } - port: 443, - addr: "127.0.0.1" -} - -domains = [{ - domain: "www.example.com", - // optional - cors: true, - https: { - ssl: { - private: "tests/data/www.example.com.key" - public: "tests/data/www.example.com.pem" - } - http_redirect_to_https: 443 - } -}] \ No newline at end of file diff --git a/test/config.test.acme.conf b/test/config.test.acme.conf deleted file mode 100644 index 924d9a0..0000000 --- a/test/config.test.acme.conf +++ /dev/null @@ -1,85 +0,0 @@ -# http bind, if set port <= 0, will disable http server(need set https config) -http { - port = 8080 - addr = "0.0.0.0" -} - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "./tests/data/web" - -# enable cors, default is false, its implementation is simple now. -# Access-Control-Allow-Origin: $ORIGIN -# Access-Control-Allow-Methods: OPTION,GET,HEAD -# Access-Control-Max-Age: 3600 -cors = true - -# https config, optional -https { - # acme config, it doest not support run with https.ssl config. - acme { - emails = ["mailto:zsy.evan@gmail.com"] - # directory to store account and certificate - # optional, default is ${file_dir}/acme - #dir = "/data/acme" - type = ci - ci_ca_path = "./tests/data/pebble/certs/pebble.minica.pem" - } - - # https bind address - port = 8443 - addr = "0.0.0.0" - - # if set port, http server(80) will send client - # status code:301(Moved Permanently) to tell client redirect to https - http_redirect_to_https = 8443 -} - - - -# default cache config -cache { - # if file size > max_size, it will not be cached. default is (10MB). - max_size = 20 - - # http header Cache-Control config, - # optional, if not set, won't sender this header to client - client_cache = [{ - expire = 30d - extension_names = [icon,gif,jpg,jpeg,png,js] - }, { - // set 0, would set Cache-Control: no-cache - expire = 0 - extension_names = [html] - }] - - # gzip compression for js/json/icon/json, default is false, - # only support gzip algo, and only compress cached files, - # be careful to set it true - compression = true - -} - -# admin server config -# admin server don't support hot reload. the config should not change. -# optional, and it's disabled by default. -# if you use spa-client to upload files, control version. Need to open it -admin_config { - # bind host - port = 9000 - addr = "127.0.0.1" - - # this is used to check client request - # put it in http header, Authorization: Bearer $token - token = "token" - -// # max file size allowed to be uploaded, -// max_upload_size = 31457280 - -// # delete deprecated version by cron -// deprecated_version_delete { -// # default value: every day at 3am. -// cron: "0 0 3 * * *", -// # default value is 2 -// max_preserve: 2, -// } -} diff --git a/test/config.test.conf b/test/config.test.conf deleted file mode 100644 index 6dd1f4b..0000000 --- a/test/config.test.conf +++ /dev/null @@ -1,103 +0,0 @@ -# http bind, if set port <= 0, will disable http server(need set https config) -http { - port = 8080 - addr = "0.0.0.0" -} - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "./tests/data/web" - -# enable cors, default is false, its implementation is simple now. -# Access-Control-Allow-Origin: $ORIGIN -# Access-Control-Allow-Methods: OPTION,GET,HEAD -# Access-Control-Max-Age: 3600 -cors = true - -# https config, optional -//https { -// # default value for https ssl -// ssl { -// # private ssl key -// private = "private.key path", -// # public ssl cert -// public = "public.cert path" -// } - -// # https bind address -// port = 443 -// addr = "0.0.0.0" - -// # if set port, http server(80) will send client -// # status code:301(Moved Permanently) to tell client redirect to https -// http_redirect_to_https = 443 -//} - - - -# default cache config -cache { - # if file size > max_size, it will not be cached. default is (10MB). - max_size = 10MB - - # http header Cache-Control config, - # optional, if not set, won't sender this header to client - client_cache = [{ - expire = 30d - extension_names = [icon,gif,jpg,jpeg,png,js] - }, { - // set 0, would set Cache-Control: no-cache - expire = 0 - extension_names = [html] - }] - -// # gzip compression for js/json/icon/json, default is false, -// # only support gzip algo, and only compress cached files, -// # be careful to set it true - compression = true - -} - -# admin server config -# admin server don't support hot reload. the config should not change. -# optional, and it's disabled by default. -# if you use spa-client to upload files, control version. Need to open it -admin_config { -//# bind host - port = 9000 - addr = "127.0.0.1" - -// # this is used to check client request -// # put it in http header, Authorization: Bearer $token - token = "token" - -// # max file size allowed to be uploaded, -// max_upload_size = 31457280 - -// # delete deprecated version by cron - deprecated_version_delete { - # default value: every day at 3am. - cron: "0 0 3 * * *", - # default value is 2 - max_preserve: 10, - } -} - - -# optional, domains specfic config, it will use the default config if not set -//domains = [{ -// # domain name -// domain: "www.example.com", -// // optional, same with cache config, if not set, will use default cache config. -// cache: { -// client_cache:${cache.client_cache} -// max_size: ${cache.max_size} -// client_cache = ${cache.client_cache} -// }, -// # cors -// cors: ${cors}, -// # domain https config, if not set, will use default https config. -// https: { -// ssl: ${https.ssl} -// http_redirect_to_https: ${https.http_redirect_to_https} -// } -//}] \ No newline at end of file diff --git a/test/config.test.https.conf b/test/config.test.https.conf deleted file mode 100644 index 8860e85..0000000 --- a/test/config.test.https.conf +++ /dev/null @@ -1,99 +0,0 @@ -# http bind, if set port <= 0, will disable http server(need set https config) -http { - port = 8080 - addr = "0.0.0.0" -} - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "./tests/data/web" - -# enable cors, default is false, its implementation is simple now. -# Access-Control-Allow-Origin: $ORIGIN -# Access-Control-Allow-Methods: OPTION,GET,HEAD -# Access-Control-Max-Age: 3600 -cors = true - -# https config, optional -https { - // # default value for https ssl - ssl { - # private ssl key - private = "./tests/data/cert/local.fornetcode.com.key", - # public ssl cert - public = "./tests/data/cert/local.fornetcode.com.pem" - } - - // # https bind address - port = 443 - addr = "0.0.0.0" - - # if set port, http server(80) will send client - # status code:301(Moved Permanently) to tell client redirect to https - http_redirect_to_https = 443 -} - - - -# default cache config -cache { - # if file size > max_size, it will not be cached. default is (10MB). - max_size = 20 - - # http header Cache-Control config, - # optional, if not set, won't sender this header to client - client_cache = [{ - expire = 30d - extension_names = [icon,gif,jpg,jpeg,png,js] - }, { - // set 0, would set Cache-Control: no-cache - expire = 0 - extension_names = [html] - }] - - # gzip compression for js/json/icon/json, default is false, - # only support gzip algo, and only compress cached files, - # be careful to set it true - compression = true - -} - -# admin server config -# admin server don't support hot reload. the config should not change. -# optional, and it's disabled by default. -# if you use spa-client to upload files, control version. Need to open it -admin_config { - # bind host - port = 9000 - addr = "127.0.0.1" - - # this is used to check client request - # put it in http header, Authorization: Bearer $token - token = "token" - - // # max file size allowed to be uploaded, - // max_upload_size = 31457280 - - // # delete deprecated version by cron - // deprecated_version_delete { - // # default value: every day at 3am. - // cron: "0 0 3 * * *", - // # default value is 2 - // max_preserve: 2, - // } -} - - -# optional, domains specfic config, it will use the default config if not set -domains = [{ - # domain name - domain: "local.fornetcode.com", - # optional, same with cache config, if not set, will use default cache config. - // cache: { - // client_cache:${cache.client_cache} - // max_size: ${cache.max_size} - // client_cache = ${cache.client_cache} - // }, - # cors - // cors: true, - // # domain https config, if not set, will use default https config. -}] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index fb6d502..a45ec95 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -14,10 +14,15 @@ name = "acme_test" spa-client = { path = "../client" } spa-server = { path = "../server" } tokio = { version = "1", features = ["macros", "rt-multi-thread", "io-std", "sync", "time", "tokio-macros", "test-util"] } # sync with spa-server -reqwest = { version = "0.11.27", features = ["json", "multipart", "stream", "rustls-tls"] } # from spa-client +reqwest = { version = "0.12", features = ["json", "multipart", "stream", "rustls-tls"] } # from spa-client tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } anyhow = "1.0.56" console-subscriber = "0.3" +opentelemetry-stdout = { version = "0.4", features = ["trace"] } +tracing-opentelemetry = "0.24" +opentelemetry = { version = "0.23", features = ["trace", "metrics"] } +opentelemetry_sdk = { version = "0.23", features = ["rt-tokio", "metrics"] } + #rustls = "0.21.12" # from reqwest #rustls-pemfile = "2.1.2" diff --git a/tests/data/client_config.conf b/tests/data/client_config.conf deleted file mode 100644 index 82be235..0000000 --- a/tests/data/client_config.conf +++ /dev/null @@ -1,10 +0,0 @@ -# admin server, if set --config-dir -server { - address: "http://127.0.0.1:9000" - auth_token: "token" -} - -upload { - # default value is:3 - parallel: 3 -} \ No newline at end of file diff --git a/tests/data/client_config.toml b/tests/data/client_config.toml new file mode 100644 index 0000000..8882f32 --- /dev/null +++ b/tests/data/client_config.toml @@ -0,0 +1,6 @@ +[server] +address = "http://127.0.0.1:9000" +auth_token = "token" +# [upload] +## default value is:3 +# parallel = 3 \ No newline at end of file diff --git a/tests/data/server_config.conf b/tests/data/server_config.conf deleted file mode 100644 index 2eacebb..0000000 --- a/tests/data/server_config.conf +++ /dev/null @@ -1,64 +0,0 @@ -# http bind, if set port <= 0, will disable http server(need set https config) -http { - port = 8080 - addr = "0.0.0.0" -} - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "./data/web" - -# enable cors, default is false, its implementation is simple now. -# Access-Control-Allow-Origin: $ORIGIN -# Access-Control-Allow-Methods: OPTION,GET,HEAD -# Access-Control-Max-Age: 3600 -cors = true - - -# default cache config -cache { - # if file size > max_size, it will not be cached. default is (10MB). - max_size = 10MB - - # http header Cache-Control config, - # optional, if not set, won't sender this header to client - client_cache = [{ - expire = 30d - extension_names = [icon,gif,jpg,jpeg,png,js] - }, { - // set 0, would set Cache-Control: no-cache - expire = 0 - extension_names = [html] - }] - -// # gzip compression for js/json/icon/json, default is false, -// # only support gzip algo, and only compress cached files, -// # be careful to set it true - compression = true - -} - -# admin server config -# admin server don't support hot reload. the config should not change. -# optional, and it's disabled by default. -# if you use spa-client to upload files, control version. Need to open it -admin_config { -//# bind host - port = 9000 - addr = "127.0.0.1" - -// # this is used to check client request -// # put it in http header, Authorization: Bearer $token - token = "token" - -// # max file size allowed to be uploaded, -// # default is 30MB(30*1000*1000) -// max_upload_size = 31457280 - -// # delete deprecated version by cron - deprecated_version_delete { - # default value: every day at 3am. - cron: "0 0 3 * * *", - # default value is 2 - max_preserve: 2, - } -} diff --git a/tests/data/server_config.toml b/tests/data/server_config.toml new file mode 100644 index 0000000..bde88d7 --- /dev/null +++ b/tests/data/server_config.toml @@ -0,0 +1,28 @@ +file_dir = "./data/web" +cors = true +[http] +port = 8080 +addr = "0.0.0.0" + +[cache] +max_size = 10_000_000 +compression = true +[[cache.client_cache]] +expire = '30d' +extension_names = ['icon','gif','jpg','jpeg','png','js'] + +[[cache.client_cache]] +expire = '0' +extension_names = ['html'] + + + +[admin_config] +# bind host +port = 9000 +addr = "127.0.0.1" + +# this is used to check client request +# put it in http header, Authorization: Bearer $token +token = "token" +deprecated_version_delete = { cron= "0 0 3 * * *", max_preserve = 2 } \ No newline at end of file diff --git a/tests/data/server_config_acme.conf b/tests/data/server_config_acme.conf deleted file mode 100644 index 5496fae..0000000 --- a/tests/data/server_config_acme.conf +++ /dev/null @@ -1,84 +0,0 @@ -# http bind, if set port <= 0, will disable http server(need set https config) -http { - port = 8080 - addr = "0.0.0.0" -} - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "./data/web" - -# enable cors, default is false, its implementation is simple now. -# Access-Control-Allow-Origin: $ORIGIN -# Access-Control-Allow-Methods: OPTION,GET,HEAD -# Access-Control-Max-Age: 3600 -cors = true - -# https config, optional -https { - - # acme config, it doest not support run with https.ssl config. - acme { - emails = ["mailto:zsy.evan@gmail.com"] - # directory to store account and certificate - # optional, default is ${file_dir}/acme - #dir = "/data/acme" - # optional ,default is false - type = "ci" - ci_ca_path = "./data/pebble/certs/pebble.minica.pem" - } - // # https bind address - port = 8443 - addr = "0.0.0.0" - external_port = 8443 -} - - - -# default cache config -cache { - # if file size > max_size, it will not be cached. default is (10MB). - max_size = 20 - - # http header Cache-Control config, - # optional, if not set, won't sender this header to client - client_cache = [{ - expire = 30d - extension_names = [icon,gif,jpg,jpeg,png,js] - }, { - // set 0, would set Cache-Control: no-cache - expire = 0 - extension_names = [html] - }] - - # gzip compression for js/json/icon/json, default is false, - # only support gzip algo, and only compress cached files, - # be careful to set it true - compression = true - -} - -# admin server config -# admin server don't support hot reload. the config should not change. -# optional, and it's disabled by default. -# if you use spa-client to upload files, control version. Need to open it -admin_config { - # bind host - port = 9000 - addr = "127.0.0.1" - - # this is used to check client request - # put it in http header, Authorization: Bearer $token - token = "token" - -// # max file size allowed to be uploaded, - -// max_upload_size = 31457280 - -// # delete deprecated version by cron -// deprecated_version_delete { -// # default value: every day at 3am. -// cron: "0 0 3 * * *", -// # default value is 2 -// max_preserve: 2, -// } -} diff --git a/tests/data/server_config_acme.toml b/tests/data/server_config_acme.toml new file mode 100644 index 0000000..7dbbfb8 --- /dev/null +++ b/tests/data/server_config_acme.toml @@ -0,0 +1,54 @@ +file_dir = "./data/web" +cors = true + +# http bind, if set port <= 0, will disable http server(need set https config) +[http] +port = 8080 +addr = "0.0.0.0" + +# https config, optional +[https] +port = 8443 +addr = "0.0.0.0" +external_port = 8443 + +[https.acme] +emails = ["mailto:zsy.evan@gmail.com"] +# directory to store account and certificate +# optional, default is ${file_dir}/acme +#dir = "/data/acme" +# optional ,default is false +type = "ci" +ci_ca_path = "./data/pebble/certs/pebble.minica.pem" + +[cache] + +# if file size > max_size, it will not be cached. default is (10MB). +max_size = 20 +compression = true + +[[cache.client_cache]] +expire = '30d' +extension_names = ['icon', 'gif', 'jpg', 'jpeg', 'png', 'js'] + +[[cache.client_cache]] +expire = '0' +extension_names = ['html'] + + +# admin server config +# admin server don't support hot reload. the config should not change. +# optional, and it's disabled by default. +# if you use spa-client to upload files, control version. Need to open it +[admin_config] +# bind host +port = 9000 +addr = "127.0.0.1" + +# this is used to check client request +# put it in http header, Authorization: Bearer $token +token = "token" + +[[domains]] +domain = "local.fornetcode.com" +alias = ["local2.fornetcode.com"] diff --git a/tests/data/server_config_https.conf b/tests/data/server_config_https.conf deleted file mode 100644 index 48b779f..0000000 --- a/tests/data/server_config_https.conf +++ /dev/null @@ -1,76 +0,0 @@ -# http bind, if set port <= 0, will disable http server(need set https config) -http { - port = 8080 - addr = "0.0.0.0" -} - -# directory to store static web files. if you use docker, please mount a persistence volume for it. -file_dir = "./data/web" - -# enable cors, default is false, its implementation is simple now. -# Access-Control-Allow-Origin: $ORIGIN -# Access-Control-Allow-Methods: OPTION,GET,HEAD -# Access-Control-Max-Age: 3600 -cors = true - -# https config, optional -https { - // # default value for https ssl - ssl { - # private ssl key - private = "./data/cert/local.fornetcode.com.key", - # public ssl cert - public = "./data/cert/local.fornetcode.com.pem" - } - -// # https bind address - port = 8443 - addr = "0.0.0.0" - external_port = 8443 -} - - - -# default cache config -cache { - # if file size > max_size, it will not be cached. default is (10MB). - max_size = 20 - - # http header Cache-Control config, - # optional, if not set, won't sender this header to client - client_cache = [{ - expire = 30d - extension_names = [icon,gif,jpg,jpeg,png,js] - }, { - // set 0, would set Cache-Control: no-cache - expire = 0 - extension_names = [html] - }] - - # gzip compression for js/json/icon/json, default is false, - # only support gzip algo, and only compress cached files, - # be careful to set it true - compression = true - -} - -# admin server config -# admin server don't support hot reload. the config should not change. -# optional, and it's disabled by default. -# if you use spa-client to upload files, control version. Need to open it -admin_config { - # bind host - port = 9000 - addr = "127.0.0.1" - - # this is used to check client request - # put it in http header, Authorization: Bearer $token - token = "token" -} - - -# optional, domains specfic config, it will use the default config if not set -domains = [{ - # domain name - domain: "local.fornetcode.com", -}] diff --git a/tests/data/server_config_https.toml b/tests/data/server_config_https.toml new file mode 100644 index 0000000..3d9a914 --- /dev/null +++ b/tests/data/server_config_https.toml @@ -0,0 +1,46 @@ +file_dir = "./data/web" +cors = true + +# http bind, if set port <= 0, will disable http server(need set https config) +[http] +port = 8080 +addr = "0.0.0.0" + +# https config, optional +[https] +port = 8443 +addr = "0.0.0.0" +external_port = 8443 + +[https.ssl] +private = "./data/cert/local.fornetcode.com.key" +public = "./data/cert/local.fornetcode.com.pem" + +[cache] +# if file size > max_size, it will not be cached. default is (10MB). +max_size = 20 +compression = true + +[[cache.client_cache]] +expire = '30d' +extension_names = ['icon', 'gif', 'jpg', 'jpeg', 'png', 'js'] + +[[cache.client_cache]] +expire = '0' +extension_names = ['html'] + + +# admin server config +# admin server don't support hot reload. the config should not change. +# optional, and it's disabled by default. +# if you use spa-client to upload files, control version. Need to open it +[admin_config] +# bind host +port = 9000 +addr = "127.0.0.1" + +# this is used to check client request +# put it in http header, Authorization: Bearer $token +token = "token" + + diff --git a/tests/src/main.rs b/tests/src/main.rs index 1f6566c..206b5ba 100644 --- a/tests/src/main.rs +++ b/tests/src/main.rs @@ -8,8 +8,9 @@ use tracing_subscriber::EnvFilter; async fn main() -> Result<()> { env::set_var( "SPA_CONFIG", - get_test_dir().join("server_config.conf").display().to_string(), + get_test_dir().join("server_config.toml").display().to_string(), ); + tracing_subscriber::fmt() .with_env_filter( EnvFilter::builder() diff --git a/tests/tests/acme_test.rs b/tests/tests/acme_test.rs index 0ada053..5973a11 100644 --- a/tests/tests/acme_test.rs +++ b/tests/tests/acme_test.rs @@ -27,11 +27,11 @@ async fn simple_acme_test() { clean_web_domain_dir(LOCAL_HOST); clean_cert(); // console_subscriber::init(); - run_server_with_config("server_config_acme.conf"); + run_server_with_config("server_config_acme.toml"); sleep(Duration::from_secs(2)).await; upload_file_and_check(domain, request_prefix, 1, vec![]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); let mut wait_count = 0; loop { assert!(wait_count < 60, "60 seconds doest not have cert"); @@ -81,7 +81,7 @@ async fn simple_acme_test() { wait_count +=1; } // sometimes it output error. don't know why - run_server_with_config("server_config_acme.conf"); + run_server_with_config("server_config_acme.toml"); */ sleep(Duration::from_secs(2)).await; assert_files(domain, request_prefix, 1, vec!["", "index.html"]).await; @@ -95,11 +95,11 @@ async fn simple_acme_test2() { let request_prefix = &request_prefix; clean_web_domain_dir(LOCAL_HOST); clean_cert(); - run_server_with_config("server_config_acme.conf"); + run_server_with_config("server_config_acme.toml"); sleep(Duration::from_secs(2)).await; upload_file_and_check(domain, request_prefix, 1, vec![]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); let mut wait_count = 0; loop { assert!(wait_count < 60, "60 seconds doest not have cert"); @@ -128,7 +128,7 @@ async fn alias_acme() { sleep(Duration::from_secs(2)).await; upload_file_and_check(domain, request_prefix, 1, vec![]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); let mut wait_count = 0; loop { assert!(wait_count < 60, "60 seconds doest not have cert"); diff --git a/tests/tests/common.rs b/tests/tests/common.rs index 284101a..9e12180 100644 --- a/tests/tests/common.rs +++ b/tests/tests/common.rs @@ -5,10 +5,16 @@ use spa_client::api::API; use std::path::{Path, PathBuf}; use std::sync::OnceLock; use std::{env, fs, io}; +use opentelemetry::trace::TracerProvider as _; +use opentelemetry_sdk::trace::TracerProvider; +use opentelemetry_stdout::SpanExporter; //use tokio::sync::oneshot; use tokio::task::JoinHandle; use tracing::{debug, error}; use tracing_subscriber::EnvFilter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; + pub const LOCAL_HOST: &str = "local.fornetcode.com"; pub const LOCAL_HOST2: &str = "local2.fornetcode.com"; @@ -67,13 +73,27 @@ pub fn run_server_with_config(config_file_name: &str) -> JoinHandle<()> { "SPA_CONFIG", get_test_dir().join(config_file_name).display().to_string(), ); - let _ = tracing_subscriber::fmt() - .with_env_filter( + let provider = TracerProvider::builder() + .with_simple_exporter(SpanExporter::default()) + .build(); + let tracer = provider.tracer("spa-server"); + let telemetry = tracing_opentelemetry::layer().with_tracer(tracer); + + let _ = tracing_subscriber::registry() + .with( EnvFilter::try_from_default_env() .unwrap_or_else(|_| "info,spa_server=debug,spa_client=debug".into()), ) - .with_test_writer() - .try_init(); + .with(tracing_subscriber::fmt::layer().compact()) + .with(telemetry).try_init(); + + // let _ = tracing_subscriber::fmt() + // .with_env_filter( + // EnvFilter::try_from_default_env() + // .unwrap_or_else(|_| "info,spa_server=debug,spa_client=debug".into()), + // ) + // .with_test_writer() + // .try_init(); tokio::spawn(async move { let result = spa_server::run_server().await; if result.is_err() { @@ -84,12 +104,12 @@ pub fn run_server_with_config(config_file_name: &str) -> JoinHandle<()> { }) } pub fn run_server() -> JoinHandle<()> { - run_server_with_config("server_config.conf") + run_server_with_config("server_config.toml") } pub async fn reload_server() { let client_config = - spa_client::config::Config::load(Some(get_test_dir().join("client_config.conf"))).unwrap(); + spa_client::config::Config::load(Some(get_test_dir().join("client_config.toml"))).unwrap(); let client_api = API::new(&client_config).unwrap(); client_api.reload_spa_server().await.unwrap() } @@ -106,7 +126,7 @@ pub async fn upload_file_and_check( version: u32, check_path: Vec<&'static str>, ) { - let (client_api, client_config) = get_client_api("client_config.conf"); + let (client_api, client_config) = get_client_api("client_config.toml"); println!("begin to upload file"); spa_client::upload_files( diff --git a/tests/tests/http_test.rs b/tests/tests/http_test.rs index ead043e..dffd938 100644 --- a/tests/tests/http_test.rs +++ b/tests/tests/http_test.rs @@ -41,7 +41,7 @@ async fn start_server_and_client_upload_file() { assert_files(domain, request_prefix, 1, vec!["1.html"]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); api.remove_files(Some(domain.to_string()), Some(1)) .await .unwrap(); @@ -80,7 +80,7 @@ async fn start_server_with_single_domain() { assert_files(domain, request_prefix, 1, vec!["1.html"]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); api.remove_files(Some(domain.to_string()), Some(1)) .await .unwrap(); @@ -109,7 +109,7 @@ async fn multiple_domain_check() { upload_file_and_check(domain, request_prefix, 1, vec!["index.html"]).await; upload_file_and_check(domain2, request_prefix2, 1, vec!["index.html"]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); let result = api.get_domain_info(None).await.unwrap(); assert_eq!(result.len(), 2); } @@ -137,7 +137,7 @@ async fn evoke_cache_when_serving_new_version() { .await; assert_files(domain, request_prefix, 2, vec!["2.html"]).await; assert_files_no_exists(request_prefix, vec!["1.html"]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); let result = api.get_domain_info(None).await.unwrap(); assert_eq!(result.len(), 1); } @@ -163,7 +163,7 @@ async fn cold_start_server_and_serving_files() { debug!("begin to loop server close"); - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); loop { assert!(wait_count < 10, "10 seconds server does not stop"); sleep(Duration::from_secs(1)).await; @@ -216,7 +216,7 @@ async fn self_signed_cert_https() { let request_prefix = format!("https://{LOCAL_HOST}:8443/27"); let request_prefix = &request_prefix; - run_server_with_config("server_config_https.conf"); + run_server_with_config("server_config_https.toml"); tokio::time::sleep(Duration::from_secs(2)).await; upload_file_and_check(domain, request_prefix, 1, vec!["index.html", "1.html"]).await; assert_redirect_correct(request_prefix, "/27/").await; @@ -255,7 +255,7 @@ async fn single_domain_reject_multiple_update() { let domain = format!("{LOCAL_HOST}/27"); let domain = &domain; - let (client_api, client_config) = get_client_api("client_config.conf"); + let (client_api, client_config) = get_client_api("client_config.toml"); let upload_result = spa_client::upload_files( client_api.clone(), @@ -282,7 +282,7 @@ async fn multiple_domain_reject_single_update() { let domain = format!("{LOCAL_HOST}"); let domain = &domain; - let (client_api, client_config) = get_client_api("client_config.conf"); + let (client_api, client_config) = get_client_api("client_config.toml"); let upload_result = spa_client::upload_files( client_api.clone(), @@ -309,7 +309,7 @@ async fn revoke_version() { upload_file_and_check(domain, request_prefix, 1, vec![]).await; upload_file_and_check(domain, request_prefix, 2, vec![]).await; upload_file_and_check(domain, request_prefix, 3, vec![]).await; - let (api, _) = get_client_api("client_config.conf"); + let (api, _) = get_client_api("client_config.toml"); api.revoke_version(domain.to_string(), 2).await.unwrap(); assert_files(domain, request_prefix, 2, vec!["index.html", "2.html"]).await;