Skip to content

Commit 3b95758

Browse files
committed
更新 .gitignore 文件以排除测试脚本;修改 README.md 中的安装链接和命令行选项描述;在 main.rs 中添加对目标 URL 的规范化支持,并增强参数的灵活性;新增 CONTRIBUTING.md 文件以指导贡献流程。
1 parent 2c7baf7 commit 3b95758

File tree

4 files changed

+161
-16
lines changed

4 files changed

+161
-16
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ Cargo.lock
1414
# OS
1515
.DS_Store
1616
Thumbs.db
17+
18+
# Test scripts
19+
test_target_formats.sh
20+
manual_test.sh

CONTRIBUTING.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Contributing to apxy
2+
3+
Thank you for your interest in contributing to apxy! 🎉
4+
5+
## Development Setup
6+
7+
### Prerequisites
8+
9+
- Rust 1.70 or later
10+
- cargo
11+
12+
### Clone and Build
13+
14+
```bash
15+
git clone https://github.com/talkincode/apxy
16+
cd apxy
17+
cargo build
18+
```
19+
20+
### Run Tests
21+
22+
```bash
23+
cargo test
24+
cargo clippy
25+
cargo fmt --check
26+
```
27+
28+
## Making Changes
29+
30+
1. Fork the repository
31+
2. Create a feature branch: `git checkout -b feature/my-feature`
32+
3. Make your changes
33+
4. Run tests: `cargo test`
34+
5. Format code: `cargo fmt`
35+
6. Check lints: `cargo clippy`
36+
7. Commit: `git commit -am 'Add some feature'`
37+
8. Push: `git push origin feature/my-feature`
38+
9. Create a Pull Request
39+
40+
## Code Style
41+
42+
- Follow standard Rust formatting (use `cargo fmt`)
43+
- Keep functions small and focused
44+
- Add comments for complex logic
45+
- Update documentation for API changes
46+
47+
## Pull Request Process
48+
49+
1. Update README.md if needed
50+
2. Update version in Cargo.toml following semver
51+
3. Ensure CI passes
52+
4. Request review
53+
54+
## Report Bugs
55+
56+
Open an issue with:
57+
58+
- OS and architecture
59+
- apxy version
60+
- Steps to reproduce
61+
- Expected vs actual behavior
62+
63+
## Feature Requests
64+
65+
Open an issue describing:
66+
67+
- The problem you're trying to solve
68+
- Your proposed solution
69+
- Any alternatives considered
70+
71+
## License
72+
73+
By contributing, you agree that your contributions will be licensed under the MIT License.

README.md

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@ cargo install apxy
2222

2323
```bash
2424
# Linux/macOS ARM64
25-
curl -L https://github.com/YOUR_USERNAME/apxy/releases/latest/download/apxy-aarch64-unknown-linux-musl -o apxy
25+
curl -L https://github.com/talkincode/apxy/releases/latest/download/apxy-aarch64-unknown-linux-musl -o apxy
2626
chmod +x apxy
2727
sudo mv apxy /usr/local/bin/
2828

2929
# Linux AMD64
30-
curl -L https://github.com/YOUR_USERNAME/apxy/releases/latest/download/apxy-x86_64-unknown-linux-musl -o apxy
30+
curl -L https://github.com/talkincode/apxy/releases/latest/download/apxy-x86_64-unknown-linux-musl -o apxy
3131
chmod +x apxy
3232
sudo mv apxy /usr/local/bin/
3333

3434
# macOS ARM64
35-
curl -L https://github.com/YOUR_USERNAME/apxy/releases/latest/download/apxy-aarch64-apple-darwin -o apxy
35+
curl -L https://github.com/talkincode/apxy/releases/latest/download/apxy-aarch64-apple-darwin -o apxy
3636
chmod +x apxy
3737
sudo mv apxy /usr/local/bin/
3838

3939
# macOS AMD64
40-
curl -L https://github.com/YOUR_USERNAME/apxy/releases/latest/download/apxy-x86_64-apple-darwin -o apxy
40+
curl -L https://github.com/talkincode/apxy/releases/latest/download/apxy-x86_64-apple-darwin -o apxy
4141
chmod +x apxy
4242
sudo mv apxy /usr/local/bin/
4343
```
@@ -47,10 +47,20 @@ sudo mv apxy /usr/local/bin/
4747
### Basic Proxy (No Authentication)
4848

4949
```bash
50+
# Full URL format
5051
apxy --target http://localhost:3000
52+
53+
# Shorthand: just port number
54+
apxy --target 3000
55+
56+
# Shorthand: with colon
57+
apxy --target :3000
58+
59+
# Shorthand: localhost with port
60+
apxy --target localhost:3000
5161
```
5262

53-
This will start a proxy on port 8080 that forwards all requests to `http://localhost:3000`.
63+
All the above commands will proxy to `http://localhost:3000`. This will start a proxy on port 8080 that forwards all requests.
5464

5565
### With Authentication
5666

@@ -89,15 +99,26 @@ apxy \
8999

90100
## Command Line Options
91101

92-
```
102+
```text
93103
Options:
94104
-p, --port <PORT> Port to listen on [default: 8080]
95-
-t, --target <TARGET> Target URL to proxy to
105+
-t, --target <TARGET> Target URL to proxy to (supports shorthand: port, :port, localhost:port, or full URL)
96106
-a, --auth-token <AUTH_TOKEN> Authentication token
97107
-h, --help Print help
98108
-V, --version Print version
99109
```
100110

111+
### Target Format Examples
112+
113+
The `--target` parameter supports various formats for convenience:
114+
115+
- **Port only**: `3000``http://localhost:3000`
116+
- **With colon**: `:3000``http://localhost:3000`
117+
- **Host:port**: `localhost:3000``http://localhost:3000`
118+
- **Full URL**: `http://localhost:3000``http://localhost:3000`
119+
- **Remote**: `example.com:8080``http://example.com:8080`
120+
- **HTTPS**: `https://api.example.com``https://api.example.com`
121+
101122
## Use Cases
102123

103124
- Add authentication layer to existing services
@@ -108,7 +129,7 @@ Options:
108129
## Building from Source
109130

110131
```bash
111-
git clone https://github.com/YOUR_USERNAME/apxy
132+
git clone https://github.com/talkincode/apxy
112133
cd apxy
113134
cargo build --release
114135
```

src/main.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,28 @@ use hyper_util::rt::TokioIo;
88
use std::net::SocketAddr;
99
use tokio::net::TcpListener;
1010

11-
#[derive(Parser, Debug)]
11+
#[derive(Parser, Debug, Clone)]
1212
#[command(author, version, about, long_about = None)]
1313
struct Args {
1414
/// Port to listen on
1515
#[arg(short, long, default_value = "8080")]
1616
port: u16,
1717

18-
/// Target URL to proxy to
18+
/// Target URL to proxy to (supports shorthand: port, :port, localhost:port, or full URL)
1919
#[arg(short, long)]
2020
target: String,
2121

22-
/// Authentication token (can be passed via URL param 'token' or header 'X-Auth-Token')
22+
/// Authentication token (can be passed via URL param or header)
2323
#[arg(short, long)]
2424
auth_token: Option<String>,
25+
26+
/// URL parameter name for authentication token
27+
#[arg(long, default_value = "token")]
28+
auth_param: String,
29+
30+
/// Header name for authentication token
31+
#[arg(long, default_value = "X-Auth-Token")]
32+
auth_header: String,
2533
}
2634

2735
type BoxBody = http_body_util::combinators::BoxBody<Bytes, hyper::Error>;
@@ -32,10 +40,40 @@ fn full<T: Into<Bytes>>(chunk: T) -> BoxBody {
3240
.boxed()
3341
}
3442

43+
/// Normalize target URL from various shorthand formats
44+
fn normalize_target(target: &str) -> String {
45+
let target = target.trim();
46+
47+
// If it already starts with http:// or https://, return as is
48+
if target.starts_with("http://") || target.starts_with("https://") {
49+
return target.to_string();
50+
}
51+
52+
// If it's just a port number (e.g., "3000")
53+
if target.parse::<u16>().is_ok() {
54+
return format!("http://localhost:{}", target);
55+
}
56+
57+
// If it starts with : (e.g., ":3000")
58+
if target.starts_with(':') {
59+
return format!("http://localhost{}", target);
60+
}
61+
62+
// If it's localhost:port or 127.0.0.1:port format (no protocol)
63+
if !target.contains("://") {
64+
return format!("http://{}", target);
65+
}
66+
67+
// Default: assume it's meant to be http
68+
format!("http://{}", target)
69+
}
70+
3571
async fn handle_request(
3672
req: Request<Incoming>,
3773
target: String,
3874
auth_token: Option<String>,
75+
auth_param: String,
76+
auth_header: String,
3977
) -> Result<Response<BoxBody>, hyper::Error> {
4078
// Check authentication if token is set
4179
if let Some(expected_token) = &auth_token {
@@ -45,7 +83,7 @@ async fn handle_request(
4583
if let Some(query) = req.uri().query() {
4684
for pair in query.split('&') {
4785
if let Some((key, value)) = pair.split_once('=') {
48-
if key == "token" && value == expected_token {
86+
if key == auth_param && value == expected_token {
4987
authenticated = true;
5088
break;
5189
}
@@ -55,7 +93,7 @@ async fn handle_request(
5593

5694
// Check header
5795
if !authenticated {
58-
if let Some(header_token) = req.headers().get("x-auth-token") {
96+
if let Some(header_token) = req.headers().get(auth_header.to_lowercase().as_str()) {
5997
if header_token.to_str().unwrap_or("") == expected_token {
6098
authenticated = true;
6199
}
@@ -122,23 +160,30 @@ async fn handle_request(
122160
#[tokio::main]
123161
async fn main() -> Result<(), Box<dyn std::error::Error>> {
124162
let args = Args::parse();
163+
164+
// Normalize target URL to support shorthand formats
165+
let target = normalize_target(&args.target);
125166

126167
let addr: SocketAddr = ([0, 0, 0, 0], args.port).into();
127168
let listener = TcpListener::bind(addr).await?;
128169

129170
println!("🚀 apxy listening on http://{}", addr);
130-
println!("📡 Proxying to: {}", args.target);
171+
println!("📡 Proxying to: {}", target);
131172
if args.auth_token.is_some() {
132173
println!("🔒 Authentication: enabled");
174+
println!(" URL param: {}", args.auth_param);
175+
println!(" Header: {}", args.auth_header);
133176
} else {
134177
println!("⚠️ Authentication: disabled");
135178
}
136179

137180
loop {
138181
let (stream, _) = listener.accept().await?;
139182
let io = TokioIo::new(stream);
140-
let target = args.target.clone();
183+
let target = target.clone();
141184
let auth_token = args.auth_token.clone();
185+
let auth_param = args.auth_param.clone();
186+
let auth_header = args.auth_header.clone();
142187

143188
tokio::spawn(async move {
144189
if let Err(err) = http1::Builder::new()
@@ -147,7 +192,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
147192
service_fn(move |req| {
148193
let target = target.clone();
149194
let auth_token = auth_token.clone();
150-
handle_request(req, target, auth_token)
195+
let auth_param = auth_param.clone();
196+
let auth_header = auth_header.clone();
197+
handle_request(req, target, auth_token, auth_param, auth_header)
151198
}),
152199
)
153200
.await

0 commit comments

Comments
 (0)