django-bolt 0.2.4__tar.gz → 0.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-bolt might be problematic. Click here for more details.
- {django_bolt-0.2.4 → django_bolt-0.2.5}/Cargo.lock +1 -1
- {django_bolt-0.2.4 → django_bolt-0.2.5}/Cargo.toml +1 -1
- {django_bolt-0.2.4 → django_bolt-0.2.5}/PKG-INFO +1 -1
- {django_bolt-0.2.4 → django_bolt-0.2.5}/pyproject.toml +1 -1
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/management/commands/runbolt.py +9 -2
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/direct_stream.rs +27 -27
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/error.rs +43 -19
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/handler.rs +41 -41
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/json.rs +1 -1
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/metadata.rs +10 -3
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/middleware/auth.rs +37 -24
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/middleware/mod.rs +1 -1
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/middleware/rate_limit.rs +8 -7
- django_bolt-0.2.5/src/permissions.rs +91 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/request.rs +5 -5
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/router.rs +6 -2
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/server.rs +86 -46
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/state.rs +1 -1
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/test_state.rs +224 -121
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/validation.rs +2 -2
- django_bolt-0.2.4/src/permissions.rs +0 -102
- {django_bolt-0.2.4 → django_bolt-0.2.5}/.github/workflows/CI.yml +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/.gitignore +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/.python-version +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/BENCHMARK_BASELINE.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/BENCHMARK_DEV.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/CLAUDE.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/Makefile +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/README.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/TODO.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/ANNOTATION_GUIDE.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/ASYNC_DJANGO.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/CLASS_BASED_VIEWS.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/COMPRESSION.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/DEPENDENCY_INJECTION.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/DJANGO_ADMIN.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/EXCEPTIONS.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/GETTING_STARTED.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/GIL_OPTIMIZATION.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/LOGGING.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/MIDDLEWARE.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/OPENAPI.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/OPENAPI_ERROR_RESPONSES.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/OPENAPI_METADATA_IMPLEMENTATION.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/PAGINATION.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/PUBLISHING.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/README.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/RESPONSES.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/SECURITY.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/SERIALIZATION.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/TESTING_UTILITIES.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/favicon.png +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/icon.png +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/docs/logo.png +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/pytest.ini +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/_json.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/admin/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/admin/admin_detection.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/admin/asgi_bridge.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/admin/routes.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/admin/static.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/admin/static_routes.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/api.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/apps.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/async_collector.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/auth/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/auth/backends.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/auth/guards.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/auth/jwt_utils.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/auth/revocation.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/auth/token.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/binding.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/bootstrap.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/cli.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/compression.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/decorators.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/dependencies.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/error_handlers.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/exceptions.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/health.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/logging/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/logging/config.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/logging/middleware.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/management/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/management/commands/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/middleware/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/middleware/compiler.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/middleware/middleware.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/config.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/plugins.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/routes.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/schema_generator.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/base.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/callback.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/components.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/contact.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/discriminator.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/encoding.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/enums.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/example.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/external_documentation.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/header.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/info.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/license.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/link.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/media_type.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/oauth_flow.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/oauth_flows.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/open_api.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/operation.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/parameter.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/path_item.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/paths.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/reference.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/request_body.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/response.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/responses.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/schema.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/security_requirement.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/security_scheme.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/server.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/server_variable.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/tag.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/openapi/spec/xml.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/pagination.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/param_functions.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/params.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/request_parsing.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/responses.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/router.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/serialization.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/status_codes.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/testing/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/testing/client.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/testing/helpers.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/types.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/typing.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/django_bolt/views.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/README.md +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/admin.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/api.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/apps.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/migrations/0001_initial.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/migrations/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/models.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/tests.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/bench/views.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/manage.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/100B +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/100B.txt +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/100K +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/100K.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/100K.txt +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/10K +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/10K.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/10K.txt +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/1K +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/1K.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/1K.txt +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/1M +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/1M.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/1M.txt +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/500K +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/500K.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/500K.txt +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/5M +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/5M.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/5M.txt +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/FILE_UPLOAD_1K +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/FILE_UPLOAD_1K_HEADERS.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/FORM_URLENCODED_1K +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/FORM_URLENCODED_1K_HEADERS.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/MULTIPART_1K +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/MULTIPART_1K_HEADERS.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/objects.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/persons_100.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/persons_50.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/test_data/persons_500.json +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/api.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/asgi.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/settings.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/test_api.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/urls.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/views.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/testproject/wsgi.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/admin.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/api.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/apps.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/migrations/0001_initial.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/migrations/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/models.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/tests.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/example/users/views.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/admin_tests/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/admin_tests/conftest.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/admin_tests/test_admin_with_django.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/admin_tests/urls.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/cbv/__init__.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/cbv/test_class_views.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/cbv/test_class_views_django_orm.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/cbv/test_class_views_features.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/cbv/test_class_views_with_client.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/conftest.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_action_decorator.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_auth_secret_key.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_cors_implementation.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_decorator_syntax.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_error_handling.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_file_response.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_global_cors.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_guards_auth.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_guards_integration.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_health.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_integration_validation.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_json_validation.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_jwt_auth.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_jwt_token.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_logging.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_logging_merge.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_middleware.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_middleware_server.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_model_viewset.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_models.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_openapi_docs.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_openapi_metadata.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_pagination.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_parameter_validation.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_request_get.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_syntax.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_testing_utilities.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_testing_utilities_simple.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_types.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/python/tests/test_viewset_unified.py +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/scripts/benchmark.sh +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/scripts/install_hey.sh +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/scripts/release.sh +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/lib.rs +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/streaming.rs +0 -0
- {django_bolt-0.2.4 → django_bolt-0.2.5}/src/testing.rs +0 -0
|
@@ -22,7 +22,7 @@ class Command(BaseCommand):
|
|
|
22
22
|
"--processes", type=int, default=1, help="Number of processes (default: 1)"
|
|
23
23
|
)
|
|
24
24
|
parser.add_argument(
|
|
25
|
-
"--workers", type=int, default=
|
|
25
|
+
"--workers", type=int, default=1, help="Workers per process (default: 1)"
|
|
26
26
|
)
|
|
27
27
|
parser.add_argument(
|
|
28
28
|
"--no-admin",
|
|
@@ -34,6 +34,12 @@ class Command(BaseCommand):
|
|
|
34
34
|
action="store_true",
|
|
35
35
|
help="Enable auto-reload on file changes (development mode)"
|
|
36
36
|
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--backlog",
|
|
39
|
+
type=int,
|
|
40
|
+
default=1024,
|
|
41
|
+
help="Socket listen backlog size (default: 1024)"
|
|
42
|
+
)
|
|
37
43
|
|
|
38
44
|
def handle(self, *args, **options):
|
|
39
45
|
processes = options['processes']
|
|
@@ -246,9 +252,10 @@ class Command(BaseCommand):
|
|
|
246
252
|
else:
|
|
247
253
|
self.stdout.write(self.style.SUCCESS(f"[django-bolt] Starting server on http://{options['host']}:{options['port']}"))
|
|
248
254
|
self.stdout.write(f"[django-bolt] Workers: {options['workers']}, Processes: {options['processes']}")
|
|
249
|
-
# Set environment variable for Rust to read worker count
|
|
255
|
+
# Set environment variable for Rust to read worker count and backlog
|
|
250
256
|
import os
|
|
251
257
|
os.environ['DJANGO_BOLT_WORKERS'] = str(options['workers'])
|
|
258
|
+
os.environ['DJANGO_BOLT_BACKLOG'] = str(options['backlog'])
|
|
252
259
|
|
|
253
260
|
# Determine compression config (server-level in Actix)
|
|
254
261
|
# Priority: Django setting > first API with compression config
|
|
@@ -20,11 +20,11 @@ impl PythonDirectStream {
|
|
|
20
20
|
pub fn new(content: Py<PyAny>) -> Self {
|
|
21
21
|
// Determine if we should collect or stream
|
|
22
22
|
let collect_threshold = 8192; // 8KB threshold for SSE
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
// Resolve iterator and check if async
|
|
25
25
|
let (iterator, is_async) = Python::attach(|py| {
|
|
26
26
|
let obj = content.bind(py);
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
// If callable, call it to get iterator
|
|
29
29
|
let target = if obj.is_callable() {
|
|
30
30
|
match obj.call0() {
|
|
@@ -34,11 +34,11 @@ impl PythonDirectStream {
|
|
|
34
34
|
} else {
|
|
35
35
|
content.clone_ref(py)
|
|
36
36
|
};
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
let bound = target.bind(py);
|
|
39
|
-
let is_async = bound.hasattr("__aiter__").unwrap_or(false)
|
|
39
|
+
let is_async = bound.hasattr("__aiter__").unwrap_or(false)
|
|
40
40
|
|| bound.hasattr("__anext__").unwrap_or(false);
|
|
41
|
-
|
|
41
|
+
|
|
42
42
|
// For sync iterators, get the iterator object
|
|
43
43
|
let iter = if !is_async {
|
|
44
44
|
if bound.hasattr("__next__").unwrap_or(false) {
|
|
@@ -62,10 +62,10 @@ impl PythonDirectStream {
|
|
|
62
62
|
target
|
|
63
63
|
}
|
|
64
64
|
};
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
(iter, is_async)
|
|
67
67
|
});
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
Self {
|
|
70
70
|
iterator: Some(iterator),
|
|
71
71
|
is_async,
|
|
@@ -75,20 +75,20 @@ impl PythonDirectStream {
|
|
|
75
75
|
collect_threshold,
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
/// Try to collect small responses for optimization
|
|
80
80
|
pub fn try_collect_small(&mut self) -> Option<Bytes> {
|
|
81
81
|
if self.is_async || self.exhausted {
|
|
82
82
|
return None;
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
let mut collected = Vec::new();
|
|
86
86
|
let mut total_size = 0usize;
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
Python::attach(|py| {
|
|
89
89
|
if let Some(ref iter) = self.iterator {
|
|
90
90
|
let bound = iter.bind(py);
|
|
91
|
-
|
|
91
|
+
|
|
92
92
|
// Try to collect up to threshold
|
|
93
93
|
while total_size < self.collect_threshold {
|
|
94
94
|
match bound.call_method0("__next__") {
|
|
@@ -109,7 +109,7 @@ impl PythonDirectStream {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
if self.exhausted && total_size < self.collect_threshold {
|
|
114
114
|
// Small response - return all as one
|
|
115
115
|
let mut result = Vec::with_capacity(total_size);
|
|
@@ -129,24 +129,24 @@ impl PythonDirectStream {
|
|
|
129
129
|
|
|
130
130
|
impl Stream for PythonDirectStream {
|
|
131
131
|
type Item = Result<Bytes, std::io::Error>;
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
|
134
134
|
if self.exhausted && self.buffer.is_empty() {
|
|
135
135
|
return Poll::Ready(None);
|
|
136
136
|
}
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
// Return buffered items first
|
|
139
139
|
if !self.buffer.is_empty() {
|
|
140
140
|
return Poll::Ready(Some(Ok(self.buffer.remove(0))));
|
|
141
141
|
}
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
if self.is_async {
|
|
144
144
|
// Async iterators not supported in direct stream yet
|
|
145
145
|
// This should have been handled by try_collect_small or fallback
|
|
146
146
|
self.exhausted = true;
|
|
147
147
|
return Poll::Ready(None);
|
|
148
148
|
}
|
|
149
|
-
|
|
149
|
+
|
|
150
150
|
// Sync iterator - get next chunk directly
|
|
151
151
|
let next = Python::attach(|py| {
|
|
152
152
|
if let Some(ref iter) = self.iterator {
|
|
@@ -164,7 +164,7 @@ impl Stream for PythonDirectStream {
|
|
|
164
164
|
None
|
|
165
165
|
}
|
|
166
166
|
});
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
match next {
|
|
169
169
|
Some(bytes) => Poll::Ready(Some(Ok(bytes))),
|
|
170
170
|
None => {
|
|
@@ -174,7 +174,7 @@ impl Stream for PythonDirectStream {
|
|
|
174
174
|
} else {
|
|
175
175
|
Poll::Ready(Some(Err(std::io::Error::new(
|
|
176
176
|
std::io::ErrorKind::Other,
|
|
177
|
-
"Iterator error"
|
|
177
|
+
"Iterator error",
|
|
178
178
|
))))
|
|
179
179
|
}
|
|
180
180
|
}
|
|
@@ -188,28 +188,28 @@ fn convert_to_bytes(value: &Bound<'_, PyAny>) -> Option<Bytes> {
|
|
|
188
188
|
if let Ok(s) = value.downcast::<PyString>() {
|
|
189
189
|
return Some(Bytes::from(s.to_string_lossy().into_owned()));
|
|
190
190
|
}
|
|
191
|
-
|
|
191
|
+
|
|
192
192
|
if let Ok(b) = value.downcast::<PyBytes>() {
|
|
193
193
|
return Some(Bytes::copy_from_slice(b.as_bytes()));
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
|
|
196
196
|
// Try extract string
|
|
197
197
|
if let Ok(s) = value.extract::<String>() {
|
|
198
198
|
return Some(Bytes::from(s));
|
|
199
199
|
}
|
|
200
|
-
|
|
200
|
+
|
|
201
201
|
// Try __bytes__ method
|
|
202
202
|
if let Ok(bobj) = value.call_method0("__bytes__") {
|
|
203
203
|
if let Ok(b) = bobj.downcast::<PyBytes>() {
|
|
204
204
|
return Some(Bytes::copy_from_slice(b.as_bytes()));
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
-
|
|
207
|
+
|
|
208
208
|
// Last resort: str()
|
|
209
209
|
if let Ok(s) = value.str() {
|
|
210
210
|
return Some(Bytes::from(s.to_string()));
|
|
211
211
|
}
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
None
|
|
214
214
|
}
|
|
215
215
|
|
|
@@ -218,9 +218,9 @@ pub fn create_sse_response(
|
|
|
218
218
|
content: Py<PyAny>,
|
|
219
219
|
) -> Result<actix_web::HttpResponse, actix_web::Error> {
|
|
220
220
|
use actix_web::HttpResponse;
|
|
221
|
-
|
|
221
|
+
|
|
222
222
|
let mut stream = PythonDirectStream::new(content);
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
// Try to collect small responses
|
|
225
225
|
if let Some(body) = stream.try_collect_small() {
|
|
226
226
|
// Small response - send as single body
|
|
@@ -230,11 +230,11 @@ pub fn create_sse_response(
|
|
|
230
230
|
.append_header(("X-Accel-Buffering", "no"))
|
|
231
231
|
.body(body));
|
|
232
232
|
}
|
|
233
|
-
|
|
233
|
+
|
|
234
234
|
// Large response - use streaming
|
|
235
235
|
Ok(HttpResponse::Ok()
|
|
236
236
|
.content_type("text/event-stream")
|
|
237
237
|
.append_header(("Cache-Control", "no-cache"))
|
|
238
238
|
.append_header(("X-Accel-Buffering", "no"))
|
|
239
239
|
.streaming(Box::pin(stream)))
|
|
240
|
-
}
|
|
240
|
+
}
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
use actix_web::{
|
|
1
|
+
use actix_web::{http::StatusCode, HttpResponse};
|
|
2
2
|
use pyo3::prelude::*;
|
|
3
3
|
use pyo3::types::PyDict;
|
|
4
4
|
|
|
5
5
|
/// Extract error information from a Python HTTPException
|
|
6
|
-
pub fn extract_http_exception(
|
|
6
|
+
pub fn extract_http_exception(
|
|
7
|
+
_py: Python,
|
|
8
|
+
exc: &Bound<PyAny>,
|
|
9
|
+
) -> Option<(u16, String, Vec<(String, String)>, Option<Py<PyAny>>)> {
|
|
7
10
|
// Try to extract HTTPException fields
|
|
8
|
-
let status_code: u16 = exc
|
|
11
|
+
let status_code: u16 = exc
|
|
12
|
+
.getattr("status_code")
|
|
9
13
|
.ok()
|
|
10
14
|
.and_then(|v| v.extract().ok())
|
|
11
15
|
.unwrap_or(500);
|
|
12
16
|
|
|
13
|
-
let detail: String = exc
|
|
17
|
+
let detail: String = exc
|
|
18
|
+
.getattr("detail")
|
|
14
19
|
.ok()
|
|
15
20
|
.and_then(|v| v.extract().ok())
|
|
16
21
|
.unwrap_or_else(|| "Internal Server Error".to_string());
|
|
17
22
|
|
|
18
|
-
let headers: Vec<(String, String)> = exc
|
|
23
|
+
let headers: Vec<(String, String)> = exc
|
|
24
|
+
.getattr("headers")
|
|
19
25
|
.ok()
|
|
20
26
|
.and_then(|h| {
|
|
21
27
|
if let Ok(dict) = h.downcast::<PyDict>() {
|
|
@@ -32,9 +38,10 @@ pub fn extract_http_exception(_py: Python, exc: &Bound<PyAny>) -> Option<(u16, S
|
|
|
32
38
|
})
|
|
33
39
|
.unwrap_or_else(|| Vec::new());
|
|
34
40
|
|
|
35
|
-
let extra: Option<Py<PyAny>> =
|
|
36
|
-
.
|
|
37
|
-
|
|
41
|
+
let extra: Option<Py<PyAny>> =
|
|
42
|
+
exc.getattr("extra")
|
|
43
|
+
.ok()
|
|
44
|
+
.and_then(|e| if e.is_none() { None } else { Some(e.unbind()) });
|
|
38
45
|
|
|
39
46
|
Some((status_code, detail, headers, extra))
|
|
40
47
|
}
|
|
@@ -45,7 +52,8 @@ pub fn is_http_exception(py: Python, exc: &Bound<PyAny>) -> bool {
|
|
|
45
52
|
let exceptions_module = py.import("django_bolt.exceptions")?;
|
|
46
53
|
let http_exc_class = exceptions_module.getattr("HTTPException")?;
|
|
47
54
|
exc.is_instance(&http_exc_class)
|
|
48
|
-
})()
|
|
55
|
+
})()
|
|
56
|
+
.unwrap_or(false)
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
/// Check if a Python exception is a ValidationError
|
|
@@ -55,7 +63,8 @@ pub fn is_validation_error(py: Python, exc: &Bound<PyAny>) -> bool {
|
|
|
55
63
|
let msgspec = py.import("msgspec")?;
|
|
56
64
|
let val_err_class = msgspec.getattr("ValidationError")?;
|
|
57
65
|
exc.is_instance(&val_err_class)
|
|
58
|
-
})()
|
|
66
|
+
})()
|
|
67
|
+
.unwrap_or(false);
|
|
59
68
|
|
|
60
69
|
if is_msgspec {
|
|
61
70
|
return true;
|
|
@@ -66,7 +75,8 @@ pub fn is_validation_error(py: Python, exc: &Bound<PyAny>) -> bool {
|
|
|
66
75
|
let exceptions_module = py.import("django_bolt.exceptions")?;
|
|
67
76
|
let val_exc_class = exceptions_module.getattr("ValidationException")?;
|
|
68
77
|
exc.is_instance(&val_exc_class)
|
|
69
|
-
})()
|
|
78
|
+
})()
|
|
79
|
+
.unwrap_or(false)
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
/// Build an error response from exception information
|
|
@@ -120,7 +130,8 @@ pub fn handle_python_exception(
|
|
|
120
130
|
let django_conf = py.import("django.conf")?;
|
|
121
131
|
let settings = django_conf.getattr("settings")?;
|
|
122
132
|
settings.getattr("DEBUG")?.extract::<bool>()
|
|
123
|
-
})()
|
|
133
|
+
})()
|
|
134
|
+
.unwrap_or(debug);
|
|
124
135
|
// Check if it's an HTTPException
|
|
125
136
|
if is_http_exception(py, exc) {
|
|
126
137
|
if let Some((status_code, detail, headers, extra)) = extract_http_exception(py, exc) {
|
|
@@ -137,15 +148,20 @@ pub fn handle_python_exception(
|
|
|
137
148
|
let response_tuple = handle_exception.call1((exc, debug))?;
|
|
138
149
|
|
|
139
150
|
// Extract (status_code, headers, body)
|
|
140
|
-
if let Ok((status, headers, body)) =
|
|
141
|
-
|
|
151
|
+
if let Ok((status, headers, body)) =
|
|
152
|
+
response_tuple.extract::<(u16, Vec<(String, String)>, Vec<u8>)>()
|
|
153
|
+
{
|
|
154
|
+
let status_code =
|
|
155
|
+
StatusCode::from_u16(status).unwrap_or(StatusCode::UNPROCESSABLE_ENTITY);
|
|
142
156
|
let mut response = HttpResponse::build(status_code);
|
|
143
157
|
for (k, v) in headers {
|
|
144
158
|
response.append_header((k, v));
|
|
145
159
|
}
|
|
146
160
|
Ok(response.body(body))
|
|
147
161
|
} else {
|
|
148
|
-
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
|
|
162
|
+
Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
|
|
163
|
+
"Invalid response format",
|
|
164
|
+
))
|
|
149
165
|
}
|
|
150
166
|
})();
|
|
151
167
|
|
|
@@ -156,7 +172,10 @@ pub fn handle_python_exception(
|
|
|
156
172
|
|
|
157
173
|
// Generic exception handling
|
|
158
174
|
let exc_str = exc.to_string();
|
|
159
|
-
let exc_type = exc
|
|
175
|
+
let exc_type = exc
|
|
176
|
+
.get_type()
|
|
177
|
+
.name()
|
|
178
|
+
.ok()
|
|
160
179
|
.map(|s| s.to_string())
|
|
161
180
|
.unwrap_or_else(|| "Exception".to_string());
|
|
162
181
|
|
|
@@ -170,7 +189,8 @@ pub fn handle_python_exception(
|
|
|
170
189
|
let tb = format_exception.call1((exc_type, exc, tb_attr))?;
|
|
171
190
|
let tb_list: Vec<String> = tb.extract()?;
|
|
172
191
|
Ok(tb_list.join(""))
|
|
173
|
-
})()
|
|
192
|
+
})()
|
|
193
|
+
.unwrap_or_else(|_| "Traceback unavailable".to_string());
|
|
174
194
|
|
|
175
195
|
(
|
|
176
196
|
format!("{}: {}", exc_type, exc_str),
|
|
@@ -181,14 +201,18 @@ pub fn handle_python_exception(
|
|
|
181
201
|
escape_json(&traceback),
|
|
182
202
|
escape_json(path),
|
|
183
203
|
escape_json(method),
|
|
184
|
-
))
|
|
204
|
+
)),
|
|
185
205
|
)
|
|
186
206
|
} else {
|
|
187
207
|
("Internal Server Error".to_string(), None)
|
|
188
208
|
};
|
|
189
209
|
|
|
190
210
|
let body = if let Some(extra) = extra_info {
|
|
191
|
-
format!(
|
|
211
|
+
format!(
|
|
212
|
+
r#"{{"detail":"{}","extra":{}}}"#,
|
|
213
|
+
escape_json(&detail),
|
|
214
|
+
extra
|
|
215
|
+
)
|
|
192
216
|
} else {
|
|
193
217
|
format!(r#"{{"detail":"{}"}}"#, escape_json(&detail))
|
|
194
218
|
};
|
|
@@ -53,9 +53,10 @@ fn add_cors_headers_rust(
|
|
|
53
53
|
|
|
54
54
|
// Add exposed headers using cached HeaderValue
|
|
55
55
|
if let Some(ref cached_val) = cors_config.expose_headers_header {
|
|
56
|
-
response
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
response.headers_mut().insert(
|
|
57
|
+
actix_web::http::header::ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
58
|
+
cached_val.clone(),
|
|
59
|
+
);
|
|
59
60
|
}
|
|
60
61
|
return true; // Origin allowed
|
|
61
62
|
}
|
|
@@ -65,15 +66,17 @@ fn add_cors_headers_rust(
|
|
|
65
66
|
|
|
66
67
|
// Handle allow_all_origins (wildcard) without credentials
|
|
67
68
|
if cors_config.allow_all_origins {
|
|
68
|
-
response
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
response.headers_mut().insert(
|
|
70
|
+
actix_web::http::header::ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
71
|
+
HeaderValue::from_static("*"),
|
|
72
|
+
);
|
|
71
73
|
|
|
72
74
|
// Add exposed headers using cached HeaderValue
|
|
73
75
|
if let Some(ref cached_val) = cors_config.expose_headers_header {
|
|
74
|
-
response
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
response.headers_mut().insert(
|
|
77
|
+
actix_web::http::header::ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
78
|
+
cached_val.clone(),
|
|
79
|
+
);
|
|
77
80
|
}
|
|
78
81
|
return true; // Origin allowed (wildcard)
|
|
79
82
|
}
|
|
@@ -99,10 +102,16 @@ fn add_cors_headers_rust(
|
|
|
99
102
|
|
|
100
103
|
// Check regex match using route-level regexes, then global regexes
|
|
101
104
|
let regex_match = if !cors_config.compiled_origin_regexes.is_empty() {
|
|
102
|
-
cors_config
|
|
105
|
+
cors_config
|
|
106
|
+
.compiled_origin_regexes
|
|
107
|
+
.iter()
|
|
108
|
+
.any(|re| re.is_match(req_origin))
|
|
103
109
|
} else {
|
|
104
110
|
!state.cors_origin_regexes.is_empty()
|
|
105
|
-
&& state
|
|
111
|
+
&& state
|
|
112
|
+
.cors_origin_regexes
|
|
113
|
+
.iter()
|
|
114
|
+
.any(|re| re.is_match(req_origin))
|
|
106
115
|
};
|
|
107
116
|
|
|
108
117
|
// Origin not allowed
|
|
@@ -133,9 +142,10 @@ fn add_cors_headers_rust(
|
|
|
133
142
|
|
|
134
143
|
// Add exposed headers using cached HeaderValue (zero allocations)
|
|
135
144
|
if let Some(ref cached_val) = cors_config.expose_headers_header {
|
|
136
|
-
response
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
response.headers_mut().insert(
|
|
146
|
+
actix_web::http::header::ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
147
|
+
cached_val.clone(),
|
|
148
|
+
);
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
true // Origin allowed
|
|
@@ -145,23 +155,26 @@ fn add_cors_headers_rust(
|
|
|
145
155
|
fn add_cors_preflight_headers(response: &mut HttpResponse, cors_config: &CorsConfig) {
|
|
146
156
|
// Use cached HeaderValue for methods (zero allocations)
|
|
147
157
|
if let Some(ref cached_val) = cors_config.methods_header {
|
|
148
|
-
response
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
response.headers_mut().insert(
|
|
159
|
+
actix_web::http::header::ACCESS_CONTROL_ALLOW_METHODS,
|
|
160
|
+
cached_val.clone(),
|
|
161
|
+
);
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
// Use cached HeaderValue for headers (zero allocations)
|
|
154
165
|
if let Some(ref cached_val) = cors_config.headers_header {
|
|
155
|
-
response
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
response.headers_mut().insert(
|
|
167
|
+
actix_web::http::header::ACCESS_CONTROL_ALLOW_HEADERS,
|
|
168
|
+
cached_val.clone(),
|
|
169
|
+
);
|
|
158
170
|
}
|
|
159
171
|
|
|
160
172
|
// Use cached HeaderValue for max_age (zero allocations)
|
|
161
173
|
if let Some(ref cached_val) = cors_config.max_age_header {
|
|
162
|
-
response
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
response.headers_mut().insert(
|
|
175
|
+
actix_web::http::header::ACCESS_CONTROL_MAX_AGE,
|
|
176
|
+
cached_val.clone(),
|
|
177
|
+
);
|
|
165
178
|
}
|
|
166
179
|
|
|
167
180
|
// Add Vary headers for preflight requests
|
|
@@ -220,12 +233,8 @@ pub async fn handle_request(
|
|
|
220
233
|
req.headers().get("origin").and_then(|v| v.to_str().ok());
|
|
221
234
|
|
|
222
235
|
// Validate origin and add CORS headers
|
|
223
|
-
let origin_allowed =
|
|
224
|
-
&mut response,
|
|
225
|
-
origin,
|
|
226
|
-
cors_cfg,
|
|
227
|
-
&state,
|
|
228
|
-
);
|
|
236
|
+
let origin_allowed =
|
|
237
|
+
add_cors_headers_rust(&mut response, origin, cors_cfg, &state);
|
|
229
238
|
|
|
230
239
|
// CRITICAL: Only add preflight headers if origin was validated
|
|
231
240
|
// Per RFC 6454, preflight must validate origin before granting access
|
|
@@ -590,12 +599,7 @@ pub async fn handle_request(
|
|
|
590
599
|
if let Some(ref route_meta) = route_metadata {
|
|
591
600
|
if let Some(ref cors_cfg) = route_meta.cors_config {
|
|
592
601
|
let origin = req.headers().get("origin").and_then(|v| v.to_str().ok());
|
|
593
|
-
let _ = add_cors_headers_rust(
|
|
594
|
-
&mut response,
|
|
595
|
-
origin,
|
|
596
|
-
cors_cfg,
|
|
597
|
-
&state,
|
|
598
|
-
);
|
|
602
|
+
let _ = add_cors_headers_rust(&mut response, origin, cors_cfg, &state);
|
|
599
603
|
}
|
|
600
604
|
}
|
|
601
605
|
|
|
@@ -719,12 +723,8 @@ pub async fn handle_request(
|
|
|
719
723
|
if let Some(ref cors_cfg) = route_meta.cors_config {
|
|
720
724
|
let origin =
|
|
721
725
|
req.headers().get("origin").and_then(|v| v.to_str().ok());
|
|
722
|
-
let _ =
|
|
723
|
-
&mut response,
|
|
724
|
-
origin,
|
|
725
|
-
cors_cfg,
|
|
726
|
-
&state,
|
|
727
|
-
);
|
|
726
|
+
let _ =
|
|
727
|
+
add_cors_headers_rust(&mut response, origin, cors_cfg, &state);
|
|
728
728
|
}
|
|
729
729
|
}
|
|
730
730
|
return response;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This module contains JSON serialization utilities that are currently unused
|
|
2
|
-
// but kept for potential future optimizations
|
|
2
|
+
// but kept for potential future optimizations
|
|
@@ -109,10 +109,14 @@ impl CorsConfig {
|
|
|
109
109
|
|
|
110
110
|
// Compile origin regex patterns at startup
|
|
111
111
|
config.origin_regexes = origin_regexes.clone();
|
|
112
|
-
config.compiled_origin_regexes = origin_regexes
|
|
112
|
+
config.compiled_origin_regexes = origin_regexes
|
|
113
|
+
.iter()
|
|
113
114
|
.filter_map(|pattern| {
|
|
114
115
|
Regex::new(pattern).ok().or_else(|| {
|
|
115
|
-
eprintln!(
|
|
116
|
+
eprintln!(
|
|
117
|
+
"[django-bolt] Warning: Invalid route-level CORS origin regex pattern: {}",
|
|
118
|
+
pattern
|
|
119
|
+
);
|
|
116
120
|
None
|
|
117
121
|
})
|
|
118
122
|
})
|
|
@@ -331,7 +335,10 @@ fn parse_cors_config(dict: &HashMap<String, Py<PyAny>>, py: Python) -> Option<Co
|
|
|
331
335
|
}
|
|
332
336
|
|
|
333
337
|
/// Parse rate limiting configuration from middleware dict
|
|
334
|
-
fn parse_rate_limit_config(
|
|
338
|
+
fn parse_rate_limit_config(
|
|
339
|
+
dict: &HashMap<String, Py<PyAny>>,
|
|
340
|
+
py: Python,
|
|
341
|
+
) -> Option<RateLimitConfig> {
|
|
335
342
|
let mut config = RateLimitConfig::default();
|
|
336
343
|
|
|
337
344
|
// Parse rps (required)
|