@@ -8,20 +8,28 @@ use hyper_util::rt::TokioIo;
88use std:: net:: SocketAddr ;
99use tokio:: net:: TcpListener ;
1010
11- #[ derive( Parser , Debug ) ]
11+ #[ derive( Parser , Debug , Clone ) ]
1212#[ command( author, version, about, long_about = None ) ]
1313struct 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
2735type 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+
3571async 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]
123161async 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