Skip to content
41 changes: 41 additions & 0 deletions docs/en/reference/response.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Response object reference.
| `headers` | `dict` | Response headers |
| `content` | `bytes` | Raw bytes |
| `method` | `str` | HTTP method |
| `url` | `str` | Request URL |

## Methods

Expand Down Expand Up @@ -38,6 +39,46 @@ Get request body as text:
sent = resp.req_text() # Returns str or None
```

### assets()

Extract CSS and JavaScript asset URLs from HTML response:

```python
result = resp.assets()
# Returns: {"css": [...], "js": [...]}
```

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `css` | `bool` | `True` | Include CSS links |
| `js` | `bool` | `True` | Include JavaScript links |

**Examples:**

```python
# Get all assets (CSS + JS)
@app.get(url="https://example.com")
async def handler(resp: Response):
return resp.assets()
# Returns: {"css": ["https://example.com/style.css"], "js": ["https://example.com/app.js"]}

# CSS only
@app.get(url="https://example.com")
async def handler(resp: Response):
return resp.assets(js=False)
# Returns: {"css": [...], "js": []}

# JS only
@app.get(url="https://example.com")
async def handler(resp: Response):
return resp.assets(css=False)
# Returns: {"css": [], "js": [...]}
```

The method parses `<link rel="stylesheet" href="...">` for CSS and `<script src="...">` for JavaScript. All URLs are normalized using `urljoin`, so relative paths are converted to absolute URLs based on the request URL.

## Example

```python
Expand Down
22 changes: 22 additions & 0 deletions docs/en/tutorial/response-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ Returns request body as text:
sent = resp.req_text() # Returns "raw data"
```

### assets()

Extracts CSS and JavaScript asset URLs from HTML page:

```python
# Get all assets
result = resp.assets()
# Returns: {"css": [...], "js": [...]}

# CSS only
css_only = resp.assets(js=False)

# JS only
js_only = resp.assets(css=False)
```

The method parses:
- CSS: `<link rel="stylesheet" href="...">` tags
- JS: `<script src="...">` tags

All URLs are normalized relative to the request URL.

## Error Handling

FastHTTP automatically handles errors and logs them:
Expand Down
41 changes: 41 additions & 0 deletions docs/ru/reference/response.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
| `headers` | `dict` | Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΊΠΈ ΠΎΡ‚Π²Π΅Ρ‚Π° |
| `content` | `bytes` | Π‘Ρ‹Ρ€Ρ‹Π΅ Π±Π°ΠΉΡ‚Ρ‹ |
| `method` | `str` | HTTP ΠΌΠ΅Ρ‚ΠΎΠ΄ |
| `url` | `str` | URL запроса |

## ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹

Expand All @@ -30,6 +31,46 @@ data = resp.json() # Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ dict ΠΈΠ»ΠΈ list
sent = resp.req_json() # Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ dict ΠΈΠ»ΠΈ None
```

### assets()

Π˜Π·Π²Π»Π΅Ρ‡ΡŒ URL-адрСса CSS ΠΈ JavaScript рСсурсов ΠΈΠ· HTML-ΠΎΡ‚Π²Π΅Ρ‚Π°:

```python
result = resp.assets()
# Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚: {"css": [...], "js": [...]}
```

**ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹:**

| ΠŸΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€ | Π’ΠΈΠΏ | По ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ | ОписаниС |
|----------|-----|--------------|----------|
| `css` | `bool` | `True` | Π’ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ ссылки CSS |
| `js` | `bool` | `True` | Π’ΠΊΠ»ΡŽΡ‡Π°Ρ‚ΡŒ ссылки JavaScript |

**ΠŸΡ€ΠΈΠΌΠ΅Ρ€Ρ‹:**

```python
# ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ всС рСсурсы (CSS + JS)
@app.get(url="https://example.com")
async def handler(resp: Response):
return resp.assets()
# Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚: {"css": ["https://example.com/style.css"], "js": ["https://example.com/app.js"]}

# Волько CSS
@app.get(url="https://example.com")
async def handler(resp: Response):
return resp.assets(js=False)
# Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚: {"css": [...], "js": []}

# Волько JS
@app.get(url="https://example.com")
async def handler(resp: Response):
return resp.assets(css=False)
# Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚: {"css": [], "js": [...]}
```

ΠœΠ΅Ρ‚ΠΎΠ΄ парсит `<link rel="stylesheet" href="...">` для CSS ΠΈ `<script src="...">` для JavaScript. ВсС URL Π½ΠΎΡ€ΠΌΠ°Π»ΠΈΠ·ΡƒΡŽΡ‚ΡΡ Ρ‡Π΅Ρ€Π΅Π· `urljoin`, поэтому ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹Π΅ ΠΏΡƒΡ‚ΠΈ ΠΏΡ€Π΅ΠΎΠ±Ρ€Π°Π·ΡƒΡŽΡ‚ΡΡ Π² Π°Π±ΡΠΎΠ»ΡŽΡ‚Π½Ρ‹Π΅ Π½Π° основС URL запроса.

## ΠŸΡ€ΠΈΠΌΠ΅Ρ€

```python
Expand Down
22 changes: 22 additions & 0 deletions docs/ru/tutorial/response-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ text = resp.text # Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ str
sent = resp.req_json() # Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ {"name": "John"}
```

### assets()

Π˜Π·Π²Π»Π΅ΠΊΠ°Π΅Ρ‚ URL-адрСса CSS ΠΈ JavaScript рСсурсов ΠΈΠ· HTML-страницы:

```python
# ΠŸΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ всС рСсурсы
result = resp.assets()
# Returns: {"css": [...], "js": [...]}

# Волько CSS
css_only = resp.assets(js=False)

# Волько JS
js_only = resp.assets(css=False)
```

ΠœΠ΅Ρ‚ΠΎΠ΄ парсит:
- CSS: Ρ‚Π΅Π³ΠΈ `<link rel="stylesheet" href="...">`
- JS: Ρ‚Π΅Π³ΠΈ `<script src="...">`

ВсС ссылки Π½ΠΎΡ€ΠΌΠ°Π»ΠΈΠ·ΡƒΡŽΡ‚ΡΡ ΠΎΡ‚Π½ΠΎΡΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ URL запроса.

## ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ошибок

FastHTTP автоматичСски ΠΎΠ±Ρ€Π°Π±Π°Ρ‚Ρ‹Π²Π°Π΅Ρ‚ ошибки:
Expand Down
24 changes: 24 additions & 0 deletions examples/basic/extract_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from fasthttp import FastHTTP
from fasthttp.response import Response


app = FastHTTP(debug=True)


@app.get(url="https://example.com")
async def get_assets(resp: Response) -> dict:
return resp.assets()


@app.get(url="https://example.com")
async def get_css_only(resp: Response) -> dict:
return resp.assets(js=False)


@app.get(url="https://example.com")
async def get_js_only(resp: Response) -> dict:
return resp.assets(css=False)


if __name__ == "__main__":
app.run()
13 changes: 8 additions & 5 deletions fasthttp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@
from annotated_doc import Doc

from .client import HTTPClient
from .helpers.routing import apply_base_url, check_annotated_parameters, check_annotated_return, check_https_url
from .graphql.client import create_graphql_client
from .helpers.route_inspect import (
check_annotated_parameters,
check_annotated_return,
validate_handler,
)
from .helpers.routing import apply_base_url, check_https_url
from .logging import setup_logger
from .middleware import BaseMiddleware, MiddlewareManager
from .openapi.generator import generate_openapi_schema
Expand Down Expand Up @@ -469,8 +474,7 @@ def _add_route(
responses: dict[int, dict[Literal["model"], type[BaseModel]]] | None = None,
) -> Callable[[Callable[..., object]], Callable[..., object]]:
def decorator(func: Callable[..., object]) -> Callable[..., object]:
self._check_annotated_parameters(func=func)
self._check_annotated_func(func=func)
validate_handler(func=func)

self.routes.append(
Route(
Expand Down Expand Up @@ -795,8 +799,7 @@ async def get_user(resp: Response) -> dict:
```
"""
def decorator(func: Callable[..., object]) -> Callable[..., object]:
self._check_annotated_parameters(func=func)
self._check_annotated_func(func=func)
validate_handler(func=func)

async def graphql_handler(response: Response) -> object:
from inspect import iscoroutinefunction
Expand Down
5 changes: 4 additions & 1 deletion fasthttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ def _build_response(
config: dict,
response: httpx.Response
) -> Response:
return Response(
resp = Response(
status=response.status_code,
text=response.text,
headers=dict(response.headers),
Expand All @@ -249,6 +249,8 @@ def _build_response(
req_json=route.json,
req_data=route.data,
)
resp._set_url(route.url)
return resp

async def _process_handler_result(
self,
Expand Down Expand Up @@ -334,6 +336,7 @@ async def send(
headers={},
method=route.method,
)
empty_response._set_url(route.url)
handler_result = await route.handler(empty_response)
return await self._process_handler_result(
empty_response,
Expand Down
Loading
Loading