11#!/usr/bin/env python3
22from ..model import Service
33from ..http .model import HTTPRequest , HTTPResponse
4+ from ..http .parser import iparseCookie , formatCookie
45from ..decorators import on
56from ..server import run
67from ..client import HTTPClient
@@ -54,11 +55,12 @@ class ProxyTarget(NamedTuple):
5455 timeout : float = 2.0 # Default backend response timeout
5556
5657
58+ # TODO: Abstract headers pre-processing and post-processing
5759class ProxyService (Service ):
5860 def __init__ (self , target : ProxyTarget , * , prefix : str | None = None ):
5961 super ().__init__ (prefix = prefix )
6062 self .target : ProxyTarget = target
61- info (f"Proxy service { self .prefix or "" } to { target .uri } " )
63+ info (f"Proxy service { self .prefix or "/ " } to { target .uri } " )
6264
6365 @on (
6466 GET = "{path:any}" ,
@@ -97,7 +99,7 @@ async def proxy(self, request: HTTPRequest, path: str) -> HTTPResponse:
9799 # Now we do the request
98100 res : HTTPResponse | None = None
99101 req_path : str = f"{ uri .path or "" } { path } " if self .prefix else path
100- info (f"Proxying { path } to { self . target . uri } " )
102+ info (f"Proxying { path } to { uri . host } { req_path } " )
101103 async for atom in HTTPClient .Request (
102104 host = uri .host or "localhost" ,
103105 path = req_path ,
@@ -121,16 +123,17 @@ async def proxy(self, request: HTTPRequest, path: str) -> HTTPResponse:
121123 del res .headers .headers [name ]
122124 updated_headers : dict [str , str ] = {}
123125 # SEE: https://stackoverflow.com/questions/46288437/set-cookies-for-cross-origin-requests
124- # for name, value in res.headers.headers.items():
125- # match name:
126- # # We loosen the cookies
127- # case "Set-Cookie":
128- # # FIXME: This is super brittle
129- # updated_headers[name] = (
130- # value.replace("Secure; ", "")
131- # .replace("SameSite=Strict; ", "SameSite=Lax; ")
132- # .replace("HTTPOnly", "")
133- # ).strip(";")
126+ for name , value in res .headers .headers .items ():
127+ match name :
128+ # We loosen the cookies so that they flow
129+ case "Set-Cookie" :
130+ cookies = []
131+ for cookie in iparseCookie (value ):
132+ if cookie .key in ("Secure" , "HTTPOnly" ):
133+ continue
134+ elif cookie .value == "Secure" :
135+ cookies .append (cookie ._replace (value = "Lax" ))
136+ updated_headers [name ] = formatCookie (cookies )
134137 res .headers .headers .update (updated_headers )
135138 return (
136139 setCORSHeaders (res , origin = request .getHeader ("Origin" ))
@@ -192,19 +195,11 @@ def main(args: list[str]) -> None:
192195 # If args is None, it defaults to sys.argv[1:]
193196 options = parser .parse_args (args = args )
194197
195- # The positional arguments are now in options.url
196- # No need for len(args) == 0 check as nargs='+' handles it by raising an error
197- # if no URL is provided.
198- # If you want to handle it gracefully without exiting, you might need a try-except
199- # around parse_args or check options.url after parsing.
200- # For now, we assume argparse's default error handling is sufficient.
201-
202198 components : list [Service ] = []
203199 for url in options .url :
204200 lc = url .split ("=" , 1 )
205201 prefix , target = (None , url ) if len (lc ) == 1 else lc
206202 uri = URI .Parse (target )
207- print (uri .asDict ())
208203 if not uri .host :
209204 raise RuntimeError (f"URI has no host: { url } " )
210205 components .append (
@@ -217,9 +212,11 @@ def main(args: list[str]) -> None:
217212 )
218213 )
219214
215+ # TODO: If proxy services start with /, this is pointless.
220216 if options .files :
221217 components .append (FileService (options .files ))
222- return run (* components )
218+
219+ return run (* components , port = options .port )
223220
224221
225222if __name__ == "__main__" :
0 commit comments