From 7bf928841e1104fced059a54c5306e2bb0e73eba Mon Sep 17 00:00:00 2001 From: dap0am Date: Fri, 1 Aug 2025 23:35:54 +0100 Subject: [PATCH 1/3] docs: add comprehensive routing rules syntax documentation Addresses issue #2962 by adding detailed documentation for: - Dynamic path parameter syntax () - Supported characters in path parameters - Regex pattern conversion process - Advanced routing examples with complex parameters - Catch-all routes with regex patterns - Route matching priority and behavior The new documentation provides clear explanations of how routing rules work, the regex syntax supported, and examples of complex routing patterns that were previously undocumented. --- docs/core/event_handler/api_gateway.md | 136 ++++++++++++++++++++++++- 1 file changed, 132 insertions(+), 4 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index 76991182737..b2f019141e2 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -214,17 +214,145 @@ Each dynamic route you set must be part of your function signature. This allows ???+ tip You can also nest dynamic paths, for example `/todos//`. +#### Routing Rules Syntax + +The routing system uses a specific syntax to define dynamic URL patterns. Understanding this syntax is crucial for creating flexible and robust API routes. + +##### Dynamic Path Parameters + +Dynamic path parameters are defined using angle brackets `` syntax. These parameters are automatically converted to regex patterns for efficient route matching. + +**Syntax**: `/path/` + +* **Parameter names** must contain only word characters (letters, numbers, underscore) +* **Captured values** can contain letters, numbers, underscores, and these special characters: `-._~()'!*:@,;=+&$%<> \[]{}|^` + +=== "routing_syntax_basic.py" + + ```python + from aws_lambda_powertools.event_handler import APIGatewayRestResolver + + app = APIGatewayRestResolver() + + @app.get("/users/") + def get_user(user_id: str): + # user_id can be: "123", "user-456", "john.doe", "user_with_underscores" + return {"user_id": user_id} + + @app.get("/orders//items/") + def get_order_item(order_id: str, item_id: str): + # Multiple parameters: /orders/ORD-123/items/ITEM_456 + return {"order_id": order_id, "item_id": item_id} + ``` + +##### Regex Pattern Conversion + +Behind the scenes, dynamic routes are converted to regex patterns for efficient matching: + +| Route Pattern | Generated Regex | Matches | Doesn't Match | +|---------------|-----------------|---------|---------------| +| `/users/` | `^/users/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/users/123`, `/users/user-456` | `/users/123/profile` | +| `/api//users` | `^/api/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)/users$` | `/api/v1/users`, `/api/2.0/users` | `/api/users` | +| `/files/` | `^/files/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/files/document.pdf`, `/files/folder%20name` | `/files/sub/folder/file.txt` | + +???+ warning "Route Matching Behavior" + * Routes are matched **exactly** - no partial matches + * Dynamic parameters match **non-slash characters only** by default + * For paths with slashes, use [catch-all routes](#catch-all-routes) instead + +##### Advanced Examples + +**Complex Parameter Names** + +```python +@app.get("/api//resources//") +def get_resource(api_version: str, resource_type: str, resource_id: str): + # Matches: /api/v1/resources/users/123 + # api_version = "v1", resource_type = "users", resource_id = "123" + return { + "version": api_version, + "type": resource_type, + "id": resource_id + } +``` + +**Mixed Static and Dynamic Paths** + +```python +@app.get("/organizations//teams//members") +def list_team_members(org_id: str, team_id: str): + # Matches: /organizations/acme-corp/teams/engineering/members + return {"org": org_id, "team": team_id, "action": "list_members"} +``` + +**Handling Special Characters** + +```python +@app.get("/files/") +def get_file(filename: str): + # These all work: + # /files/document.pdf → filename = "document.pdf" + # /files/my-file_v2.txt → filename = "my-file_v2.txt" + # /files/file%20with%20spaces → filename = "file%20with%20spaces" + return {"filename": filename} +``` + +???+ tip "Function Parameter Names Must Match" + The parameter names in your route (``) must exactly match the parameter names in your function signature (`user_id: str`). This is how the framework knows which captured values to pass to which parameters. + #### Catch-all routes ???+ note We recommend having explicit routes whenever possible; use catch-all routes sparingly. -You can use a [regex](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} string to handle an arbitrary number of paths within a request, for example `.+`. +For scenarios where you need to handle arbitrary or deeply nested paths, you can use regex patterns directly in your route definitions. These are particularly useful for proxy routes or when dealing with file paths. -You can also combine nested paths with greedy regex to catch in between routes. +##### Using Regex Patterns -???+ warning - We choose the most explicit registered route that matches an incoming event. +You can use standard [Python regex patterns](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} in your route definitions: + +| Pattern | Description | Examples | +|---------|-------------|----------| +| `.+` | Matches one or more characters (greedy) | `/proxy/.+` matches `/proxy/any/deep/path` | +| `.*` | Matches zero or more characters (greedy) | `/files/.*` matches `/files/` and `/files/deep/path` | +| `[^/]+` | Matches one or more non-slash characters | `/api/[^/]+` matches `/api/v1` but not `/api/v1/users` | +| `\w+` | Matches one or more word characters | `/users/\w+` matches `/users/john123` | + +**Common Regex Route Examples:** + +```python +# File path proxy - captures everything after /files/ +@app.get("/files/.+") +def serve_file(): + file_path = app.current_event.path.replace("/files/", "") + return {"file_path": file_path} + +# API versioning with any format +@app.get("/api/v\d+/.*") # Matches /api/v1/users, /api/v2/posts/123 +def handle_versioned_api(): + return {"api_version": "handled"} + +# Catch-all for unmatched routes +@app.route(".*", method=["GET", "POST"]) # Must be last route +def catch_all(): + return {"message": "Route not found", "path": app.current_event.path} +``` + +##### Combining Dynamic Parameters with Regex + +```python +# Mixed: dynamic parameter + regex catch-all +@app.get("/users//files/.+") +def get_user_files(user_id: str): + file_path = app.current_event.path.split(f"/users/{user_id}/files/")[1] + return {"user_id": user_id, "file_path": file_path} +``` + +???+ warning "Route Matching Priority" + * Routes are matched in **order of specificity**, not registration order + * More specific routes (exact matches) take precedence over regex patterns + * Among regex routes, the first registered matching route wins + * Always place catch-all routes (`.*`) last === "dynamic_routes_catch_all.py" From c018f9c6a81f03f9f5dedce642e069e7cbd89214 Mon Sep 17 00:00:00 2001 From: dap0am Date: Tue, 5 Aug 2025 00:52:18 +0100 Subject: [PATCH 2/3] docs: refactor routing rules syntax documentation per review feedback - Remove redundant routing rules syntax introduction section - Update header levels from h5 to h4 for better hierarchy - Move inline code examples to separate files in examples/event_handler_rest/src - Add proper Lambda handlers with Logger and Tracer utilities - Reorganize content by moving regex pattern table to Dynamic Path Parameters section - Update tip formatting to match project style - Simplify inline comments to follow project conventions --- docs/core/event_handler/api_gateway.md | 76 +++---------------- .../src/routing_advanced_examples.py | 39 ++++++++++ .../src/routing_syntax_basic.py | 28 +++++++ 3 files changed, 79 insertions(+), 64 deletions(-) create mode 100644 examples/event_handler_rest/src/routing_advanced_examples.py create mode 100644 examples/event_handler_rest/src/routing_syntax_basic.py diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index b2f019141e2..c9e9c434d1c 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -214,90 +214,38 @@ Each dynamic route you set must be part of your function signature. This allows ???+ tip You can also nest dynamic paths, for example `/todos//`. -#### Routing Rules Syntax +#### Dynamic Path Parameters -The routing system uses a specific syntax to define dynamic URL patterns. Understanding this syntax is crucial for creating flexible and robust API routes. - -##### Dynamic Path Parameters - -Dynamic path parameters are defined using angle brackets `` syntax. These parameters are automatically converted to regex patterns for efficient route matching. +Dynamic path parameters are defined using angle brackets `` syntax. These parameters are automatically converted to regex patterns for efficient route matching and performance gains. **Syntax**: `/path/` * **Parameter names** must contain only word characters (letters, numbers, underscore) * **Captured values** can contain letters, numbers, underscores, and these special characters: `-._~()'!*:@,;=+&$%<> \[]{}|^` -=== "routing_syntax_basic.py" - - ```python - from aws_lambda_powertools.event_handler import APIGatewayRestResolver - - app = APIGatewayRestResolver() - - @app.get("/users/") - def get_user(user_id: str): - # user_id can be: "123", "user-456", "john.doe", "user_with_underscores" - return {"user_id": user_id} - - @app.get("/orders//items/") - def get_order_item(order_id: str, item_id: str): - # Multiple parameters: /orders/ORD-123/items/ITEM_456 - return {"order_id": order_id, "item_id": item_id} - ``` - -##### Regex Pattern Conversion - -Behind the scenes, dynamic routes are converted to regex patterns for efficient matching: - | Route Pattern | Generated Regex | Matches | Doesn't Match | |---------------|-----------------|---------|---------------| | `/users/` | `^/users/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/users/123`, `/users/user-456` | `/users/123/profile` | | `/api//users` | `^/api/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)/users$` | `/api/v1/users`, `/api/2.0/users` | `/api/users` | | `/files/` | `^/files/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/files/document.pdf`, `/files/folder%20name` | `/files/sub/folder/file.txt` | -???+ warning "Route Matching Behavior" - * Routes are matched **exactly** - no partial matches - * Dynamic parameters match **non-slash characters only** by default - * For paths with slashes, use [catch-all routes](#catch-all-routes) instead +=== "routing_syntax_basic.py" -##### Advanced Examples + ```python hl_lines="11 18" + --8<-- "examples/event_handler_rest/src/routing_syntax_basic.py" + ``` -**Complex Parameter Names** -```python -@app.get("/api//resources//") -def get_resource(api_version: str, resource_type: str, resource_id: str): - # Matches: /api/v1/resources/users/123 - # api_version = "v1", resource_type = "users", resource_id = "123" - return { - "version": api_version, - "type": resource_type, - "id": resource_id - } -``` -**Mixed Static and Dynamic Paths** +#### Advanced Examples -```python -@app.get("/organizations//teams//members") -def list_team_members(org_id: str, team_id: str): - # Matches: /organizations/acme-corp/teams/engineering/members - return {"org": org_id, "team": team_id, "action": "list_members"} -``` - -**Handling Special Characters** +=== "routing_advanced_examples.py" -```python -@app.get("/files/") -def get_file(filename: str): - # These all work: - # /files/document.pdf → filename = "document.pdf" - # /files/my-file_v2.txt → filename = "my-file_v2.txt" - # /files/file%20with%20spaces → filename = "file%20with%20spaces" - return {"filename": filename} -``` + ```python hl_lines="12 25 33" + --8<-- "examples/event_handler_rest/src/routing_advanced_examples.py" + ``` -???+ tip "Function Parameter Names Must Match" +???+ tip "Function parameter names must match" The parameter names in your route (``) must exactly match the parameter names in your function signature (`user_id: str`). This is how the framework knows which captured values to pass to which parameters. #### Catch-all routes diff --git a/examples/event_handler_rest/src/routing_advanced_examples.py b/examples/event_handler_rest/src/routing_advanced_examples.py new file mode 100644 index 00000000000..ece9e5e1999 --- /dev/null +++ b/examples/event_handler_rest/src/routing_advanced_examples.py @@ -0,0 +1,39 @@ +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.logging import correlation_paths +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() +app = APIGatewayRestResolver() + + +@app.get("/api//resources//") +@tracer.capture_method +def get_resource(api_version: str, resource_type: str, resource_id: str): + # handles nested dynamic parameters in API versioned routes + return { + "version": api_version, + "type": resource_type, + "id": resource_id + } + + +@app.get("/organizations//teams//members") +@tracer.capture_method +def list_team_members(org_id: str, team_id: str): + # combines dynamic paths with static segments + return {"org": org_id, "team": team_id, "action": "list_members"} + + +@app.get("/files/") +@tracer.capture_method +def get_file(filename: str): + # handles special characters including dots, dashes, and URL-encoded spaces + return {"filename": filename} + + +@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) +@tracer.capture_lambda_handler +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) \ No newline at end of file diff --git a/examples/event_handler_rest/src/routing_syntax_basic.py b/examples/event_handler_rest/src/routing_syntax_basic.py new file mode 100644 index 00000000000..26d3e36eafd --- /dev/null +++ b/examples/event_handler_rest/src/routing_syntax_basic.py @@ -0,0 +1,28 @@ +from aws_lambda_powertools import Logger, Tracer +from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.logging import correlation_paths +from aws_lambda_powertools.utilities.typing import LambdaContext + +tracer = Tracer() +logger = Logger() +app = APIGatewayRestResolver() + + +@app.get("/users/") +@tracer.capture_method +def get_user(user_id: str): + # user_id value comes as a string with special chars support + return {"user_id": user_id} + + +@app.get("/orders//items/") +@tracer.capture_method +def get_order_item(order_id: str, item_id: str): + # multiple dynamic parameters are supported + return {"order_id": order_id, "item_id": item_id} + + +@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) +@tracer.capture_lambda_handler +def lambda_handler(event: dict, context: LambdaContext) -> dict: + return app.resolve(event, context) \ No newline at end of file From 5974441acecae5daa0550903bdc20a775aa49116 Mon Sep 17 00:00:00 2001 From: Leandro Damascena Date: Tue, 5 Aug 2025 13:21:27 +0100 Subject: [PATCH 3/3] Small changes --- docs/core/event_handler/api_gateway.md | 83 +++++-------------- .../src/dynamic_routes_catch_all.py | 26 ++++++ .../src/routing_advanced_examples.py | 11 +-- .../src/routing_syntax_basic.py | 2 +- 4 files changed, 49 insertions(+), 73 deletions(-) diff --git a/docs/core/event_handler/api_gateway.md b/docs/core/event_handler/api_gateway.md index c9e9c434d1c..3dc09f61131 100644 --- a/docs/core/event_handler/api_gateway.md +++ b/docs/core/event_handler/api_gateway.md @@ -196,8 +196,8 @@ You can use `/todos/` to configure dynamic URL paths, where `` Each dynamic route you set must be part of your function signature. This allows us to call your function using keyword arguments when matching your dynamic route. -???+ note - For brevity, we will only include the necessary keys for each sample request for the example to work. +???+ tip + You can also nest dynamic paths, for example `/todos//`. === "dynamic_routes.py" @@ -211,23 +211,21 @@ Each dynamic route you set must be part of your function signature. This allows --8<-- "examples/event_handler_rest/src/dynamic_routes.json" ``` -???+ tip - You can also nest dynamic paths, for example `/todos//`. - -#### Dynamic Path Parameters +#### Dynamic path mechanism Dynamic path parameters are defined using angle brackets `` syntax. These parameters are automatically converted to regex patterns for efficient route matching and performance gains. **Syntax**: `/path/` * **Parameter names** must contain only word characters (letters, numbers, underscore) -* **Captured values** can contain letters, numbers, underscores, and these special characters: `-._~()'!*:@,;=+&$%<> \[]{}|^` +* **Captured values** can contain letters, numbers, underscores, and these special characters: `-._~()'!*:@,;=+&$%<> \[]{}|^`. Reserved characters must be percent-encoded in URLs to prevent errors. -| Route Pattern | Generated Regex | Matches | Doesn't Match | -|---------------|-----------------|---------|---------------| -| `/users/` | `^/users/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/users/123`, `/users/user-456` | `/users/123/profile` | -| `/api//users` | `^/api/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)/users$` | `/api/v1/users`, `/api/2.0/users` | `/api/users` | -| `/files/` | `^/files/(?P[-._~()'!*:@,;=+&$%<> \[\]{}|^\w]+)$` | `/files/document.pdf`, `/files/folder%20name` | `/files/sub/folder/file.txt` | +| Route Pattern | Matches | Doesn't Match | +|---------------|---------|---------------| +| `/users/` | `/users/123`, `/users/user-456` | `/users/123/profile` | +| `/api//users` | `/api/v1/users`, `/api/2.0/users` | `/api/users` | +| `/files/` | `/files/document.pdf`, `/files/folder%20name` | `/files/sub/folder/file.txt` | +| `/files//` | `/files/src/document.pdf`, `/files/src/test.txt` | `/files/sub/folder/file.txt` | === "routing_syntax_basic.py" @@ -235,13 +233,9 @@ Dynamic path parameters are defined using angle brackets `` synt --8<-- "examples/event_handler_rest/src/routing_syntax_basic.py" ``` - - -#### Advanced Examples - === "routing_advanced_examples.py" - ```python hl_lines="12 25 33" + ```python hl_lines="11 22" --8<-- "examples/event_handler_rest/src/routing_advanced_examples.py" ``` @@ -250,14 +244,13 @@ Dynamic path parameters are defined using angle brackets `` synt #### Catch-all routes -???+ note - We recommend having explicit routes whenever possible; use catch-all routes sparingly. - For scenarios where you need to handle arbitrary or deeply nested paths, you can use regex patterns directly in your route definitions. These are particularly useful for proxy routes or when dealing with file paths. +**We recommend** having explicit routes whenever possible; use catch-all routes sparingly. + ##### Using Regex Patterns -You can use standard [Python regex patterns](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} in your route definitions: +You can use standard [Python regex patterns](https://docs.python.org/3/library/re.html#regular-expression-syntax){target="_blank" rel="nofollow"} in your route definitions, for example: | Pattern | Description | Examples | |---------|-------------|----------| @@ -266,53 +259,17 @@ You can use standard [Python regex patterns](https://docs.python.org/3/library/r | `[^/]+` | Matches one or more non-slash characters | `/api/[^/]+` matches `/api/v1` but not `/api/v1/users` | | `\w+` | Matches one or more word characters | `/users/\w+` matches `/users/john123` | -**Common Regex Route Examples:** - -```python -# File path proxy - captures everything after /files/ -@app.get("/files/.+") -def serve_file(): - file_path = app.current_event.path.replace("/files/", "") - return {"file_path": file_path} - -# API versioning with any format -@app.get("/api/v\d+/.*") # Matches /api/v1/users, /api/v2/posts/123 -def handle_versioned_api(): - return {"api_version": "handled"} - -# Catch-all for unmatched routes -@app.route(".*", method=["GET", "POST"]) # Must be last route -def catch_all(): - return {"message": "Route not found", "path": app.current_event.path} -``` - -##### Combining Dynamic Parameters with Regex - -```python -# Mixed: dynamic parameter + regex catch-all -@app.get("/users//files/.+") -def get_user_files(user_id: str): - file_path = app.current_event.path.split(f"/users/{user_id}/files/")[1] - return {"user_id": user_id, "file_path": file_path} -``` - -???+ warning "Route Matching Priority" - * Routes are matched in **order of specificity**, not registration order - * More specific routes (exact matches) take precedence over regex patterns - * Among regex routes, the first registered matching route wins - * Always place catch-all routes (`.*`) last - === "dynamic_routes_catch_all.py" - ```python hl_lines="11" + ```python hl_lines="11 17 18 24 25 30 31 36 37" --8<-- "examples/event_handler_rest/src/dynamic_routes_catch_all.py" ``` -=== "dynamic_routes_catch_all.json" - - ```json - --8<-- "examples/event_handler_rest/src/dynamic_routes_catch_all.json" - ``` +???+ warning "Route Matching Priority" + - Routes are matched in **order of specificity**, not registration order + - More specific routes (exact matches) take precedence over regex patterns + - Among regex routes, the first registered matching route wins + - Always place catch-all routes (`.*`) last ### HTTP Methods diff --git a/examples/event_handler_rest/src/dynamic_routes_catch_all.py b/examples/event_handler_rest/src/dynamic_routes_catch_all.py index f615f2a8dee..7242edea786 100644 --- a/examples/event_handler_rest/src/dynamic_routes_catch_all.py +++ b/examples/event_handler_rest/src/dynamic_routes_catch_all.py @@ -14,6 +14,32 @@ def catch_any_route_get_method(): return {"path_received": app.current_event.path} +# File path proxy - captures everything after /files/ +@app.get("/files/.+") +def serve_file(): + file_path = app.current_event.path.replace("/files/", "") + return {"file_path": file_path} + + +# API versioning with any format +@app.get(r"/api/v\d+/.*") # Matches /api/v1/users, /api/v2/posts/123 +def handle_versioned_api(): + return {"api_version": "handled"} + + +# Catch-all for unmatched routes +@app.route(".*", method=["GET", "POST"]) # Must be last route +def catch_all(): + return {"message": "Route not found", "path": app.current_event.path} + + +# Mixed: dynamic parameter + regex catch-all +@app.get("/users//files/.+") +def get_user_files(user_id: str): + file_path = app.current_event.path.split(f"/users/{user_id}/files/")[1] + return {"user_id": user_id, "file_path": file_path} + + # You can continue to use other utilities just as before @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) @tracer.capture_lambda_handler diff --git a/examples/event_handler_rest/src/routing_advanced_examples.py b/examples/event_handler_rest/src/routing_advanced_examples.py index ece9e5e1999..857a486078b 100644 --- a/examples/event_handler_rest/src/routing_advanced_examples.py +++ b/examples/event_handler_rest/src/routing_advanced_examples.py @@ -15,7 +15,7 @@ def get_resource(api_version: str, resource_type: str, resource_id: str): return { "version": api_version, "type": resource_type, - "id": resource_id + "id": resource_id, } @@ -26,14 +26,7 @@ def list_team_members(org_id: str, team_id: str): return {"org": org_id, "team": team_id, "action": "list_members"} -@app.get("/files/") -@tracer.capture_method -def get_file(filename: str): - # handles special characters including dots, dashes, and URL-encoded spaces - return {"filename": filename} - - @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) @tracer.capture_lambda_handler def lambda_handler(event: dict, context: LambdaContext) -> dict: - return app.resolve(event, context) \ No newline at end of file + return app.resolve(event, context) diff --git a/examples/event_handler_rest/src/routing_syntax_basic.py b/examples/event_handler_rest/src/routing_syntax_basic.py index 26d3e36eafd..46a2c6dc81f 100644 --- a/examples/event_handler_rest/src/routing_syntax_basic.py +++ b/examples/event_handler_rest/src/routing_syntax_basic.py @@ -25,4 +25,4 @@ def get_order_item(order_id: str, item_id: str): @logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_REST) @tracer.capture_lambda_handler def lambda_handler(event: dict, context: LambdaContext) -> dict: - return app.resolve(event, context) \ No newline at end of file + return app.resolve(event, context) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy