问题
很早之前遇到一个问题,命令行怎样通过代理去访问被墙的网站。
比如,在没有科学上网的情况下:
curl google.com
curl: (28) Failed to connect to google.com port 80 after 21074 ms: Could not connect to server
网上的大多数教程,都是说在环境变量增加 http_proxy 和 https_proxy
配置好之后,再重新打开“命令行提示符”执行:
curl google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.com/">here</A>.
</BODY></HTML>
这样就可以解决问题了。 但是当不需要通过代理访问时,又需要手动删除掉这两个环境变量。
需要(手动添加)<-> 不需要(手动删除),在这两者之间切换时,每次手动去添加和删除就很痛苦,有时嫌麻烦,索性就不访问被墙的网站了。
我没有找到合适的客户端(GUI),如果你知道有好用的客户端能解决我这个痛点,麻烦评论区告诉我。
既然没有好用的 GUI 应用,那就撸起袖子自己写一个。
最近 Zed 发布了 Windows 版本,它的 gpui 库也发布到 creates.io 上了。
那这次就用 gpui 来练练手。
开发
开发前准备
- 操作系统: Windows 11 专业版(24H2)
- 编辑器: Zed
- rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
- 编译工具链: stable-x86_64-pc-windows-msvc (active, default)
- 配置好 crates.io 镜像代理,参考 RsProxy
创建项目并添加依赖
cargo new env_gui
zed env_gui
cargo add gpui tokio
写代码
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use gpui::{
App, Application, Bounds, Context, SharedString, Window, WindowBounds, WindowOptions, div,
prelude::*, px, rgb, size,
};
use std::process::Command;
use tokio::task;
async fn set_proxy_vars(port: SharedString) {
task::spawn_blocking(move || {
let proxy_url = format!("http://127.0.0.1:{port}");
set_env_item("https_proxy", &proxy_url);
set_env_item("http_proxy", &proxy_url);
println!("set proxy vars end");
})
.await
.ok();
}
// 设置用户环境变量
fn set_env_item(key: &str, value: &String) {
let mut binding = Command::new("setx");
let command = binding.args([key, value.as_str()]);
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
command.creation_flags(0x08000000);
}
command.status().expect("Failed to set {key}");
}
async fn unset_proxy_vars() {
task::spawn_blocking(move || {
unset_env_item("http_proxy");
unset_env_item("https_proxy");
println!("unset proxy vars end");
})
.await
.ok();
}
// 删除用户环境变量
fn unset_env_item(key: &str) {
let mut binding = Command::new("reg");
let command = binding.args(["delete", "HKCU\\Environment", "/v", key, "/f"]);
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
command.creation_flags(0x08000000);
}
command.status().expect("Failed to remove {key}");
}
struct ProxyClient {
port: SharedString,
processing: bool,
}
impl Render for ProxyClient {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let text = if self.processing { "处理中..." } else { "" };
div()
.flex()
.flex_col()
.gap_3()
.bg(rgb(0x505050))
.size_full()
.justify_center()
.items_center()
.text_xl()
.text_color(rgb(0xffffff))
.child(div().child(text).absolute().top(px(10.0)))
.child(
div()
.flex()
.gap_2()
.child(
div()
.bg(gpui::green())
.px_2()
.child("添加")
.cursor(gpui::CursorStyle::PointingHand)
.id("start")
.on_click(cx.listener(move |this, _, _window, cx| {
this.processing = true;
cx.notify();
let port = this.port.clone();
cx.spawn(async move |this, cx| {
set_proxy_vars(port).await;
let _ = this.update(cx, |this, cx| {
this.processing = false;
cx.notify();
});
})
.detach();
})),
)
.child(
div()
.bg(gpui::red())
.px_2()
.child("移除")
.cursor(gpui::CursorStyle::PointingHand)
.id("stop")
.on_click(cx.listener(move |this, _, _window, cx| {
this.processing = true;
cx.notify();
cx.spawn(async move |this, cx| {
unset_proxy_vars().await;
let _ = this.update(cx, |this, cx| {
this.processing = false;
cx.notify();
});
})
.detach();
})),
),
)
}
}
#[tokio::main]
async fn main() {
Application::new().run(|cx: &mut App| {
let bounds = Bounds::centered(None, size(px(500.), px(250.0)), cx);
cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
..Default::default()
},
|_, cx| {
cx.new(|_| ProxyClient {
port: "9999".into(),
processing: false,
})
},
)
.unwrap();
});
}
Cargo.toml
[package]
name = "env_gui"
version = "0.0.1"
edition = "2024"
[dependencies]
gpui = "0.2.2"
tokio = {version = "1.48.0", features = ["full"]}
[profile.release]
opt-level = 's'
lto = true
strip = true
效果
- 效果图
-
exe 大小:4928 KB(4.18 MB)
-
内存占用