about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock921
-rw-r--r--Cargo.toml23
-rwxr-xr-xdev.sh2
-rw-r--r--src/database/file/mod.rs51
-rw-r--r--src/database/mod.rs38
-rw-r--r--src/lib.rs270
-rw-r--r--src/main.rs118
-rw-r--r--src/metrics.rs23
-rw-r--r--src/micropub/mod.rs128
9 files changed, 861 insertions, 713 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d4d0544..1b054ff 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -81,22 +81,25 @@ dependencies = [
 ]
 
 [[package]]
-name = "anyhow"
-version = "1.0.51"
+name = "alloc-no-stdlib"
+version = "2.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
+checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
 
 [[package]]
-name = "arrayref"
-version = "0.3.6"
+name = "alloc-stdlib"
+version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
+checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2"
+dependencies = [
+ "alloc-no-stdlib",
+]
 
 [[package]]
-name = "arrayvec"
-version = "0.5.2"
+name = "anyhow"
+version = "1.0.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
+checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
 
 [[package]]
 name = "assert-json-diff"
@@ -109,16 +112,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "async-attributes"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
-dependencies = [
- "quote",
- "syn",
-]
-
-[[package]]
 name = "async-channel"
 version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -130,13 +123,17 @@ dependencies = [
 ]
 
 [[package]]
-name = "async-dup"
-version = "1.2.2"
+name = "async-compression"
+version = "0.3.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7427a12b8dc09291528cfb1da2447059adb4a257388c2acd6497a79d55cf6f7c"
+checksum = "f2bf394cfbbe876f0ac67b13b6ca819f9c9f2fb9ec67223cceb1555fbab1c31a"
 dependencies = [
- "futures-io",
- "simple-mutex",
+ "brotli",
+ "flate2",
+ "futures-core",
+ "memchr 2.4.1",
+ "pin-project-lite",
+ "tokio",
 ]
 
 [[package]]
@@ -170,22 +167,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "async-h1"
-version = "2.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8101020758a4fc3a7c326cb42aa99e9fa77cbfb76987c128ad956406fe1f70a7"
-dependencies = [
- "async-channel",
- "async-dup",
- "async-std",
- "futures-core",
- "http-types",
- "httparse",
- "log 0.4.14",
- "pin-project",
-]
-
-[[package]]
 name = "async-io"
 version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -223,69 +204,15 @@ dependencies = [
 ]
 
 [[package]]
-name = "async-process"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6"
-dependencies = [
- "async-io",
- "blocking",
- "cfg-if 1.0.0",
- "event-listener",
- "futures-lite",
- "libc",
- "once_cell",
- "signal-hook",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "async-session"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "345022a2eed092cd105cc1b26fd61c341e100bd5fcbbd792df4baf31c2cc631f"
-dependencies = [
- "anyhow",
- "async-std",
- "async-trait",
- "base64 0.12.3",
- "bincode",
- "blake3",
- "chrono",
- "hmac 0.8.1",
- "kv-log-macro",
- "rand 0.7.3",
- "serde",
- "serde_json",
- "sha2",
-]
-
-[[package]]
-name = "async-sse"
-version = "4.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53bba003996b8fd22245cd0c59b869ba764188ed435392cf2796d03b805ade10"
-dependencies = [
- "async-channel",
- "async-std",
- "http-types",
- "log 0.4.14",
- "memchr 2.4.1",
- "pin-project-lite 0.1.12",
-]
-
-[[package]]
 name = "async-std"
 version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952"
 dependencies = [
- "async-attributes",
  "async-channel",
  "async-global-executor",
  "async-io",
  "async-lock",
- "async-process",
  "crossbeam-utils",
  "futures-channel",
  "futures-core",
@@ -297,7 +224,7 @@ dependencies = [
  "memchr 2.4.1",
  "num_cpus",
  "once_cell",
- "pin-project-lite 0.2.7",
+ "pin-project-lite",
  "pin-utils",
  "slab",
  "wasm-bindgen-futures",
@@ -321,19 +248,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "async-tls"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d85a97c4a0ecce878efd3f945f119c78a646d8975340bca0398f9bb05c30cc52"
-dependencies = [
- "futures-core",
- "futures-io",
- "rustls",
- "webpki",
- "webpki-roots",
-]
-
-[[package]]
 name = "async-trait"
 version = "0.1.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -375,51 +289,30 @@ checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
 
 [[package]]
 name = "base64"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
-
-[[package]]
-name = "base64"
 version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 
 [[package]]
-name = "bincode"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
-dependencies = [
- "serde",
-]
-
-[[package]]
 name = "bitflags"
 version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
-name = "blake3"
-version = "0.3.8"
+name = "block-buffer"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
 dependencies = [
- "arrayref",
- "arrayvec",
- "cc",
- "cfg-if 0.1.10",
- "constant_time_eq",
- "crypto-mac 0.8.0",
- "digest",
+ "generic-array",
 ]
 
 [[package]]
 name = "block-buffer"
-version = "0.9.0"
+version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
 dependencies = [
  "generic-array",
 ]
@@ -439,6 +332,37 @@ dependencies = [
 ]
 
 [[package]]
+name = "brotli"
+version = "3.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f838e47a451d5a8fa552371f80024dd6ace9b7acdf25c4c3d0f9bc6816fb1c39"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+dependencies = [
+ "memchr 2.4.1",
+ "safemem",
+]
+
+[[package]]
 name = "bumpalo"
 version = "3.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -470,12 +394,6 @@ checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
 
 [[package]]
 name = "cfg-if"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-
-[[package]]
-name = "cfg-if"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
@@ -523,7 +441,7 @@ dependencies = [
  "bytes",
  "futures-core",
  "memchr 2.4.1",
- "pin-project-lite 0.2.7",
+ "pin-project-lite",
  "tokio",
  "tokio-util",
 ]
@@ -538,29 +456,12 @@ dependencies = [
 ]
 
 [[package]]
-name = "config"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
-dependencies = [
- "lazy_static",
- "nom",
- "serde",
-]
-
-[[package]]
 name = "const_fn"
 version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f92cfa0fd5690b3cf8c1ef2cabbd9b7ef22fa53cf5e1f92b05103f6d5d1cf6e7"
 
 [[package]]
-name = "constant_time_eq"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
-
-[[package]]
 name = "convert_case"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -573,9 +474,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
 dependencies = [
  "aes-gcm",
- "base64 0.13.0",
+ "base64",
  "hkdf",
- "hmac 0.10.1",
+ "hmac",
  "percent-encoding",
  "rand 0.8.4",
  "sha2",
@@ -604,17 +505,7 @@ version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
 dependencies = [
- "cfg-if 1.0.0",
-]
-
-[[package]]
-name = "crossbeam-queue"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9"
-dependencies = [
- "cfg-if 1.0.0",
- "crossbeam-utils",
+ "cfg-if",
 ]
 
 [[package]]
@@ -623,18 +514,17 @@ version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "lazy_static",
 ]
 
 [[package]]
-name = "crypto-mac"
-version = "0.8.0"
+name = "crypto-common"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
+checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
 dependencies = [
  "generic-array",
- "subtle",
 ]
 
 [[package]]
@@ -655,7 +545,7 @@ checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a"
 dependencies = [
  "cssparser-macros",
  "dtoa-short",
- "itoa",
+ "itoa 0.4.8",
  "matches",
  "phf",
  "proc-macro2",
@@ -694,36 +584,12 @@ dependencies = [
 ]
 
 [[package]]
-name = "dashmap"
-version = "4.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
-dependencies = [
- "cfg-if 1.0.0",
- "num_cpus",
-]
-
-[[package]]
 name = "data-encoding"
 version = "2.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
 
 [[package]]
-name = "deadpool"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d126179d86aee4556e54f5f3c6bf6d9884e7cc52cef82f77ee6f90a7747616d"
-dependencies = [
- "async-trait",
- "config",
- "crossbeam-queue",
- "num_cpus",
- "serde",
- "tokio",
-]
-
-[[package]]
 name = "derive_more"
 version = "0.99.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -752,6 +618,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "digest"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
+dependencies = [
+ "block-buffer 0.10.2",
+ "crypto-common",
+]
+
+[[package]]
 name = "discard"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -793,15 +669,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "encoding_rs"
-version = "0.8.29"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
-dependencies = [
- "cfg-if 1.0.0",
-]
-
-[[package]]
 name = "env_logger"
 version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -845,34 +712,18 @@ version = "3.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cfc110fe50727d46a428eed832df40affe9bf74d077cac1bf3f2718e823f14c5"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "windows-sys",
 ]
 
 [[package]]
-name = "femme"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2af1a24f391a5a94d756db5092c6576aad494b88a71a5a36b20c67b63e0df034"
-dependencies = [
- "cfg-if 0.1.10",
- "js-sys",
- "log 0.4.14",
- "serde",
- "serde_derive",
- "serde_json",
- "wasm-bindgen",
- "web-sys",
-]
-
-[[package]]
 name = "flate2"
 version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "crc32fast",
  "libc",
  "miniz_oxide",
@@ -969,7 +820,7 @@ dependencies = [
  "futures-io",
  "memchr 2.4.1",
  "parking",
- "pin-project-lite 0.2.7",
+ "pin-project-lite",
  "waker-fn",
 ]
 
@@ -1009,7 +860,7 @@ dependencies = [
  "futures-sink",
  "futures-task",
  "memchr 2.4.1",
- "pin-project-lite 0.2.7",
+ "pin-project-lite",
  "pin-utils",
  "slab",
 ]
@@ -1039,7 +890,7 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
@@ -1050,7 +901,7 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "wasi 0.10.0+wasi-snapshot-preview1",
 ]
@@ -1079,6 +930,56 @@ dependencies = [
 ]
 
 [[package]]
+name = "h2"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "headers"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes",
+ "headers-core",
+ "http",
+ "httpdate",
+ "mime",
+ "sha-1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
+[[package]]
 name = "hermit-abi"
 version = "0.1.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1099,18 +1000,8 @@ version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
 dependencies = [
- "digest",
- "hmac 0.10.1",
-]
-
-[[package]]
-name = "hmac"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840"
-dependencies = [
- "crypto-mac 0.8.0",
- "digest",
+ "digest 0.9.0",
+ "hmac",
 ]
 
 [[package]]
@@ -1119,8 +1010,8 @@ version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
 dependencies = [
- "crypto-mac 0.10.1",
- "digest",
+ "crypto-mac",
+ "digest 0.9.0",
 ]
 
 [[package]]
@@ -1138,22 +1029,25 @@ dependencies = [
 ]
 
 [[package]]
-name = "http-client"
-version = "6.5.1"
+name = "http"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea880b03c18a7e981d7fb3608b8904a98425d53c440758fcebf7d934aa56547c"
+checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
 dependencies = [
- "async-h1",
- "async-std",
- "async-tls",
- "async-trait",
- "cfg-if 1.0.0",
- "dashmap",
- "deadpool",
- "futures",
- "http-types",
- "log 0.4.14",
- "rustls",
+ "bytes",
+ "fnv",
+ "itoa 1.0.1",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
 ]
 
 [[package]]
@@ -1165,11 +1059,11 @@ dependencies = [
  "anyhow",
  "async-channel",
  "async-std",
- "base64 0.13.0",
+ "base64",
  "cookie",
  "futures-lite",
  "infer",
- "pin-project-lite 0.2.7",
+ "pin-project-lite",
  "rand 0.7.3",
  "serde",
  "serde_json",
@@ -1180,9 +1074,15 @@ dependencies = [
 
 [[package]]
 name = "httparse"
-version = "1.5.1"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
 
 [[package]]
 name = "humantime"
@@ -1191,6 +1091,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 [[package]]
+name = "hyper"
+version = "0.14.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa 1.0.1",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
 name = "idna"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1202,6 +1126,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "indexmap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
 name = "infer"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1213,7 +1147,7 @@ version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -1223,6 +1157,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
 
 [[package]]
+name = "itoa"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
+
+[[package]]
 name = "js-sys"
 version = "0.3.55"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1246,7 +1186,6 @@ name = "kittybox"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "async-std",
  "async-trait",
  "chrono",
  "data-encoding",
@@ -1257,6 +1196,7 @@ dependencies = [
  "futures",
  "futures-util",
  "http-types",
+ "hyper",
  "lazy_static",
  "log 0.4.14",
  "markdown",
@@ -1273,12 +1213,11 @@ dependencies = [
  "serde_json",
  "serde_urlencoded",
  "sha2",
- "surf",
  "tempdir",
  "test-logger",
- "tide",
- "tide-testing",
+ "tokio",
  "url",
+ "warp",
 ]
 
 [[package]]
@@ -1309,23 +1248,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
-name = "lexical-core"
-version = "0.7.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
-dependencies = [
- "arrayvec",
- "bitflags",
- "cfg-if 1.0.0",
- "ryu",
- "static_assertions",
-]
-
-[[package]]
 name = "libc"
-version = "0.2.109"
+version = "0.2.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f98a04dce437184842841303488f70d0188c5f51437d2a834dc097eafa909a01"
+checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c"
 
 [[package]]
 name = "lock_api"
@@ -1351,7 +1277,7 @@ version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "value-bag",
 ]
 
@@ -1378,7 +1304,7 @@ version = "0.12.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e56549270844a0e513d26db15562a783dd282e351baec8650c6f4c1bcaee54ef"
 dependencies = [
- "itoa",
+ "itoa 0.4.8",
  "markup-proc-macro",
 ]
 
@@ -1455,6 +1381,28 @@ dependencies = [
 ]
 
 [[package]]
+name = "mio"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
+dependencies = [
+ "libc",
+ "log 0.4.14",
+ "miow",
+ "ntapi",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
 name = "mockito"
 version = "0.30.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1473,6 +1421,24 @@ dependencies = [
 ]
 
 [[package]]
+name = "multipart"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
+dependencies = [
+ "buf_redux",
+ "httparse",
+ "log 0.4.14",
+ "mime",
+ "mime_guess",
+ "quick-error",
+ "rand 0.8.4",
+ "safemem",
+ "tempfile",
+ "twoway",
+]
+
+[[package]]
 name = "new_debug_unreachable"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1491,14 +1457,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
 
 [[package]]
-name = "nom"
-version = "5.1.2"
+name = "ntapi"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
+checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
 dependencies = [
- "lexical-core",
- "memchr 2.4.1",
- "version_check",
+ "winapi 0.3.9",
 ]
 
 [[package]]
@@ -1565,7 +1529,7 @@ version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "instant",
  "libc",
  "redox_syscall",
@@ -1641,18 +1605,18 @@ dependencies = [
 
 [[package]]
 name = "pin-project"
-version = "1.0.8"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08"
+checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.0.8"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389"
+checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1661,12 +1625,6 @@ dependencies = [
 
 [[package]]
 name = "pin-project-lite"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
-
-[[package]]
-name = "pin-project-lite"
 version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
@@ -1689,7 +1647,7 @@ version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "log 0.4.14",
  "wepoll-ffi",
@@ -1754,7 +1712,7 @@ version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5986aa8d62380092d2f50f8b1cdba9cb9b6731ffd4b25b51fd126b6c3e05b99c"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "fnv",
  "lazy_static",
  "libc",
@@ -1772,6 +1730,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "47c327e191621a2158159df97cdbc2e7074bb4e940275e35abf38eb3d2595754"
 
 [[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
 name = "quote"
 version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1914,15 +1878,14 @@ version = "0.21.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f23ceed4c0e76b322657c2c3352ea116f9ec60a1a1aefeb3c84ed062c50865b"
 dependencies = [
- "async-std",
  "async-trait",
  "bytes",
  "combine",
  "dtoa",
  "futures-util",
- "itoa",
+ "itoa 0.4.8",
  "percent-encoding",
- "pin-project-lite 0.2.7",
+ "pin-project-lite",
  "sha1",
  "tokio",
  "tokio-util",
@@ -2002,27 +1965,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "ring"
-version = "0.16.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
-dependencies = [
- "cc",
- "libc",
- "once_cell",
- "spin",
- "untrusted",
- "web-sys",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "route-recognizer"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56770675ebc04927ded3e60633437841581c285dc6236109ea25fbf3beb7b59e"
-
-[[package]]
 name = "rustc_version"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2041,39 +1983,28 @@ dependencies = [
 ]
 
 [[package]]
-name = "rustls"
-version = "0.18.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
-dependencies = [
- "base64 0.12.3",
- "log 0.4.14",
- "ring",
- "sct",
- "webpki",
-]
-
-[[package]]
 name = "ryu"
 version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
 
 [[package]]
-name = "scopeguard"
-version = "1.1.0"
+name = "safemem"
+version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
 
 [[package]]
-name = "sct"
-version = "0.6.1"
+name = "scoped-tls"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
-dependencies = [
- "ring",
- "untrusted",
-]
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
 name = "selectors"
@@ -2142,7 +2073,7 @@ version = "1.0.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
 dependencies = [
- "itoa",
+ "itoa 0.4.8",
  "ryu",
  "serde",
 ]
@@ -2165,7 +2096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
 dependencies = [
  "form_urlencoded",
- "itoa",
+ "itoa 0.4.8",
  "ryu",
  "serde",
 ]
@@ -2181,6 +2112,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "sha-1"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.10.2",
+]
+
+[[package]]
 name = "sha1"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2192,24 +2134,14 @@ version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
 dependencies = [
- "block-buffer",
- "cfg-if 1.0.0",
+ "block-buffer 0.9.0",
+ "cfg-if",
  "cpufeatures",
- "digest",
+ "digest 0.9.0",
  "opaque-debug",
 ]
 
 [[package]]
-name = "signal-hook"
-version = "0.3.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c98891d737e271a2954825ef19e46bd16bdb98e2746f2eec4f7a4ef7946efd1"
-dependencies = [
- "libc",
- "signal-hook-registry",
-]
-
-[[package]]
 name = "signal-hook-registry"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2219,15 +2151,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "simple-mutex"
-version = "1.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38aabbeafa6f6dead8cebf246fe9fae1f9215c8d29b3a69f93bd62a9e4a3dcd6"
-dependencies = [
- "event-listener",
-]
-
-[[package]]
 name = "siphasher"
 version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2256,12 +2179,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "spin"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-
-[[package]]
 name = "stable_deref_trait"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2277,12 +2194,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
-[[package]]
 name = "stdweb"
 version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2364,36 +2275,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 
 [[package]]
-name = "surf"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "718b1ae6b50351982dedff021db0def601677f2120938b070eadb10ba4038dd7"
-dependencies = [
- "async-std",
- "async-trait",
- "cfg-if 1.0.0",
- "encoding_rs",
- "futures-util",
- "getrandom 0.2.3",
- "http-client",
- "http-types",
- "log 0.4.14",
- "mime_guess",
- "once_cell",
- "pin-project-lite 0.2.7",
- "rustls",
- "serde",
- "serde_json",
- "web-sys",
-]
-
-[[package]]
-name = "sval"
-version = "1.0.0-alpha.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08"
-
-[[package]]
 name = "syn"
 version = "1.0.82"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2415,6 +2296,20 @@ dependencies = [
 ]
 
 [[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand 0.8.4",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi 0.3.9",
+]
+
+[[package]]
 name = "tendril"
 version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2489,41 +2384,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "tide"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c459573f0dd2cc734b539047f57489ea875af8ee950860ded20cf93a79a1dee0"
-dependencies = [
- "async-h1",
- "async-session",
- "async-sse",
- "async-std",
- "async-trait",
- "femme",
- "futures-util",
- "http-client",
- "http-types",
- "kv-log-macro",
- "log 0.4.14",
- "pin-project-lite 0.2.7",
- "route-recognizer",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "tide-testing"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a59ea33dec6d205e4173cf7825dcfc78600c1726d931132d99b38b932495111"
-dependencies = [
- "serde",
- "serde_json",
- "surf",
- "tide",
-]
-
-[[package]]
 name = "time"
 version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2589,14 +2449,43 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
 
 [[package]]
 name = "tokio"
-version = "1.14.0"
+version = "1.16.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
+checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a"
 dependencies = [
- "autocfg",
  "bytes",
+ "libc",
  "memchr 2.4.1",
- "pin-project-lite 0.2.7",
+ "mio",
+ "num_cpus",
+ "once_cell",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "tokio-macros",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
 ]
 
 [[package]]
@@ -2609,11 +2498,53 @@ dependencies = [
  "futures-core",
  "futures-sink",
  "log 0.4.14",
- "pin-project-lite 0.2.7",
+ "pin-project-lite",
  "tokio",
 ]
 
 [[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d8d93354fe2a8e50d5953f5ae2e47a3fc2ef03292e7ea46e3cc38f549525fb9"
+dependencies = [
+ "cfg-if",
+ "log 0.4.14",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "twoway"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
+dependencies = [
+ "memchr 2.4.1",
+]
+
+[[package]]
 name = "typenum"
 version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2666,12 +2597,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "untrusted"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
-
-[[package]]
 name = "url"
 version = "2.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2703,7 +2628,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f"
 dependencies = [
  "ctor",
- "sval",
  "version_check",
 ]
 
@@ -2720,6 +2644,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
 
 [[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log 0.4.14",
+ "try-lock",
+]
+
+[[package]]
+name = "warp"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e"
+dependencies = [
+ "async-compression",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "headers",
+ "http",
+ "hyper",
+ "log 0.4.14",
+ "mime",
+ "mime_guess",
+ "multipart",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
 name = "wasi"
 version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2737,9 +2701,7 @@ version = "0.2.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
 dependencies = [
- "cfg-if 1.0.0",
- "serde",
- "serde_json",
+ "cfg-if",
  "wasm-bindgen-macro",
 ]
 
@@ -2764,7 +2726,7 @@ version = "0.4.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -2810,25 +2772,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "webpki"
-version = "0.21.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
-dependencies = [
- "ring",
- "untrusted",
-]
-
-[[package]]
-name = "webpki-roots"
-version = "0.20.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
-dependencies = [
- "webpki",
-]
-
-[[package]]
 name = "wepoll-ffi"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 44115df..8b43c63 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Vika <vika@fireburn.ru>"]
 edition = "2021"
 
 [features]
-default = ["util"]
+default = []
 util = ["anyhow"]
 
 [[bin]]
@@ -26,7 +26,6 @@ required-features = ["util", "redis"]
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dev-dependencies]
-tide-testing = "^0.1.3"      # tide testing helper
 mockito = "^0.30.0"          # HTTP mocking for Rust.
 tempdir = "^0.3.7"           # A library for managing a temporary directory and deleting all contents when it's dropped
 paste = "^1.0.5"             # Macros for all your token pasting needs
@@ -51,32 +50,34 @@ rand = "^0.8.4"              # Random number generators.
 retainer = "^0.2.2"          # Minimal async cache in Rust with support for key expirations
 serde_json = "^1.0.64"       # A JSON serialization file format
 serde_urlencoded = "^0.7.0"  # `x-www-form-urlencoded` meets Serde
-tide = "^0.16.0"             # A minimal and pragmatic Rust web application framework built for rapid development
 relative-path = "^1.5.0"     # Portable relative paths for Rust
 sha2 = "^0.9.8"              # SHA-2 series of algorithms for Rust
+[dependencies.tokio]
+version = "^1.16.1"
+features = ["full"] # TODO determine if my app doesn't need some features
 [dependencies.anyhow]
 version = "^1.0.42"
 optional = true
-[dependencies.async-std]     # Async version of the Rust standard library
-version = "^1.9.0"
-features = ["attributes", "unstable"]
 [dependencies.chrono]        # Date and time library for Rust
 version = "^0.4.19"
 features = ["serde"]
 [dependencies.redis]
 version = "^0.21.3"
 optional = true
-features = ["aio", "async-std-comp"]
+features = ["aio", "tokio-comp"]
 [dependencies.prometheus]    # Prometheus instrumentation library for Rust applications
 version = "^0.12.0"
 features = ["process"]
 [dependencies.serde]         # A generic serialization/deserialization framework
 version = "^1.0.125"
 features = ["derive"]
-[dependencies.surf]          # Surf the web - HTTP client framework
-version = "^2.2.0"
-default-features = false
-features = ["h1-client-rustls", "encoding", "middleware-logger"]
 [dependencies.url]           # URL library for Rust, based on the WHATWG URL Standard
 version = "^2.2.1"
 features = ["serde"]
+[dependencies.warp]
+version = "^0.3.2"
+default-features = false
+features = ["multipart", "compression"]
+[dependencies.hyper]
+version = "^0.14.17"
+features = ["client", "stream", "runtime"]
\ No newline at end of file
diff --git a/dev.sh b/dev.sh
index 1faca15..6ebbd2a 100755
--- a/dev.sh
+++ b/dev.sh
@@ -1,5 +1,5 @@
 #!/bin/sh
-export RUST_LOG="debug,retainer::cache=warn,html5ever=info"
+export RUST_LOG="debug,retainer::cache=warn,html5ever=info,hyper::proto=info"
 export BACKEND_URI=file://./test-dir
 export TOKEN_ENDPOINT=https://token.indieauth.com/token
 export AUTHORIZATION_ENDPOINT=https://indieauth.com/auth
diff --git a/src/database/file/mod.rs b/src/database/file/mod.rs
index 3717023..6cbe3c6 100644
--- a/src/database/file/mod.rs
+++ b/src/database/file/mod.rs
@@ -2,11 +2,10 @@ use crate::database::{filter_post, ErrorKind, Result, Storage, StorageError};
 use std::fs::{File, OpenOptions};
 use std::io::{ErrorKind as IOErrorKind, Seek, SeekFrom, Read, Write};
 use std::time::Duration;
-use async_std::future::TimeoutError;
-use async_std::task::spawn_blocking;
+use tokio::task::spawn_blocking;
 use async_trait::async_trait;
 use fd_lock::RwLock;
-use futures::stream;
+use futures_util::stream;
 use futures_util::StreamExt;
 use futures_util::TryStreamExt;
 use log::debug;
@@ -27,8 +26,8 @@ impl From<std::io::Error> for StorageError {
     }
 }
 
-impl From<TimeoutError> for StorageError {
-    fn from(source: TimeoutError) -> Self {
+impl From<tokio::time::error::Elapsed> for StorageError {
+    fn from(source: tokio::time::error::Elapsed) -> Self {
         Self::with_source(
             ErrorKind::Backend,
             "timeout on I/O operation",
@@ -259,14 +258,14 @@ impl Storage for FileStorage {
     async fn post_exists(&self, url: &str) -> Result<bool> {
         let path = url_to_path(&self.root_dir, url);
         debug!("Checking if {:?} exists...", path);
-        Ok(spawn_blocking(move || path.is_file()).await)
+        Ok(spawn_blocking(move || path.is_file()).await.unwrap())
     }
 
     async fn get_post(&self, url: &str) -> Result<Option<serde_json::Value>> {
         let path = url_to_path(&self.root_dir, url);
         debug!("Opening {:?}", path);
         // Use exclusively synchronous operations to never transfer a lock over an await boundary
-        async_std::future::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
+        tokio::time::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
             match File::open(&path) {
                 Ok(file) => {
                     let lock = RwLock::new(file);
@@ -289,7 +288,7 @@ impl Storage for FileStorage {
                     }
                 }
             }
-        })).await?
+        })).await?.unwrap()
     }
 
     async fn put_post<'a>(&self, post: &'a serde_json::Value, user: &'a str) -> Result<()> {
@@ -303,7 +302,7 @@ impl Storage for FileStorage {
         let post_json = post.to_string();
         let post_path = path.clone();
         // Use exclusively synchronous operations to never transfer a lock over an await boundary
-        async_std::future::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
+        tokio::time::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
             let parent = post_path.parent().unwrap().to_owned();
             if !parent.is_dir() {
                 std::fs::create_dir_all(post_path.parent().unwrap())?;
@@ -323,7 +322,7 @@ impl Storage for FileStorage {
             drop(guard);
 
             Result::Ok(())
-        })).await??;
+        })).await?.unwrap()?;
 
         if post["properties"]["url"].is_array() {
             for url in post["properties"]["url"]
@@ -345,7 +344,7 @@ impl Storage for FileStorage {
                     })?;
                     let relative = path_relative_from(&orig, basedir).unwrap();
                     println!("{:?} - {:?} = {:?}", &orig, &basedir, &relative);
-                    async_std::future::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
+                    tokio::time::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
                         println!("Created a symlink at {:?}", &link);
                         let symlink_result;
                         #[cfg(unix)]
@@ -362,7 +361,7 @@ impl Storage for FileStorage {
                         } else {
                             Result::Ok(())
                         }
-                    })).await??;
+                    })).await?.unwrap()?;
                 }
             }
         }
@@ -386,7 +385,7 @@ impl Storage for FileStorage {
                 .unwrap_or_else(String::default);
             let key = key.to_string();
             drop(post);
-            async_std::future::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
+            tokio::time::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
                 let file = OpenOptions::new()
                     .read(true)
                     .write(true)
@@ -417,15 +416,15 @@ impl Storage for FileStorage {
                 (*guard).write_all(serde_json::to_string(&channels)?.as_bytes())?;
 
                 Result::Ok(())
-            })).await??;
+            })).await?.unwrap()?;
         }
         Ok(())
     }
 
     async fn update_post<'a>(&self, url: &'a str, update: serde_json::Value) -> Result<()> {
         let path = url_to_path(&self.root_dir, url);
-
-        let (old_json, new_json) = async_std::future::timeout(
+        #[allow(unused_variables)]
+        let (old_json, new_json) = tokio::time::timeout(
             Duration::from_secs(IO_TIMEOUT),
             spawn_blocking(move || {
                 let f = OpenOptions::new()
@@ -450,7 +449,7 @@ impl Storage for FileStorage {
 
                 Result::Ok((json, new_json))
             })
-        ).await??;
+        ).await?.unwrap()?;
         // TODO check if URLs changed between old and new JSON
         Ok(())
     }
@@ -461,7 +460,7 @@ impl Storage for FileStorage {
         path.push("channels");
 
         let path = path.to_path(&self.root_dir);
-        async_std::future::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
+        tokio::time::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
             match File::open(&path) {
                 Ok(f) => {
                     let lock = RwLock::new(f);
@@ -484,7 +483,7 @@ impl Storage for FileStorage {
                     }
                 }
             }
-        })).await?
+        })).await?.unwrap()
     }
 
     async fn read_feed_with_limit<'a>(
@@ -548,7 +547,7 @@ impl Storage for FileStorage {
 
     async fn delete_post<'a>(&self, url: &'a str) -> Result<()> {
         let path = url_to_path(&self.root_dir, url);
-        if let Err(e) = async_std::fs::remove_file(path).await {
+        if let Err(e) = tokio::fs::remove_file(path).await {
             Err(e.into())
         } else {
             // TODO check for dangling references in the channel list
@@ -565,7 +564,7 @@ impl Storage for FileStorage {
 
         let path = path.to_path(&self.root_dir);
         let setting = setting.to_string();
-        async_std::future::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
+        tokio::time::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
             let lock = RwLock::new(File::open(path)?);
             let guard = lock.read()?;
 
@@ -579,7 +578,7 @@ impl Storage for FileStorage {
                 .get(&setting)
                 .cloned()
                 .ok_or_else(|| StorageError::new(ErrorKind::Backend, "Setting not set"))
-        })).await?
+        })).await?.unwrap()
     }
 
     async fn set_setting<'a>(&self, setting: &'a str, user: &'a str, value: &'a str) -> Result<()> {
@@ -591,13 +590,13 @@ impl Storage for FileStorage {
         let path = path.to_path(&self.root_dir);
 
         let parent = path.parent().unwrap().to_owned();
-        if !spawn_blocking(move || parent.is_dir()).await {
-            async_std::fs::create_dir_all(path.parent().unwrap()).await?;
+        if !spawn_blocking(move || parent.is_dir()).await.unwrap() {
+            tokio::fs::create_dir_all(path.parent().unwrap()).await?;
         }
 
         let (setting, value) = (setting.to_string(), value.to_string());
 
-        async_std::future::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
+        tokio::time::timeout(Duration::from_secs(IO_TIMEOUT), spawn_blocking(move || {
             let file = OpenOptions::new()
                 .write(true)
                 .read(true)
@@ -622,6 +621,6 @@ impl Storage for FileStorage {
             (&mut *guard).set_len(0)?;
             (&mut *guard).write_all(serde_json::to_string(&settings)?.as_bytes())?;
             Result::Ok(())
-        })).await?
+        })).await?.unwrap()
     }
 }
diff --git a/src/database/mod.rs b/src/database/mod.rs
index c0f9f29..55ab027 100644
--- a/src/database/mod.rs
+++ b/src/database/mod.rs
@@ -2,13 +2,6 @@
 use async_trait::async_trait;
 use serde::{Deserialize, Serialize};
 
-//#[cfg(feature="redis")]
-//mod redis;
-//#[cfg(feature="redis")]
-//pub use crate::database::redis::RedisStorage;
-//#[cfg(all(redis, test))]
-//pub use redis::tests::{get_redis_instance, RedisInstance};
-
 mod file;
 pub use crate::database::file::FileStorage;
 
@@ -49,7 +42,7 @@ pub struct StorageError {
     kind: ErrorKind,
 }
 
-impl From<StorageError> for tide::Response {
+/*impl From<StorageError> for tide::Response {
     fn from(err: StorageError) -> Self {
         tide::Response::builder(match err.kind() {
             ErrorKind::BadRequest => 400,
@@ -66,7 +59,8 @@ impl From<StorageError> for tide::Response {
         }))
         .build()
     }
-}
+}*/
+
 impl std::error::Error for StorageError {
     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
         self.source
@@ -431,24 +425,7 @@ mod tests {
         );
     }
 
-    /*macro_rules! redis_test {
-        ($func_name:expr) => {
-            paste! {
-                #[cfg(feature="redis")]
-                #[async_std::test]
-                async fn [<redis_ $func_name>] () {
-                    test_logger::ensure_env_logger_initialized();
-                    let redis_instance = get_redis_instance().await;
-                    let backend = super::RedisStorage::new(redis_instance.uri().to_string())
-                        .await
-                        .unwrap();
-                    $func_name(backend).await
-                }
-            }
-        }
-    }*/
-
-    macro_rules! file_test {
+    /*macro_rules! file_test {
         ($func_name:expr) => {
             paste! {
                 #[async_std::test]
@@ -461,13 +438,10 @@ mod tests {
             }
         };
     }
-
-    /*redis_test!(test_backend_basic_operations);
-    redis_test!(test_backend_get_channel_list);
-    redis_test!(test_backend_settings);
-    redis_test!(test_backend_update);*/
+    
     file_test!(test_backend_basic_operations);
     file_test!(test_backend_get_channel_list);
     file_test!(test_backend_settings);
     file_test!(test_backend_update);
+    */
 }
diff --git a/src/lib.rs b/src/lib.rs
index 2b4d1cc..2585227 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,87 +1,172 @@
-use tide::{Request, Response};
-
-/// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database.
-pub mod database;
+//use tide::{Request, Response};
+use warp::Filter;
+/*pub mod database;
 mod frontend;
 mod indieauth;
-mod metrics;
-mod micropub;
+mod micropub;*/
+pub mod metrics;
+/// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database.
+pub mod database;
+pub mod micropub;
+//pub mod indieauth;
 
-use crate::indieauth::IndieAuthMiddleware;
-use crate::micropub::CORSMiddleware;
+/*use crate::indieauth::IndieAuthMiddleware;
+use crate::micropub::CORSMiddleware;*/
 
-#[derive(Clone)]
-pub struct ApplicationState<StorageBackend>
-where
-    StorageBackend: database::Storage + Send + Sync + 'static,
-{
-    token_endpoint: surf::Url,
-    authorization_endpoint: surf::Url,
-    media_endpoint: Option<String>,
-    internal_token: Option<String>,
-    cookie_secret: String,
-    http_client: surf::Client,
-    storage: StorageBackend,
+pub mod rejections {
+    #[derive(Debug)]
+    pub struct UnacceptableContentType;
+    impl warp::reject::Reject for UnacceptableContentType {}
+
+    #[derive(Debug)]
+    pub struct HostHeaderUnset;
+    impl warp::reject::Reject for HostHeaderUnset {}
 }
 
-type App<Storage> = tide::Server<ApplicationState<Storage>>;
-
-static MICROPUB_CLIENT: &[u8] = include_bytes!("./index.html");
-
-fn equip_app<Storage>(mut app: App<Storage>) -> App<Storage>
-where
-    Storage: database::Storage + Send + Sync + Clone,
-{
-    app.at("/micropub")
-        .with(CORSMiddleware {})
-        .with(IndieAuthMiddleware::new())
-        .get(micropub::get_handler)
-        .post(micropub::post_handler);
-    // The Micropub client. It'll start small, but could grow into something full-featured
-    app.at("/micropub/client").get(|_: Request<_>| async move {
-        Ok(Response::builder(200)
-            .body(MICROPUB_CLIENT)
-            .content_type("text/html")
-            .build())
-    });
-    app.at("/")
-        .with(CORSMiddleware {})
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::mainpage)
-        .post(frontend::onboarding_receiver);
-    app.at("/login")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::login::form)
-        .post(frontend::login::handler);
-    app.at("/login/callback")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::login::callback);
-    app.at("/static/*path")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::handle_static);
-    app.at("/*path")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::render_post);
-    app.at("/coffee")
-        .with(frontend::ErrorHandlerMiddleware {})
-        .get(frontend::coffee);
-    // TODO make sure the health check actually checks the backend or something
-    // otherwise it'll get false-negatives for application faults like resource
-    // exhaustion
-    app.at("/health").get(|_| async { Ok("OK") });
-    app.at("/metrics").get(metrics::gather);
-
-    app.with(metrics::InstrumentationMiddleware {});
-    app.with(
-        tide::sessions::SessionMiddleware::new(
-            tide::sessions::CookieStore::new(),
-            app.state().cookie_secret.as_bytes(),
-        )
-        .with_cookie_name("kittybox_session")
-        .without_save_unchanged(),
-    );
-    app
+pub static MICROPUB_CLIENT: &[u8] = include_bytes!("./index.html");
+
+pub mod util {
+    use warp::{Filter, host::Authority};
+    use super::rejections;
+
+    pub fn require_host() -> impl Filter<Extract = (Authority,), Error = warp::Rejection> + Copy {
+        warp::host::optional()
+            .and_then(|authority: Option<Authority>| async move {
+                authority.ok_or_else(|| warp::reject::custom(rejections::HostHeaderUnset))
+            })
+    }
+
+    pub fn template<R>(
+        template: R
+    ) -> impl warp::Reply
+    where
+        R: markup::Render + std::fmt::Display
+    {
+        warp::reply::html(template.to_string())
+    }
+
+    pub fn parse_accept() -> impl Filter<Extract = (http_types::Mime,), Error = warp::Rejection> + Copy {
+        warp::header::value("Accept").and_then(|accept: warp::http::HeaderValue| async move {
+            let mut accept: http_types::content::Accept = {
+                // This is unneccesarily complicated because I want to reuse some http-types parsing
+                // and http-types has constructor for Headers private so I need to construct
+                // a mock Request to reason about headers... this is so dumb wtf
+                let bytes: &[u8] = accept.as_bytes();
+                let value = http_types::headers::HeaderValue::from_bytes(bytes.to_vec()).unwrap();
+                let values: http_types::headers::HeaderValues = vec![value].into();
+                let mut request = http_types::Request::new(http_types::Method::Get, "http://example.com/");
+                request.append_header("Accept".parse::<http_types::headers::HeaderName>().unwrap(), &values);
+                http_types::content::Accept::from_headers(&request).unwrap().unwrap()
+            };
+
+            // This code is INCREDIBLY dumb, honestly...
+            // why did I even try to use it?
+            // TODO vendor this stuff in so I can customize it
+            match accept.negotiate(&[
+                "text/html; encoding=\"utf-8\"".into(),
+                "application/json; encoding=\"utf-8\"".into(),
+                "text/html".into(),
+                "application/json".into(),
+
+            ]) {
+                Ok(mime) => {
+                    Ok(http_types::Mime::from(mime.value().as_str()))
+                },
+                Err(err) => {
+                    log::error!("Content-Type negotiation error: {:?}, accepting: {:?}", err, accept);
+                    Err(warp::reject::custom(rejections::UnacceptableContentType))
+                }
+            }
+        })
+    }
+
+    mod tests {
+        #[tokio::test]
+        async fn test_require_host_with_host() {
+            use super::require_host;
+
+            let filter = require_host();
+
+            let res = warp::test::request()
+                .path("/")
+                .header("Host", "localhost:8080")
+                .filter(&filter)
+                .await
+                .unwrap();
+
+            assert_eq!(res, "localhost:8080");
+            
+        }
+
+        #[tokio::test]
+        async fn test_require_host_no_host() {
+            use super::require_host;
+
+            let filter = require_host();
+
+            let res = warp::test::request()
+                .path("/")
+                .filter(&filter)
+                .await;
+
+            assert!(res.is_err());
+        }
+    }
 }
+// fn equip_app<Storage>(mut app: App<Storage>) -> App<Storage>
+// where
+//     Storage: database::Storage + Send + Sync + Clone,
+// {
+//     app.at("/micropub")
+//         .with(CORSMiddleware {})
+//         .with(IndieAuthMiddleware::new())
+//         .get(micropub::get_handler)
+//         .post(micropub::post_handler);
+//     // The Micropub client. It'll start small, but could grow into something full-featured
+//     app.at("/micropub/client").get(|_: Request<_>| async move {
+//         Ok(Response::builder(200)
+//             .body(MICROPUB_CLIENT)
+//             .content_type("text/html")
+//             .build())
+//     });
+//     app.at("/")
+//         .with(CORSMiddleware {})
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::mainpage)
+//         .post(frontend::onboarding_receiver);
+//     app.at("/login")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::login::form)
+//         .post(frontend::login::handler);
+//     app.at("/login/callback")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::login::callback);
+//     app.at("/static/*path")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::handle_static);
+//     app.at("/*path")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::render_post);
+//     app.at("/coffee")
+//         .with(frontend::ErrorHandlerMiddleware {})
+//         .get(frontend::coffee);
+//     // TODO make sure the health check actually checks the backend or something
+//     // otherwise it'll get false-negatives for application faults like resource
+//     // exhaustion
+//     app.at("/health").get(|_| async { Ok("OK") });
+//     app.at("/metrics").get(metrics::gather);
+
+//     app.with(metrics::InstrumentationMiddleware {});
+//     app.with(
+//         tide::sessions::SessionMiddleware::new(
+//             tide::sessions::CookieStore::new(),
+//             app.state().cookie_secret.as_bytes(),
+//         )
+//         .with_cookie_name("kittybox_session")
+//         .without_save_unchanged(),
+//     );
+//     app
+// }
 
 /*#[cfg(feature="redis")]
 pub async fn get_app_with_redis(
@@ -103,30 +188,7 @@ pub async fn get_app_with_redis(
     equip_app(app)
 }*/
 
-pub async fn get_app_with_file(
-    token_endpoint: surf::Url,
-    authorization_endpoint: surf::Url,
-    backend_uri: String,
-    media_endpoint: Option<String>,
-    cookie_secret: String,
-    internal_token: Option<String>,
-) -> App<database::FileStorage> {
-    let folder = backend_uri.strip_prefix("file://").unwrap();
-    let path = std::path::PathBuf::from(folder);
-    let app = tide::with_state(ApplicationState {
-        token_endpoint,
-        media_endpoint,
-        authorization_endpoint,
-        internal_token,
-        cookie_secret,
-        storage: database::FileStorage::new(path).await.unwrap(),
-        http_client: surf::Client::new(),
-    });
-
-    equip_app(app)
-}
-
-#[cfg(test)]
+/*#[cfg(test)]
 pub async fn get_app_with_test_file(
     token_endpoint: surf::Url,
 ) -> (
@@ -151,7 +213,7 @@ pub async fn get_app_with_test_file(
     (tempdir, backend, equip_app(app))
 }
 
-/*#[cfg(all(redis, test))]
+#[cfg(all(redis, test))]
 pub async fn get_app_with_test_redis(
     token_endpoint: surf::Url,
 ) -> (
@@ -176,7 +238,7 @@ pub async fn get_app_with_test_redis(
     (redis_instance, backend, equip_app(app))
 }*/
 
-#[cfg(test)]
+/*#[cfg(test)]
 #[allow(unused_variables)]
 mod tests {
     use super::*;
@@ -459,4 +521,4 @@ mod tests {
         assert_eq!(new_feed["children"][0].as_str().unwrap(), uid);
         assert_eq!(new_feed["children"][1].as_str().unwrap(), first_uid);
     }
-}
+}*/
diff --git a/src/main.rs b/src/main.rs
index 79e0cf5..4036d46 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,10 @@
 use log::{debug, error, info};
 use std::env;
-use surf::Url;
+use http_types::Url;
+use warp::{Filter, host::Authority, path::FullPath};
 
-#[async_std::main]
-async fn main() -> Result<(), std::io::Error> {
+#[tokio::main]
+async fn main() -> Result<(), kittybox::database::StorageError> {
     // TODO json logging in the future?
     let logger_env = env_logger::Env::new().filter_or("RUST_LOG", "info");
     env_logger::init_from_env(logger_env);
@@ -64,13 +65,14 @@ async fn main() -> Result<(), std::io::Error> {
         Some(value) => value,
         None => {
             if let Ok(filename) = env::var("COOKIE_SECRET_FILE") {
-                use async_std::io::ReadExt;
+                /*use async_std::io::ReadExt;
 
                 let mut file = async_std::fs::File::open(filename).await?;
                 let mut temp_string = String::new();
                 file.read_to_string(&mut temp_string).await?;
 
-                temp_string
+                temp_string*/
+                todo!()
             } else {
                 error!("COOKIE_SECRET or COOKIE_SECRET_FILE is not set, will not be able to log in users securely!");
                 std::process::exit(1);
@@ -78,24 +80,106 @@ async fn main() -> Result<(), std::io::Error> {
         }
     };
 
-    let host = env::var("SERVE_AT")
+    let host: std::net::SocketAddr = match env::var("SERVE_AT")
         .ok()
-        .unwrap_or_else(|| "0.0.0.0:8080".to_string());
+        .unwrap_or_else(|| "0.0.0.0:8080".to_string())
+        .parse() {
+            Ok(addr) => addr,
+            Err(e) => {
+                error!("Cannot parse SERVE_AT: {}", e);
+                std::process::exit(1);
+            }
+        };
 
     if backend_uri.starts_with("redis") {
         println!("The Redis backend is deprecated.");
         std::process::exit(1);
     } else if backend_uri.starts_with("file") {
-        let app = kittybox::get_app_with_file(
-            token_endpoint,
-            authorization_endpoint,
-            backend_uri,
-            media_endpoint,
-            cookie_secret,
-            internal_token,
-        )
-        .await;
-        app.listen(host).await
+        
+        let database = {
+            let folder = backend_uri.strip_prefix("file://").unwrap();
+            let path = std::path::PathBuf::from(folder);
+            kittybox::database::FileStorage::new(path).await?
+        };
+
+        // TODO interpret HEAD
+        let homepage = kittybox::util::require_host()
+            .and(warp::get())
+            .and(warp::path::end())
+            // TODO fetch content from the database
+            // TODO parse content-type and determine appropriate response
+            .map(|host| format!("front page for {}!", host));
+        
+        let micropub = warp::path("micropub")
+            .and(warp::path::end()
+                 .and(warp::get()
+                      .and(kittybox::micropub::query(database))
+                      .or(warp::post()
+                          .and(kittybox::util::require_host())
+                          .map(|host| "micropub post!"))
+                      .or(warp::options()
+                          .map(|| warp::reply::json::<Option<()>>(&None))
+                          // TODO: why doesn't this work?
+                          // .map(warp::reply::with::header("Allow", "GET, POST"))
+                          .map(|reply| warp::reply::with_header(reply, "Allow", "GET, POST"))
+                      ))
+                 .or(warp::get()
+                     .and(warp::path("client"))
+                     .and(warp::path::end())
+                     .map(|| kittybox::MICROPUB_CLIENT)));
+
+        let media = warp::path("media")
+            .and(warp::path::end()
+                 .and(kittybox::util::require_host())
+                 .map(|host| "media endpoint?...")
+                 .or(kittybox::util::require_host()
+                     .and(warp::path::param())
+                     .map(|host: Authority, path: String| format!("media file {}", path))));
+        
+        // TODO remember how login logic works because I forgor
+        let login = warp::path("login")
+            .and(warp::path("callback")
+                 .map(|| "callback!")
+                 // TODO form on GET and handler on POST
+                 .or(warp::path::end().map(|| "login page!")));
+
+        // TODO prettier error response
+        let coffee = warp::path("coffee")
+            .map(|| warp::reply::with_status("I'm a teapot!", warp::http::StatusCode::IM_A_TEAPOT));
+        
+        // TODO interpret HEAD
+        let static_files = warp::get()
+            .and(warp::path!("static" / String))
+            .map(|path| path);
+
+        // TODO interpret HEAD
+        let catchall = warp::get()
+            .and(kittybox::util::require_host())
+            .and(warp::path::full())
+            .map(|host: Authority, path: FullPath| host.to_string() + path.as_str() + ".json")
+            // TODO fetch content from the database
+            // TODO parse content-type and determine appropriate response
+            ;
+
+        let health = warp::path("health").and(warp::path::end()).map(|| "OK");
+        // TODO instrumentation middleware (see metrics.rs for comments)
+        //let metrics = warp::path("metrics").and(warp::path::end()).map(kittybox::metrics::gather);
+        let app = homepage
+            .or(login)
+            .or(static_files)
+            .or(coffee)
+            .or(health)
+            .or(micropub)
+            .or(media)
+            .or(catchall)
+            ;
+
+        let server = warp::serve(app);
+
+        // TODO use warp::Server::bind_with_graceful_shutdown
+        info!("Listening on {:?}", host);
+        server.bind(host).await;
+        Ok(())
     } else {
         println!("Unknown backend, not starting.");
         std::process::exit(1);
diff --git a/src/metrics.rs b/src/metrics.rs
index 9f512dd..7bfa2d2 100644
--- a/src/metrics.rs
+++ b/src/metrics.rs
@@ -1,3 +1,4 @@
+#![allow(unused_imports, dead_code)]
 use async_trait::async_trait;
 use lazy_static::lazy_static;
 use prometheus::{
@@ -5,7 +6,7 @@ use prometheus::{
     TextEncoder,
 };
 use std::time::{Duration, Instant};
-use tide::{Next, Request, Response, Result};
+//use tide::{Next, Request, Response, Result};
 
 // Copied from https://docs.rs/prometheus/0.12.0/src/prometheus/histogram.rs.html#885-889
 #[inline]
@@ -29,7 +30,7 @@ lazy_static! {
     .unwrap();
 }
 
-pub struct InstrumentationMiddleware {}
+/*pub struct InstrumentationMiddleware {}
 
 #[async_trait]
 impl<S> tide::Middleware<S> for InstrumentationMiddleware
@@ -55,9 +56,9 @@ where
 
         Ok(res)
     }
-}
+}*/
 
-pub async fn gather<S>(_: Request<S>) -> Result
+/*pub async fn gather<S>(_: Request<S>) -> Result
 where
     S: Send + Sync + Clone,
 {
@@ -67,4 +68,18 @@ where
     encoder.encode(&metric_families, &mut buffer).unwrap();
 
     Ok(Response::builder(200).body(buffer).build())
+}*/
+
+// TODO metrics middleware
+// warp doesn't allow running a filter manually
+// so you need to escape into the world of hyper
+// to collect metrics on requests
+
+pub fn gather() -> Vec<u8> {
+    let mut buffer: Vec<u8> = vec![];
+    let encoder = TextEncoder::new();
+    let metric_families = prometheus::gather();
+    encoder.encode(&metric_families, &mut buffer).unwrap();
+
+    buffer
 }
diff --git a/src/micropub/mod.rs b/src/micropub/mod.rs
index 23f20c4..95595cf 100644
--- a/src/micropub/mod.rs
+++ b/src/micropub/mod.rs
@@ -1,31 +1,101 @@
-pub mod get;
-pub mod post;
-
-pub use get::get_handler;
-pub use post::normalize_mf2;
-pub use post::post_handler;
-
-pub struct CORSMiddleware {}
-
-use crate::database;
-use crate::ApplicationState;
-use async_trait::async_trait;
-use tide::{Next, Request, Result};
-
-#[async_trait]
-impl<B> tide::Middleware<ApplicationState<B>> for CORSMiddleware
-where
-    B: database::Storage + Send + Sync + Clone,
-{
-    async fn handle(
-        &self,
-        req: Request<ApplicationState<B>>,
-        next: Next<'_, ApplicationState<B>>,
-    ) -> Result {
-        let mut res = next.run(req).await;
-
-        res.insert_header("Access-Control-Allow-Origin", "*");
-
-        Ok(res)
+use warp::http::StatusCode;
+use warp::{Filter, Rejection, reject::InvalidQuery};
+use serde_json::{json, Value};
+use serde::{Serialize, Deserialize};
+use crate::database::{MicropubChannel, Storage};
+
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+enum QueryType {
+    Source,
+    Config,
+    Channel,
+    SyndicateTo
+}
+
+#[derive(Serialize, Deserialize)]
+struct MicropubQuery {
+    q: QueryType,
+    url: Option<String>
+}
+
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+enum ErrorType {
+    InvalidRequest,
+    InternalServerError
+}
+
+#[derive(Serialize, Deserialize)]
+struct MicropubError {
+    error: ErrorType,
+    error_description: String
+}
+
+impl From<MicropubError> for StatusCode {
+    fn from(err: MicropubError) -> Self {
+        match err.error {
+            ErrorType::InvalidRequest => StatusCode::BAD_REQUEST,
+            ErrorType::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR
+        }
+    }
+}
+
+impl MicropubError {
+    fn new(error: ErrorType, error_description: &str) -> Self {
+        Self {
+            error,
+            error_description: error_description.to_owned()
+        }
     }
 }
+
+pub fn query<D: Storage>(db: D) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
+    warp::get()
+        .map(move || db.clone())
+        .and(crate::util::require_host())
+        .and(warp::query::<MicropubQuery>())
+        .then(|db: D, host: warp::host::Authority, query: MicropubQuery| async move {
+            match query.q {
+                QueryType::Config => {
+                    let channels: Vec<MicropubChannel> = match db.get_channels(host.as_str()).await {
+                        Ok(chans) => chans,
+                        Err(err) => return warp::reply::json(&MicropubError::new(
+                            ErrorType::InternalServerError,
+                            &format!("Error fetching channels: {}", err)
+                        ))
+                    };
+                    
+                    warp::reply::json(json!({
+                        "q": [
+                            QueryType::Source,
+                            QueryType::Config,
+                            QueryType::Channel,
+                            QueryType::SyndicateTo
+                        ],
+                        "channels": channels,
+                        "_kittybox_authority": host.as_str()
+                    }).as_object().unwrap())
+                },
+                _ => {
+                    todo!()
+                }
+            }
+        })
+        .recover(|err: Rejection| async move {
+            let error = if let Some(_) = err.find::<InvalidQuery>() {
+                MicropubError::new(
+                    ErrorType::InvalidRequest,
+                    "Invalid query parameters sent. Try ?q=config to see what you can do."
+                )
+            } else {
+                log::error!("Unhandled rejection: {:?}", err);
+                MicropubError::new(
+                    ErrorType::InternalServerError,
+                    &format!("Unknown error: {:?}", err)
+                )
+            };
+
+            Ok(warp::reply::with_status(warp::reply::json(&error), error.into()))
+        })
+}