[PR #5508] [MERGED] feat(relay): control redirect follow #5244

Closed
opened 2026-03-17 02:42:34 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/hoppscotch/hoppscotch/pull/5508
Author: @CuriousCorrelation
Created: 10/24/2025
Status: Merged
Merged: 10/27/2025
Merged by: @jamesgeorge007

Base: nextHead: relay-feat-control-redirect-follow


📝 Commits (1)

  • 97003a8 feat(relay): control redirect follow

📊 Changes

18 files changed (+254 additions, -18 deletions)

View changed files

📝 packages/hoppscotch-agent/src-tauri/Cargo.lock (+3 -3)
📝 packages/hoppscotch-common/locales/en.json (+1 -0)
📝 packages/hoppscotch-common/src/components/settings/Agent.vue (+23 -0)
📝 packages/hoppscotch-common/src/components/settings/Native.vue (+23 -0)
📝 packages/hoppscotch-common/src/helpers/functional/domain-settings.ts (+32 -2)
📝 packages/hoppscotch-common/src/platform/std/kernel-interceptors/agent/store.ts (+17 -1)
📝 packages/hoppscotch-common/src/platform/std/kernel-interceptors/native/store.ts (+17 -1)
📝 packages/hoppscotch-desktop/plugin-workspace/relay/src/interop.rs (+17 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/relay/src/request.rs (+82 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock (+1 -1)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/dist-js/index.d.ts (+12 -0)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/dist-js/index.d.ts.map (+1 -1)
📝 packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/guest-js/index.ts (+15 -0)
📝 packages/hoppscotch-desktop/src-tauri/Cargo.lock (+2 -2)
📝 packages/hoppscotch-desktop/src-tauri/Cargo.toml (+1 -1)
📝 packages/hoppscotch-kernel/package.json (+1 -1)
📝 packages/hoppscotch-kernel/src/relay/impl/desktop/v/1.ts (+1 -0)
📝 pnpm-lock.yaml (+5 -5)

📄 Description

Add per-domain toggle to disable automatic HTTP redirect following in
the Native and Agent interceptors. When disabled, requests return the
redirect response (status code, headers, body) without following the
Location header.

Closes FE-1035
Closes #2095

Previously HTTP redirects were always followed (on browser, can't do
much about that, see
https://fetch.spec.whatwg.org/#atomic-http-redirect-handling) without
option to inspect the redirect response itself. This prevented
developers from accessing redirect metadata needed when testing OAuth
flows (PKCE where intermediate responses contain authorization tokens),
authentication endpoints that return codes in Location headers with 302
status, and debugging API redirect chains. But on the desktop app,
redirects were just never followed, creating the opposite effect.

The browser's fetch API applies atomic HTTP redirect handling per spec,
making it impossible to intercept redirects and inspect their responses.
The Native and Agent interceptors use curl and native HTTP clients
respectively, both supporting redirect control, making this feature
viable for these specific interceptors. (Proxyscotch tbd).

Changes

Frontend Settings Pages

Added toggle in Native.vue and Agent.vue interceptor settings
positioned after "Verify Peer" in the security section, labeled
"Follow Redirects" with default enabled to maintain backward
compatibility.

<div class="flex items-center">
  <HoppSmartToggle
    :on="domainSettings[selectedDomain]?.options?.followRedirects ?? true"
    @change="toggleFollowRedirects"
  />
  {{ t("settings.follow_redirects") }}
</div>

Defaulting to true for consistency with the browser and overall
Hoppscotch ecosystem mainly. If one platform prescribes, it's better to
stick with it.

Domain Settings Structure

Extended InputDomainSetting type with new options namespace storing
request-level behavior configuration:

options?: {
  followRedirects?: boolean
  maxRedirects?: number
  timeout?: number
  decompress?: boolean
  cookies?: boolean
  keepAlive?: boolean
}

This was already present in hoppscotch-kernel for just such
use-cases.

The options field defaults to { followRedirects: true } in default
domain config for both interceptors, preserving existing behavior as
mentioned above.

Settings Conversion

Updated domain-settings.ts converter to map domain settings options
to RelayRequest.meta.options. The converter uses Either/Option types
for safe(r) composition:

const convertOptions = (
  options?: InputDomainSetting["options"]
): O.Option<NonNullable<RelayRequest["meta"]>["options"]> =>
  pipe(
    O.fromNullable(options),
    O.map((opts) => ({
      ...(opts.followRedirects !== undefined && {
        followRedirects: opts.followRedirects,
      }),
      // ... rest
    })),
    O.filter((opts) => Object.keys(opts).length > 0)
  )

Stores merge domain-specific overrides with global defaults, applying
domain settings when resolving merged configuration via
getMergedSettings(host).

Rust Backend Types

Added request metadata structs in relay interop:

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RequestMeta {
    pub options: Option<RequestOptions>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RequestOptions {
    pub follow_redirects: Option<bool>,
    pub max_redirects: Option<u32>,
    pub timeout: Option<u64>,
    // ... rest
}

Request struct includes meta: Option<RequestMeta> field passed from
frontend through the relay plugin.

Again this was already present in hoppscotch-kernel for just such
use-cases.

Curl Configuration

Request processing applies settings to curl handle. When follow_redirects
is false, curl returns the initial redirect response:

if let Some(follow) = options.follow_redirects {
  self.handle.follow_location(follow).map_err(|e| {
    RelayError::Network {
      message: "Failed to set redirect behavior".into(),
      cause: Some(e.to_string()),
    }
  })?;
}

Error handling logs at appropriate levels (debug for success, error for
failures) with contextual messages. Although I don't imagine this
happening.

Behavior

With followRedirects: true (default)

Request to https://httpbin.org/redirect/3 follows all redirects and
returns 200 from final destination with complete response body.

NOTE: Check Network tab from the dev tools.

With followRedirects: false

Same request returns 302 with redirect HTML body and Location header
intact, allowing inspection of redirect details.

NOTE: Check Network tab from the dev tools.

Compatibility

API Surface

No changes to request/response contracts. The meta field is optional
and backend applies defaults when not provided.

Browser Interceptors

Extension and Proxy interceptor show no redirect control in settings
since browser APIs prevent redirect interception. These interceptors
continue using browser defaults (always follow redirects).

Bundle Format

No changes. This is purely request-time behavior configuration.

Testing

Verify with https://httpbin.org/redirect/3:

NOTE: Check Network tab from the dev tools.

  1. With followRedirects: true (default):

    • Returns 200 status
    • Response body from /get endpoint
    • No redirect HTML
  2. With followRedirects: false:

    • Returns 302 status
    • Response body contains redirect HTML
    • Location header visible in response headers

Backward Compatibility

Default behavior maintains automatic redirect following. Existing users
experience no change unless they explicitly disable redirects for a
domain. All domain settings migration preserves existing configuration
without loss. For example it would be prevented on desktop, but "default"
is follow always, hence consistency across platforms.

This is purely additive functionality that does not modify existing
request/response handling as per the web app.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/hoppscotch/hoppscotch/pull/5508 **Author:** [@CuriousCorrelation](https://github.com/CuriousCorrelation) **Created:** 10/24/2025 **Status:** ✅ Merged **Merged:** 10/27/2025 **Merged by:** [@jamesgeorge007](https://github.com/jamesgeorge007) **Base:** `next` ← **Head:** `relay-feat-control-redirect-follow` --- ### 📝 Commits (1) - [`97003a8`](https://github.com/hoppscotch/hoppscotch/commit/97003a8097dfaa721f29d76b5689179ed1e8ccb0) feat(relay): control redirect follow ### 📊 Changes **18 files changed** (+254 additions, -18 deletions) <details> <summary>View changed files</summary> 📝 `packages/hoppscotch-agent/src-tauri/Cargo.lock` (+3 -3) 📝 `packages/hoppscotch-common/locales/en.json` (+1 -0) 📝 `packages/hoppscotch-common/src/components/settings/Agent.vue` (+23 -0) 📝 `packages/hoppscotch-common/src/components/settings/Native.vue` (+23 -0) 📝 `packages/hoppscotch-common/src/helpers/functional/domain-settings.ts` (+32 -2) 📝 `packages/hoppscotch-common/src/platform/std/kernel-interceptors/agent/store.ts` (+17 -1) 📝 `packages/hoppscotch-common/src/platform/std/kernel-interceptors/native/store.ts` (+17 -1) 📝 `packages/hoppscotch-desktop/plugin-workspace/relay/src/interop.rs` (+17 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/relay/src/request.rs` (+82 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/Cargo.lock` (+1 -1) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/dist-js/index.d.ts` (+12 -0) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/dist-js/index.d.ts.map` (+1 -1) 📝 `packages/hoppscotch-desktop/plugin-workspace/tauri-plugin-relay/guest-js/index.ts` (+15 -0) 📝 `packages/hoppscotch-desktop/src-tauri/Cargo.lock` (+2 -2) 📝 `packages/hoppscotch-desktop/src-tauri/Cargo.toml` (+1 -1) 📝 `packages/hoppscotch-kernel/package.json` (+1 -1) 📝 `packages/hoppscotch-kernel/src/relay/impl/desktop/v/1.ts` (+1 -0) 📝 `pnpm-lock.yaml` (+5 -5) </details> ### 📄 Description Add per-domain toggle to disable automatic HTTP redirect following in the Native and Agent interceptors. When disabled, requests return the redirect response (status code, headers, body) without following the Location header. Closes FE-1035 Closes #2095 Previously HTTP redirects were always followed (on browser, can't do much about that, see https://fetch.spec.whatwg.org/#atomic-http-redirect-handling) without option to inspect the redirect response itself. This prevented developers from accessing redirect metadata needed when testing OAuth flows (PKCE where intermediate responses contain authorization tokens), authentication endpoints that return codes in Location headers with 302 status, and debugging API redirect chains. But on the desktop app, redirects were just never followed, creating the opposite effect. The browser's fetch API applies atomic HTTP redirect handling per spec, making it impossible to intercept redirects and inspect their responses. The Native and Agent interceptors use curl and native HTTP clients respectively, both supporting redirect control, making this feature viable for these specific interceptors. (Proxyscotch tbd). ## Changes ### Frontend Settings Pages Added toggle in `Native.vue` and `Agent.vue` interceptor settings positioned after "Verify Peer" in the security section, labeled "Follow Redirects" with default enabled to maintain backward compatibility. ```typescript <div class="flex items-center"> <HoppSmartToggle :on="domainSettings[selectedDomain]?.options?.followRedirects ?? true" @change="toggleFollowRedirects" /> {{ t("settings.follow_redirects") }} </div> ``` Defaulting to `true` for consistency with the browser and overall Hoppscotch ecosystem mainly. If one platform prescribes, it's better to stick with it. ### Domain Settings Structure Extended `InputDomainSetting` type with new `options` namespace storing request-level behavior configuration: ```typescript options?: { followRedirects?: boolean maxRedirects?: number timeout?: number decompress?: boolean cookies?: boolean keepAlive?: boolean } ``` This was already present in `hoppscotch-kernel` for just such use-cases. The `options` field defaults to `{ followRedirects: true }` in default domain config for both interceptors, preserving existing behavior as mentioned above. ### Settings Conversion Updated `domain-settings.ts` converter to map domain settings `options` to `RelayRequest.meta.options`. The converter uses Either/Option types for safe(r) composition: ```typescript const convertOptions = ( options?: InputDomainSetting["options"] ): O.Option<NonNullable<RelayRequest["meta"]>["options"]> => pipe( O.fromNullable(options), O.map((opts) => ({ ...(opts.followRedirects !== undefined && { followRedirects: opts.followRedirects, }), // ... rest })), O.filter((opts) => Object.keys(opts).length > 0) ) ``` Stores merge domain-specific overrides with global defaults, applying domain settings when resolving merged configuration via `getMergedSettings(host)`. ### Rust Backend Types Added request metadata structs in relay interop: ```rust #[derive(Debug, Serialize, Deserialize, Clone)] pub struct RequestMeta { pub options: Option<RequestOptions>, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct RequestOptions { pub follow_redirects: Option<bool>, pub max_redirects: Option<u32>, pub timeout: Option<u64>, // ... rest } ``` Request struct includes `meta: Option<RequestMeta>` field passed from frontend through the relay plugin. Again this was already present in `hoppscotch-kernel` for just such use-cases. ### Curl Configuration Request processing applies settings to curl handle. When `follow_redirects` is false, curl returns the initial redirect response: ```rust if let Some(follow) = options.follow_redirects { self.handle.follow_location(follow).map_err(|e| { RelayError::Network { message: "Failed to set redirect behavior".into(), cause: Some(e.to_string()), } })?; } ``` Error handling logs at appropriate levels (debug for success, error for failures) with contextual messages. Although I don't imagine this happening. ## Behavior ### With followRedirects: true (default) Request to `https://httpbin.org/redirect/3` follows all redirects and returns 200 from final destination with complete response body. NOTE: Check `Network` tab from the dev tools. ### With followRedirects: false Same request returns 302 with redirect HTML body and Location header intact, allowing inspection of redirect details. NOTE: Check `Network` tab from the dev tools. ## Compatibility ### API Surface No changes to request/response contracts. The `meta` field is optional and backend applies defaults when not provided. ### Browser Interceptors Extension and Proxy interceptor show no redirect control in settings since browser APIs prevent redirect interception. These interceptors continue using browser defaults (always follow redirects). ### Bundle Format No changes. This is purely request-time behavior configuration. ## Testing Verify with https://httpbin.org/redirect/3: NOTE: Check `Network` tab from the dev tools. 1. With `followRedirects: true` (default): - Returns 200 status - Response body from `/get` endpoint - No redirect HTML 2. With `followRedirects: false`: - Returns 302 status - Response body contains redirect HTML - Location header visible in response headers ## Backward Compatibility Default behavior maintains automatic redirect following. Existing users experience no change unless they explicitly disable redirects for a domain. All domain settings migration preserves existing configuration without loss. For example it would be prevented on desktop, but "default" is follow always, hence consistency across platforms. This is purely additive functionality that does not modify existing request/response handling as per the web app. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
kerem 2026-03-17 02:42:34 +03:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/hoppscotch#5244
No description provided.