feat: openssl based hoppscotch-relay for request forwarding (#4442)
This commit is contained in:
3
packages/hoppscotch-relay/.envrc
Normal file
3
packages/hoppscotch-relay/.envrc
Normal file
@@ -0,0 +1,3 @@
|
||||
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
|
||||
|
||||
use devenv
|
||||
10
packages/hoppscotch-relay/.gitignore
vendored
Normal file
10
packages/hoppscotch-relay/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/target
|
||||
# Devenv
|
||||
.devenv*
|
||||
devenv.local.nix
|
||||
|
||||
# direnv
|
||||
.direnv
|
||||
|
||||
# pre-commit
|
||||
.pre-commit-config.yaml
|
||||
644
packages/hoppscotch-relay/Cargo.lock
generated
Normal file
644
packages/hoppscotch-relay/Cargo.lock
generated
Normal file
@@ -0,0 +1,644 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||
|
||||
[[package]]
|
||||
name = "curl"
|
||||
version = "0.4.47"
|
||||
source = "git+https://github.com/CuriousCorrelation/curl-rust.git#1ec8079cf527b9cf47cc7a48c68b458affdae273"
|
||||
dependencies = [
|
||||
"curl-sys",
|
||||
"libc",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"socket2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curl-sys"
|
||||
version = "0.4.77+curl-8.10.1"
|
||||
source = "git+https://github.com/CuriousCorrelation/curl-rust.git#1ec8079cf527b9cf47cc7a48c68b458affdae273"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter",
|
||||
"humantime",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "hoppscotch-relay"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"curl",
|
||||
"env_logger",
|
||||
"http",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio-util",
|
||||
"url-escape",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.4.0+3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.82"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "url-escape"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44e0ce4d1246d075ca5abec4b41d33e87a6054d08e2366b63205665e950db218"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
21
packages/hoppscotch-relay/Cargo.toml
Normal file
21
packages/hoppscotch-relay/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "hoppscotch-relay"
|
||||
version = "0.1.1"
|
||||
description = "A HTTP request-response relay used by Hoppscotch Desktop and Hoppscotch Agent for advanced request handling including custom headers, certificates, proxies, and local system integration."
|
||||
authors = ["CuriousCorrelation"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
curl = { git = "https://github.com/CuriousCorrelation/curl-rust.git", features = ["ntlm"] }
|
||||
tokio-util = "0.7.12"
|
||||
openssl = { version = "0.10.66", features = ["vendored"] }
|
||||
# NOTE: This crate follows `openssl-sys` from https://github.com/CuriousCorrelation/curl-rust.git
|
||||
# to avoid issues from version mismatch when compiling from source.
|
||||
openssl-sys = { version = "0.9.64", features = ["vendored"] }
|
||||
log = "0.4.22"
|
||||
env_logger = "0.11.5"
|
||||
thiserror = "1.0.64"
|
||||
http = "1.1.0"
|
||||
url-escape = "0.1.1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
21
packages/hoppscotch-relay/LICENSE.md
Normal file
21
packages/hoppscotch-relay/LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 CuriousCorrelation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
201
packages/hoppscotch-relay/README.md
Normal file
201
packages/hoppscotch-relay/README.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# Hoppscotch Relay
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
A high-performance HTTP request-response relay used by Hoppscotch Desktop and Hoppscotch Agent for advanced request handling including CORS override, custom headers, certificates, proxies, and local system integration. It uses a custom fork of curl-rust with static OpenSSL builds for consistent SSL/TLS behavior across different platforms.
|
||||
|
||||
## Features
|
||||
|
||||
- 🚀 **Full HTTP Support**: Handle GET, POST, PUT, DELETE, and other HTTP methods
|
||||
- 📦 **Multiple Body Types**:
|
||||
- Raw text/JSON
|
||||
- URL-encoded forms
|
||||
- Multipart form data
|
||||
- File uploads
|
||||
- 🔒 **Security**:
|
||||
- Client certificate authentication (PEM & PFX/PKCS#12)
|
||||
- Custom root certificate bundles
|
||||
- Certificate validation control
|
||||
- 🌐 **Proxy Support**:
|
||||
- HTTP/HTTPS proxy configuration
|
||||
- Authentication support
|
||||
- NTLM support
|
||||
- ⚡ **Performance**:
|
||||
- Async design
|
||||
- Request cancellation support
|
||||
- Progress logs
|
||||
- 📊 **Detailed Metrics**:
|
||||
- Response timing
|
||||
- Status tracking
|
||||
- Header parsing
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
hoppscotch-relay = "0.1.1"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Request
|
||||
|
||||
```rust
|
||||
use hoppscotch_relay::{RequestWithMetadata, KeyValuePair};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
// Create a basic GET request
|
||||
let request = RequestWithMetadata::new(
|
||||
1, // Request ID
|
||||
"GET".to_string(), // Method
|
||||
"https://api.example.com/data".to_string(), // Endpoint
|
||||
vec![ // Headers
|
||||
KeyValuePair {
|
||||
key: "Accept".to_string(),
|
||||
value: "application/json".to_string(),
|
||||
}
|
||||
],
|
||||
None, // Body
|
||||
true, // Validate certificates
|
||||
vec![], // Root certificate bundles
|
||||
None, // Client certificate
|
||||
None, // Proxy configuration
|
||||
);
|
||||
|
||||
// Execute the request with cancellation support
|
||||
let cancel_token = CancellationToken::new();
|
||||
let response = hoppscotch_relay::run_request_task(&request, cancel_token)?;
|
||||
|
||||
println!("Status: {} {}", response.status, response.status_text);
|
||||
println!("Response time: {}ms", response.time_end_ms - response.time_start_ms);
|
||||
```
|
||||
|
||||
### POST Request with JSON Body
|
||||
|
||||
```rust
|
||||
let mut request = RequestWithMetadata::new(
|
||||
2,
|
||||
"POST".to_string(),
|
||||
"https://api.example.com/users".to_string(),
|
||||
vec![
|
||||
KeyValuePair {
|
||||
key: "Content-Type".to_string(),
|
||||
value: "application/json".to_string(),
|
||||
}
|
||||
],
|
||||
Some(BodyDef::Text(r#"{"name": "John Doe"}"#.to_string())),
|
||||
true,
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
let response = hoppscotch_relay::run_request_task(&request, CancellationToken::new())?;
|
||||
```
|
||||
|
||||
### File Upload with Form Data
|
||||
|
||||
```rust
|
||||
let form_data = vec![
|
||||
FormDataEntry {
|
||||
key: "file".to_string(),
|
||||
value: FormDataValue::File {
|
||||
filename: "document.pdf".to_string(),
|
||||
data: std::fs::read("document.pdf")?,
|
||||
mime: "application/pdf".to_string(),
|
||||
},
|
||||
},
|
||||
FormDataEntry {
|
||||
key: "description".to_string(),
|
||||
value: FormDataValue::Text("Important document".to_string()),
|
||||
},
|
||||
];
|
||||
|
||||
let mut request = RequestWithMetadata::new(
|
||||
3,
|
||||
"POST".to_string(),
|
||||
"https://api.example.com/upload".to_string(),
|
||||
vec![],
|
||||
Some(BodyDef::FormData(form_data)),
|
||||
true,
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
```
|
||||
|
||||
### Client Certificate Authentication
|
||||
|
||||
```rust
|
||||
let client_cert = ClientCertDef::PEMCert {
|
||||
certificate_pem: std::fs::read("client.crt")?,
|
||||
key_pem: std::fs::read("client.key")?,
|
||||
};
|
||||
|
||||
let mut request = RequestWithMetadata::new(
|
||||
4,
|
||||
"GET".to_string(),
|
||||
"https://secure-api.example.com".to_string(),
|
||||
vec![],
|
||||
None,
|
||||
true,
|
||||
vec![],
|
||||
Some(client_cert),
|
||||
None,
|
||||
);
|
||||
```
|
||||
|
||||
### Proxy Configuration
|
||||
|
||||
```rust
|
||||
let proxy_config = ProxyConfig {
|
||||
url: "http://proxy.example.com:8080".to_string(),
|
||||
};
|
||||
|
||||
let mut request = RequestWithMetadata::new(
|
||||
5,
|
||||
"GET".to_string(),
|
||||
"https://api.example.com".to_string(),
|
||||
vec![],
|
||||
None,
|
||||
true,
|
||||
vec![],
|
||||
None,
|
||||
Some(proxy_config),
|
||||
);
|
||||
```
|
||||
|
||||
## Request Cancellation
|
||||
|
||||
The library supports request cancellation through Tokio's `CancellationToken`:
|
||||
|
||||
```rust
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
let cancel_token = CancellationToken::new();
|
||||
let cancel_token_clone = cancel_token.clone();
|
||||
|
||||
// Spawn the request in a separate task
|
||||
let request_handle = tokio::spawn(async move {
|
||||
hoppscotch_relay::run_request_task(&request, cancel_token_clone)
|
||||
});
|
||||
|
||||
// Cancel the request after 5 seconds
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
cancel_token.cancel();
|
||||
```
|
||||
|
||||
## Building from Source
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/hoppscotch/hoppscotch-relay
|
||||
cd hoppscotch-relay
|
||||
```
|
||||
|
||||
2. Build the project:
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
153
packages/hoppscotch-relay/devenv.lock
Normal file
153
packages/hoppscotch-relay/devenv.lock
Normal file
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"nodes": {
|
||||
"devenv": {
|
||||
"locked": {
|
||||
"dir": "src/modules",
|
||||
"lastModified": 1729641677,
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "4f634c92037d3fb7a7cc2feddc4d686ace83b57f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "src/modules",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729578683,
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "d66cda53e8193a878742dcadb5bb75f4df7c3c0a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"pre-commit-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1729501122,
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "56c7c4a3f5fdbef5bf81c7d9c28fbb45dc626611",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1729449015,
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "89172919243df199fe237ba0f776c3e3e3d72367",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-24.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1729104314,
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "3c3e88f0f544d6bb54329832616af7eb971b6be6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"fenix": "fenix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1729618852,
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "c2867868889a549562a7b53fb572719f852a8a6f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
54
packages/hoppscotch-relay/devenv.nix
Normal file
54
packages/hoppscotch-relay/devenv.nix
Normal file
@@ -0,0 +1,54 @@
|
||||
{ pkgs, lib, config, inputs, ... }:
|
||||
|
||||
{
|
||||
# https://devenv.sh/packages/
|
||||
packages = with pkgs; [
|
||||
git
|
||||
# Cargo
|
||||
cargo-edit
|
||||
];
|
||||
|
||||
# https://devenv.sh/basics/
|
||||
env = {
|
||||
APP_GREET = "Hoppscotch";
|
||||
};
|
||||
|
||||
# https://devenv.sh/scripts/
|
||||
scripts.hello.exec = "echo hello from $APP_GREET";
|
||||
|
||||
enterShell = ''
|
||||
git --version
|
||||
'';
|
||||
|
||||
# https://devenv.sh/tests/
|
||||
enterTest = ''
|
||||
echo "Running tests"
|
||||
'';
|
||||
|
||||
# https://devenv.sh/integrations/dotenv/
|
||||
dotenv.enable = true;
|
||||
|
||||
# https://devenv.sh/languages/
|
||||
languages.rust = {
|
||||
enable = true;
|
||||
channel = "nightly";
|
||||
components = [
|
||||
"rustc"
|
||||
"cargo"
|
||||
"clippy"
|
||||
"rustfmt"
|
||||
"rust-analyzer"
|
||||
"llvm-tools-preview"
|
||||
"rust-src"
|
||||
"rustc-codegen-cranelift-preview"
|
||||
];
|
||||
};
|
||||
|
||||
# https://devenv.sh/pre-commit-hooks/
|
||||
# pre-commit.hooks.shellcheck.enable = true;
|
||||
|
||||
# https://devenv.sh/processes/
|
||||
# processes.ping.exec = "ping example.com";
|
||||
|
||||
# See full reference at https://devenv.sh/reference/options/
|
||||
}
|
||||
23
packages/hoppscotch-relay/devenv.yaml
Normal file
23
packages/hoppscotch-relay/devenv.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# yaml-language-server: $schema=https://devenv.sh/devenv.schema.json
|
||||
inputs:
|
||||
# For NodeJS-22 and above
|
||||
nixpkgs:
|
||||
url: github:NixOS/nixpkgs/nixpkgs-unstable
|
||||
# nixpkgs:
|
||||
# url: github:cachix/devenv-nixpkgs/rolling
|
||||
fenix:
|
||||
url: github:nix-community/fenix
|
||||
inputs:
|
||||
nixpkgs:
|
||||
follows: nixpkgs
|
||||
|
||||
# If you're using non-OSS software, you can set allowUnfree to true.
|
||||
allowUnfree: true
|
||||
|
||||
# If you're willing to use a package that's vulnerable
|
||||
# permittedInsecurePackages:
|
||||
# - "openssl-1.1.1w"
|
||||
|
||||
# If you have more than one devenv you can merge them
|
||||
#imports:
|
||||
# - ./backend
|
||||
16
packages/hoppscotch-relay/src/error.rs
Normal file
16
packages/hoppscotch-relay/src/error.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, Serialize)]
|
||||
pub enum RelayError {
|
||||
#[error("Invalid method")]
|
||||
InvalidMethod,
|
||||
#[error("Invalid URL")]
|
||||
InvalidUrl,
|
||||
#[error("Invalid headers")]
|
||||
InvalidHeaders,
|
||||
#[error("Request run error: {0}")]
|
||||
RequestRunError(String),
|
||||
}
|
||||
|
||||
pub type RelayResult<T> = std::result::Result<T, RelayError>;
|
||||
96
packages/hoppscotch-relay/src/interop.rs
Normal file
96
packages/hoppscotch-relay/src/interop.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct KeyValuePair {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum FormDataValue {
|
||||
Text(String),
|
||||
File {
|
||||
filename: String,
|
||||
data: Vec<u8>,
|
||||
mime: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct FormDataEntry {
|
||||
pub key: String,
|
||||
pub value: FormDataValue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum BodyDef {
|
||||
Text(String),
|
||||
URLEncoded(Vec<KeyValuePair>),
|
||||
FormData(Vec<FormDataEntry>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RequestWithMetadata {
|
||||
pub req_id: usize,
|
||||
pub method: String,
|
||||
pub endpoint: String,
|
||||
pub headers: Vec<KeyValuePair>,
|
||||
pub body: Option<BodyDef>,
|
||||
pub validate_certs: bool,
|
||||
pub root_cert_bundle_files: Vec<Vec<u8>>,
|
||||
pub client_cert: Option<ClientCertDef>,
|
||||
pub proxy: Option<ProxyConfig>,
|
||||
}
|
||||
|
||||
impl RequestWithMetadata {
|
||||
pub fn new(
|
||||
req_id: usize,
|
||||
method: String,
|
||||
endpoint: String,
|
||||
headers: Vec<KeyValuePair>,
|
||||
body: Option<BodyDef>,
|
||||
validate_certs: bool,
|
||||
root_cert_bundle_files: Vec<Vec<u8>>,
|
||||
client_cert: Option<ClientCertDef>,
|
||||
proxy: Option<ProxyConfig>,
|
||||
) -> Self {
|
||||
Self {
|
||||
req_id,
|
||||
method,
|
||||
endpoint,
|
||||
headers,
|
||||
body,
|
||||
validate_certs,
|
||||
root_cert_bundle_files,
|
||||
client_cert,
|
||||
proxy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ProxyConfig {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum ClientCertDef {
|
||||
PEMCert {
|
||||
certificate_pem: Vec<u8>,
|
||||
key_pem: Vec<u8>,
|
||||
},
|
||||
PFXCert {
|
||||
certificate_pfx: Vec<u8>,
|
||||
password: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ResponseWithMetadata {
|
||||
pub status: u16,
|
||||
pub status_text: String,
|
||||
pub headers: Vec<KeyValuePair>,
|
||||
pub data: Vec<u8>,
|
||||
pub time_start_ms: u128,
|
||||
pub time_end_ms: u128,
|
||||
}
|
||||
23
packages/hoppscotch-relay/src/lib.rs
Normal file
23
packages/hoppscotch-relay/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod interop;
|
||||
pub(crate) mod relay;
|
||||
pub(crate) mod util;
|
||||
|
||||
pub use error::{RelayError, RelayResult};
|
||||
pub use interop::{RequestWithMetadata, ResponseWithMetadata};
|
||||
pub use relay::run_request_task;
|
||||
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
595
packages/hoppscotch-relay/src/relay.rs
Normal file
595
packages/hoppscotch-relay/src/relay.rs
Normal file
@@ -0,0 +1,595 @@
|
||||
use curl::easy::{Easy, List};
|
||||
use openssl::{pkcs12::Pkcs12, ssl::SslContextBuilder, x509::X509};
|
||||
use openssl_sys::SSL_CTX;
|
||||
use std::time::SystemTime;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
error::RelayError,
|
||||
interop::{
|
||||
BodyDef, ClientCertDef, FormDataValue, KeyValuePair, RequestWithMetadata,
|
||||
ResponseWithMetadata,
|
||||
},
|
||||
util::get_status_text,
|
||||
};
|
||||
|
||||
pub fn run_request_task(
|
||||
req: &RequestWithMetadata,
|
||||
cancel_token: CancellationToken,
|
||||
) -> Result<ResponseWithMetadata, RelayError> {
|
||||
log::info!(
|
||||
"Starting request task: [Method: {}] [URL: {}] [Validate Certs: {}] [Has Body: {}] [Proxy Enabled: {}]",
|
||||
req.method,
|
||||
req.endpoint,
|
||||
req.validate_certs,
|
||||
req.body.is_some(),
|
||||
req.proxy.is_some()
|
||||
);
|
||||
|
||||
let mut curl_handle = Easy::new();
|
||||
log::debug!("Initialized new curl handle with default settings");
|
||||
|
||||
match curl_handle.progress(true) {
|
||||
Ok(_) => log::debug!("Progress tracking enabled for request monitoring"),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Critical failure enabling progress tracking: {}\nError details: {:?}",
|
||||
err,
|
||||
err
|
||||
);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
match curl_handle.custom_request(&req.method) {
|
||||
Ok(_) => log::debug!("HTTP method set: {}", req.method),
|
||||
Err(err) => {
|
||||
log::error!("Failed to set HTTP method '{}'. Error: {}", req.method, err);
|
||||
return Err(RelayError::InvalidMethod);
|
||||
}
|
||||
}
|
||||
|
||||
match curl_handle.url(&req.endpoint) {
|
||||
Ok(_) => log::debug!("Target URL configured: {}", req.endpoint),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"URL configuration failed for '{}'\nError: {}",
|
||||
req.endpoint,
|
||||
err
|
||||
);
|
||||
return Err(RelayError::InvalidUrl);
|
||||
}
|
||||
}
|
||||
|
||||
let headers = match get_headers_list(&req) {
|
||||
Ok(headers) => {
|
||||
log::debug!("Generated headers list");
|
||||
headers
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Header generation failed:\nError: {:?}", err);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
|
||||
match curl_handle.http_headers(headers) {
|
||||
Ok(_) => log::debug!("Successfully configured request headers"),
|
||||
Err(err) => {
|
||||
log::error!("Failed to set HTTP headers: {}", err);
|
||||
return Err(RelayError::InvalidHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = apply_body_to_curl_handle(&mut curl_handle, &req) {
|
||||
log::error!(
|
||||
"Request body application failed:\nError: {:?}\nContent-Type: {:?}",
|
||||
err,
|
||||
req.headers
|
||||
.iter()
|
||||
.find(|h| h.key.to_lowercase() == "content-type")
|
||||
.map(|h| &h.value)
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
log::debug!("Request body configured successfully");
|
||||
|
||||
match curl_handle.ssl_verify_peer(req.validate_certs) {
|
||||
Ok(_) => log::debug!(
|
||||
"SSL peer verification setting applied: {}",
|
||||
req.validate_certs
|
||||
),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"SSL peer verification configuration failed: {}\nRequested setting: {}",
|
||||
err,
|
||||
req.validate_certs
|
||||
);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
match curl_handle.ssl_verify_host(req.validate_certs) {
|
||||
Ok(_) => log::debug!(
|
||||
"SSL host verification setting applied: {}",
|
||||
req.validate_certs
|
||||
),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"SSL host verification configuration failed: {}\nRequested setting: {}",
|
||||
err,
|
||||
req.validate_certs
|
||||
);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = apply_client_cert_to_curl_handle(&mut curl_handle, &req) {
|
||||
log::error!(
|
||||
"Client certificate configuration failed:\nError: {:?}\nCert Info: {:#?}",
|
||||
err,
|
||||
req.client_cert.as_ref()
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
log::debug!("Client certificate configuration successful");
|
||||
|
||||
if let Err(err) = apply_proxy_config_to_curl_handle(&mut curl_handle, &req) {
|
||||
log::error!(
|
||||
"Proxy configuration failed:\nError: {:?}\nProxy Info: {:?}",
|
||||
err,
|
||||
req.proxy.as_ref()
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
log::debug!("Proxy configuration applied successfully");
|
||||
|
||||
let mut response_body = Vec::new();
|
||||
let mut response_headers = Vec::new();
|
||||
let (start_time_ms, end_time_ms) = {
|
||||
let mut transfer = curl_handle.transfer();
|
||||
log::debug!("Created curl transfer object for request execution");
|
||||
|
||||
match transfer.ssl_ctx_function(|ssl_ctx_ptr| {
|
||||
let cert_list = match get_x509_certs_from_root_cert_bundle_safe(&req) {
|
||||
Ok(certs) => {
|
||||
log::debug!("Found {} certificates in root bundle", certs.len());
|
||||
certs
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to load certificates from bundle: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if !cert_list.is_empty() {
|
||||
let mut ssl_ctx_builder =
|
||||
unsafe { SslContextBuilder::from_ptr(ssl_ctx_ptr as *mut SSL_CTX) };
|
||||
|
||||
let cert_store = ssl_ctx_builder.cert_store_mut();
|
||||
|
||||
for (index, cert) in cert_list.iter().enumerate() {
|
||||
log::debug!(
|
||||
"Processing certificate {}: Subject: {:?}, Not Before: {:?}, Not After: {:?}",
|
||||
index,
|
||||
cert.subject_name(),
|
||||
cert.not_before(),
|
||||
cert.not_after()
|
||||
);
|
||||
|
||||
if let Err(e) = cert_store.add_cert(cert.clone()) {
|
||||
log::warn!(
|
||||
"Failed to add certificate {} to store\nError: {}\nCert details: {:?}",
|
||||
index,
|
||||
e,
|
||||
cert.subject_name()
|
||||
);
|
||||
} else {
|
||||
log::debug!(
|
||||
"Successfully added certificate {} to store\nSubject: {:?}",
|
||||
index,
|
||||
cert.subject_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: We need to prevent Rust from dropping the `SslContextBuilder` because
|
||||
// the underlying `SSL_CTX` pointer is owned and managed by curl, not us.
|
||||
// From curl docs: "libcurl does not guarantee the lifetime of the passed in
|
||||
// object once this callback function has returned"
|
||||
// and `SslContextBuilder` is just a safe wrapper around curl's `SSL_CTX` from
|
||||
// `openssl_sys::SSL_CTX`.
|
||||
// If dropped, Rust would try to free the `SSL_CTX` which curl still needs.
|
||||
//
|
||||
// This intentional "leak" is safe because:
|
||||
// - We're only leaking the thin Rust wrapper
|
||||
// - Curl manages the actual `SSL_CTX` memory
|
||||
// - Curl will free the `SSL_CTX` during connection cleanup
|
||||
//
|
||||
// See: https://curl.se/libcurl/c/CURLOPT_SSL_CTX_FUNCTION.html
|
||||
std::mem::forget(ssl_ctx_builder);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}) {
|
||||
Ok(_) => log::debug!("SSL context function configured successfully"),
|
||||
Err(err) => {
|
||||
log::error!("SSL context function setup failed: {}", err);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
match transfer.progress_function(|dltotal, dlnow, ultotal, ulnow| {
|
||||
let cancelled = cancel_token.is_cancelled();
|
||||
if cancelled {
|
||||
log::warn!(
|
||||
"Request cancelled by user\nDownload: {}/{} bytes\nUpload: {}/{} bytes",
|
||||
dlnow,
|
||||
dltotal,
|
||||
ulnow,
|
||||
ultotal
|
||||
);
|
||||
} else {
|
||||
log::debug!(
|
||||
"Progress - Download: {}/{} bytes, Upload: {}/{} bytes",
|
||||
dlnow,
|
||||
dltotal,
|
||||
ulnow,
|
||||
ultotal
|
||||
);
|
||||
}
|
||||
!cancelled
|
||||
}) {
|
||||
Ok(_) => log::debug!("Progress monitoring function configured"),
|
||||
Err(err) => {
|
||||
log::error!("Progress function setup failed: {}", err);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
match transfer.header_function(|header| {
|
||||
let header = String::from_utf8_lossy(header).into_owned();
|
||||
if let Some((key, value)) = header.split_once(':') {
|
||||
log::debug!("Received header: [{}] = [{}]", key.trim(), value.trim());
|
||||
response_headers.push(KeyValuePair {
|
||||
key: key.trim().to_string(),
|
||||
value: value.trim().to_string(),
|
||||
});
|
||||
} else {
|
||||
log::debug!("Received header line (no key-value): {}", header.trim());
|
||||
}
|
||||
true
|
||||
}) {
|
||||
Ok(_) => log::debug!("Header processing function configured"),
|
||||
Err(err) => {
|
||||
log::error!("Header function setup failed: {}", err);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
match transfer.write_function(|data| {
|
||||
let chunk_size = data.len();
|
||||
response_body.extend_from_slice(data);
|
||||
log::debug!(
|
||||
"Received response chunk: {} bytes (Total size so far: {} bytes)",
|
||||
chunk_size,
|
||||
response_body.len()
|
||||
);
|
||||
Ok(chunk_size)
|
||||
}) {
|
||||
Ok(_) => log::debug!("Response body processing function configured"),
|
||||
Err(err) => {
|
||||
log::error!("Write function setup failed: {}", err);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let start_time_ms = SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
log::info!(
|
||||
"Initiating request transfer at timestamp: {}",
|
||||
start_time_ms
|
||||
);
|
||||
|
||||
if let Err(err) = transfer.perform() {
|
||||
log::error!(
|
||||
"Request transfer failed:\nError: {}\nTime elapsed: {}ms",
|
||||
err,
|
||||
SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
- start_time_ms,
|
||||
);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
|
||||
let end_time_ms = SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
|
||||
log::info!(
|
||||
"Request transfer completed:\nDuration: {}ms",
|
||||
end_time_ms - start_time_ms,
|
||||
);
|
||||
|
||||
(start_time_ms, end_time_ms)
|
||||
};
|
||||
|
||||
let response_status = match curl_handle.response_code() {
|
||||
Ok(status) => {
|
||||
let status = status as u16;
|
||||
log::info!(
|
||||
"Response status code: {} ({})",
|
||||
status,
|
||||
get_status_text(status)
|
||||
);
|
||||
status
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to retrieve response code: {}", err);
|
||||
return Err(RelayError::RequestRunError(err.description().to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
let response_status_text = get_status_text(response_status).to_string();
|
||||
log::info!(
|
||||
"Request completed successfully:\nStatus: {} ({})\nDuration: {}ms\n\
|
||||
Response size: {} bytes\nHeaders: {} received\nEndpoint: {}",
|
||||
response_status,
|
||||
response_status_text,
|
||||
end_time_ms - start_time_ms,
|
||||
response_body.len(),
|
||||
response_headers.len(),
|
||||
req.endpoint
|
||||
);
|
||||
|
||||
Ok(ResponseWithMetadata {
|
||||
status: response_status,
|
||||
status_text: response_status_text,
|
||||
headers: response_headers,
|
||||
data: response_body,
|
||||
time_start_ms: start_time_ms,
|
||||
time_end_ms: end_time_ms,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_headers_list(req: &RequestWithMetadata) -> Result<List, RelayError> {
|
||||
let mut result = List::new();
|
||||
|
||||
for KeyValuePair { key, value } in &req.headers {
|
||||
result
|
||||
.append(&format!("{}: {}", key, value))
|
||||
.map_err(|err| RelayError::RequestRunError(err.description().to_string()))?;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn apply_body_to_curl_handle(
|
||||
curl_handle: &mut Easy,
|
||||
req: &RequestWithMetadata,
|
||||
) -> Result<(), RelayError> {
|
||||
match &req.body {
|
||||
Some(BodyDef::Text(text)) => {
|
||||
curl_handle
|
||||
.post_fields_copy(text.as_bytes())
|
||||
.map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Error while setting body: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Some(BodyDef::FormData(entries)) => {
|
||||
let mut form = curl::easy::Form::new();
|
||||
|
||||
for entry in entries {
|
||||
let mut part = form.part(&entry.key);
|
||||
|
||||
match &entry.value {
|
||||
FormDataValue::Text(data) => {
|
||||
part.contents(data.as_bytes());
|
||||
}
|
||||
FormDataValue::File {
|
||||
filename,
|
||||
data,
|
||||
mime,
|
||||
} => {
|
||||
part.buffer(filename, data.clone()).content_type(mime);
|
||||
}
|
||||
};
|
||||
|
||||
part.add().map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Error while setting body: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
}
|
||||
|
||||
curl_handle.httppost(form).map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Error while setting body: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Some(BodyDef::URLEncoded(entries)) => {
|
||||
let data = entries
|
||||
.iter()
|
||||
.map(|KeyValuePair { key, value }| {
|
||||
format!(
|
||||
"{}={}",
|
||||
&url_escape::encode_www_form_urlencoded(key),
|
||||
url_escape::encode_www_form_urlencoded(value)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("&");
|
||||
|
||||
curl_handle
|
||||
.post_fields_copy(data.as_bytes())
|
||||
.map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Error while setting body: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_client_cert_to_curl_handle(
|
||||
handle: &mut Easy,
|
||||
req: &RequestWithMetadata,
|
||||
) -> Result<(), RelayError> {
|
||||
match &req.client_cert {
|
||||
Some(ClientCertDef::PEMCert {
|
||||
certificate_pem,
|
||||
key_pem,
|
||||
}) => {
|
||||
handle.ssl_cert_type("PEM").map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM Cert Type: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.ssl_cert_blob(certificate_pem).map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM Cert Blob: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.ssl_key_type("PEM").map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM key type: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.ssl_key_blob(key_pem).map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM Cert blob: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Some(ClientCertDef::PFXCert {
|
||||
certificate_pfx,
|
||||
password,
|
||||
}) => {
|
||||
let pkcs12 = Pkcs12::from_der(&certificate_pfx).map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed to parse PFX certificate from DER: {}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
|
||||
let parsed = pkcs12.parse2(password).map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed to parse PFX certificate with provided password: {}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
|
||||
if let (Some(cert), Some(key)) = (parsed.cert, parsed.pkey) {
|
||||
let certificate_pem = cert.to_pem().map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed to convert PFX certificate to PEM format: {}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
|
||||
let key_pem = key.private_key_to_pem_pkcs8().map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed to convert PFX private key to PEM format: {}",
|
||||
err
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.ssl_cert_type("PEM").map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM Cert Type for converted PFX: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.ssl_cert_blob(&certificate_pem).map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM Cert Blob for converted PFX: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.ssl_key_type("PEM").map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM key type for converted PFX: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.ssl_key_blob(&key_pem).map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed setting PEM key blob for converted PFX: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
} else {
|
||||
return Err(RelayError::RequestRunError(
|
||||
"PFX certificate parsing succeeded, but either cert or private key is missing"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_x509_certs_from_root_cert_bundle_safe(
|
||||
req: &RequestWithMetadata,
|
||||
) -> Result<Vec<X509>, openssl::error::ErrorStack> {
|
||||
let mut certs = Vec::new();
|
||||
|
||||
for pem_bundle in &req.root_cert_bundle_files {
|
||||
match openssl::x509::X509::stack_from_pem(pem_bundle) {
|
||||
Ok(mut bundle_certs) => certs.append(&mut bundle_certs),
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse certificate bundle: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
fn apply_proxy_config_to_curl_handle(
|
||||
handle: &mut Easy,
|
||||
req: &RequestWithMetadata,
|
||||
) -> Result<(), RelayError> {
|
||||
if let Some(proxy_config) = &req.proxy {
|
||||
handle
|
||||
.proxy_auth(curl::easy::Auth::new().auto(true))
|
||||
.map_err(|err| {
|
||||
RelayError::RequestRunError(format!(
|
||||
"Failed to set proxy Auth Mode: {}",
|
||||
err.description()
|
||||
))
|
||||
})?;
|
||||
|
||||
handle.proxy(&proxy_config.url).map_err(|err| {
|
||||
RelayError::RequestRunError(format!("Failed to set proxy URL: {}", err.description()))
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
6
packages/hoppscotch-relay/src/util.rs
Normal file
6
packages/hoppscotch-relay/src/util.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub fn get_status_text(status: u16) -> &'static str {
|
||||
http::StatusCode::from_u16(status)
|
||||
.map(|status| status.canonical_reason())
|
||||
.unwrap_or(Some("Unknown Status"))
|
||||
.unwrap_or("Unknown Status")
|
||||
}
|
||||
Reference in New Issue
Block a user