Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HH-231026 small fixes #742

Merged
merged 1 commit into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion frontik/handler_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from functools import partial
from typing import TYPE_CHECKING, Optional

from fastapi.routing import APIRoute
from tornado import httputil
from tornado.httputil import HTTPServerRequest

Expand Down Expand Up @@ -79,12 +80,15 @@ async def process_request(
assert tornado_request.method is not None

scope = find_route(tornado_request.path, tornado_request.method)
route: Optional[APIRoute] = scope['route']

if scope['route'] is None:
if route is None:
response = await make_not_found_response(frontik_app, tornado_request, debug_mode)
elif scope['page_cls'] is not None:
tornado_request._path_format = route.path_format # type: ignore
response = await execute_tornado_page(frontik_app, tornado_request, scope, debug_mode)
else:
tornado_request._path_format = route.path_format # type: ignore
response = await execute_asgi_page(asgi_app, tornado_request, scope, debug_mode)

if debug_mode.enabled and not response.data_written:
Expand Down
52 changes: 29 additions & 23 deletions frontik/request_integrations/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from opentelemetry.util.http import normalise_response_header_name
from opentelemetry.trace.status import Status, StatusCode
from tornado import httputil
from tornado.httputil import HTTPServerRequest
from frontik import request_context

_traced_request_attrs = get_traced_request_attrs('TORNADO')
Expand Down Expand Up @@ -81,32 +82,37 @@ def _collect_custom_request_headers_attributes(request_headers):
return attributes


def _finish_span(span, dto: IntegrationDto):
def _finish_span(span, dto: IntegrationDto, tornado_request: HTTPServerRequest):
if not span.is_recording():
return

status_code = dto.response.status_code
resp_headers = dto.response.headers

if span.is_recording():
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
if (handler_name := request_context.get_handler_name()) is not None:
method_path, method_name = handler_name.rsplit('.', 1)
span.update_name(f'{method_path}.{method_name}')
span.set_attribute(SpanAttributes.CODE_FUNCTION, method_name)
span.set_attribute(SpanAttributes.CODE_NAMESPACE, method_path)

otel_status_code = http_status_to_status_code(status_code, server_span=True)
otel_status_description = None
if otel_status_code is StatusCode.ERROR:
otel_status_description = httputil.responses.get(status_code, 'Unknown')
span.set_status(
Status(
status_code=otel_status_code,
description=otel_status_description,
)
span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
span.set_attribute(SpanAttributes.USER_AGENT_ORIGINAL, tornado_request.headers.get('User-Agent', 'noUserAgent'))
if hasattr(tornado_request, '_path_format'):
span.set_attribute(SpanAttributes.HTTP_ROUTE, getattr(tornado_request, '_path_format'))
if (handler_name := request_context.get_handler_name()) is not None:
method_path, method_name = handler_name.rsplit('.', 1)
span.update_name(f'{method_path}.{method_name}')
span.set_attribute(SpanAttributes.CODE_FUNCTION, method_name)
span.set_attribute(SpanAttributes.CODE_NAMESPACE, method_path)

otel_status_code = http_status_to_status_code(status_code, server_span=True)
otel_status_description = None
if otel_status_code is StatusCode.ERROR:
otel_status_description = httputil.responses.get(status_code, 'Unknown')
span.set_status(
Status(
status_code=otel_status_code,
description=otel_status_description,
)
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
custom_attributes = _collect_custom_response_headers_attributes(resp_headers)
if len(custom_attributes) > 0:
span.set_attributes(custom_attributes)
)
if span.is_recording() and span.kind == trace.SpanKind.SERVER:
custom_attributes = _collect_custom_response_headers_attributes(resp_headers)
if len(custom_attributes) > 0:
span.set_attributes(custom_attributes)


def _collect_custom_response_headers_attributes(response_headers):
Expand All @@ -133,4 +139,4 @@ def otel_instrumentation_ctx(frontik_app, tornado_request):
try:
yield dto
finally:
_finish_span(span, dto)
_finish_span(span, dto, tornado_request)
2 changes: 1 addition & 1 deletion frontik/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ async def _run_server(self, frontik_app):

await frontik_app.init()

http_server = HTTPServer(frontik_app)
http_server = HTTPServer(frontik_app, xheaders=options.xheaders, max_body_size=options.max_body_size)
http_server.add_sockets([sock])

yield
Expand Down
75 changes: 75 additions & 0 deletions tests/test_handler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import pytest
import requests

from frontik import media_types
from frontik.app import FrontikApplication
from frontik.handler import PageHandler, get_current_handler
from frontik.options import options
from frontik.routing import plain_router
from frontik.testing import FrontikTestBase
from tests.instances import frontik_no_debug_app, frontik_test_app


Expand Down Expand Up @@ -112,3 +118,72 @@ def test_permanent_followed_redirect_with_argument(self):
response = frontik_test_app.get_page('redirect/permanent?code=403', allow_redirects=True)
assert response.status_code == 403
assert response.content == b'success'


@plain_router.get('/xsrf', cls=PageHandler)
@plain_router.post('/xsrf', cls=PageHandler)
async def xsrf_page() -> None:
pass


class TestXsrf(FrontikTestBase):
def setup_method(self):
options.xsrf_cookies = True

def teardown_method(self):
options.xsrf_cookies = False

async def test_xsrf(self):
response = await self.fetch('/xsrf', method='GET', headers={'Cookie': '_xsrf=123'}, data={'_xsrf': '456'})
assert response.status_code == 200
response = await self.fetch('/xsrf', method='POST', headers={'Cookie': '_xsrf=123'}, data={'_xsrf': '456'})
assert response.status_code == 403
response = await self.fetch('/xsrf', method='POST', headers={'Cookie': '_xsrf=123'}, data={'_xsrf': '123'})
assert response.status_code == 200


@plain_router.get('/client_ip', cls=PageHandler)
async def client_ip_page(handler: PageHandler = get_current_handler()) -> None:
assert handler.request.protocol == 'https'
handler.text = handler.request.remote_ip


class TestClientIp(FrontikTestBase):
@pytest.fixture(scope='class')
def frontik_app(self) -> FrontikApplication:
options.xheaders = True
return FrontikApplication()

def teardown_method(self):
options.xheaders = False

async def test_client_ip(self):
response = await self.fetch(
'/client_ip',
headers={
'X-Forwarded-Proto': 'https',
'X-Real-Ip': '1.2.3.4',
'X-Forwarded-For': '4.5.6.7',
},
)
assert response.status_code == 200
assert response.raw_body == b'1.2.3.4'

response = await self.fetch(
'/client_ip',
headers={
'X-Forwarded-Proto': 'https',
'X-Forwarded-For': '3.5.7.9',
},
)
assert response.status_code == 200
assert response.raw_body == b'3.5.7.9'

response = await self.fetch(
'/client_ip',
headers={
'X-Forwarded-Proto': 'https',
},
)
assert response.status_code == 200
assert response.raw_body == b'127.0.0.1'
6 changes: 3 additions & 3 deletions tests/test_request_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from frontik.handler import PageHandler
from frontik.loggers import JSON_FORMATTER
from frontik.routing import plain_router
from frontik.routing import plain_router, router
from frontik.testing import FrontikTestBase

known_loggers = ['handler', 'stages']
Expand All @@ -19,12 +19,12 @@ async def request_id_long_page() -> None:
await asyncio.sleep(2)


@plain_router.get('/asgi_request_id')
@router.get('/asgi_request_id')
async def asgi_request_id_page() -> None:
pass


@plain_router.get('/asgi_request_id_long')
@router.get('/asgi_request_id_long')
async def asgi_request_id_long_page() -> None:
await asyncio.sleep(2)

Expand Down
2 changes: 2 additions & 0 deletions tests/test_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,5 @@ async def test_parent_span(self, frontik_app: FrontikApplication) -> None:
assert server_b_span.attributes is not None
assert server_b_span.attributes.get(SpanAttributes.CODE_FUNCTION) == 'get_page_b'
assert server_b_span.attributes.get(SpanAttributes.CODE_NAMESPACE) == 'tests.test_telemetry'
assert server_b_span.attributes.get(SpanAttributes.USER_AGENT_ORIGINAL) == 'app'
assert server_b_span.attributes.get(SpanAttributes.HTTP_ROUTE) == '/page_b'
Loading