refactor: migrate web server to Actix Web (#229)

This commit is contained in:
Rob Ede 2024-11-02 09:46:14 +00:00 committed by GitHub
parent ff6d9e880f
commit e0eb5474ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 958 additions and 591 deletions

556
Cargo.lock generated
View file

@ -8,6 +8,238 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
[[package]]
name = "actix-codec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a"
dependencies = [
"bitflags",
"bytes",
"futures-core",
"futures-sink",
"memchr",
"pin-project-lite",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "actix-http"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d48f96fc3003717aeb9856ca3d02a8c7de502667ad76eeacd830b48d2e91fac4"
dependencies = [
"actix-codec",
"actix-rt",
"actix-service",
"actix-utils",
"ahash",
"base64",
"bitflags",
"brotli",
"bytes",
"bytestring",
"derive_more 0.99.18",
"encoding_rs",
"flate2",
"futures-core",
"h2 0.3.26",
"http 0.2.12",
"httparse",
"httpdate",
"itoa",
"language-tags",
"local-channel",
"mime",
"percent-encoding",
"pin-project-lite",
"rand",
"sha1",
"smallvec",
"tokio",
"tokio-util",
"tracing",
"zstd",
]
[[package]]
name = "actix-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "actix-router"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8"
dependencies = [
"bytestring",
"cfg-if",
"http 0.2.12",
"regex",
"regex-lite",
"serde",
"tracing",
]
[[package]]
name = "actix-rt"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208"
dependencies = [
"futures-core",
"tokio",
]
[[package]]
name = "actix-server"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca2549781d8dd6d75c40cf6b6051260a2cc2f3c62343d761a969a0640646894"
dependencies = [
"actix-rt",
"actix-service",
"actix-utils",
"futures-core",
"futures-util",
"mio",
"socket2",
"tokio",
"tracing",
]
[[package]]
name = "actix-service"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a"
dependencies = [
"futures-core",
"paste",
"pin-project-lite",
]
[[package]]
name = "actix-utils"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8"
dependencies = [
"local-waker",
"pin-project-lite",
]
[[package]]
name = "actix-web"
version = "4.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9180d76e5cc7ccbc4d60a506f2c727730b154010262df5b910eb17dbe4b8cb38"
dependencies = [
"actix-codec",
"actix-http",
"actix-macros",
"actix-router",
"actix-rt",
"actix-server",
"actix-service",
"actix-utils",
"actix-web-codegen",
"ahash",
"bytes",
"bytestring",
"cfg-if",
"cookie",
"derive_more 0.99.18",
"encoding_rs",
"futures-core",
"futures-util",
"impl-more",
"itoa",
"language-tags",
"log",
"mime",
"once_cell",
"pin-project-lite",
"regex",
"regex-lite",
"serde",
"serde_json",
"serde_urlencoded",
"smallvec",
"socket2",
"time",
"url",
]
[[package]]
name = "actix-web-codegen"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8"
dependencies = [
"actix-router",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "actix-web-lab"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7675c1a84eec1b179c844cdea8488e3e409d8e4984026e92fa96c87dd86f33c6"
dependencies = [
"actix-http",
"actix-router",
"actix-service",
"actix-utils",
"actix-web",
"actix-web-lab-derive",
"ahash",
"arc-swap",
"async-trait",
"bytes",
"bytestring",
"csv",
"derive_more 0.99.18",
"futures-core",
"futures-util",
"http 0.2.12",
"impl-more",
"itertools",
"local-channel",
"mediatype",
"mime",
"once_cell",
"pin-project-lite",
"regex",
"serde",
"serde_html_form",
"serde_json",
"tokio",
"tokio-stream",
"tracing",
]
[[package]]
name = "actix-web-lab-derive"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aa0b287c8de4a76b691f29dbb5451e8dd5b79d777eaf87350c9b0cbfdb5e968"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.22.0" version = "0.22.0"
@ -36,6 +268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy",
@ -50,6 +283,21 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
dependencies = [
"alloc-no-stdlib",
]
[[package]] [[package]]
name = "allocator-api2" name = "allocator-api2"
version = "0.2.18" version = "0.2.18"
@ -102,6 +350,17 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "async-trait"
version = "0.1.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "atomic-waker" name = "atomic-waker"
version = "1.1.2" version = "1.1.2"
@ -161,6 +420,27 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "brotli"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.10.0" version = "1.10.0"
@ -190,6 +470,15 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "bytestring"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72"
dependencies = [
"bytes",
]
[[package]] [[package]]
name = "cadence" name = "cadence"
version = "1.4.0" version = "1.4.0"
@ -223,6 +512,8 @@ version = "1.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
dependencies = [ dependencies = [
"jobserver",
"libc",
"shlex", "shlex",
] ]
@ -257,6 +548,23 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24" checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cookie"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb"
dependencies = [
"percent-encoding",
"time",
"version_check",
]
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.4" version = "0.9.4"
@ -377,6 +685,27 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "csv"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "cvss" name = "cvss"
version = "2.0.0" version = "2.0.0"
@ -431,6 +760,19 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "derive_more"
version = "0.99.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "1.0.0" version = "1.0.0"
@ -1939,17 +2281,6 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.1" version = "1.0.1"
@ -1969,7 +2300,7 @@ dependencies = [
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http 1.1.0",
"http-body 1.0.1", "http-body",
"pin-project-lite", "pin-project-lite",
] ]
@ -1985,30 +2316,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.4.1" version = "1.4.1"
@ -2020,7 +2327,7 @@ dependencies = [
"futures-util", "futures-util",
"h2 0.4.6", "h2 0.4.6",
"http 1.1.0", "http 1.1.0",
"http-body 1.0.1", "http-body",
"httparse", "httparse",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
@ -2037,7 +2344,7 @@ checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 1.1.0", "http 1.1.0",
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"rustls", "rustls",
"rustls-native-certs", "rustls-native-certs",
@ -2056,7 +2363,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [ dependencies = [
"bytes", "bytes",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"native-tls", "native-tls",
"tokio", "tokio",
@ -2074,8 +2381,8 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"http 1.1.0", "http 1.1.0",
"http-body 1.0.1", "http-body",
"hyper 1.4.1", "hyper",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio", "tokio",
@ -2123,6 +2430,12 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "impl-more"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@ -2161,6 +2474,15 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.11"
@ -2192,6 +2514,15 @@ dependencies = [
"jiff-tzdb", "jiff-tzdb",
] ]
[[package]]
name = "jobserver"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.70" version = "0.3.70"
@ -2220,6 +2551,12 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "language-tags"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
[[package]] [[package]]
name = "lasso" name = "lasso"
version = "0.7.3" version = "0.7.3"
@ -2258,6 +2595,23 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "local-channel"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8"
dependencies = [
"futures-core",
"futures-sink",
"local-waker",
]
[[package]]
name = "local-waker"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@ -2322,6 +2676,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "mediatype"
version = "0.19.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8878cd8d1b3c8c8ae4b2ba0a36652b7cf192f618a599a7fbdfa25cffd4ea72dd"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -2369,6 +2729,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"libc", "libc",
"log",
"wasi", "wasi",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -2521,6 +2882,12 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -2836,6 +3203,12 @@ dependencies = [
"regex-syntax 0.8.4", "regex-syntax 0.8.4",
] ]
[[package]]
name = "regex-lite"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.29" version = "0.6.29"
@ -2872,9 +3245,9 @@ dependencies = [
"futures-util", "futures-util",
"h2 0.4.6", "h2 0.4.6",
"http 1.1.0", "http 1.1.0",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-rustls", "hyper-rustls",
"hyper-tls", "hyper-tls",
"hyper-util", "hyper-util",
@ -2924,12 +3297,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "route-recognizer"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@ -2942,6 +3309,15 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.35" version = "0.38.35"
@ -3122,6 +3498,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "serde_html_form"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5"
dependencies = [
"form_urlencoded",
"indexmap 2.5.0",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.127" version = "1.0.127"
@ -3196,6 +3585,17 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sha1_smol" name = "sha1_smol"
version = "1.0.1" version = "1.0.1"
@ -3221,11 +3621,13 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
name = "shiny-robots" name = "shiny-robots"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-web",
"actix-web-lab",
"anyhow", "anyhow",
"badge", "badge",
"cadence", "cadence",
"crates-index", "crates-index",
"derive_more", "derive_more 1.0.0",
"dotenvy", "dotenvy",
"either", "either",
"error_reporter", "error_reporter",
@ -3233,16 +3635,15 @@ dependencies = [
"futures-util", "futures-util",
"gix 0.63.0", "gix 0.63.0",
"grass", "grass",
"hyper 0.14.30",
"indexmap 2.5.0", "indexmap 2.5.0",
"lru_time_cache", "lru_time_cache",
"maud", "maud",
"mime",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"pulldown-cmark", "pulldown-cmark",
"relative-path", "relative-path",
"reqwest", "reqwest",
"route-recognizer",
"rustsec", "rustsec",
"semver", "semver",
"serde", "serde",
@ -3261,6 +3662,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "siphasher" name = "siphasher"
version = "0.3.11" version = "0.3.11"
@ -3492,7 +3902,9 @@ dependencies = [
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
"parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -3530,6 +3942,17 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-stream"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.11" version = "0.7.11"
@ -3644,6 +4067,7 @@ version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [ dependencies = [
"log",
"pin-project-lite", "pin-project-lite",
"tracing-attributes", "tracing-attributes",
"tracing-core", "tracing-core",
@ -4127,3 +4551,31 @@ name = "zeroize"
version = "1.8.1" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zstd"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.13+zstd.1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
dependencies = [
"cc",
"pkg-config",
]

View file

@ -14,6 +14,8 @@ edition = "2021"
[dependencies] [dependencies]
badge = { path = "./libs/badge" } badge = { path = "./libs/badge" }
actix-web = "4"
actix-web-lab = "0.20"
anyhow = "1" anyhow = "1"
cadence = "1" cadence = "1"
crates-index = { version = "3", default-features = false, features = ["git"] } crates-index = { version = "3", default-features = false, features = ["git"] }
@ -22,23 +24,22 @@ dotenvy = "0.15"
either = "1.12" either = "1.12"
font-awesome-as-a-crate = "0.3" font-awesome-as-a-crate = "0.3"
futures-util = { version = "0.3", default-features = false, features = ["std"] } futures-util = { version = "0.3", default-features = false, features = ["std"] }
hyper = { version = "0.14.10", features = ["full"] }
error_reporter = "1" error_reporter = "1"
indexmap = { version = "2", features = ["serde"] } indexmap = { version = "2", features = ["serde"] }
lru_time_cache = "0.11" lru_time_cache = "0.11"
maud = "0.26" maud = "0.26"
mime = "0.3"
once_cell = "1" once_cell = "1"
parking_lot = "0.12" parking_lot = "0.12"
pulldown-cmark = "0.12" pulldown-cmark = "0.12"
relative-path = { version = "1", features = ["serde"] } relative-path = { version = "1", features = ["serde"] }
reqwest = { version = "0.12", features = ["json"] } reqwest = { version = "0.12", features = ["json"] }
route-recognizer = "0.3"
rustsec = "0.29" rustsec = "0.29"
semver = { version = "1", features = ["serde"] } semver = { version = "1", features = ["serde"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_urlencoded = "0.7" serde_urlencoded = "0.7"
serde_with = "3" serde_with = "3"
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros", "sync", "time"] } tokio = { version = "1.24.2", features = ["rt", "macros", "sync", "time"] }
toml = "0.8" toml = "0.8"
tracing = "0.1.30" tracing = "0.1.30"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }

View file

@ -1,5 +1,7 @@
use anyhow::Error; use anyhow::Error;
use futures_util::{future::BoxFuture, stream::FuturesOrdered, FutureExt as _, StreamExt as _}; use futures_util::{
future::LocalBoxFuture, stream::FuturesOrdered, FutureExt as _, StreamExt as _,
};
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use crate::{ use crate::{
@ -16,8 +18,9 @@ pub async fn crawl_manifest(
entry_point: RelativePathBuf, entry_point: RelativePathBuf,
) -> anyhow::Result<ManifestCrawlerOutput> { ) -> anyhow::Result<ManifestCrawlerOutput> {
let mut crawler = ManifestCrawler::new(); let mut crawler = ManifestCrawler::new();
let mut futures: FuturesOrdered<BoxFuture<'static, Result<(RelativePathBuf, String), Error>>> = let mut futures: FuturesOrdered<
FuturesOrdered::new(); LocalBoxFuture<'static, Result<(RelativePathBuf, String), Error>>,
> = FuturesOrdered::new();
let engine2 = engine.clone(); let engine2 = engine.clone();
let repo_path2 = repo_path.clone(); let repo_path2 = repo_path.clone();
@ -28,7 +31,7 @@ pub async fn crawl_manifest(
.await?; .await?;
Ok((entry_point, contents)) Ok((entry_point, contents))
} }
.boxed(); .boxed_local();
futures.push_back(fut); futures.push_back(fut);
@ -47,7 +50,7 @@ pub async fn crawl_manifest(
let contents = engine.retrieve_manifest_at_path(&repo_path, &path).await?; let contents = engine.retrieve_manifest_at_path(&repo_path, &path).await?;
Ok((path, contents)) Ok((path, contents))
} }
.boxed(); .boxed_local();
futures.push_back(fut); futures.push_back(fut);
} }

View file

@ -5,14 +5,14 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use actix_web::dev::Service;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use cadence::{MetricSink, NopMetricSink, StatsdClient}; use cadence::{MetricSink, NopMetricSink, StatsdClient};
use futures_util::{ use futures_util::{
future::try_join_all, future::try_join_all,
stream::{self, BoxStream}, stream::{self, LocalBoxStream},
StreamExt as _, StreamExt as _,
}; };
use hyper::service::Service;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use relative_path::{RelativePath, RelativePathBuf}; use relative_path::{RelativePath, RelativePathBuf};
use rustsec::database::Database; use rustsec::database::Database;
@ -38,7 +38,7 @@ mod machines;
use self::fut::{analyze_dependencies, crawl_manifest}; use self::fut::{analyze_dependencies, crawl_manifest};
#[derive(Clone, Debug)] #[derive(Debug, Clone)]
pub struct Engine { pub struct Engine {
metrics: Arc<StatsdClient>, metrics: Arc<StatsdClient>,
query_crate: Cache<QueryCrate, CrateName>, query_crate: Cache<QueryCrate, CrateName>,
@ -255,7 +255,10 @@ impl Engine {
Ok(latest) Ok(latest)
} }
fn fetch_releases<'a, I>(&'a self, names: I) -> BoxStream<'a, anyhow::Result<Vec<CrateRelease>>> fn fetch_releases<'a, I>(
&'a self,
names: I,
) -> LocalBoxStream<'a, anyhow::Result<Vec<CrateRelease>>>
where where
I: IntoIterator<Item = CrateName>, I: IntoIterator<Item = CrateName>,
<I as IntoIterator>::IntoIter: Send + 'a, <I as IntoIterator>::IntoIter: Send + 'a,
@ -277,7 +280,7 @@ impl Engine {
) -> Result<String, Error> { ) -> Result<String, Error> {
let manifest_path = path.join(RelativePath::new("Cargo.toml")); let manifest_path = path.join(RelativePath::new("Cargo.toml"));
let mut service = self.retrieve_file_at_path.clone(); let service = self.retrieve_file_at_path.clone();
service.call((repo_path.clone(), manifest_path)).await service.call((repo_path.clone(), manifest_path)).await
} }

View file

@ -1,19 +1,16 @@
use std::{ use std::{fmt, str};
fmt, str,
task::{Context, Poll},
};
use actix_web::dev::Service;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use crates_index::{Crate, DependencyKind}; use crates_index::{Crate, DependencyKind};
use futures_util::FutureExt as _; use futures_util::{future::LocalBoxFuture, FutureExt as _};
use hyper::service::Service;
use semver::{Version, VersionReq}; use semver::{Version, VersionReq};
use serde::Deserialize; use serde::Deserialize;
use tokio::task::spawn_blocking; use tokio::task::spawn_blocking;
use crate::{ use crate::{
models::crates::{CrateDep, CrateDeps, CrateName, CratePath, CrateRelease}, models::crates::{CrateDep, CrateDeps, CrateName, CratePath, CrateRelease},
BoxFuture, ManagedIndex, ManagedIndex,
}; };
const CRATES_API_BASE_URI: &str = "https://crates.io/api/v1"; const CRATES_API_BASE_URI: &str = "https://crates.io/api/v1";
@ -86,13 +83,11 @@ impl fmt::Debug for QueryCrate {
impl Service<CrateName> for QueryCrate { impl Service<CrateName> for QueryCrate {
type Response = QueryCrateResponse; type Response = QueryCrateResponse;
type Error = Error; type Error = Error;
type Future = BoxFuture<Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_web::dev::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, crate_name: CrateName) -> Self::Future { fn call(&self, crate_name: CrateName) -> Self::Future {
let index = self.index.clone(); let index = self.index.clone();
Self::query(index, crate_name).boxed() Self::query(index, crate_name).boxed()
} }
@ -150,13 +145,11 @@ impl fmt::Debug for GetPopularCrates {
impl Service<()> for GetPopularCrates { impl Service<()> for GetPopularCrates {
type Response = Vec<CratePath>; type Response = Vec<CratePath>;
type Error = Error; type Error = Error;
type Future = BoxFuture<Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_web::dev::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: ()) -> Self::Future { fn call(&self, _req: ()) -> Self::Future {
let client = self.client.clone(); let client = self.client.clone();
Self::query(client).boxed() Self::query(client).boxed()
} }

View file

@ -1,17 +1,11 @@
use std::{ use std::fmt;
fmt,
task::{Context, Poll},
};
use actix_web::dev::Service;
use anyhow::Error; use anyhow::Error;
use futures_util::FutureExt as _; use futures_util::{future::LocalBoxFuture, FutureExt as _};
use hyper::service::Service;
use serde::Deserialize; use serde::Deserialize;
use crate::{ use crate::models::repo::{RepoPath, Repository};
models::repo::{RepoPath, Repository},
BoxFuture,
};
const GITHUB_API_BASE_URI: &str = "https://api.github.com"; const GITHUB_API_BASE_URI: &str = "https://api.github.com";
@ -72,13 +66,11 @@ impl fmt::Debug for GetPopularRepos {
impl Service<()> for GetPopularRepos { impl Service<()> for GetPopularRepos {
type Response = Vec<Repository>; type Response = Vec<Repository>;
type Error = Error; type Error = Error;
type Future = BoxFuture<Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_web::dev::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: ()) -> Self::Future { fn call(&self, _req: ()) -> Self::Future {
let client = self.client.clone(); let client = self.client.clone();
Self::query(client).boxed() Self::query(client).boxed()
} }

View file

@ -1,14 +1,11 @@
use std::{ use std::fmt;
fmt,
task::{Context, Poll},
};
use actix_web::dev::Service;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use futures_util::FutureExt as _; use futures_util::{future::LocalBoxFuture, FutureExt as _};
use hyper::service::Service;
use relative_path::RelativePathBuf; use relative_path::RelativePathBuf;
use crate::{models::repo::RepoPath, BoxFuture}; use crate::models::repo::RepoPath;
pub mod crates; pub mod crates;
pub mod github; pub mod github;
@ -43,13 +40,11 @@ impl RetrieveFileAtPath {
impl Service<(RepoPath, RelativePathBuf)> for RetrieveFileAtPath { impl Service<(RepoPath, RelativePathBuf)> for RetrieveFileAtPath {
type Response = String; type Response = String;
type Error = Error; type Error = Error;
type Future = BoxFuture<Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_web::dev::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, (repo_path, path): (RepoPath, RelativePathBuf)) -> Self::Future { fn call(&self, (repo_path, path): (RepoPath, RelativePathBuf)) -> Self::Future {
let client = self.client.clone(); let client = self.client.clone();
Self::query(client, repo_path, path).boxed() Self::query(client, repo_path, path).boxed()
} }

View file

@ -1,16 +1,10 @@
use std::{ use std::{fmt, sync::Arc};
fmt,
sync::Arc,
task::{Context, Poll},
};
use actix_web::dev::Service;
use anyhow::Error; use anyhow::Error;
use futures_util::FutureExt as _; use futures_util::{future::LocalBoxFuture, FutureExt as _};
use hyper::service::Service;
use rustsec::database::Database; use rustsec::database::Database;
use crate::BoxFuture;
#[derive(Clone)] #[derive(Clone)]
pub struct FetchAdvisoryDatabase { pub struct FetchAdvisoryDatabase {
client: reqwest::Client, client: reqwest::Client,
@ -30,20 +24,19 @@ impl FetchAdvisoryDatabase {
impl Service<()> for FetchAdvisoryDatabase { impl Service<()> for FetchAdvisoryDatabase {
type Response = Arc<Database>; type Response = Arc<Database>;
type Error = Error; type Error = Error;
type Future = BoxFuture<Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_web::dev::always_ready!();
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: ()) -> Self::Future { fn call(&self, _req: ()) -> Self::Future {
let client = self.client.clone(); let client = self.client.clone();
Self::fetch(client).boxed() Self::fetch(client).boxed_local()
} }
} }
impl fmt::Debug for FetchAdvisoryDatabase { impl fmt::Debug for FetchAdvisoryDatabase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("FetchAdvisoryDatabase") f.debug_struct("FetchAdvisoryDatabase")
.finish_non_exhaustive()
} }
} }

View file

@ -3,20 +3,14 @@
use std::{ use std::{
env, env,
future::Future, net::{Ipv4Addr, UdpSocket},
net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket},
pin::Pin,
time::Duration, time::Duration,
}; };
use actix_web::{middleware::Logger, web};
use actix_web_lab::{extract::ThinData, middleware::NormalizePath};
use cadence::{QueuingMetricSink, UdpMetricSink}; use cadence::{QueuingMetricSink, UdpMetricSink};
use hyper::{
server::conn::AddrStream,
service::{make_service_fn, service_fn},
Server,
};
use reqwest::redirect::Policy as RedirectPolicy; use reqwest::redirect::Policy as RedirectPolicy;
use tracing::Instrument as _;
mod engine; mod engine;
mod interactors; mod interactors;
@ -25,10 +19,7 @@ mod parsers;
mod server; mod server;
mod utils; mod utils;
use self::{engine::Engine, server::App, utils::index::ManagedIndex}; use self::{engine::Engine, utils::index::ManagedIndex};
/// Future crate's BoxFuture without the explicit lifetime parameter.
pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
const DEPS_RS_UA: &str = "deps.rs"; const DEPS_RS_UA: &str = "deps.rs";
@ -59,7 +50,7 @@ fn init_tracing_subscriber() {
.init(); .init();
} }
#[tokio::main] #[tokio::main(flavor = "current_thread")]
async fn main() { async fn main() {
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
init_tracing_subscriber(); init_tracing_subscriber();
@ -77,8 +68,6 @@ async fn main() {
.parse() .parse()
.expect("could not read port"); .expect("could not read port");
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port);
let index = ManagedIndex::new(); let index = ManagedIndex::new();
{ {
@ -92,25 +81,24 @@ async fn main() {
let mut engine = Engine::new(client.clone(), index); let mut engine = Engine::new(client.clone(), index);
engine.set_metrics(metrics); engine.set_metrics(metrics);
let make_svc = make_service_fn(move |_socket: &AddrStream| { let server = actix_web::HttpServer::new(move || {
let engine = engine.clone(); actix_web::App::new()
.app_data(ThinData(engine.clone()))
async move { .service(server::index)
let server = App::new(engine.clone()); .service(server::crate_redirect)
Ok::<_, hyper::Error>(service_fn(move |req| { .service(server::crate_latest_status_svg)
let server = server.clone(); .service(server::crate_status_svg)
async move { .service(server::crate_status_html)
let path = req.uri().path().to_owned(); .service(server::repo_status_svg)
.service(server::repo_status_html)
server .configure(server::static_files)
.handle(req) .default_service(web::to(server::not_found))
.instrument(tracing::info_span!("@", %path)) .wrap(NormalizePath::trim())
.await .wrap(Logger::default())
} })
})) .bind_auto_h2c((Ipv4Addr::UNSPECIFIED, port))
} .unwrap()
}); .run();
let server = Server::bind(&addr).serve(make_svc);
tracing::info!("Server running on port {port}"); tracing::info!("Server running on port {port}");

View file

@ -100,7 +100,7 @@ impl FromStr for RepoSite {
if let Some((site, domain)) = input.split_once('/') { if let Some((site, domain)) = input.split_once('/') {
match site { match site {
"gitea" => Ok(RepoSite::Gitea(domain.parse()?)), "gitea" => Ok(RepoSite::Gitea(domain.parse()?)),
_ => Err(anyhow!("unknown repo site identifier")), site => Err(anyhow!("unknown repo site identifier: {site}")),
} }
} else { } else {
match input { match input {
@ -109,7 +109,7 @@ impl FromStr for RepoSite {
"bitbucket" => Ok(RepoSite::Bitbucket), "bitbucket" => Ok(RepoSite::Bitbucket),
"sourcehut" => Ok(RepoSite::Sourcehut), "sourcehut" => Ok(RepoSite::Sourcehut),
"codeberg" => Ok(RepoSite::Codeberg), "codeberg" => Ok(RepoSite::Codeberg),
_ => Err(anyhow!("unknown repo site identifier")), site => Err(anyhow!("unknown repo site identifier: {site}")),
} }
} }
} }

View file

@ -4,11 +4,9 @@ pub const STATIC_STYLE_CSS_PATH: &str = concat!(
include_str!(concat!(env!("OUT_DIR"), "/style.css.sha1")), include_str!(concat!(env!("OUT_DIR"), "/style.css.sha1")),
".css" ".css"
); );
pub const STATIC_STYLE_CSS_ETAG: &str = concat!( pub const STATIC_STYLE_CSS_ETAG: &str = include_str!(concat!(env!("OUT_DIR"), "/style.css.sha1"));
"\"",
include_str!(concat!(env!("OUT_DIR"), "/style.css.sha1")), pub const STATIC_FAVICON_PATH: &str = "/static/logo.svg";
"\""
);
pub static STATIC_FAVICON: &[u8] = include_bytes!("../../assets/logo.svg"); pub static STATIC_FAVICON: &[u8] = include_bytes!("../../assets/logo.svg");
pub static STATIC_LINKS_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/links.js")); pub static STATIC_LINKS_JS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/links.js"));
@ -17,8 +15,4 @@ pub const STATIC_LINKS_JS_PATH: &str = concat!(
include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1")), include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1")),
".js" ".js"
); );
pub const STATIC_LINKS_JS_ETAG: &str = concat!( pub const STATIC_LINKS_JS_ETAG: &str = include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1"));
"\"",
include_str!(concat!(env!("OUT_DIR"), "/links.js.sha1")),
"\""
);

79
src/server/error.rs Normal file
View file

@ -0,0 +1,79 @@
use actix_web::{
http::{header::ContentType, StatusCode},
HttpResponse, ResponseError,
};
use derive_more::Display;
use maud::Markup;
use crate::server::views::html::error::{render, render_404};
#[derive(Debug, Display)]
pub(crate) enum ServerError {
#[display("Could not retrieve popular items")]
PopularItemsFailed,
#[display("Crate not found")]
CrateNotFound,
#[display("Could not parse crate path")]
BadCratePath,
#[display("Could not fetch crate information")]
CrateFetchFailed,
#[display("Could not parse repository path")]
BadRepoPath,
#[display("Crate/repo analysis failed")]
AnalysisFailed(Markup),
}
impl ResponseError for ServerError {
fn status_code(&self) -> StatusCode {
match self {
ServerError::PopularItemsFailed => StatusCode::INTERNAL_SERVER_ERROR,
ServerError::CrateNotFound => StatusCode::NOT_FOUND,
ServerError::BadCratePath => StatusCode::BAD_REQUEST,
ServerError::CrateFetchFailed => StatusCode::NOT_FOUND,
ServerError::BadRepoPath => StatusCode::BAD_REQUEST,
ServerError::AnalysisFailed(_) => StatusCode::BAD_REQUEST,
}
}
fn error_response(&self) -> HttpResponse {
let mut res = HttpResponse::build(self.status_code());
let res = res.insert_header(ContentType::html());
match self {
ServerError::PopularItemsFailed => res.body(render(self.to_string(), "").0),
ServerError::CrateNotFound => res.body(render_404().0),
ServerError::BadCratePath => res.body(
render(
self.to_string(),
"Please make sure to provide a valid crate name and version.",
)
.0,
),
ServerError::CrateFetchFailed => res.body(
render(
self.to_string(),
"Please make sure to provide a valid crate name.",
)
.0,
),
ServerError::BadRepoPath => res.body(
render(
self.to_string(),
"Please make sure to provide a valid repository path.",
)
.0,
),
Self::AnalysisFailed(html) => res.body(html.0.clone()),
}
}
}

View file

@ -1,21 +1,35 @@
use std::{env, sync::Arc, time::Instant}; use std::env;
use actix_web::{
get,
http::{
header::{ContentType, ETag, EntityTag},
Uri,
},
web::{Redirect, ServiceConfig},
Either, HttpResponse, Resource, Responder,
};
use actix_web_lab::{
extract::{Path, ThinData},
header::{CacheControl, CacheDirective},
respond::Html,
};
use assets::STATIC_FAVICON_PATH;
use badge::BadgeStyle; use badge::BadgeStyle;
use futures_util::future; use futures_util::future;
use hyper::{
header::{CACHE_CONTROL, CONTENT_TYPE, ETAG, LOCATION},
Body, Error as HyperError, Method, Request, Response, StatusCode,
};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use route_recognizer::{Params, Router};
use semver::VersionReq; use semver::VersionReq;
use serde::Deserialize; use serde::Deserialize;
mod assets; mod assets;
mod error;
mod views; mod views;
use self::assets::{ use self::{
assets::{
STATIC_LINKS_JS_ETAG, STATIC_LINKS_JS_PATH, STATIC_STYLE_CSS_ETAG, STATIC_STYLE_CSS_PATH, STATIC_LINKS_JS_ETAG, STATIC_LINKS_JS_PATH, STATIC_STYLE_CSS_ETAG, STATIC_STYLE_CSS_PATH,
},
error::ServerError,
}; };
use crate::{ use crate::{
engine::{AnalyzeDependenciesOutcome, Engine}, engine::{AnalyzeDependenciesOutcome, Engine},
@ -35,244 +49,109 @@ enum StatusFormat {
Svg, Svg,
} }
#[derive(Debug, Clone, Copy)] #[get("/")]
enum StaticFile { pub(crate) async fn index(ThinData(engine): ThinData<Engine>) -> actix_web::Result<impl Responder> {
StyleCss, let popular = future::try_join(engine.get_popular_repos(), engine.get_popular_crates()).await;
FaviconPng,
LinksJs,
}
enum Route {
Index,
Static(StaticFile),
RepoStatus(StatusFormat),
CrateRedirect,
CrateStatus(StatusFormat),
LatestCrateBadge,
}
#[derive(Clone)]
pub struct App {
engine: Engine,
router: Arc<Router<Route>>,
}
impl App {
pub fn new(engine: Engine) -> App {
let mut router = Router::new();
router.add("/", Route::Index);
router.add(STATIC_STYLE_CSS_PATH, Route::Static(StaticFile::StyleCss));
router.add("/static/logo.svg", Route::Static(StaticFile::FaviconPng));
router.add(STATIC_LINKS_JS_PATH, Route::Static(StaticFile::LinksJs));
router.add(
"/repo/*site/:qual/:name",
Route::RepoStatus(StatusFormat::Html),
);
router.add(
"/repo/*site/:qual/:name/status.svg",
Route::RepoStatus(StatusFormat::Svg),
);
router.add("/crate/:name", Route::CrateRedirect);
router.add(
"/crate/:name/:version",
Route::CrateStatus(StatusFormat::Html),
);
router.add("/crate/:name/latest/status.svg", Route::LatestCrateBadge);
router.add(
"/crate/:name/:version/status.svg",
Route::CrateStatus(StatusFormat::Svg),
);
App {
engine,
router: Arc::new(router),
}
}
pub async fn handle(&self, req: Request<Body>) -> Result<Response<Body>, HyperError> {
let start = Instant::now();
// allows `/path/` to also match `/path`
let normalized_path = req.uri().path().trim_end_matches('/');
let res = if let Ok(route_match) = self.router.recognize(normalized_path) {
match (req.method(), route_match.handler()) {
(&Method::GET, Route::Index) => self.index(req, route_match.params().clone()).await,
(&Method::GET, Route::RepoStatus(format)) => {
self.repo_status(req, route_match.params().clone(), *format)
.await
}
(&Method::GET, Route::CrateStatus(format)) => {
self.crate_status(req, route_match.params().clone(), *format)
.await
}
(&Method::GET, Route::LatestCrateBadge) => {
self.crate_status(req, route_match.params().clone(), StatusFormat::Svg)
.await
}
(&Method::GET, Route::CrateRedirect) => {
self.crate_redirect(req, route_match.params().clone()).await
}
(&Method::GET, Route::Static(file)) => Ok(App::static_file(*file)),
_ => Ok(not_found()),
}
} else {
Ok(not_found())
};
let end = Instant::now();
let diff = end - start;
match &res {
Ok(res) => tracing::info!(
status = %res.status(),
time = %format_args!("{}ms", diff.as_millis()),
),
Err(err) => tracing::error!(%err),
};
res
}
}
impl App {
async fn index(
&self,
_req: Request<Body>,
_params: Params,
) -> Result<Response<Body>, HyperError> {
let engine = self.engine.clone();
let popular =
future::try_join(engine.get_popular_repos(), engine.get_popular_crates()).await;
match popular { match popular {
Err(err) => { Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let mut response = Err(ServerError::PopularItemsFailed.into())
views::html::error::render("Could not retrieve popular items", "");
*response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
Ok(response)
}
Ok((popular_repos, popular_crates)) => {
Ok(views::html::index::render(popular_repos, popular_crates))
}
} }
Ok((popular_repos, popular_crates)) => Ok(Html::new(
views::html::index::render(popular_repos, popular_crates).0,
)),
} }
}
async fn repo_status( #[get("/repo/{site:.+?}/{qual}/{name}/status.svg")]
&self, pub(crate) async fn repo_status_svg(
req: Request<Body>, ThinData(engine): ThinData<Engine>,
params: Params, uri: Uri,
Path(params): Path<(String, String, String)>,
) -> actix_web::Result<impl Responder> {
repo_status(engine, uri, params, StatusFormat::Svg).await
}
#[get("/repo/{site:.+?}/{qual}/{name}")]
pub(crate) async fn repo_status_html(
ThinData(engine): ThinData<Engine>,
uri: Uri,
Path(params): Path<(String, String, String)>,
) -> actix_web::Result<impl Responder> {
repo_status(engine, uri, params, StatusFormat::Html).await
}
async fn repo_status(
engine: Engine,
uri: Uri,
(site, qual, name): (String, String, String),
format: StatusFormat, format: StatusFormat,
) -> Result<Response<Body>, HyperError> { ) -> actix_web::Result<impl Responder> {
let server = self.clone(); let extra_knobs = ExtraConfig::from_query_string(uri.query());
let site = params.find("site").expect("route param 'site' not found"); let repo_path_result = RepoPath::from_parts(&site, &qual, &name);
let qual = params.find("qual").expect("route param 'qual' not found");
let name = params.find("name").expect("route param 'name' not found");
let extra_knobs = ExtraConfig::from_query_string(req.uri().query()); let repo_path = match repo_path_result {
Ok(repo_path) => repo_path,
let repo_path_result = RepoPath::from_parts(site, qual, name);
match repo_path_result {
Err(err) => { Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let mut response = views::html::error::render( return Err(ServerError::BadRepoPath.into());
"Could not parse repository path",
"Please make sure to provide a valid repository path.",
);
*response.status_mut() = StatusCode::BAD_REQUEST;
Ok(response)
} }
};
Ok(repo_path) => { let analyze_result = engine
let analyze_result = server
.engine
.analyze_repo_dependencies(repo_path.clone(), &extra_knobs.path) .analyze_repo_dependencies(repo_path.clone(), &extra_knobs.path)
.await; .await;
match analyze_result { match analyze_result {
Err(err) => { Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let response = App::status_format_analysis( let response =
None, status_format_analysis(None, format, SubjectPath::Repo(repo_path), extra_knobs);
format,
SubjectPath::Repo(repo_path),
extra_knobs,
);
Ok(response) Ok(response)
} }
Ok(analysis_outcome) => { Ok(analysis_outcome) => {
let response = App::status_format_analysis( let response = status_format_analysis(
Some(analysis_outcome), Some(analysis_outcome),
format, format,
SubjectPath::Repo(repo_path), SubjectPath::Repo(repo_path),
extra_knobs, extra_knobs,
); );
Ok(response) Ok(response)
} }
} }
} }
}
}
async fn crate_redirect( #[get("/crate/{name}")]
&self, async fn crate_redirect(
_req: Request<Body>, ThinData(engine): ThinData<Engine>,
params: Params, Path((name,)): Path<(String,)>,
) -> Result<Response<Body>, HyperError> { ) -> actix_web::Result<impl Responder> {
let engine = self.engine.clone();
let name = params.find("name").expect("route param 'name' not found");
let crate_name_result = name.parse::<CrateName>(); let crate_name_result = name.parse::<CrateName>();
match crate_name_result { let crate_name = match crate_name_result {
Ok(crate_name) => crate_name,
Err(err) => { Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let mut response = views::html::error::render( return Err(ServerError::BadCratePath.into());
"Could not parse crate name",
"Please make sure to provide a valid crate name.",
);
*response.status_mut() = StatusCode::BAD_REQUEST;
Ok(response)
} }
};
Ok(crate_name) => {
let release_result = engine let release_result = engine
.find_latest_stable_crate_release(crate_name, VersionReq::STAR) .find_latest_stable_crate_release(crate_name, VersionReq::STAR)
.await; .await
.inspect_err(|err| {
match release_result {
Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let mut response = views::html::error::render( });
"Could not fetch crate information",
"Please make sure to provide a valid crate name.", let Ok(Some(release)) = release_result else {
); return Err(ServerError::CrateFetchFailed.into());
*response.status_mut() = StatusCode::NOT_FOUND; };
Ok(response)
}
Ok(None) => {
let mut response = views::html::error::render(
"Could not fetch crate information",
"Please make sure to provide a valid crate name.",
);
*response.status_mut() = StatusCode::NOT_FOUND;
Ok(response)
}
Ok(Some(release)) => {
let redirect_url = format!( let redirect_url = format!(
"{}/crate/{}/{}", "{}/crate/{}/{}",
&SELF_BASE_URL as &str, &SELF_BASE_URL as &str,
@ -280,97 +159,86 @@ impl App {
release.version release.version
); );
let res = Response::builder() Ok(Redirect::to(redirect_url))
.status(StatusCode::TEMPORARY_REDIRECT) }
.header(LOCATION, redirect_url)
.body(Body::empty())
.unwrap();
Ok(res) #[get("/crate/{name}/{version}")]
} async fn crate_status_html(
} ThinData(engine): ThinData<Engine>,
} uri: Uri,
} Path((name, version)): Path<(String, String)>,
} ) -> actix_web::Result<impl Responder> {
crate_status(engine, uri, (name, Some(version)), StatusFormat::Html).await
}
async fn crate_status( #[get("/crate/{name}/latest/status.svg")]
&self, async fn crate_latest_status_svg(
req: Request<Body>, ThinData(engine): ThinData<Engine>,
params: Params, uri: Uri,
Path((name,)): Path<(String,)>,
) -> actix_web::Result<impl Responder> {
crate_status(engine, uri, (name, None), StatusFormat::Svg).await
}
#[get("/crate/{name}/{version}/status.svg")]
async fn crate_status_svg(
ThinData(engine): ThinData<Engine>,
uri: Uri,
Path((name, version)): Path<(String, String)>,
) -> actix_web::Result<impl Responder> {
crate_status(engine, uri, (name, Some(version)), StatusFormat::Svg).await
}
async fn crate_status(
engine: Engine,
uri: Uri,
(name, version): (String, Option<String>),
format: StatusFormat, format: StatusFormat,
) -> Result<Response<Body>, HyperError> { ) -> actix_web::Result<impl Responder> {
let server = self.clone(); let version = match version {
let name = params.find("name").expect("route param 'name' not found");
let version = match params.find("version") {
Some(ver) => ver.to_owned(), Some(ver) => ver.to_owned(),
None => { None => {
let crate_name = match name.parse() { let crate_name = match name.parse() {
Ok(name) => name, Ok(name) => name,
Err(_) => { Err(_) => return Err(ServerError::BadCratePath.into()),
let mut response = views::html::error::render(
"Could not parse crate path",
"Please make sure to provide a valid crate name and version.",
);
*response.status_mut() = StatusCode::BAD_REQUEST;
return Ok(response);
}
}; };
match server match engine
.engine
.find_latest_stable_crate_release(crate_name, VersionReq::STAR) .find_latest_stable_crate_release(crate_name, VersionReq::STAR)
.await .await
{ {
Ok(Some(latest_rel)) => latest_rel.version.to_string(), Ok(Some(latest_rel)) => latest_rel.version.to_string(),
Ok(None) => return Ok(not_found()),
Ok(None) => return Err(ServerError::CrateNotFound.into()),
Err(err) => { Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let mut response = views::html::error::render( return Err(ServerError::CrateFetchFailed.into());
"Could not fetch crate information",
"Please make sure to provide a valid crate name.",
);
*response.status_mut() = StatusCode::NOT_FOUND;
return Ok(response);
} }
} }
} }
}; };
let crate_path_result = CratePath::from_parts(name, &version); let crate_path_result = CratePath::from_parts(&name, &version);
let badge_knobs = ExtraConfig::from_query_string(req.uri().query()); let badge_knobs = ExtraConfig::from_query_string(uri.query());
match crate_path_result { match crate_path_result {
Err(err) => { Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let mut response = views::html::error::render( Err(ServerError::BadCratePath.into())
"Could not parse crate path",
"Please make sure to provide a valid crate name and version.",
);
*response.status_mut() = StatusCode::BAD_REQUEST;
Ok(response)
} }
Ok(crate_path) => { Ok(crate_path) => {
let analyze_result = server let analysis_outcome = engine
.engine
.analyze_crate_dependencies(crate_path.clone()) .analyze_crate_dependencies(crate_path.clone())
.await; .await
.inspect_err(|err| {
match analyze_result {
Err(err) => {
tracing::error!(%err); tracing::error!(%err);
let response = App::status_format_analysis( })
None, .ok();
format,
SubjectPath::Crate(crate_path), let response = status_format_analysis(
badge_knobs, analysis_outcome,
);
Ok(response)
}
Ok(analysis_outcome) => {
let response = App::status_format_analysis(
Some(analysis_outcome),
format, format,
SubjectPath::Crate(crate_path), SubjectPath::Crate(crate_path),
badge_knobs, badge_knobs,
@ -379,48 +247,62 @@ impl App {
Ok(response) Ok(response)
} }
} }
}
}
}
fn status_format_analysis(
analysis_outcome: Option<AnalyzeDependenciesOutcome>,
format: StatusFormat,
subject_path: SubjectPath,
badge_knobs: ExtraConfig,
) -> Response<Body> {
match format {
StatusFormat::Svg => views::badge::response(analysis_outcome.as_ref(), badge_knobs),
StatusFormat::Html => {
views::html::status::render(analysis_outcome, subject_path, badge_knobs)
}
}
}
fn static_file(file: StaticFile) -> Response<Body> {
match file {
StaticFile::StyleCss => Response::builder()
.header(CONTENT_TYPE, "text/css; charset=utf-8")
.header(ETAG, STATIC_STYLE_CSS_ETAG)
.header(CACHE_CONTROL, "public, max-age=365000000, immutable")
.body(Body::from(assets::STATIC_STYLE_CSS))
.unwrap(),
StaticFile::FaviconPng => Response::builder()
.header(CONTENT_TYPE, "image/svg+xml")
.body(Body::from(assets::STATIC_FAVICON))
.unwrap(),
StaticFile::LinksJs => Response::builder()
.header(CONTENT_TYPE, "text/javascript; charset=utf-8")
.header(ETAG, STATIC_LINKS_JS_ETAG)
.header(CACHE_CONTROL, "public, max-age=365000000, immutable")
.body(Body::from(assets::STATIC_LINKS_JS))
.unwrap(),
}
}
} }
fn not_found() -> Response<Body> { fn status_format_analysis(
views::html::error::render_404() analysis_outcome: Option<AnalyzeDependenciesOutcome>,
format: StatusFormat,
subject_path: SubjectPath,
badge_knobs: ExtraConfig,
) -> impl Responder {
match format {
StatusFormat::Svg => Either::Left(views::badge::response(
analysis_outcome.as_ref(),
badge_knobs,
)),
StatusFormat::Html => Either::Right(views::html::status::response(
analysis_outcome,
subject_path,
badge_knobs,
)),
}
}
pub(crate) fn static_files(cfg: &mut ServiceConfig) {
cfg.service(Resource::new(STATIC_STYLE_CSS_PATH).get(|| async {
HttpResponse::Ok()
.insert_header(ContentType(mime::TEXT_CSS_UTF_8))
.insert_header(ETag(EntityTag::new_strong(
STATIC_STYLE_CSS_ETAG.to_owned(),
)))
.insert_header(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(365000000),
CacheDirective::Immutable,
]))
.body(assets::STATIC_STYLE_CSS)
}))
.service(Resource::new(STATIC_FAVICON_PATH).get(|| async {
HttpResponse::Ok()
.insert_header(ContentType(mime::IMAGE_SVG))
.body(assets::STATIC_FAVICON)
}))
.service(Resource::new(STATIC_LINKS_JS_PATH).get(|| async {
HttpResponse::Ok()
.insert_header(ContentType(mime::APPLICATION_JAVASCRIPT_UTF_8))
.insert_header(ETag(EntityTag::new_strong(STATIC_LINKS_JS_ETAG.to_owned())))
.insert_header(CacheControl(vec![
CacheDirective::Public,
CacheDirective::MaxAge(365000000),
CacheDirective::Immutable,
]))
.body(assets::STATIC_LINKS_JS)
}));
}
pub(crate) async fn not_found() -> impl Responder {
Html::new(views::html::error::render_404().0)
} }
static SELF_BASE_URL: Lazy<String> = static SELF_BASE_URL: Lazy<String> =

View file

@ -1,5 +1,5 @@
use actix_web::{http::header::ContentType, HttpResponse};
use badge::{Badge, BadgeOptions}; use badge::{Badge, BadgeOptions};
use hyper::{header::CONTENT_TYPE, Body, Response};
use crate::{engine::AnalyzeDependenciesOutcome, server::ExtraConfig}; use crate::{engine::AnalyzeDependenciesOutcome, server::ExtraConfig};
@ -68,11 +68,10 @@ pub fn badge(
pub fn response( pub fn response(
analysis_outcome: Option<&AnalyzeDependenciesOutcome>, analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
badge_knobs: ExtraConfig, badge_knobs: ExtraConfig,
) -> Response<Body> { ) -> HttpResponse {
let badge = badge(analysis_outcome, badge_knobs).to_svg(); let badge = badge(analysis_outcome, badge_knobs).to_svg();
Response::builder() HttpResponse::Ok()
.header(CONTENT_TYPE, "image/svg+xml; charset=utf-8") .insert_header(ContentType(mime::IMAGE_SVG))
.body(Body::from(badge)) .body(badge)
.unwrap()
} }

View file

@ -1,14 +1,12 @@
use hyper::{ use maud::{html, Markup};
header::{CACHE_CONTROL, CONTENT_TYPE},
Body, Response, StatusCode,
};
use maud::html;
use crate::server::assets::STATIC_STYLE_CSS_PATH; use crate::server::assets::STATIC_STYLE_CSS_PATH;
pub fn render(title: &str, descr: &str) -> Response<Body> { pub fn render(title: impl Into<String>, desc: &str) -> Markup {
let title = title.into();
super::render_html( super::render_html(
title, title.clone(),
html! { html! {
section class="hero is-light" { section class="hero is-light" {
div class="hero-head" { (super::render_navbar()) } div class="hero-head" { (super::render_navbar()) }
@ -17,7 +15,7 @@ pub fn render(title: &str, descr: &str) -> Response<Body> {
div class="container" { div class="container" {
div class="notification is-danger" { div class="notification is-danger" {
p class="title is-3" { (title) } p class="title is-3" { (title) }
p { (descr) } p { (desc) }
} }
} }
} }
@ -26,8 +24,8 @@ pub fn render(title: &str, descr: &str) -> Response<Body> {
) )
} }
pub fn render_404() -> Response<Body> { pub fn render_404() -> Markup {
let rendered = html! { html! {
html { html {
head { head {
meta charset="utf-8"; meta charset="utf-8";
@ -53,12 +51,5 @@ pub fn render_404() -> Response<Body> {
(super::render_footer(None)) (super::render_footer(None))
} }
} }
}; }
Response::builder()
.status(StatusCode::NOT_FOUND)
.header(CONTENT_TYPE, "text/html; charset=utf-8")
.header(CACHE_CONTROL, "public, max-age=300, immutable")
.body(Body::from(rendered.0))
.unwrap()
} }

View file

@ -1,4 +1,3 @@
use hyper::{Body, Response};
use maud::{html, Markup}; use maud::{html, Markup};
use crate::{ use crate::{
@ -161,7 +160,7 @@ fn popular_table(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>)
} }
} }
pub fn render(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>) -> Response<Body> { pub fn render(popular_repos: Vec<Repository>, popular_crates: Vec<CratePath>) -> Markup {
super::render_html( super::render_html(
"Keep your dependencies up-to-date", "Keep your dependencies up-to-date",
html! { html! {

View file

@ -1,6 +1,5 @@
use std::time::Duration; use std::time::Duration;
use hyper::{header::CONTENT_TYPE, Body, Response};
use maud::{html, Markup, Render, DOCTYPE}; use maud::{html, Markup, Render, DOCTYPE};
pub mod error; pub mod error;
@ -9,8 +8,10 @@ pub mod status;
use crate::server::{assets::STATIC_STYLE_CSS_PATH, SELF_BASE_URL}; use crate::server::{assets::STATIC_STYLE_CSS_PATH, SELF_BASE_URL};
fn render_html<B: Render>(title: &str, body: B) -> Response<Body> { fn render_html<B: Render>(title: impl Into<String>, body: B) -> Markup {
let rendered = html! { let title = title.into();
html! {
(DOCTYPE) (DOCTYPE)
html { html {
head { head {
@ -24,12 +25,7 @@ fn render_html<B: Render>(title: &str, body: B) -> Response<Body> {
} }
body { (body) } body { (body) }
} }
}; }
Response::builder()
.header(CONTENT_TYPE, "text/html; charset=utf-8")
.body(Body::from(rendered.0))
.unwrap()
} }
fn render_navbar() -> Markup { fn render_navbar() -> Markup {

View file

@ -1,5 +1,6 @@
use actix_web::Responder;
use actix_web_lab::respond::Html;
use font_awesome_as_a_crate::{svg as fa, Type as FaType}; use font_awesome_as_a_crate::{svg as fa, Type as FaType};
use hyper::{Body, Response};
use indexmap::IndexMap; use indexmap::IndexMap;
use maud::{html, Markup, PreEscaped}; use maud::{html, Markup, PreEscaped};
use pulldown_cmark::{html, Parser}; use pulldown_cmark::{html, Parser};
@ -13,9 +14,11 @@ use crate::{
repo::RepoSite, repo::RepoSite,
SubjectPath, SubjectPath,
}, },
server::{views::badge, ExtraConfig}, server::{error::ServerError, views::badge, ExtraConfig},
}; };
use super::render_html;
fn get_crates_url(name: impl AsRef<str>) -> String { fn get_crates_url(name: impl AsRef<str>) -> String {
format!("https://crates.io/crates/{}", name.as_ref()) format!("https://crates.io/crates/{}", name.as_ref())
} }
@ -453,11 +456,11 @@ fn render_success(
} }
} }
pub fn render( pub fn response(
analysis_outcome: Option<AnalyzeDependenciesOutcome>, analysis_outcome: Option<AnalyzeDependenciesOutcome>,
subject_path: SubjectPath, subject_path: SubjectPath,
extra_config: ExtraConfig, extra_config: ExtraConfig,
) -> Response<Body> { ) -> actix_web::Result<impl Responder> {
let title = match subject_path { let title = match subject_path {
SubjectPath::Repo(ref repo_path) => { SubjectPath::Repo(ref repo_path) => {
format!("{} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref()) format!("{} / {}", repo_path.qual.as_ref(), repo_path.name.as_ref())
@ -468,8 +471,12 @@ pub fn render(
}; };
if let Some(outcome) = analysis_outcome { if let Some(outcome) = analysis_outcome {
super::render_html(&title, render_success(outcome, subject_path, extra_config)) Ok(Html::new(render_html(
&title,
render_success(outcome, subject_path, extra_config),
)))
} else { } else {
super::render_html(&title, render_failure(subject_path)) let html = render_html(&title, render_failure(subject_path));
Err(ServerError::AnalysisFailed(html).into())
} }
} }

View file

@ -1,7 +1,7 @@
use std::{fmt, sync::Arc, time::Duration}; use std::{fmt, sync::Arc, time::Duration};
use actix_web::dev::Service;
use derive_more::{Display, Error, From}; use derive_more::{Display, Error, From};
use hyper::service::Service;
use lru_time_cache::LruCache; use lru_time_cache::LruCache;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -65,7 +65,7 @@ where
cache = "miss", cache = "miss",
); );
let mut service = self.inner.clone(); let service = self.inner.clone();
let fresh = service.call(req.clone()).await?; let fresh = service.call(req.clone()).await?;
{ {