@greenpandastudios/aug-cli 0.19.0
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.
- package/LICENSE +21 -0
- package/README.md +13 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/bin/aug.mjs +3 -0
- package/docs/api/crypto.md +351 -0
- package/docs/api/io.md +207 -0
- package/docs/api/json.md +21 -0
- package/docs/api/memory.md +109 -0
- package/docs/api/time.md +54 -0
- package/docs/api/web.md +184 -0
- package/docs/assets/benchmarks/execution.svg +2978 -0
- package/docs/assets/benchmarks/http.svg +1674 -0
- package/docs/assets/benchmarks/improvements.svg +2315 -0
- package/docs/assets/benchmarks/memory.svg +1891 -0
- package/docs/benchmark-baseline.json +1087 -0
- package/docs/benchmark-results.json +91123 -0
- package/docs/benchmarks.json +38 -0
- package/docs/compatibility.md +42 -0
- package/docs/concurrency-implementation.md +18 -0
- package/docs/diagnostics.md +97 -0
- package/docs/docker.md +35 -0
- package/docs/example-projects.json +26 -0
- package/docs/examples/approved-design/counters.md +253 -0
- package/docs/examples/approved-design/dependencies/august/0.19.0/io/contracts.md +395 -0
- package/docs/examples/approved-design/domain/app.md +165 -0
- package/docs/examples/approved-design/domain/export.md +83 -0
- package/docs/examples/approved-design/domain/models.md +80 -0
- package/docs/examples/approved-design/domain/numbers.md +248 -0
- package/docs/examples/approved-design/index.md +39 -0
- package/docs/examples/approved-design/main-yaml.md +24 -0
- package/docs/examples/approved-design/main.md +215 -0
- package/docs/examples/benchmark/index.md +33 -0
- package/docs/examples/benchmark/main-yaml.md +17 -0
- package/docs/examples/benchmark/main.md +117 -0
- package/docs/examples/cli-args/index.md +32 -0
- package/docs/examples/cli-args/main.md +107 -0
- package/docs/examples/collections/index.md +32 -0
- package/docs/examples/collections/main.md +118 -0
- package/docs/examples/collections-benchmark/index.md +34 -0
- package/docs/examples/collections-benchmark/main.md +115 -0
- package/docs/examples/cpu-benchmark/index.md +34 -0
- package/docs/examples/cpu-benchmark/main.md +90 -0
- package/docs/examples/developer-workflow/calculator.md +357 -0
- package/docs/examples/developer-workflow/dependencies/august/0.19.0/io/contracts.md +395 -0
- package/docs/examples/developer-workflow/index.md +37 -0
- package/docs/examples/developer-workflow/logging/console.md +124 -0
- package/docs/examples/developer-workflow/logging/export.md +70 -0
- package/docs/examples/developer-workflow/logging/logger.md +111 -0
- package/docs/examples/developer-workflow/main.md +172 -0
- package/docs/examples/drop/index.md +33 -0
- package/docs/examples/drop/main.md +85 -0
- package/docs/examples/drop/resource.md +95 -0
- package/docs/examples/errors/errors.md +85 -0
- package/docs/examples/errors/index.md +33 -0
- package/docs/examples/errors/main.md +92 -0
- package/docs/examples/ffi/index.md +33 -0
- package/docs/examples/ffi/main.md +75 -0
- package/docs/examples/ffi/native.md +93 -0
- package/docs/examples/generic-di/dependencies/august/0.19.0/io/contracts.md +395 -0
- package/docs/examples/generic-di/index.md +33 -0
- package/docs/examples/generic-di/main.md +111 -0
- package/docs/examples/generic-di/types.md +180 -0
- package/docs/examples/generics/index.md +33 -0
- package/docs/examples/generics/main.md +120 -0
- package/docs/examples/generics/types.md +189 -0
- package/docs/examples/hello/app/export.md +67 -0
- package/docs/examples/hello/app/greeter.md +190 -0
- package/docs/examples/hello/dependencies/august/0.19.0/io/contracts.md +395 -0
- package/docs/examples/hello/index.md +37 -0
- package/docs/examples/hello/logging/console.md +121 -0
- package/docs/examples/hello/logging/export.md +71 -0
- package/docs/examples/hello/logging/logger.md +120 -0
- package/docs/examples/hello/main.md +115 -0
- package/docs/examples/http-benchmark/index.md +35 -0
- package/docs/examples/http-benchmark/main.md +79 -0
- package/docs/examples/http-benchmark/routes.md +88 -0
- package/docs/examples/index.md +60 -0
- package/docs/examples/interceptors/app.md +250 -0
- package/docs/examples/interceptors/dependencies/august/0.19.0/io/contracts.md +395 -0
- package/docs/examples/interceptors/index.md +35 -0
- package/docs/examples/interceptors/interceptors.md +284 -0
- package/docs/examples/interceptors/logging.md +155 -0
- package/docs/examples/interceptors/main.md +162 -0
- package/docs/examples/json-benchmark/data.md +71 -0
- package/docs/examples/json-benchmark/dependencies/august/0.19.0/json/contracts.md +103 -0
- package/docs/examples/json-benchmark/index.md +35 -0
- package/docs/examples/json-benchmark/main.md +131 -0
- package/docs/examples/new-syntax/console.md +118 -0
- package/docs/examples/new-syntax/dependencies/august/0.19.0/io/contracts.md +395 -0
- package/docs/examples/new-syntax/greeter.md +145 -0
- package/docs/examples/new-syntax/index.md +36 -0
- package/docs/examples/new-syntax/logger.md +111 -0
- package/docs/examples/new-syntax/main.md +135 -0
- package/docs/examples/new-syntax/math.md +79 -0
- package/docs/examples/oidc-login/client/contracts.md +151 -0
- package/docs/examples/oidc-login/client/endpoints.md +263 -0
- package/docs/examples/oidc-login/client/export.md +107 -0
- package/docs/examples/oidc-login/client/login.md +515 -0
- package/docs/examples/oidc-login/client/logout.md +249 -0
- package/docs/examples/oidc-login/client/protocol.md +595 -0
- package/docs/examples/oidc-login/client/session.md +297 -0
- package/docs/examples/oidc-login/client/views.md +144 -0
- package/docs/examples/oidc-login/common/export.md +115 -0
- package/docs/examples/oidc-login/common/headers.md +162 -0
- package/docs/examples/oidc-login/common/keys.md +336 -0
- package/docs/examples/oidc-login/common/settings.md +116 -0
- package/docs/examples/oidc-login/common/views.md +103 -0
- package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/contracts.md +925 -0
- package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/jose.md +434 -0
- package/docs/examples/oidc-login/dependencies/august/0.19.0/json/contracts.md +103 -0
- package/docs/examples/oidc-login/dependencies/august/0.19.0/memory/store.md +374 -0
- package/docs/examples/oidc-login/dependencies/august/0.19.0/time/contracts.md +150 -0
- package/docs/examples/oidc-login/dependencies/august/0.19.0/web/contracts.md +532 -0
- package/docs/examples/oidc-login/index.md +57 -0
- package/docs/examples/oidc-login/main-yaml.md +28 -0
- package/docs/examples/oidc-login/main.md +283 -0
- package/docs/examples/oidc-login/provider/authorization.md +457 -0
- package/docs/examples/oidc-login/provider/contracts.md +267 -0
- package/docs/examples/oidc-login/provider/credentials.md +148 -0
- package/docs/examples/oidc-login/provider/discovery.md +215 -0
- package/docs/examples/oidc-login/provider/export.md +131 -0
- package/docs/examples/oidc-login/provider/token.md +364 -0
- package/docs/examples/oidc-login/provider/userinfo.md +228 -0
- package/docs/examples/oidc-login/provider/views.md +137 -0
- package/docs/examples/ownership/counter.md +140 -0
- package/docs/examples/ownership/index.md +33 -0
- package/docs/examples/ownership/main.md +90 -0
- package/docs/examples/ownership-transfer/dependencies/august/0.19.0/io/contracts.md +395 -0
- package/docs/examples/ownership-transfer/index.md +33 -0
- package/docs/examples/ownership-transfer/main.md +125 -0
- package/docs/examples/ownership-transfer/resource.md +150 -0
- package/docs/examples/packages-app/dependencies/packages/@example/aug-math/0.1.0/arithmetic.md +117 -0
- package/docs/examples/packages-app/index.md +34 -0
- package/docs/examples/packages-app/main-yaml.md +18 -0
- package/docs/examples/packages-app/main.md +80 -0
- package/docs/examples/packages-math/aug-package-json.md +24 -0
- package/docs/examples/packages-math/index.md +36 -0
- package/docs/examples/packages-math/package-json.md +25 -0
- package/docs/examples/packages-math/src/arithmetic.md +121 -0
- package/docs/examples/packages-math/src/export.md +63 -0
- package/docs/examples/startup-benchmark/index.md +34 -0
- package/docs/examples/startup-benchmark/main.md +68 -0
- package/docs/examples/visibility/counter.md +142 -0
- package/docs/examples/visibility/index.md +33 -0
- package/docs/examples/visibility/main.md +96 -0
- package/docs/examples.json +47 -0
- package/docs/getting-started.md +5 -0
- package/docs/grammar.md +136 -0
- package/docs/implementation-map.md +106 -0
- package/docs/index.md +46 -0
- package/docs/language-conformance.md +30 -0
- package/docs/language-constructs.md +1461 -0
- package/docs/language-design-audit.md +147 -0
- package/docs/maintaining-docs.md +43 -0
- package/docs/packages.md +184 -0
- package/docs/performance.md +535 -0
- package/docs/production-readiness.md +39 -0
- package/docs/reference.md +361 -0
- package/docs/release-review.md +23 -0
- package/docs/releasing.md +79 -0
- package/docs/roadmap.md +13 -0
- package/docs/specifications.md +86 -0
- package/docs/testing.md +120 -0
- package/docs/tooling.md +197 -0
- package/docs/web-implementation-plan.md +39 -0
- package/docs/web-library-gaps.md +27 -0
- package/docs/web.md +197 -0
- package/examples/approved-design/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
- package/examples/approved-design/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
- package/examples/approved-design/.aug-spec/manifest.json +14 -0
- package/examples/approved-design/counters.aug +22 -0
- package/examples/approved-design/counters.aug.md +177 -0
- package/examples/approved-design/domain/app.aug +12 -0
- package/examples/approved-design/domain/app.aug.md +97 -0
- package/examples/approved-design/domain/export.aug +5 -0
- package/examples/approved-design/domain/export.aug.md +25 -0
- package/examples/approved-design/domain/models.aug +2 -0
- package/examples/approved-design/domain/models.aug.md +30 -0
- package/examples/approved-design/domain/numbers.aug +29 -0
- package/examples/approved-design/domain/numbers.aug.md +141 -0
- package/examples/approved-design/main.aug +26 -0
- package/examples/approved-design/main.aug.md +108 -0
- package/examples/approved-design/main.yaml +8 -0
- package/examples/benchmark/.aug-spec/manifest.json +7 -0
- package/examples/benchmark/main.aug +17 -0
- package/examples/benchmark/main.aug.md +43 -0
- package/examples/benchmark/main.yaml +1 -0
- package/examples/cli-args/.aug-spec/manifest.json +7 -0
- package/examples/cli-args/main.aug +15 -0
- package/examples/cli-args/main.aug.md +38 -0
- package/examples/collections/.aug-spec/manifest.json +7 -0
- package/examples/collections/main.aug +18 -0
- package/examples/collections/main.aug.md +43 -0
- package/examples/developer-workflow/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
- package/examples/developer-workflow/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
- package/examples/developer-workflow/.aug-spec/manifest.json +13 -0
- package/examples/developer-workflow/calculator.aug +55 -0
- package/examples/developer-workflow/calculator.aug.md +228 -0
- package/examples/developer-workflow/logging/console.aug +8 -0
- package/examples/developer-workflow/logging/console.aug.md +67 -0
- package/examples/developer-workflow/logging/export.aug +2 -0
- package/examples/developer-workflow/logging/export.aug.md +19 -0
- package/examples/developer-workflow/logging/logger.aug +6 -0
- package/examples/developer-workflow/logging/logger.aug.md +57 -0
- package/examples/developer-workflow/main.aug +25 -0
- package/examples/developer-workflow/main.aug.md +79 -0
- package/examples/drop/.aug-spec/manifest.json +8 -0
- package/examples/drop/main.aug +3 -0
- package/examples/drop/main.aug.md +35 -0
- package/examples/drop/resource.aug +8 -0
- package/examples/drop/resource.aug.md +44 -0
- package/examples/errors/.aug-spec/manifest.json +8 -0
- package/examples/errors/errors.aug +6 -0
- package/examples/errors/errors.aug.md +33 -0
- package/examples/errors/main.aug +7 -0
- package/examples/errors/main.aug.md +36 -0
- package/examples/ffi/.aug-spec/manifest.json +8 -0
- package/examples/ffi/main.aug +2 -0
- package/examples/ffi/main.aug.md +27 -0
- package/examples/ffi/native.aug +6 -0
- package/examples/ffi/native.aug.md +43 -0
- package/examples/generic-di/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
- package/examples/generic-di/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
- package/examples/generic-di/.aug-spec/manifest.json +10 -0
- package/examples/generic-di/main.aug +9 -0
- package/examples/generic-di/main.aug.md +49 -0
- package/examples/generic-di/types.aug +17 -0
- package/examples/generic-di/types.aug.md +124 -0
- package/examples/generics/.aug-spec/manifest.json +8 -0
- package/examples/generics/main.aug +9 -0
- package/examples/generics/main.aug.md +58 -0
- package/examples/generics/types.aug +19 -0
- package/examples/generics/types.aug.md +132 -0
- package/examples/hello/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
- package/examples/hello/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
- package/examples/hello/.aug-spec/manifest.json +14 -0
- package/examples/hello/app/export.aug +1 -0
- package/examples/hello/app/export.aug.md +17 -0
- package/examples/hello/app/greeter.aug +22 -0
- package/examples/hello/app/greeter.aug.md +109 -0
- package/examples/hello/logging/console.aug +7 -0
- package/examples/hello/logging/console.aug.md +65 -0
- package/examples/hello/logging/export.aug +2 -0
- package/examples/hello/logging/export.aug.md +19 -0
- package/examples/hello/logging/logger.aug +9 -0
- package/examples/hello/logging/logger.aug.md +59 -0
- package/examples/hello/main.aug +9 -0
- package/examples/hello/main.aug.md +49 -0
- package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
- package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
- package/examples/interceptors/.aug-spec/manifest.json +12 -0
- package/examples/interceptors/app.aug +28 -0
- package/examples/interceptors/app.aug.md +162 -0
- package/examples/interceptors/interceptors.aug +40 -0
- package/examples/interceptors/interceptors.aug.md +180 -0
- package/examples/interceptors/logging.aug +12 -0
- package/examples/interceptors/logging.aug.md +96 -0
- package/examples/interceptors/main.aug +22 -0
- package/examples/interceptors/main.aug.md +76 -0
- package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
- package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
- package/examples/new-syntax/.aug-spec/manifest.json +13 -0
- package/examples/new-syntax/console.aug +7 -0
- package/examples/new-syntax/console.aug.md +63 -0
- package/examples/new-syntax/greeter.aug +10 -0
- package/examples/new-syntax/greeter.aug.md +89 -0
- package/examples/new-syntax/logger.aug +6 -0
- package/examples/new-syntax/logger.aug.md +57 -0
- package/examples/new-syntax/main.aug +12 -0
- package/examples/new-syntax/main.aug.md +64 -0
- package/examples/new-syntax/math.aug +3 -0
- package/examples/new-syntax/math.aug.md +29 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/contracts.aug +73 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/contracts.aug.md +791 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/jose.aug +68 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/jose.aug.md +266 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/json/contracts.aug +6 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/json/contracts.aug.md +55 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/memory/store.aug +45 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/memory/store.aug.md +250 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/time/contracts.aug +11 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/time/contracts.aug.md +96 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/web/contracts.aug +58 -0
- package/examples/oidc-login/.aug-spec/august/0.19.0/web/contracts.aug.md +420 -0
- package/examples/oidc-login/.aug-spec/manifest.json +40 -0
- package/examples/oidc-login/README.md +29 -0
- package/examples/oidc-login/client/contracts.aug +7 -0
- package/examples/oidc-login/client/contracts.aug.md +80 -0
- package/examples/oidc-login/client/endpoints.aug +21 -0
- package/examples/oidc-login/client/endpoints.aug.md +161 -0
- package/examples/oidc-login/client/export.aug +7 -0
- package/examples/oidc-login/client/export.aug.md +29 -0
- package/examples/oidc-login/client/login.aug +58 -0
- package/examples/oidc-login/client/login.aug.md +331 -0
- package/examples/oidc-login/client/logout.aug +18 -0
- package/examples/oidc-login/client/logout.aug.md +150 -0
- package/examples/oidc-login/client/protocol.aug +106 -0
- package/examples/oidc-login/client/protocol.aug.md +326 -0
- package/examples/oidc-login/client/session.aug +34 -0
- package/examples/oidc-login/client/session.aug.md +155 -0
- package/examples/oidc-login/client/views.aug +21 -0
- package/examples/oidc-login/client/views.aug.md +68 -0
- package/examples/oidc-login/common/export.aug +9 -0
- package/examples/oidc-login/common/export.aug.md +33 -0
- package/examples/oidc-login/common/headers.aug +11 -0
- package/examples/oidc-login/common/headers.aug.md +79 -0
- package/examples/oidc-login/common/keys.aug +37 -0
- package/examples/oidc-login/common/keys.aug.md +207 -0
- package/examples/oidc-login/common/settings.aug +4 -0
- package/examples/oidc-login/common/settings.aug.md +47 -0
- package/examples/oidc-login/common/views.aug +16 -0
- package/examples/oidc-login/common/views.aug.md +34 -0
- package/examples/oidc-login/main.aug +39 -0
- package/examples/oidc-login/main.aug.md +166 -0
- package/examples/oidc-login/main.yaml +12 -0
- package/examples/oidc-login/provider/authorization.aug +59 -0
- package/examples/oidc-login/provider/authorization.aug.md +264 -0
- package/examples/oidc-login/provider/contracts.aug +15 -0
- package/examples/oidc-login/provider/contracts.aug.md +193 -0
- package/examples/oidc-login/provider/credentials.aug +10 -0
- package/examples/oidc-login/provider/credentials.aug.md +64 -0
- package/examples/oidc-login/provider/discovery.aug +13 -0
- package/examples/oidc-login/provider/discovery.aug.md +133 -0
- package/examples/oidc-login/provider/export.aug +13 -0
- package/examples/oidc-login/provider/export.aug.md +41 -0
- package/examples/oidc-login/provider/token.aug +37 -0
- package/examples/oidc-login/provider/token.aug.md +223 -0
- package/examples/oidc-login/provider/userinfo.aug +26 -0
- package/examples/oidc-login/provider/userinfo.aug.md +104 -0
- package/examples/oidc-login/provider/views.aug +18 -0
- package/examples/oidc-login/provider/views.aug.md +63 -0
- package/examples/ownership/.aug-spec/manifest.json +8 -0
- package/examples/ownership/counter.aug +14 -0
- package/examples/ownership/counter.aug.md +85 -0
- package/examples/ownership/main.aug +4 -0
- package/examples/ownership/main.aug.md +38 -0
- package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
- package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
- package/examples/ownership-transfer/.aug-spec/manifest.json +10 -0
- package/examples/ownership-transfer/main.aug +9 -0
- package/examples/ownership-transfer/main.aug.md +63 -0
- package/examples/ownership-transfer/resource.aug +16 -0
- package/examples/ownership-transfer/resource.aug.md +89 -0
- package/examples/packages/README.md +15 -0
- package/examples/packages/app/.aug-spec/manifest.json +9 -0
- package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug +9 -0
- package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug.md +63 -0
- package/examples/packages/app/main.aug +2 -0
- package/examples/packages/app/main.aug.md +33 -0
- package/examples/packages/app/main.yaml +2 -0
- package/examples/packages/math/.aug-spec/manifest.json +8 -0
- package/examples/packages/math/README.md +3 -0
- package/examples/packages/math/aug-package.json +8 -0
- package/examples/packages/math/package.json +9 -0
- package/examples/packages/math/src/arithmetic.aug +8 -0
- package/examples/packages/math/src/arithmetic.aug.md +63 -0
- package/examples/packages/math/src/export.aug +1 -0
- package/examples/packages/math/src/export.aug.md +17 -0
- package/examples/visibility/.aug-spec/manifest.json +8 -0
- package/examples/visibility/counter.aug +14 -0
- package/examples/visibility/counter.aug.md +87 -0
- package/examples/visibility/main.aug +7 -0
- package/examples/visibility/main.aug.md +39 -0
- package/package.json +50 -0
- package/runtime/aug_crypto.c +121 -0
- package/runtime/aug_html.c +93 -0
- package/runtime/aug_http.c +829 -0
- package/runtime/aug_json.c +165 -0
- package/runtime/aug_runtime.c +792 -0
- package/runtime/aug_runtime.h +245 -0
- package/runtime/aug_tasks.c +172 -0
- package/runtime/aug_time.c +5 -0
- package/runtime/aug_values.c +80 -0
- package/scripts/bootstrap-native.mjs +121 -0
- package/scripts/native-dependencies.lock.json +65 -0
- package/scripts/native-home.mjs +11 -0
- package/src/actions.js +109 -0
- package/src/ast.js +19 -0
- package/src/builtins.js +93 -0
- package/src/checker.js +2896 -0
- package/src/cli.js +344 -0
- package/src/codegen.js +1159 -0
- package/src/config.js +149 -0
- package/src/continuation.js +110 -0
- package/src/di.js +24 -0
- package/src/documentation.js +27 -0
- package/src/editor.js +782 -0
- package/src/effects.js +29 -0
- package/src/fixes.js +222 -0
- package/src/formatter.js +296 -0
- package/src/freshness.js +154 -0
- package/src/help.js +212 -0
- package/src/html.js +27 -0
- package/src/http-policies.js +90 -0
- package/src/inference.js +16 -0
- package/src/interceptors.js +70 -0
- package/src/javadoc.js +67 -0
- package/src/lexer.js +199 -0
- package/src/libraries.js +31 -0
- package/src/lsp.js +180 -0
- package/src/native.js +144 -0
- package/src/navigation.js +162 -0
- package/src/openapi.js +220 -0
- package/src/ownership.js +232 -0
- package/src/package-manager.js +299 -0
- package/src/parser.js +1155 -0
- package/src/policies.js +130 -0
- package/src/project-init.js +19 -0
- package/src/project.js +292 -0
- package/src/schemas.js +62 -0
- package/src/semantic.js +255 -0
- package/src/spec.js +738 -0
- package/src/testing.js +186 -0
- package/src/types.js +10 -0
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
#include "aug_runtime.h"
|
|
2
|
+
#include <libwebsockets.h>
|
|
3
|
+
#include <stdlib.h>
|
|
4
|
+
#include <string.h>
|
|
5
|
+
#include <strings.h>
|
|
6
|
+
#include <stdio.h>
|
|
7
|
+
#include <signal.h>
|
|
8
|
+
#include <errno.h>
|
|
9
|
+
#include <math.h>
|
|
10
|
+
#include <limits.h>
|
|
11
|
+
#include <time.h>
|
|
12
|
+
#include <zlib.h>
|
|
13
|
+
|
|
14
|
+
/* The transport owns buffers; managed request and response values remain GC roots. */
|
|
15
|
+
typedef struct AugHttpJob AugHttpJob;
|
|
16
|
+
typedef struct {
|
|
17
|
+
AugValue roots[8]; AugRetained retained; bool rooted, submitted, headers_sent, head;
|
|
18
|
+
char *body; size_t body_length, body_capacity, sent;
|
|
19
|
+
const AugRoute *route;
|
|
20
|
+
AugHttpJob *job;
|
|
21
|
+
bool streaming, stream_pending, stream_done;
|
|
22
|
+
bool compress;
|
|
23
|
+
} AugHttpSession;
|
|
24
|
+
struct AugHttpJob {
|
|
25
|
+
struct lws *wsi; AugHttpSession *session; AugValue task;
|
|
26
|
+
/* Request and loggers outlive transport session cleanup on a disconnect. */
|
|
27
|
+
AugValue roots[2]; AugRetained retained;
|
|
28
|
+
bool test, transport_complete, timed_out; int64_t started,deadline;
|
|
29
|
+
};
|
|
30
|
+
static const AugRoute *served_routes;
|
|
31
|
+
static size_t served_count;
|
|
32
|
+
static size_t body_limit = 1024 * 1024;
|
|
33
|
+
static size_t response_limit = 4 * 1024 * 1024;
|
|
34
|
+
static const char *listen_host = "127.0.0.1", *tls_certificate = "", *tls_private_key = "", *tls_ca = "";
|
|
35
|
+
static bool enable_http3;
|
|
36
|
+
static void check_deadline(void) {
|
|
37
|
+
AugHttpJob *job=aug_execution_current()->http_job;if(!job||!job->deadline)return;
|
|
38
|
+
struct timespec now;clock_gettime(CLOCK_MONOTONIC,&now);int64_t current=(int64_t)now.tv_sec*1000+now.tv_nsec/1000000;
|
|
39
|
+
if(current>=job->deadline){job->timed_out=true;aug_cancelled=true;}
|
|
40
|
+
}
|
|
41
|
+
static volatile sig_atomic_t stopped;
|
|
42
|
+
static struct lws_context *server_context;
|
|
43
|
+
static bool io_wakeup_pending;
|
|
44
|
+
static void service_io(bool wait);
|
|
45
|
+
/* All scheduler callbacks run on this event-loop thread. Coalesce wakeups
|
|
46
|
+
until the next service call instead of writing one pipe byte per task. */
|
|
47
|
+
static void notify_io(void) {if(server_context)io_wakeup_pending=true;}
|
|
48
|
+
static int callback_client(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t length);
|
|
49
|
+
static void stop_server(int signal_number) { (void)signal_number; stopped = 1; }
|
|
50
|
+
void aug_http_configure(const char *host, const char *certificate, const char *private_key, const char *ca, size_t request_limit, size_t result_limit, bool http3) {
|
|
51
|
+
listen_host = host; tls_certificate = certificate; tls_private_key = private_key; tls_ca = ca;
|
|
52
|
+
body_limit = request_limit; response_limit = result_limit; enable_http3 = http3;
|
|
53
|
+
aug_task_checkpoint_hook=check_deadline;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static AugValue request_error(const char *name) { return aug_error_named(name); }
|
|
57
|
+
static int64_t monotonic_ms(void){struct timespec now;clock_gettime(CLOCK_MONOTONIC,&now);return (int64_t)now.tv_sec*1000+now.tv_nsec/1000000;}
|
|
58
|
+
AugValue aug_headers_new(void) {
|
|
59
|
+
AugValue root = aug_list_new(NULL, 0); AugFrame frame; aug_frame_enter(&frame, &root, 1);
|
|
60
|
+
AugValue result = aug_new_object("Headers", 1, NULL, NULL, 0); result.as.object->kind = AUG_HEADERS_KIND;
|
|
61
|
+
aug_set_field(result, 0, root); aug_frame_leave(&frame); return result;
|
|
62
|
+
}
|
|
63
|
+
static bool valid_header(AugValue name, AugValue value) {
|
|
64
|
+
if (name.tag != AUG_STRING || value.tag != AUG_STRING || !name.as.object->text_length || name.as.object->text_length > 250 || value.as.object->text_length > 16000) return false;
|
|
65
|
+
for (size_t i = 0; i < name.as.object->text_length; i++) {
|
|
66
|
+
unsigned char c = (unsigned char)name.as.object->text[i];
|
|
67
|
+
if (!c || !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || strchr("!#$%&'*+-.^_`|~", c))) return false;
|
|
68
|
+
}
|
|
69
|
+
for (size_t i = 0; i < value.as.object->text_length; i++) {unsigned char c = (unsigned char)value.as.object->text[i]; if (c == 127 || (c < 32 && c != 9)) return false;}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
static void header_add(AugValue headers, const char *name, size_t name_size, const char *value, size_t value_size) {
|
|
73
|
+
AugValue roots[3] = {0}; AugFrame frame; aug_frame_enter(&frame, roots, 3);
|
|
74
|
+
roots[0] = aug_string_n(name, name_size); roots[1] = aug_string_n(value, value_size);
|
|
75
|
+
roots[2] = aug_tuple_new(roots, 2); aug_list_append(aug_field(headers, 0), roots[2]); aug_frame_leave(&frame);
|
|
76
|
+
}
|
|
77
|
+
static AugValue header_get(AugValue headers, const char *name, bool singular) {
|
|
78
|
+
AugValue entries = aug_field(headers, 0), result = aug_null();
|
|
79
|
+
for (size_t i = 0; i < entries.as.object->field_count; i++) {
|
|
80
|
+
AugValue pair = entries.as.object->fields[i], label = aug_field(pair, 0);
|
|
81
|
+
if (label.as.object->text_length == strlen(name) && !strncasecmp(label.as.object->text, name, strlen(name))) {
|
|
82
|
+
if (singular && result.tag != AUG_NULL) return request_error("HttpBadRequest");
|
|
83
|
+
if (result.tag == AUG_NULL) result = aug_field(pair, 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
AugValue aug_headers_with(AugValue headers, AugValue name, AugValue value) {
|
|
89
|
+
if (!valid_header(name, value) || !strcasecmp(name.as.object->text, "content-length") || !strcasecmp(name.as.object->text, "transfer-encoding") || !strcasecmp(name.as.object->text, "connection")) return request_error("HttpError");
|
|
90
|
+
AugValue roots[3] = {headers, name, value}; AugFrame frame; aug_frame_enter(&frame, roots, 3);
|
|
91
|
+
AugValue result = aug_headers_new(); AugRetained retained; aug_retain(&retained, &result, 1);
|
|
92
|
+
AugValue entries = aug_field(headers, 0); for (size_t i = 0; i < entries.as.object->field_count; i++) aug_list_append(aug_field(result, 0), entries.as.object->fields[i]);
|
|
93
|
+
header_add(result, name.as.object->text, name.as.object->text_length, value.as.object->text, value.as.object->text_length);
|
|
94
|
+
aug_freeze(result); aug_release(&retained); aug_frame_leave(&frame); return result;
|
|
95
|
+
}
|
|
96
|
+
AugValue aug_headers_get(AugValue headers, AugValue name) {AugValue value = header_get(headers, aug_cstring(name), false); return value.tag == AUG_NULL ? aug_null() : value;}
|
|
97
|
+
AugValue aug_headers_all(AugValue headers, AugValue name) {
|
|
98
|
+
AugValue result = aug_list_new(NULL, 0); AugFrame frame; aug_frame_enter(&frame, &result, 1); AugValue entries = aug_field(headers, 0);
|
|
99
|
+
for (size_t i = 0; i < entries.as.object->field_count; i++) {AugValue pair = entries.as.object->fields[i]; if (!strcasecmp(aug_cstring(aug_field(pair, 0)), aug_cstring(name))) aug_list_append(result, aug_field(pair, 1));}
|
|
100
|
+
aug_freeze(result); aug_frame_leave(&frame); return result;
|
|
101
|
+
}
|
|
102
|
+
typedef struct {struct lws *wsi; AugValue headers;} HeaderReader;
|
|
103
|
+
static void custom_header(const char *name, int name_size, void *opaque) {
|
|
104
|
+
HeaderReader *reader = opaque;
|
|
105
|
+
int length = lws_hdr_custom_length(reader->wsi, name, name_size); if (length < 0) return;
|
|
106
|
+
char *value = malloc((size_t)length + 1); if (!value) return;
|
|
107
|
+
if (lws_hdr_custom_copy(reader->wsi, value, length + 1, name, name_size) >= 0)
|
|
108
|
+
header_add(reader->headers, name, name_size && name[name_size - 1] == ':' ? (size_t)name_size - 1 : (size_t)name_size, value, (size_t)length);
|
|
109
|
+
free(value);
|
|
110
|
+
}
|
|
111
|
+
static void read_headers(struct lws *wsi, AugValue headers) {
|
|
112
|
+
for (int token = 0; token < WSI_TOKEN_COUNT; token++) {
|
|
113
|
+
const unsigned char *name = lws_token_to_string((enum lws_token_indexes)token);
|
|
114
|
+
if (!name) continue; size_t size = strlen((const char *)name);
|
|
115
|
+
if (!size || name[size - 1] != ':' || name[0] == ':') continue;
|
|
116
|
+
int total = lws_hdr_total_length(wsi, (enum lws_token_indexes)token); if (total <= 0) continue;
|
|
117
|
+
char *value = malloc((size_t)total + 1); if (!value) continue;
|
|
118
|
+
for (int fragment = 0;; fragment++) {
|
|
119
|
+
int length = lws_hdr_copy_fragment(wsi, value, total + 1, (enum lws_token_indexes)token, fragment);
|
|
120
|
+
if (length < 0) break; header_add(headers, (const char *)name, size - 1, value, (size_t)length);
|
|
121
|
+
}
|
|
122
|
+
free(value);
|
|
123
|
+
}
|
|
124
|
+
HeaderReader reader = {wsi, headers}; lws_hdr_custom_name_foreach(wsi, custom_header, &reader);
|
|
125
|
+
}
|
|
126
|
+
static void map_add(AugValue map, AugValue key, AugValue value) {
|
|
127
|
+
AugValue roots[3] = {key, value, aug_null()}; AugFrame frame; aug_frame_enter(&frame, roots, 3);
|
|
128
|
+
roots[2] = aug_map_get(map, key);
|
|
129
|
+
if (roots[2].tag == AUG_NULL) { roots[2] = aug_list_new(NULL, 0); aug_map_set(map, key, roots[2]); }
|
|
130
|
+
aug_list_append(roots[2], value); aug_frame_leave(&frame);
|
|
131
|
+
}
|
|
132
|
+
static AugValue map_scalar(AugValue map, const char *name) {
|
|
133
|
+
AugValue key = aug_string(name), values = aug_map_get(map, key);
|
|
134
|
+
if (values.tag == AUG_NULL) return aug_null();
|
|
135
|
+
return values.as.object->field_count == 1 ? values.as.object->fields[0] : request_error("HttpBadRequest");
|
|
136
|
+
}
|
|
137
|
+
static bool match_path(const char *pattern, const char *path, AugValue captures, int *specificity) {
|
|
138
|
+
const char *p = pattern, *v = path; *specificity = 0;
|
|
139
|
+
while (*p && *v) {
|
|
140
|
+
if (*p == '{') {
|
|
141
|
+
const char *end = strchr(p, '}'); if (!end) return false;
|
|
142
|
+
const char *last = strchr(v, '/'); if (!last) last = v + strlen(v);
|
|
143
|
+
if (last == v) return false;
|
|
144
|
+
if (captures.tag == AUG_OBJECT) {
|
|
145
|
+
AugValue roots[2] = {0}; AugFrame frame; aug_frame_enter(&frame, roots, 2);
|
|
146
|
+
roots[0] = aug_string_n(p + 1, (size_t)(end - p - 1)); roots[1] = aug_string_n(v, (size_t)(last - v));
|
|
147
|
+
map_add(captures, roots[0], roots[1]); aug_frame_leave(&frame);
|
|
148
|
+
}
|
|
149
|
+
p = end + 1; v = last;
|
|
150
|
+
} else {
|
|
151
|
+
if (*p != *v) return false; (*specificity)++; p++; v++;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return !*p && !*v;
|
|
155
|
+
}
|
|
156
|
+
static bool cors_apply(const AugHttpPolicy *policy,AugValue request,AugValue *headers,bool preflight);
|
|
157
|
+
/* Both the listening transport and the in-file test client enter through this router. */
|
|
158
|
+
static const AugRoute *select_route(AugValue request, const AugRoute *routes, size_t count, AugValue *failure) {
|
|
159
|
+
const char *verb=aug_cstring(aug_field(request,0)), *path=aug_cstring(aug_field(request,1));
|
|
160
|
+
if(!strcmp(verb,"OPTIONS")) {
|
|
161
|
+
AugValue requested=header_get(aug_field(request,2),"access-control-request-method",true);
|
|
162
|
+
if(aug_has_error){aug_take_error();*failure=aug_http_problem(400);return NULL;}
|
|
163
|
+
if(requested.tag==AUG_STRING)for(size_t i=0;i<count;i++) {
|
|
164
|
+
int specificity;if(strcmp(routes[i].method,aug_cstring(requested))||!match_path(routes[i].path,path,aug_null(),&specificity))continue;
|
|
165
|
+
for(size_t layer=0;layer<routes[i].policy_count;layer++)if(routes[i].policies[layer].kind==AUG_HTTP_POLICY_CORS){
|
|
166
|
+
AugValue headers=aug_headers_new();AugFrame frame;aug_frame_enter(&frame,&headers,1);
|
|
167
|
+
if(cors_apply(&routes[i].policies[layer],request,&headers,true)) {
|
|
168
|
+
headers=aug_headers_with(headers,aug_string("access-control-allow-methods"),requested);
|
|
169
|
+
*failure=aug_http_response_full(aug_null(),aug_int(204),headers);
|
|
170
|
+
}else {int status=aug_has_error?aug_http_error_status():403;if(aug_has_error)aug_take_error();*failure=aug_http_problem(status);}
|
|
171
|
+
aug_frame_leave(&frame);return NULL;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const AugRoute *selected=NULL; bool path_found=false; int best=-1;
|
|
176
|
+
for(size_t i=0;i<count;i++) {
|
|
177
|
+
int specificity;if(!match_path(routes[i].path,path,aug_null(),&specificity))continue;path_found=true;
|
|
178
|
+
if((!strcmp(routes[i].method,verb)||(!strcmp(verb,"HEAD")&&!strcmp(routes[i].method,"GET")))&&specificity>best){selected=&routes[i];best=specificity;}
|
|
179
|
+
}
|
|
180
|
+
if(selected){int specificity;match_path(selected->path,path,aug_field(request,4),&specificity);return selected;}
|
|
181
|
+
*failure=aug_http_problem(path_found?405:404);
|
|
182
|
+
if(path_found) {
|
|
183
|
+
char allow[1024]="";
|
|
184
|
+
for(size_t i=0;i<count;i++) {
|
|
185
|
+
int specificity;if(!match_path(routes[i].path,path,aug_null(),&specificity))continue;
|
|
186
|
+
if(*allow)strncat(allow,", ",sizeof(allow)-strlen(allow)-1);strncat(allow,routes[i].method,sizeof(allow)-strlen(allow)-1);
|
|
187
|
+
if(!strcmp(routes[i].method,"GET"))strncat(allow,", HEAD",sizeof(allow)-strlen(allow)-1);
|
|
188
|
+
}
|
|
189
|
+
header_add(aug_field(*failure,2),"allow",5,allow,strlen(allow));
|
|
190
|
+
}
|
|
191
|
+
return NULL;
|
|
192
|
+
}
|
|
193
|
+
static AugValue coerce(AugValue value, const AugSchema *schema) {
|
|
194
|
+
if (aug_has_error) return aug_null();
|
|
195
|
+
if (value.tag == AUG_NULL) return schema->optional ? value : request_error("HttpBadRequest");
|
|
196
|
+
if (value.tag != AUG_STRING) return request_error("HttpBadRequest");
|
|
197
|
+
const char *text = value.as.object->text; size_t size = value.as.object->text_length;
|
|
198
|
+
if (memchr(text, 0, size)) return request_error("HttpBadRequest");
|
|
199
|
+
if (schema->nullable && size == 4 && !memcmp(text, "null", 4)) return aug_null();
|
|
200
|
+
if (schema->kind == AUG_SCHEMA_STRING) return value;
|
|
201
|
+
if (schema->kind == AUG_SCHEMA_BOOL) {
|
|
202
|
+
if (!strcmp(text, "true")) return aug_bool(true); if (!strcmp(text, "false")) return aug_bool(false);
|
|
203
|
+
return request_error("HttpBadRequest");
|
|
204
|
+
}
|
|
205
|
+
if (schema->kind == AUG_SCHEMA_RECORD || schema->kind == AUG_SCHEMA_LIST || schema->kind == AUG_SCHEMA_SET ||
|
|
206
|
+
schema->kind == AUG_SCHEMA_TUPLE || schema->kind == AUG_SCHEMA_MAP || schema->kind == AUG_SCHEMA_JSON) {
|
|
207
|
+
AugValue roots[2] = {value, aug_null()}; AugFrame frame; aug_frame_enter(&frame, roots, 2);
|
|
208
|
+
roots[1] = _aug_json_parse(roots[0]);
|
|
209
|
+
if (aug_has_error) {aug_take_error(); aug_frame_leave(&frame); return request_error("HttpBadRequest");}
|
|
210
|
+
AugValue result = aug_json_decode(roots[1], schema);
|
|
211
|
+
if (aug_has_error) {aug_take_error(); aug_frame_leave(&frame); return request_error("HttpValidationError");}
|
|
212
|
+
aug_frame_leave(&frame); return result;
|
|
213
|
+
}
|
|
214
|
+
if (!size || *text == ' ' || *text == '\t' || *text == '+') return request_error("HttpBadRequest");
|
|
215
|
+
char *end; errno = 0;
|
|
216
|
+
if (schema->kind == AUG_SCHEMA_INT || schema->kind == AUG_SCHEMA_C_INT) {
|
|
217
|
+
long long number = strtoll(text, &end, 10);
|
|
218
|
+
return errno || end != text + size || (schema->kind == AUG_SCHEMA_C_INT && (number < INT32_MIN || number > INT32_MAX)) ? request_error("HttpBadRequest") : aug_int((int64_t)number);
|
|
219
|
+
}
|
|
220
|
+
if (schema->kind == AUG_SCHEMA_FLOAT) {
|
|
221
|
+
double number = strtod(text, &end);
|
|
222
|
+
return errno || !isfinite(number) || end != text + size ? request_error("HttpBadRequest") : aug_float(number);
|
|
223
|
+
}
|
|
224
|
+
return request_error("HttpBadRequest");
|
|
225
|
+
}
|
|
226
|
+
static int hex_digit(unsigned char c) {return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : c >= 'A' && c <= 'F' ? c - 'A' + 10 : -1;}
|
|
227
|
+
static AugValue decode_form_part(const char *text, size_t size) {
|
|
228
|
+
char *decoded = malloc(size + 1); if (!decoded) return request_error("HttpBadRequest"); size_t written = 0;
|
|
229
|
+
for (size_t i = 0; i < size; i++) {
|
|
230
|
+
unsigned char c = (unsigned char)text[i];
|
|
231
|
+
if (c == '%') {if (i + 2 >= size || hex_digit(text[i + 1]) < 0 || hex_digit(text[i + 2]) < 0) {free(decoded); return request_error("HttpBadRequest");} c = (unsigned char)(hex_digit(text[i + 1]) * 16 + hex_digit(text[i + 2])); i += 2;}
|
|
232
|
+
else if (c == '+') c = ' ';
|
|
233
|
+
if (!c) {free(decoded); return request_error("HttpBadRequest");} decoded[written++] = (char)c;
|
|
234
|
+
}
|
|
235
|
+
AugValue bytes = aug_bytes(decoded, written, AUG_BYTES_KIND); free(decoded);
|
|
236
|
+
AugFrame frame; aug_frame_enter(&frame, &bytes, 1); AugValue result = aug_bytes_text(bytes); aug_frame_leave(&frame);
|
|
237
|
+
if (aug_has_error) {aug_take_error(); return request_error("HttpBadRequest");} return result;
|
|
238
|
+
}
|
|
239
|
+
static AugValue form_map(AugValue body) {
|
|
240
|
+
AugValue roots[3] = {aug_map_new(), aug_null(), aug_null()}; AugFrame frame; aug_frame_enter(&frame, roots, 3);
|
|
241
|
+
const char *text = body.as.object->text, *end = text + body.as.object->text_length;
|
|
242
|
+
while (text < end && !aug_has_error) {
|
|
243
|
+
const char *last = memchr(text, '&', (size_t)(end - text)); if (!last) last = end;
|
|
244
|
+
const char *equal = memchr(text, '=', (size_t)(last - text)); if (!equal) equal = last;
|
|
245
|
+
roots[1] = decode_form_part(text, (size_t)(equal - text));
|
|
246
|
+
roots[2] = decode_form_part(equal == last ? last : equal + 1, equal == last ? 0 : (size_t)(last - equal - 1));
|
|
247
|
+
if (!aug_has_error) map_add(roots[0], roots[1], roots[2]); text = last == end ? end : last + 1;
|
|
248
|
+
}
|
|
249
|
+
AugValue result = roots[0]; aug_frame_leave(&frame); return result;
|
|
250
|
+
}
|
|
251
|
+
static bool listed(const char *list,const char *value) {
|
|
252
|
+
size_t size=strlen(value);for(const char *part=list;*part;){const char *end=strchr(part,'\n');if(!end)end=part+strlen(part);if((size_t)(end-part)==size&&!memcmp(part,value,size))return true;part=*end?end+1:end;}return false;
|
|
253
|
+
}
|
|
254
|
+
static bool cors_apply(const AugHttpPolicy *policy,AugValue request,AugValue *headers,bool preflight) {
|
|
255
|
+
AugValue origin=header_get(aug_field(request,2),"origin",true);if(aug_has_error)return false;if(origin.tag==AUG_NULL)return !preflight;
|
|
256
|
+
const char *name=aug_cstring(origin);bool wildcard=listed(policy->origins,"*");
|
|
257
|
+
if(!wildcard&&!listed(policy->origins,name)){request_error("HttpForbidden");return false;}
|
|
258
|
+
*headers=aug_headers_with(*headers,aug_string("access-control-allow-origin"),wildcard?aug_string("*"):origin);
|
|
259
|
+
*headers=aug_headers_with(*headers,aug_string("vary"),aug_string("Origin"));
|
|
260
|
+
if(policy->credentials)*headers=aug_headers_with(*headers,aug_string("access-control-allow-credentials"),aug_string("true"));
|
|
261
|
+
if(preflight) {
|
|
262
|
+
AugValue asked=header_get(aug_field(request,2),"access-control-request-headers",true);if(aug_has_error)return false;
|
|
263
|
+
if(asked.tag==AUG_STRING){char *copy=strdup(aug_cstring(asked));if(!copy)abort();char *save=NULL;
|
|
264
|
+
for(char *part=strtok_r(copy,",",&save);part;part=strtok_r(NULL,",",&save)){while(*part==' '||*part=='\t')part++;size_t size=strlen(part);while(size&&(part[size-1]==' '||part[size-1]=='\t'))part[--size]=0;
|
|
265
|
+
bool found=false;const char *allowed=policy->headers;while(*allowed){const char *end=strchr(allowed,',');if(!end)end=allowed+strlen(allowed);if((size_t)(end-allowed)==size&&!strncasecmp(allowed,part,size))found=true;allowed=*end?end+1:end;while(*allowed==' ')allowed++;}
|
|
266
|
+
if(!size||!found){free(copy);request_error("HttpForbidden");return false;}}
|
|
267
|
+
free(copy);}
|
|
268
|
+
*headers=aug_headers_with(*headers,aug_string("access-control-allow-headers"),aug_string(policy->headers));
|
|
269
|
+
}
|
|
270
|
+
return !aug_has_error;
|
|
271
|
+
}
|
|
272
|
+
static bool accepts_gzip(AugValue headers) {
|
|
273
|
+
AugValue value=header_get(headers,"accept-encoding",false);if(value.tag!=AUG_STRING)return false;
|
|
274
|
+
char *copy=strdup(aug_cstring(value));if(!copy)abort();char *save=NULL;double gzip=-1,wildcard=0;
|
|
275
|
+
for(char *part=strtok_r(copy,",",&save);part;part=strtok_r(NULL,",",&save)){
|
|
276
|
+
while(*part==' '||*part=='\t')part++;char *options=strchr(part,';');if(options)*options++=0;
|
|
277
|
+
size_t size=strlen(part);while(size&&(part[size-1]==' '||part[size-1]=='\t'))part[--size]=0;
|
|
278
|
+
double quality=1;if(options){while(*options==' '||*options=='\t')options++;if(strncmp(options,"q=",2))quality=0;else {char *end;quality=strtod(options+2,&end);while(*end==' '||*end=='\t')end++;if(*end||!isfinite(quality)||quality<0||quality>1)quality=0;}}
|
|
279
|
+
if(!strcasecmp(part,"gzip"))gzip=quality;else if(!strcmp(part,"*"))wildcard=quality;
|
|
280
|
+
}
|
|
281
|
+
free(copy);return gzip<0?wildcard>0:gzip>0;
|
|
282
|
+
}
|
|
283
|
+
static AugValue gzip_bytes(AugValue value) {
|
|
284
|
+
z_stream stream={0};if(deflateInit2(&stream,Z_DEFAULT_COMPRESSION,Z_DEFLATED,15+16,8,Z_DEFAULT_STRATEGY)!=Z_OK)return request_error("HttpError");
|
|
285
|
+
size_t capacity=deflateBound(&stream,(uLong)value.as.object->text_length);unsigned char *buffer=malloc(capacity);if(!buffer)abort();
|
|
286
|
+
stream.next_in=(unsigned char *)value.as.object->text;stream.avail_in=(uInt)value.as.object->text_length;stream.next_out=buffer;stream.avail_out=(uInt)capacity;
|
|
287
|
+
int status=deflate(&stream,Z_FINISH);size_t size=stream.total_out;deflateEnd(&stream);
|
|
288
|
+
AugValue result=status==Z_STREAM_END?aug_bytes(buffer,size,AUG_BYTES_KIND):request_error("HttpError");free(buffer);return result;
|
|
289
|
+
}
|
|
290
|
+
typedef struct {const AugHttpPolicy *policy;char peer[96];int64_t until;size_t used;} RateEntry;
|
|
291
|
+
static RateEntry rate_entries[2048];
|
|
292
|
+
void aug_http_policy(const AugHttpPolicy *policy,AugValue request,AugValue dependency,AugValue second) {
|
|
293
|
+
AugHttpJob *job=aug_execution_current()->http_job;AugHttpSession *session=job?job->session:NULL;
|
|
294
|
+
if(!session){request_error("HttpError");return;}if(!job->started)job->started=monotonic_ms();
|
|
295
|
+
if(session->roots[4].tag==AUG_NULL)session->roots[4]=aug_headers_new();
|
|
296
|
+
if(policy->kind==AUG_HTTP_POLICY_REQUIRE_LOGIN||policy->kind==AUG_HTTP_POLICY_REQUIRE_PERMISSION) {
|
|
297
|
+
session->roots[5]=aug_call_method(dependency,"authenticate",&request,1);if(aug_has_error)return;
|
|
298
|
+
if(session->roots[5].tag==AUG_NULL){request_error("HttpUnauthorized");return;}
|
|
299
|
+
if(policy->kind==AUG_HTTP_POLICY_REQUIRE_PERMISSION){AugValue arguments[]={session->roots[5],aug_string(policy->permission)};AugFrame frame;aug_frame_enter(&frame,arguments,2);
|
|
300
|
+
AugValue allowed=aug_call_method(second,"authorize",arguments,2);aug_frame_leave(&frame);if(!aug_has_error&&!aug_truthy(allowed))request_error("HttpForbidden");}
|
|
301
|
+
} else if(policy->kind==AUG_HTTP_POLICY_LOG_REQUEST){if(job->roots[1].tag==AUG_NULL)job->roots[1]=aug_list_new(NULL,0);aug_list_append(job->roots[1],dependency);}
|
|
302
|
+
else if(policy->kind==AUG_HTTP_POLICY_RATE_LIMIT) {
|
|
303
|
+
const char *peer=aug_cstring(aug_field(request,6));int64_t now=monotonic_ms();RateEntry *entry=NULL,*free_entry=NULL;
|
|
304
|
+
for(size_t i=0;i<2048;i++){RateEntry *candidate=&rate_entries[i];if(candidate->policy==policy&&!strcmp(candidate->peer,peer)){entry=candidate;break;}if(!free_entry&&(!candidate->policy||candidate->until<=now))free_entry=candidate;}
|
|
305
|
+
if(!entry){entry=free_entry;if(!entry){request_error("HttpTooManyRequests");return;}entry->policy=policy;snprintf(entry->peer,sizeof(entry->peer),"%s",peer);entry->until=now+policy->seconds*1000;entry->used=0;}
|
|
306
|
+
if(entry->until<=now){entry->until=now+policy->seconds*1000;entry->used=0;}
|
|
307
|
+
if(entry->used>= (size_t)policy->amount){request_error("HttpTooManyRequests");return;}entry->used++;
|
|
308
|
+
} else if(policy->kind==AUG_HTTP_POLICY_TIMEOUT) {
|
|
309
|
+
int64_t deadline=monotonic_ms()+policy->amount;if(!job->deadline||deadline<job->deadline)job->deadline=deadline;
|
|
310
|
+
if(!job->test&&job->wsi)lws_set_timer_usecs(job->wsi,(lws_usec_t)(job->deadline-monotonic_ms())*1000);
|
|
311
|
+
} else if(policy->kind==AUG_HTTP_POLICY_CORS)cors_apply(policy,request,&session->roots[4],false);
|
|
312
|
+
else if(policy->kind==AUG_HTTP_POLICY_COMPRESS)session->compress=accepts_gzip(aug_field(request,2));
|
|
313
|
+
}
|
|
314
|
+
static AugValue policy_response(AugHttpSession *session,AugValue response) {
|
|
315
|
+
int64_t status=aug_cint(aug_field(response,1));
|
|
316
|
+
if(status==204||status==304||header_get(aug_field(response,2),"content-encoding",false).tag!=AUG_NULL)session->compress=false;
|
|
317
|
+
if(session->roots[4].tag==AUG_NULL&&!session->compress)return response;
|
|
318
|
+
AugValue roots[2]={response,aug_field(response,2)};AugFrame frame;aug_frame_enter(&frame,roots,2);
|
|
319
|
+
if(session->roots[4].tag==AUG_OBJECT){AugValue entries=aug_field(session->roots[4],0);for(size_t i=0;i<entries.as.object->field_count;i++){AugValue pair=entries.as.object->fields[i];roots[1]=aug_headers_with(roots[1],aug_field(pair,0),aug_field(pair,1));}}
|
|
320
|
+
if(session->compress){roots[1]=aug_headers_with(roots[1],aug_string("content-encoding"),aug_string("gzip"));roots[1]=aug_headers_with(roots[1],aug_string("vary"),aug_string("Accept-Encoding"));}
|
|
321
|
+
AugValue result=aug_http_response_full(aug_field(roots[0],0),aug_field(roots[0],1),roots[1]);aug_frame_leave(&frame);return result;
|
|
322
|
+
}
|
|
323
|
+
AugValue _aug_http_log(AugValue method,AugValue path,AugValue status,AugValue milliseconds) {
|
|
324
|
+
AugValue roots[]={method,path,status,milliseconds,aug_null(),aug_null()};AugFrame frame;aug_frame_enter(&frame,roots,6);
|
|
325
|
+
static const char *const names[]={"method","path","status","milliseconds"};
|
|
326
|
+
roots[4]=aug_new_object("RequestLog",4,NULL,NULL,0);roots[4].as.object->kind=AUG_RECORD_KIND;roots[4].as.object->field_names=names;
|
|
327
|
+
for(size_t i=0;i<4;i++)aug_set_field(roots[4],i,roots[i]);roots[5]=aug_json_stringify(roots[4]);
|
|
328
|
+
if(aug_has_error){aug_take_error();fputs("August HTTP log serialization failed\n",stderr);}else fprintf(stderr,"%s\n",aug_cstring(roots[5]));fflush(stderr);
|
|
329
|
+
aug_frame_leave(&frame);return aug_null();
|
|
330
|
+
}
|
|
331
|
+
typedef struct {const AugRoute *routes;size_t count;} AugHttpTestClient;
|
|
332
|
+
AugValue aug_http_test_client(const AugRoute *routes,size_t count) {
|
|
333
|
+
aug_task_checkpoint_hook=check_deadline;
|
|
334
|
+
AugHttpTestClient *client=malloc(sizeof(*client));if(!client)abort();*client=(AugHttpTestClient){routes,count};
|
|
335
|
+
AugValue result=aug_new_object("HttpTestClient",0,NULL,NULL,0);result.as.object->native=client;result.as.object->finalize=free;result.as.object->frozen=true;return result;
|
|
336
|
+
}
|
|
337
|
+
AugValue aug_httptestclient_request(AugValue client,AugValue method,AugValue target,AugValue headers,AugValue body) {
|
|
338
|
+
AugHttpTestClient *native=client.as.object->native;
|
|
339
|
+
AugHttpSession session={0};AugFrame frame;aug_frame_enter(&frame,session.roots,8);
|
|
340
|
+
const char *path=aug_cstring(target), *query=strchr(path,'?');size_t size=query?(size_t)(query-path):strlen(path);
|
|
341
|
+
if(!size||*path!='/'||strchr(path,'#')||memchr(path,0,target.as.object->text_length)||memchr(aug_cstring(method),0,method.as.object->text_length)){aug_frame_leave(&frame);return request_error("HttpError");}
|
|
342
|
+
session.roots[0]=aug_new_object("HttpRequest",7,NULL,NULL,0);session.roots[0].as.object->kind=AUG_HTTP_REQUEST_KIND;
|
|
343
|
+
aug_set_field(session.roots[0],0,method);aug_set_field(session.roots[0],1,decode_form_part(path,size));
|
|
344
|
+
aug_set_field(session.roots[0],2,headers.tag==AUG_NULL?aug_headers_new():headers);
|
|
345
|
+
aug_set_field(session.roots[0],3,body.tag==AUG_NULL?aug_bytes("",0,AUG_BYTES_KIND):body);
|
|
346
|
+
aug_set_field(session.roots[0],4,aug_map_new());
|
|
347
|
+
aug_set_field(session.roots[0],6,aug_string("127.0.0.1"));
|
|
348
|
+
session.roots[3]=aug_bytes(query?query+1:"",query?strlen(query+1):0,AUG_BYTES_KIND);
|
|
349
|
+
aug_set_field(session.roots[0],5,form_map(session.roots[3]));
|
|
350
|
+
if(aug_has_error){aug_take_error();session.roots[1]=aug_http_problem(400);goto serialize;}
|
|
351
|
+
session.route=select_route(session.roots[0],native->routes,native->count,&session.roots[1]);
|
|
352
|
+
if(!session.route)goto serialize;
|
|
353
|
+
if(aug_field(session.roots[0],3).as.object->text_length>body_limit){session.roots[1]=aug_http_problem(413);goto serialize;}
|
|
354
|
+
aug_freeze(session.roots[0]);
|
|
355
|
+
AugHttpJob job={.session=&session,.test=true,.roots={session.roots[0],aug_null()}};
|
|
356
|
+
aug_retain(&job.retained,job.roots,2);
|
|
357
|
+
void *previous=aug_execution_current()->http_job;bool previous_cancelled=aug_cancelled;aug_execution_current()->http_job=&job;
|
|
358
|
+
session.roots[1]=session.route->handler(session.roots,1);aug_execution_current()->http_job=previous;aug_release(&job.retained);
|
|
359
|
+
if(job.timed_out)aug_cancelled=previous_cancelled;
|
|
360
|
+
if(aug_has_error){aug_report_error();aug_take_error();session.roots[1]=aug_http_problem(500);}
|
|
361
|
+
if(session.streaming&&aug_cint(aug_field(session.roots[1],1))>=400){aug_frame_leave(&frame);return request_error("HttpError");}
|
|
362
|
+
serialize:;
|
|
363
|
+
AugValue value=aug_field(session.roots[1],0);int status=(int)aug_cint(aug_field(session.roots[1],1));
|
|
364
|
+
const char *type="application/json";
|
|
365
|
+
if(session.streaming)type=session.route->stream==1?"text/event-stream":session.route->stream==2?"application/octet-stream":"text/html; charset=utf-8";
|
|
366
|
+
else if(value.tag==AUG_OBJECT&&value.as.object->kind==AUG_HTML_KIND){session.roots[2]=aug_html_transport(value);type="text/html; charset=utf-8";}
|
|
367
|
+
else if(value.tag==AUG_OBJECT&&value.as.object->kind==AUG_BYTES_KIND){session.roots[2]=value;type="application/octet-stream";}
|
|
368
|
+
else session.roots[2]=aug_json_stringify(value);
|
|
369
|
+
if(session.compress&&!session.streaming)session.roots[2]=gzip_bytes(session.roots[2]);
|
|
370
|
+
if(aug_has_error||session.roots[2].as.object->text_length>response_limit){if(aug_has_error)aug_take_error();session.roots[1]=aug_http_problem(500);status=500;session.roots[2]=aug_json_stringify(aug_field(session.roots[1],0));}
|
|
371
|
+
session.roots[3]=aug_field(session.roots[1],2);
|
|
372
|
+
if(header_get(session.roots[3],"content-type",false).tag==AUG_NULL)session.roots[3]=aug_headers_with(session.roots[3],aug_string("content-type"),aug_string(type));
|
|
373
|
+
if(!strcmp(aug_cstring(method),"HEAD")||status==204||status==304)session.roots[2]=aug_bytes("",0,AUG_BYTES_KIND);
|
|
374
|
+
session.roots[1]=aug_http_response_full(session.roots[2],aug_int(status),session.roots[3]);
|
|
375
|
+
AugValue result=session.roots[1];aug_frame_leave(&frame);return result;
|
|
376
|
+
}
|
|
377
|
+
static AugValue cookie_value(AugValue headers, const char *name) {
|
|
378
|
+
AugValue entries = aug_field(headers, 0), found = aug_null(); AugFrame frame; aug_frame_enter(&frame, &found, 1);
|
|
379
|
+
for (size_t i = 0; i < entries.as.object->field_count; i++) {
|
|
380
|
+
AugValue pair = entries.as.object->fields[i]; if (strcasecmp(aug_cstring(aug_field(pair, 0)), "cookie")) continue;
|
|
381
|
+
AugValue value = aug_field(pair, 1); const char *text = value.as.object->text, *end = text + value.as.object->text_length;
|
|
382
|
+
while (text < end) {
|
|
383
|
+
while (text < end && (*text == ' ' || *text == '\t' || *text == ';')) text++;
|
|
384
|
+
const char *last = memchr(text, ';', (size_t)(end - text)); if (!last) last = end;
|
|
385
|
+
const char *equal = memchr(text, '=', (size_t)(last - text));
|
|
386
|
+
if (equal && (size_t)(equal - text) == strlen(name) && !memcmp(text, name, strlen(name))) {
|
|
387
|
+
if (found.tag != AUG_NULL) {aug_frame_leave(&frame); return request_error("HttpBadRequest");}
|
|
388
|
+
found = aug_string_n(equal + 1, (size_t)(last - equal - 1));
|
|
389
|
+
}
|
|
390
|
+
text = last == end ? end : last + 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
aug_frame_leave(&frame); return found;
|
|
394
|
+
}
|
|
395
|
+
AugValue _aug_http_url_encode(AugValue input) {
|
|
396
|
+
static const char hex[] = "0123456789ABCDEF"; size_t size = input.as.object->text_length;
|
|
397
|
+
if (size > SIZE_MAX / 3 - 1) return request_error("HttpError");
|
|
398
|
+
char *text = malloc(size * 3 + 1); if (!text) return request_error("HttpError"); size_t count = 0;
|
|
399
|
+
for (size_t i = 0; i < size; i++) {unsigned char c = (unsigned char)input.as.object->text[i];
|
|
400
|
+
if (c && ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || strchr("-._~", c))) text[count++] = (char)c;
|
|
401
|
+
else {text[count++] = '%'; text[count++] = hex[c >> 4]; text[count++] = hex[c & 15];}
|
|
402
|
+
}
|
|
403
|
+
AugValue result = aug_string_n(text, count); free(text); return result;
|
|
404
|
+
}
|
|
405
|
+
AugValue _aug_http_cookie(AugValue name, AugValue value, AugValue path, AugValue maxAge, AugValue secure) {
|
|
406
|
+
if (!valid_header(name, value) || !path.as.object->text_length || path.as.object->text[0] != '/' || maxAge.as.integer < 0 || maxAge.as.integer > 31536000) return request_error("HttpError");
|
|
407
|
+
for (size_t i = 0; i < value.as.object->text_length; i++) {unsigned char c = (unsigned char)value.as.object->text[i]; if (c < 33 || c >= 127 || c == '"' || c == ',' || c == ';' || c == '\\') return request_error("HttpError");}
|
|
408
|
+
for (size_t i = 0; i < path.as.object->text_length; i++) {unsigned char c = (unsigned char)path.as.object->text[i]; if (c < 33 || c >= 127 || c == ';') return request_error("HttpError");}
|
|
409
|
+
size_t size = name.as.object->text_length + value.as.object->text_length + path.as.object->text_length + 128;
|
|
410
|
+
char *content = malloc(size); if (!content) return request_error("HttpError");
|
|
411
|
+
snprintf(content, size, "%s=%s; Path=%s; Max-Age=%lld; HttpOnly; SameSite=Lax%s", aug_cstring(name), aug_cstring(value), aug_cstring(path), (long long)maxAge.as.integer, secure.as.boolean ? "; Secure" : "");
|
|
412
|
+
AugValue roots[2] = {aug_headers_new(), aug_null()}; AugFrame frame; aug_frame_enter(&frame, roots, 2);
|
|
413
|
+
header_add(roots[0], "set-cookie", 10, content, strlen(content)); free(content); aug_freeze(roots[0]);
|
|
414
|
+
AugValue result = roots[0]; aug_frame_leave(&frame); return result;
|
|
415
|
+
}
|
|
416
|
+
AugValue aug_http_bind(AugValue request, const char *source, const char *name, const AugSchema *schema) {
|
|
417
|
+
if (!strcmp(source, "request")) return request;
|
|
418
|
+
if (!strcmp(source, "path") || !strcmp(source, "query")) return coerce(map_scalar(aug_field(request, !strcmp(source, "path") ? 4 : 5), name), schema);
|
|
419
|
+
if (!strcmp(source, "header")) return coerce(header_get(aug_field(request, 2), name, true), schema);
|
|
420
|
+
if (!strcmp(source, "cookie")) return coerce(cookie_value(aug_field(request, 2), name), schema);
|
|
421
|
+
if (!strcmp(source, "form")) {
|
|
422
|
+
AugValue type = header_get(aug_field(request, 2), "content-type", true);
|
|
423
|
+
if (aug_has_error) return aug_null();
|
|
424
|
+
if (type.tag != AUG_STRING || strncasecmp(aug_cstring(type), "application/x-www-form-urlencoded", 33) || (type.as.object->text_length > 33 && type.as.object->text[33] != ';')) return request_error("HttpUnsupportedMedia");
|
|
425
|
+
AugValue map = form_map(aug_field(request, 3)); AugFrame frame; aug_frame_enter(&frame, &map, 1);
|
|
426
|
+
if (aug_has_error) {aug_frame_leave(&frame); return aug_null();}
|
|
427
|
+
if (schema->kind != AUG_SCHEMA_RECORD) {AugValue result = coerce(map_scalar(map, name), schema); aug_frame_leave(&frame); return result;}
|
|
428
|
+
AugValue *fields = calloc(schema->count ? schema->count : 1, sizeof(AugValue)); AugFrame field_frame; aug_frame_enter(&field_frame, fields, schema->count);
|
|
429
|
+
for (size_t i = 0; i < schema->count && !aug_has_error; i++) fields[i] = coerce(map_scalar(map, schema->names[i]), schema->fields[i]);
|
|
430
|
+
for (size_t i = 0; i < map.as.object->field_count && !aug_has_error; i += 2) {
|
|
431
|
+
bool known = false; for (size_t j = 0; j < schema->count; j++) if (!strcmp(aug_cstring(map.as.object->fields[i]), schema->names[j])) known = true;
|
|
432
|
+
if (!known) request_error("HttpBadRequest");
|
|
433
|
+
}
|
|
434
|
+
AugValue result = aug_has_error ? aug_null() : schema->make(fields, (int)schema->count);
|
|
435
|
+
aug_frame_leave(&field_frame); free(fields); aug_frame_leave(&frame); return result;
|
|
436
|
+
}
|
|
437
|
+
if (!strcmp(source, "body")) {
|
|
438
|
+
AugValue headers = aug_field(request, 2), type = header_get(headers, "content-type", true), encoding = header_get(headers, "content-encoding", true);
|
|
439
|
+
if (aug_has_error) return aug_null();
|
|
440
|
+
if (type.tag != AUG_STRING || (strncasecmp(type.as.object->text, "application/json", 16) || (type.as.object->text_length > 16 && type.as.object->text[16] != ';'))) return request_error("HttpUnsupportedMedia");
|
|
441
|
+
if (encoding.tag != AUG_NULL && strcasecmp(encoding.as.object->text, "identity")) return request_error("HttpUnsupportedMedia");
|
|
442
|
+
AugValue roots[2] = {aug_field(request, 3), aug_null()}; AugFrame frame; aug_frame_enter(&frame, roots, 2);
|
|
443
|
+
roots[1] = aug_bytes_text(roots[0]);
|
|
444
|
+
if (!aug_has_error) roots[1] = _aug_json_parse(roots[1]);
|
|
445
|
+
if (aug_has_error) { aug_take_error(); aug_frame_leave(&frame); return request_error("HttpBadRequest"); }
|
|
446
|
+
AugValue result = aug_json_decode(roots[1], schema);
|
|
447
|
+
if (aug_has_error) { aug_take_error(); aug_frame_leave(&frame); return request_error("HttpValidationError"); }
|
|
448
|
+
aug_frame_leave(&frame); return result;
|
|
449
|
+
}
|
|
450
|
+
return request_error("HttpBadRequest");
|
|
451
|
+
}
|
|
452
|
+
AugValue aug_http_response(AugValue body, int status) {
|
|
453
|
+
if (body.tag == AUG_OBJECT && body.as.object->kind == AUG_HTTP_RESPONSE_KIND) return body;
|
|
454
|
+
AugValue roots[2] = {body, aug_null()}; AugFrame frame; aug_frame_enter(&frame, roots, 2);
|
|
455
|
+
roots[1] = aug_headers_new(); AugValue result = aug_new_object("HttpResponse", 3, NULL, NULL, 0);
|
|
456
|
+
result.as.object->kind = AUG_HTTP_RESPONSE_KIND; aug_set_field(result, 0, body); aug_set_field(result, 1, aug_int(status)); aug_set_field(result, 2, roots[1]);
|
|
457
|
+
aug_frame_leave(&frame); return result;
|
|
458
|
+
}
|
|
459
|
+
AugValue aug_httprequest_form(AugValue request, const AugSchema *schema) {
|
|
460
|
+
AugValue result = aug_http_bind(request, "form", "", schema);
|
|
461
|
+
if (aug_has_error) {aug_take_error(); return request_error("HttpError");} return result;
|
|
462
|
+
}
|
|
463
|
+
AugValue aug_http_response_full(AugValue body, AugValue status, AugValue headers) {
|
|
464
|
+
int64_t code = status.tag == AUG_NULL ? 200 : aug_cint(status);
|
|
465
|
+
if (code < 200 || code > 599) return request_error("HttpError");
|
|
466
|
+
AugValue result = aug_http_response(body, (int)code);
|
|
467
|
+
if (headers.tag != AUG_NULL) aug_set_field(result, 2, headers);
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
int aug_http_error_status(void) {
|
|
471
|
+
return aug_error_is("HttpBadRequest") ? 400 : aug_error_is("HttpUnauthorized") ? 401 : aug_error_is("HttpForbidden") ? 403 : aug_error_is("HttpTooManyRequests") ? 429 : aug_error_is("HttpTimedOut") ? 504 : aug_error_is("HttpBodyTooLarge") ? 413 : aug_error_is("HttpUnsupportedMedia") ? 415 : aug_error_is("HttpValidationError") ? 422 : 500;
|
|
472
|
+
}
|
|
473
|
+
AugValue aug_http_problem(int status) {
|
|
474
|
+
const char *title = status == 400 ? "Bad Request" : status == 404 ? "Not Found" : status == 405 ? "Method Not Allowed" : status == 413 ? "Content Too Large" : status == 415 ? "Unsupported Media Type" : status == 422 ? "Unprocessable Content" : status == 401 ? "Unauthorized" : status == 403 ? "Forbidden" : status == 429 ? "Too Many Requests" : status == 504 ? "Gateway Timeout" : "Internal Server Error";
|
|
475
|
+
char json[256]; snprintf(json, sizeof(json), "{\"type\":\"about:blank\",\"title\":\"%s\",\"status\":%d}", title, status);
|
|
476
|
+
AugValue roots[2] = {0}; AugFrame frame; aug_frame_enter(&frame, roots, 2);
|
|
477
|
+
roots[0] = aug_string(json); roots[0] = _aug_json_parse(roots[0]); roots[1] = aug_http_response(roots[0], status);
|
|
478
|
+
header_add(aug_field(roots[1], 2), "content-type", 12, "application/problem+json", 24);
|
|
479
|
+
AugValue result = roots[1]; aug_frame_leave(&frame); return result;
|
|
480
|
+
}
|
|
481
|
+
static void cleanup(AugHttpSession *session) {
|
|
482
|
+
if (session->job) {AugHttpJob *job=session->job;if(job->wsi)lws_set_timer_usecs(job->wsi,LWS_SET_TIMER_USEC_CANCEL);job->session = NULL; job->wsi = NULL;if(job->transport_complete)aug_task_wake(job->task.as.object->native);else aug_task_cancel(job->task);}
|
|
483
|
+
if (session->rooted) aug_release(&session->retained);
|
|
484
|
+
free(session->body); memset(session, 0, sizeof(*session));
|
|
485
|
+
}
|
|
486
|
+
static AugValue request_task(AugValue self, AugValue *args, int count) {
|
|
487
|
+
(void)self; (void)count; aug_execution_current()->http_job=(void *)(intptr_t)aug_cint(args[2]); size_t index = (size_t)aug_cint(args[1]); return served_routes[index].handler(args, 1);
|
|
488
|
+
}
|
|
489
|
+
AugValue aug_http_event(AugValue data, AugValue id, AugValue event, AugValue retry) {
|
|
490
|
+
AugValue values[]={data,id,event,retry};
|
|
491
|
+
for(size_t i=1;i<3;i++)if(values[i].tag!=AUG_NULL&&(values[i].tag!=AUG_STRING||memchr(values[i].as.object->text,0,values[i].as.object->text_length)||strchr(values[i].as.object->text,'\r')||strchr(values[i].as.object->text,'\n')))return request_error("HttpError");
|
|
492
|
+
if(retry.tag!=AUG_NULL&&(retry.tag!=AUG_INT||retry.as.integer<0||retry.as.integer>2147483647))return request_error("HttpError");
|
|
493
|
+
AugFrame frame;aug_frame_enter(&frame,values,4);AugValue result=aug_new_object("ServerEvent",4,NULL,NULL,0);
|
|
494
|
+
for(size_t i=0;i<4;i++)aug_set_field(result,i,values[i]);result.as.object->frozen=true;aug_frame_leave(&frame);return result;
|
|
495
|
+
}
|
|
496
|
+
void aug_http_yield(AugValue value) {
|
|
497
|
+
AugHttpJob *job=aug_execution_current()->http_job;
|
|
498
|
+
if(!job||!job->session||(!job->test&&!job->wsi)){aug_cancelled=true;return;}
|
|
499
|
+
AugHttpSession *session=job->session;AugValue encoded=value;AugFrame frame;aug_frame_enter(&frame,&encoded,1);
|
|
500
|
+
if(value.tag==AUG_OBJECT&&value.as.object->kind==AUG_HTML_KIND)encoded=aug_html_transport(value);
|
|
501
|
+
if(session->route->stream==1) {
|
|
502
|
+
if(value.tag!=AUG_OBJECT||strcmp(value.as.object->type_name,"ServerEvent")){request_error("HttpError");goto finished;}
|
|
503
|
+
encoded=aug_json_stringify(aug_field(value,0));if(aug_has_error){aug_take_error();request_error("HttpError");goto finished;}
|
|
504
|
+
size_t size=encoded.as.object->text_length+16;
|
|
505
|
+
for(size_t i=1;i<3;i++){AugValue field=aug_field(value,i);if(field.tag==AUG_STRING)size+=field.as.object->text_length+8;}
|
|
506
|
+
AugValue retry=aug_field(value,3);if(retry.tag==AUG_INT)size+=32;
|
|
507
|
+
if(size>65536){request_error("HttpError");goto finished;}
|
|
508
|
+
char *buffer=malloc(size+1);if(!buffer)abort();size_t used=0;
|
|
509
|
+
for(size_t i=1;i<3;i++){AugValue field=aug_field(value,i);if(field.tag==AUG_STRING)used+=(size_t)snprintf(buffer+used,size+1-used,"%s: %s\n",i==1?"id":"event",field.as.object->text);}
|
|
510
|
+
if(retry.tag==AUG_INT)used+=(size_t)snprintf(buffer+used,size+1-used,"retry: %lld\n",(long long)retry.as.integer);
|
|
511
|
+
memcpy(buffer+used,"data: ",6);used+=6;memcpy(buffer+used,encoded.as.object->text,encoded.as.object->text_length);used+=encoded.as.object->text_length;memcpy(buffer+used,"\n\n",2);used+=2;
|
|
512
|
+
encoded=aug_bytes(buffer,used,AUG_BYTES_KIND);free(buffer);
|
|
513
|
+
}
|
|
514
|
+
if(encoded.tag!=AUG_OBJECT||(encoded.as.object->kind!=AUG_BYTES_KIND&&encoded.as.object->kind!=AUG_HTML_KIND)||encoded.as.object->text_length>65536){request_error("HttpError");goto finished;}
|
|
515
|
+
if(session->compress){encoded=gzip_bytes(encoded);if(aug_has_error||encoded.as.object->text_length>65536){if(!aug_has_error)request_error("HttpError");goto finished;}}
|
|
516
|
+
if(job->test) {
|
|
517
|
+
size_t old=session->roots[2].tag==AUG_OBJECT?session->roots[2].as.object->text_length:0, size=encoded.as.object->text_length;
|
|
518
|
+
if(size>response_limit-old){request_error("HttpError");goto finished;}
|
|
519
|
+
char *buffer=malloc(old+size+1);if(!buffer)abort();
|
|
520
|
+
if(old)memcpy(buffer,session->roots[2].as.object->text,old);memcpy(buffer+old,encoded.as.object->text,size);
|
|
521
|
+
session->roots[2]=aug_bytes(buffer,old+size,AUG_BYTES_KIND);free(buffer);session->streaming=true;goto finished;
|
|
522
|
+
}
|
|
523
|
+
session->roots[1]=policy_response(session,aug_http_response(aug_null(),session->route->status));session->roots[3]=encoded;session->streaming=true;session->stream_pending=true;session->sent=0;
|
|
524
|
+
lws_callback_on_writable(job->wsi);
|
|
525
|
+
if(session->head){session->stream_pending=false;aug_cancelled=true;goto finished;}
|
|
526
|
+
while(job->session&&job->session->stream_pending&&!aug_cancelled)aug_task_suspend();
|
|
527
|
+
finished:
|
|
528
|
+
aug_frame_leave(&frame);
|
|
529
|
+
}
|
|
530
|
+
AugValue aug_http_finish(AugValue response) {
|
|
531
|
+
AugHttpJob *job=aug_execution_current()->http_job;if(!job)return response;
|
|
532
|
+
AugHttpSession *session=job->session;AugValue roots[3]={response,job->roots[1],job->roots[0]};AugFrame frame;aug_frame_enter(&frame,roots,3);
|
|
533
|
+
if(job->timed_out)roots[0]=aug_http_problem(504);
|
|
534
|
+
else if(job->test&&job->deadline&&monotonic_ms()>=job->deadline)roots[0]=aug_http_problem(504);
|
|
535
|
+
if(session) {
|
|
536
|
+
roots[0]=policy_response(session,roots[0]);
|
|
537
|
+
bool stream_success=session->route->stream&&aug_cint(aug_field(roots[0],1))<400;
|
|
538
|
+
if(job->test&&stream_success&&!session->streaming) {
|
|
539
|
+
session->roots[2]=aug_bytes("",0,AUG_BYTES_KIND);
|
|
540
|
+
if(session->compress)session->roots[2]=gzip_bytes(session->roots[2]);
|
|
541
|
+
}
|
|
542
|
+
if(job->test&&stream_success)session->streaming=true;
|
|
543
|
+
if(!job->test) {
|
|
544
|
+
if(session->headers_sent&&aug_cint(aug_field(roots[0],1))>=400)lws_set_timeout(job->wsi,PENDING_TIMEOUT_USER_OK,LWS_TO_KILL_ASYNC);
|
|
545
|
+
else {
|
|
546
|
+
session->roots[1]=roots[0];
|
|
547
|
+
if(session->route->stream&&aug_cint(aug_field(roots[0],1))<400){if(session->compress&&!session->streaming){session->roots[3]=gzip_bytes(aug_bytes("",0,AUG_BYTES_KIND));session->stream_pending=true;}session->streaming=true;session->stream_done=true;}
|
|
548
|
+
else session->streaming=false;
|
|
549
|
+
lws_callback_on_writable(job->wsi);
|
|
550
|
+
}
|
|
551
|
+
while(job->session&&!job->transport_complete&&!aug_cancelled)aug_task_suspend();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
int status=aug_cancelled&&!job->timed_out?499:(int)aug_cint(aug_field(roots[0],1));
|
|
555
|
+
bool cancelled=aug_cancelled;aug_cancelled=false;aug_execution_current()->http_job=NULL;
|
|
556
|
+
if(roots[1].tag==AUG_OBJECT)for(size_t i=roots[1].as.object->field_count;i>0;i--){
|
|
557
|
+
AugValue arguments[]={aug_field(roots[2],0),aug_field(roots[2],1),aug_int(status),aug_int(monotonic_ms()-job->started)};
|
|
558
|
+
aug_call_method(roots[1].as.object->fields[i-1],"complete",arguments,4);
|
|
559
|
+
if(aug_has_error){aug_report_error();aug_take_error();}
|
|
560
|
+
}
|
|
561
|
+
aug_execution_current()->http_job=job;aug_cancelled=cancelled;
|
|
562
|
+
AugValue result=roots[0];aug_frame_leave(&frame);return result;
|
|
563
|
+
}
|
|
564
|
+
static void request_complete(AugValue task, void *data) {
|
|
565
|
+
AugHttpJob *job = data;
|
|
566
|
+
if (job->session && job->wsi) {
|
|
567
|
+
AugValue response = aug_field(task, 2), error = aug_field(task, 3);
|
|
568
|
+
if (error.tag != AUG_NULL) {aug_throw(error); aug_report_error(); aug_take_error(); response = aug_http_problem(500);}
|
|
569
|
+
if(job->timed_out)response=aug_http_problem(504);
|
|
570
|
+
AugHttpSession *session=job->session;session->job=NULL;
|
|
571
|
+
if(session->route->stream&&aug_cint(aug_field(response,1))<400) {session->roots[1]=response;session->streaming=true;session->stream_done=true;}
|
|
572
|
+
else if(session->streaming&&session->headers_sent) {lws_set_timeout(job->wsi,PENDING_TIMEOUT_USER_OK,LWS_TO_KILL_ASYNC);}
|
|
573
|
+
else {session->roots[1]=response;session->streaming=false;}
|
|
574
|
+
lws_callback_on_writable(job->wsi);
|
|
575
|
+
}
|
|
576
|
+
aug_task_release(task);aug_release(&job->retained);free(job);
|
|
577
|
+
}
|
|
578
|
+
static void dispatch(struct lws *wsi, AugHttpSession *session) {
|
|
579
|
+
if (session->submitted) return; session->submitted = true;
|
|
580
|
+
aug_set_field(session->roots[0], 3, aug_bytes(session->body, session->body_length, AUG_BYTES_KIND));
|
|
581
|
+
aug_freeze(session->roots[0]);
|
|
582
|
+
if (!session->route) {session->roots[1] = aug_http_problem(404); lws_callback_on_writable(wsi); return;}
|
|
583
|
+
AugHttpJob *job = calloc(1, sizeof(*job)); if (!job) return;
|
|
584
|
+
job->wsi = wsi; job->session = session; session->job = job;job->started=monotonic_ms();
|
|
585
|
+
job->roots[0]=session->roots[0];aug_retain(&job->retained,job->roots,2);
|
|
586
|
+
AugValue arguments[3] = {session->roots[0], aug_int(session->route - served_routes),aug_int((int64_t)(intptr_t)job)};
|
|
587
|
+
job->task = aug_task_spawn(request_task, aug_null(), arguments, 3, request_complete, job);
|
|
588
|
+
}
|
|
589
|
+
static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t length) {
|
|
590
|
+
AugHttpSession *session = user;
|
|
591
|
+
switch (reason) {
|
|
592
|
+
case LWS_CALLBACK_HTTP: {
|
|
593
|
+
cleanup(session); aug_retain(&session->retained, session->roots, 8); session->rooted = true;
|
|
594
|
+
char *uri; int uri_size; int method = lws_http_get_uri_and_method(wsi, &uri, &uri_size);
|
|
595
|
+
const char *methods[] = {"GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD"};
|
|
596
|
+
const char *verb = method >= 0 && method < 8 ? methods[method] : "UNKNOWN";
|
|
597
|
+
session->head = !strcmp(verb, "HEAD");
|
|
598
|
+
session->roots[0] = aug_new_object("HttpRequest", 7, NULL, NULL, 0); session->roots[0].as.object->kind = AUG_HTTP_REQUEST_KIND;
|
|
599
|
+
aug_set_field(session->roots[0], 0, aug_string(verb)); aug_set_field(session->roots[0], 1, aug_string_n(uri, (size_t)uri_size));
|
|
600
|
+
aug_set_field(session->roots[0], 2, aug_headers_new()); read_headers(wsi, aug_field(session->roots[0], 2));
|
|
601
|
+
aug_set_field(session->roots[0], 4, aug_map_new()); aug_set_field(session->roots[0], 5, aug_map_new());
|
|
602
|
+
char peer[96];lws_get_peer_simple(wsi,peer,sizeof(peer));aug_set_field(session->roots[0],6,aug_string(peer));
|
|
603
|
+
char *fragment = malloc(65536); if (!fragment) return -1;
|
|
604
|
+
for (int index = 0;; index++) {
|
|
605
|
+
int size = lws_hdr_copy_fragment(wsi, fragment, 65536, WSI_TOKEN_HTTP_URI_ARGS, index); if (size < 0) break;
|
|
606
|
+
char *equal = memchr(fragment, '=', (size_t)size); size_t key_size = equal ? (size_t)(equal - fragment) : (size_t)size;
|
|
607
|
+
AugValue roots[2] = {0}; AugFrame frame; aug_frame_enter(&frame, roots, 2);
|
|
608
|
+
roots[0] = aug_string_n(fragment, key_size); roots[1] = aug_string_n(equal ? equal + 1 : "", equal ? (size_t)size - key_size - 1 : 0);
|
|
609
|
+
map_add(aug_field(session->roots[0], 5), roots[0], roots[1]); aug_frame_leave(&frame);
|
|
610
|
+
}
|
|
611
|
+
free(fragment);
|
|
612
|
+
session->route=select_route(session->roots[0],served_routes,served_count,&session->roots[1]);
|
|
613
|
+
if (!session->route) {
|
|
614
|
+
session->submitted = true; lws_callback_on_writable(wsi); return 0;
|
|
615
|
+
}
|
|
616
|
+
AugValue content_length = header_get(aug_field(session->roots[0], 2), "content-length", true);
|
|
617
|
+
bool has_body = !strcmp(verb, "POST") || !strcmp(verb, "PUT") || !strcmp(verb, "PATCH");
|
|
618
|
+
if (aug_has_error) {aug_take_error(); session->roots[1] = aug_http_problem(400); session->submitted = true; lws_callback_on_writable(wsi); return 0;}
|
|
619
|
+
if (content_length.tag == AUG_STRING) {
|
|
620
|
+
errno = 0; char *last; unsigned long long amount = strtoull(content_length.as.object->text, &last, 10);
|
|
621
|
+
bool invalid = !content_length.as.object->text_length || content_length.as.object->text[0] < '0' || content_length.as.object->text[0] > '9' || *last;
|
|
622
|
+
if (invalid || errno || amount > body_limit) { session->roots[1] = aug_http_problem(invalid ? 400 : 413); session->submitted = true; lws_callback_on_writable(wsi); return 0; }
|
|
623
|
+
has_body = amount > 0;
|
|
624
|
+
}
|
|
625
|
+
if (header_get(aug_field(session->roots[0], 2), "transfer-encoding", false).tag == AUG_STRING) has_body = true;
|
|
626
|
+
if (!has_body) dispatch(wsi, session);
|
|
627
|
+
return 0;
|
|
628
|
+
}
|
|
629
|
+
case LWS_CALLBACK_HTTP_BODY:
|
|
630
|
+
if (session->submitted) return 0;
|
|
631
|
+
if (length > body_limit - session->body_length) { session->roots[1] = aug_http_problem(413); session->submitted = true; lws_callback_on_writable(wsi); return 0; }
|
|
632
|
+
if (session->body_length + length > session->body_capacity) {
|
|
633
|
+
session->body_capacity = session->body_length + length;
|
|
634
|
+
char *next = realloc(session->body, session->body_capacity); if (!next) return -1; session->body = next;
|
|
635
|
+
}
|
|
636
|
+
memcpy(session->body + session->body_length, in, length); session->body_length += length; return 0;
|
|
637
|
+
case LWS_CALLBACK_HTTP_BODY_COMPLETION: dispatch(wsi, session); return 0;
|
|
638
|
+
case LWS_CALLBACK_HTTP_WRITEABLE: {
|
|
639
|
+
if (!session) return 0;
|
|
640
|
+
if (session->roots[1].tag != AUG_OBJECT) return 0;
|
|
641
|
+
AugValue response = session->roots[1], body = aug_field(response, 0), headers = aug_field(response, 2);
|
|
642
|
+
if (!session->headers_sent) {
|
|
643
|
+
session->roots[2] = session->streaming ? aug_bytes("",0,AUG_BYTES_KIND) : body.tag == AUG_OBJECT && body.as.object->kind == AUG_HTML_KIND ? aug_html_transport(body) : body.tag == AUG_OBJECT && body.as.object->kind == AUG_BYTES_KIND ? body : aug_json_stringify(body);
|
|
644
|
+
if (aug_has_error || session->roots[2].as.object->text_length > response_limit) { if (aug_has_error) aug_take_error(); session->compress=false;session->roots[1] = aug_http_problem(500); session->roots[2] = aug_json_stringify(aug_field(session->roots[1], 0)); response = session->roots[1]; headers = aug_field(response, 2); }
|
|
645
|
+
if(session->compress&&!session->streaming)session->roots[2]=gzip_bytes(session->roots[2]);
|
|
646
|
+
const char *type = body.tag == AUG_OBJECT && body.as.object->kind == AUG_HTML_KIND ? "text/html; charset=utf-8" : body.tag == AUG_OBJECT && body.as.object->kind == AUG_BYTES_KIND ? "application/octet-stream" : "application/json";
|
|
647
|
+
if(session->streaming)type=session->route->stream==1?"text/event-stream":session->route->stream==2?"application/octet-stream":"text/html; charset=utf-8";
|
|
648
|
+
AugValue specified = header_get(headers, "content-type", false); if (specified.tag == AUG_STRING) type = specified.as.object->text;
|
|
649
|
+
unsigned char buffer[LWS_PRE + 16384], *start = buffer + LWS_PRE, *next = start, *end = buffer + sizeof(buffer);
|
|
650
|
+
int status = (int)aug_cint(aug_field(response, 1)); size_t size = session->roots[2].as.object->text_length;
|
|
651
|
+
bool no_body = status == 204 || status == 304; if (no_body) session->head = true;
|
|
652
|
+
if (lws_add_http_common_headers(wsi, (unsigned int)status, no_body ? NULL : type, no_body || session->streaming ? LWS_ILLEGAL_HTTP_CONTENT_LEN : (lws_filepos_t)size, &next, end)) return -1;
|
|
653
|
+
AugValue entries = aug_field(headers, 0);
|
|
654
|
+
for (size_t i = 0; i < entries.as.object->field_count; i++) {
|
|
655
|
+
AugValue pair = entries.as.object->fields[i], label = aug_field(pair, 0), value = aug_field(pair, 1);
|
|
656
|
+
if (!strcasecmp(label.as.object->text, "content-type") || !strcasecmp(label.as.object->text, "content-length") ||
|
|
657
|
+
!strcasecmp(label.as.object->text, "transfer-encoding") || !strcasecmp(label.as.object->text, "connection") ||
|
|
658
|
+
!strcasecmp(label.as.object->text, "keep-alive") || !strcasecmp(label.as.object->text, "upgrade")) continue;
|
|
659
|
+
char name[256]; if (label.as.object->text_length > 250) return -1;
|
|
660
|
+
snprintf(name, sizeof(name), "%s:", label.as.object->text);
|
|
661
|
+
if (lws_add_http_header_by_name(wsi, (unsigned char *)name, (unsigned char *)value.as.object->text, (int)value.as.object->text_length, &next, end)) return -1;
|
|
662
|
+
}
|
|
663
|
+
if (lws_finalize_write_http_header(wsi, start, &next, end)) return -1;
|
|
664
|
+
session->headers_sent = true; lws_callback_on_writable(wsi); return 0;
|
|
665
|
+
}
|
|
666
|
+
if(session->streaming&&!session->stream_pending&&!session->stream_done)return 0;
|
|
667
|
+
AugValue encoded = session->streaming&&session->stream_pending?session->roots[3]:session->roots[2]; size_t remaining = session->head ? 0 : encoded.as.object->text_length - session->sent;
|
|
668
|
+
size_t amount = remaining > 16384 ? 16384 : remaining; unsigned char buffer[LWS_PRE + 16384];
|
|
669
|
+
if (amount) memcpy(buffer + LWS_PRE, encoded.as.object->text + session->sent, amount);
|
|
670
|
+
int mode = amount == remaining && (!session->streaming || session->stream_done) ? LWS_WRITE_HTTP_FINAL : LWS_WRITE_HTTP;
|
|
671
|
+
if (lws_write(wsi, buffer + LWS_PRE, amount, (enum lws_write_protocol)mode) < 0) return -1;
|
|
672
|
+
session->sent += amount;
|
|
673
|
+
if(session->streaming&&amount==remaining&&session->stream_pending){session->stream_pending=false;session->roots[3]=aug_null();session->sent=0;if(session->job)aug_task_wake(session->job->task.as.object->native);}
|
|
674
|
+
if (mode == LWS_WRITE_HTTP_FINAL) {if(session->job)session->job->transport_complete=true; cleanup(session); return lws_http_transaction_completed(wsi) ? -1 : 0; }
|
|
675
|
+
lws_callback_on_writable(wsi); return 0;
|
|
676
|
+
}
|
|
677
|
+
case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
|
|
678
|
+
case LWS_CALLBACK_CLOSED_HTTP: if (session) cleanup(session); return 0;
|
|
679
|
+
case LWS_CALLBACK_TIMER:
|
|
680
|
+
if(session&&session->job){AugHttpJob *job=session->job;job->timed_out=true;aug_task_cancel(job->task);if(session->headers_sent)lws_set_timeout(wsi,PENDING_TIMEOUT_USER_OK,LWS_TO_KILL_ASYNC);}return 0;
|
|
681
|
+
default: return 0;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
static const struct lws_protocols protocols[] = {
|
|
685
|
+
{"august-http", callback_http, sizeof(AugHttpSession), 16384, 0, NULL, 0},
|
|
686
|
+
{"august-client", callback_client, 0, 16384, 0, NULL, 0}, LWS_PROTOCOL_LIST_TERM
|
|
687
|
+
};
|
|
688
|
+
static void service_io(bool wait) {
|
|
689
|
+
if (!server_context) return;
|
|
690
|
+
if (!wait || io_wakeup_pending) lws_cancel_service(server_context);
|
|
691
|
+
io_wakeup_pending=false;
|
|
692
|
+
lws_service(server_context, wait ? 100 : 0);
|
|
693
|
+
}
|
|
694
|
+
typedef struct {
|
|
695
|
+
struct lws *wsi; AugTask *task; AugValue roots[6]; AugRetained retained;
|
|
696
|
+
bool done, failed; size_t sent, size; char *bytes;
|
|
697
|
+
} AugClientRequest;
|
|
698
|
+
static void client_done(AugClientRequest *request, bool failed) {
|
|
699
|
+
if (!request || request->done) return;
|
|
700
|
+
request->done = true; request->failed = failed;
|
|
701
|
+
if (!failed) {
|
|
702
|
+
request->roots[4] = aug_bytes(request->bytes, request->size, AUG_BYTES_KIND);
|
|
703
|
+
request->roots[4] = aug_http_response_full(request->roots[4], request->roots[5], request->roots[2]);
|
|
704
|
+
}
|
|
705
|
+
if (request->task) aug_task_wake(request->task);
|
|
706
|
+
notify_io();
|
|
707
|
+
}
|
|
708
|
+
static int callback_client(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t length) {
|
|
709
|
+
(void)user; AugClientRequest *request = lws_get_opaque_user_data(wsi); if (!request) return 0;
|
|
710
|
+
switch (reason) {
|
|
711
|
+
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: {
|
|
712
|
+
unsigned char **next = in, *end = *next + length;
|
|
713
|
+
AugValue entries = aug_field(request->roots[2], 0);
|
|
714
|
+
for (size_t i = 0; i < entries.as.object->field_count; i++) {
|
|
715
|
+
AugValue pair = entries.as.object->fields[i], name = aug_field(pair, 0), value = aug_field(pair, 1); char label[256];
|
|
716
|
+
snprintf(label, sizeof(label), "%s:", aug_cstring(name));
|
|
717
|
+
if (lws_add_http_header_by_name(wsi, (const unsigned char *)label, (const unsigned char *)aug_cstring(value), (int)value.as.object->text_length, next, end)) return -1;
|
|
718
|
+
}
|
|
719
|
+
if (request->roots[3].tag != AUG_NULL) {
|
|
720
|
+
char size[32]; snprintf(size, sizeof(size), "%zu", request->roots[3].as.object->text_length);
|
|
721
|
+
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)size, (int)strlen(size), next, end)) return -1;
|
|
722
|
+
lws_client_http_body_pending(wsi, 1); lws_callback_on_writable(wsi);
|
|
723
|
+
}
|
|
724
|
+
return 0;
|
|
725
|
+
}
|
|
726
|
+
case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: {
|
|
727
|
+
AugValue body = request->roots[3]; if (body.tag == AUG_NULL) return 0;
|
|
728
|
+
size_t remaining = body.as.object->text_length - request->sent, amount = remaining > 16384 ? 16384 : remaining;
|
|
729
|
+
unsigned char buffer[LWS_PRE + 16384]; if (amount) memcpy(buffer + LWS_PRE, body.as.object->text + request->sent, amount);
|
|
730
|
+
bool final = amount == remaining;
|
|
731
|
+
if (final) lws_client_http_body_pending(wsi, 0);
|
|
732
|
+
if (lws_write(wsi, buffer + LWS_PRE, amount, final ? LWS_WRITE_HTTP_FINAL : LWS_WRITE_HTTP) != (int)amount) return -1;
|
|
733
|
+
request->sent += amount; if (!final) lws_callback_on_writable(wsi); return 0;
|
|
734
|
+
}
|
|
735
|
+
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
|
|
736
|
+
request->roots[5] = aug_int(lws_http_client_http_response(wsi)); request->roots[2] = aug_headers_new(); read_headers(wsi, request->roots[2]); aug_freeze(request->roots[2]); return 0;
|
|
737
|
+
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: {
|
|
738
|
+
if (length > response_limit - request->size) {client_done(request, true); return -1;}
|
|
739
|
+
char *bytes = realloc(request->bytes, request->size + length + 1); if (!bytes) {client_done(request, true); return -1;}
|
|
740
|
+
request->bytes = bytes; memcpy(bytes + request->size, in, length); request->size += length; return 0;
|
|
741
|
+
}
|
|
742
|
+
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: {
|
|
743
|
+
char buffer[LWS_PRE + 16384], *next = buffer + LWS_PRE; int size = 16384;
|
|
744
|
+
if (lws_http_client_read(wsi, &next, &size) < 0) return -1; return 0;
|
|
745
|
+
}
|
|
746
|
+
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: client_done(request, false); return 0;
|
|
747
|
+
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
|
748
|
+
fprintf(stderr, "August HTTP connection failed: %.*s\n", 250, in ? (const char *)in : "connection closed");
|
|
749
|
+
client_done(request, true); request->wsi = NULL; return 0;
|
|
750
|
+
case LWS_CALLBACK_CLOSED_CLIENT_HTTP: client_done(request, !request->done); request->wsi = NULL; return 0;
|
|
751
|
+
default: return 0;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
static bool ensure_context(void) {
|
|
755
|
+
if (server_context) return true;
|
|
756
|
+
struct lws_context_creation_info info; memset(&info, 0, sizeof(info)); info.port = CONTEXT_PORT_NO_LISTEN;
|
|
757
|
+
info.protocols = protocols; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | LWS_SERVER_OPTION_EXPLICIT_VHOSTS; info.max_http_header_data = 65535;
|
|
758
|
+
lws_set_log_level(LLL_ERR | LLL_WARN | (getenv("AUG_HTTP_TRACE") ? LLL_NOTICE | LLL_INFO : 0), NULL); server_context = lws_create_context(&info); aug_scheduler_io = service_io; aug_scheduler_notify=notify_io;
|
|
759
|
+
if (server_context) {
|
|
760
|
+
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.vhost_name = "august-client";
|
|
761
|
+
info.client_ssl_ca_filepath = *tls_ca ? tls_ca : NULL;
|
|
762
|
+
if (!lws_create_vhost(server_context, &info)) {lws_context_destroy(server_context); server_context = NULL;}
|
|
763
|
+
}
|
|
764
|
+
return server_context != NULL;
|
|
765
|
+
}
|
|
766
|
+
AugValue _aug_http_request(AugValue method, AugValue url, AugValue headers, AugValue body) {
|
|
767
|
+
if (strchr(aug_cstring(url), '#') || memchr(url.as.object->text, 0, url.as.object->text_length)) return request_error("HttpError");
|
|
768
|
+
const char *verb = aug_cstring(method); for (const char *p = verb; *p; p++) if (*p < 'A' || *p > 'Z') return request_error("HttpError");
|
|
769
|
+
if (!*verb || !ensure_context()) return request_error("HttpError");
|
|
770
|
+
lws_parse_uri_t *uri = lws_parse_uri_create(aug_cstring(url));
|
|
771
|
+
if (!uri || (strcmp(uri->scheme, "http") && strcmp(uri->scheme, "https"))) {if (uri) lws_parse_uri_destroy(&uri); return request_error("HttpError");}
|
|
772
|
+
AugClientRequest *request = calloc(1, sizeof(*request)); if (!request) {lws_parse_uri_destroy(&uri); return request_error("HttpError");}
|
|
773
|
+
request->roots[0] = method; request->roots[1] = url; request->roots[2] = headers; request->roots[3] = body;
|
|
774
|
+
aug_retain(&request->retained, request->roots, 6);
|
|
775
|
+
if (headers.tag == AUG_NULL) request->roots[2] = aug_headers_new();
|
|
776
|
+
request->task = aug_task_current();
|
|
777
|
+
struct lws_client_connect_info info; memset(&info, 0, sizeof(info));
|
|
778
|
+
info.context = server_context; info.address = uri->host; info.host = uri->host; info.port = uri->port;
|
|
779
|
+
info.vhost = lws_get_vhost_by_name(server_context, "august-client"); info.alpn = "h2,http/1.1";
|
|
780
|
+
info.path = uri->path; info.method = verb; info.protocol = "august-client"; info.opaque_user_data = request; info.pwsi = &request->wsi;
|
|
781
|
+
info.ssl_connection = LCCSCF_HTTP_NO_FOLLOW_REDIRECT | (!strcmp(uri->scheme, "https") ? LCCSCF_USE_SSL : 0);
|
|
782
|
+
if (!lws_client_connect_via_info(&info)) client_done(request, true);
|
|
783
|
+
while (!request->done && !aug_cancelled) {
|
|
784
|
+
if (request->task) aug_task_suspend(); else {if (!aug_scheduler_step()) service_io(true); else service_io(false);}
|
|
785
|
+
}
|
|
786
|
+
if (request->wsi) {lws_set_opaque_user_data(request->wsi, NULL); lws_set_timeout(request->wsi, PENDING_TIMEOUT_USER_OK, LWS_TO_KILL_SYNC);}
|
|
787
|
+
lws_parse_uri_destroy(&uri);
|
|
788
|
+
bool failed = request->failed || aug_cancelled; AugValue result = request->roots[4];
|
|
789
|
+
aug_release(&request->retained); free(request->bytes); free(request);
|
|
790
|
+
return failed ? request_error("HttpError") : result;
|
|
791
|
+
}
|
|
792
|
+
void aug_http_serve(const AugRoute *routes, size_t count, int64_t port) {
|
|
793
|
+
if (port < 0 || port > 65535 || served_routes) { request_error("HttpError"); return; }
|
|
794
|
+
served_routes = routes; served_count = count; stopped = 0;
|
|
795
|
+
if (!ensure_context()) {request_error("HttpError"); return;}
|
|
796
|
+
struct lws_context_creation_info info; memset(&info, 0, sizeof(info));
|
|
797
|
+
info.port = (int)port; info.iface = listen_host; info.protocols = protocols; info.vhost_name = "august"; info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
|
|
798
|
+
info.ssl_cert_filepath = *tls_certificate ? tls_certificate : NULL; info.ssl_private_key_filepath = *tls_private_key ? tls_private_key : NULL;
|
|
799
|
+
info.alpn = "h2,http/1.1";
|
|
800
|
+
info.max_http_header_data = 65535; info.max_http_header_pool = 64;
|
|
801
|
+
struct lws_vhost *vhost = lws_create_vhost(server_context, &info);
|
|
802
|
+
if (!vhost) { request_error("HttpError"); return; }
|
|
803
|
+
if (enable_http3) {
|
|
804
|
+
info.port = CONTEXT_PORT_NO_LISTEN_SERVER; info.vhost_name = "august-h3";
|
|
805
|
+
info.listen_accept_role = "quic"; info.listen_accept_protocol = "august-http"; info.alpn = "h3";
|
|
806
|
+
struct lws_vhost *quic = lws_create_vhost(server_context, &info);
|
|
807
|
+
struct lws *udp = quic ? lws_create_adopt_udp(quic, listen_host, lws_get_vhost_listen_port(vhost), LWS_CAUDP_BIND, "august-http", NULL, NULL, NULL, NULL, "quic_listen") : NULL;
|
|
808
|
+
if (getenv("AUG_HTTP_TRACE") && udp) fprintf(stderr, "August QUIC socket %d bound for %s:%d\n", lws_get_socket_fd(udp), listen_host, lws_get_vhost_listen_port(vhost));
|
|
809
|
+
if (!quic || !udp) {
|
|
810
|
+
lws_context_destroy(server_context); server_context = NULL; request_error("HttpError"); return;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
printf("August HTTP listening on port %d\n", lws_get_vhost_listen_port(vhost)); fflush(stdout);
|
|
814
|
+
struct sigaction action, previous_term, previous_int; memset(&action, 0, sizeof(action)); action.sa_handler = stop_server;
|
|
815
|
+
sigaction(SIGTERM, &action, &previous_term); sigaction(SIGINT, &action, &previous_int);
|
|
816
|
+
aug_scheduler_io = service_io;
|
|
817
|
+
while (!stopped) {
|
|
818
|
+
/* Drain a bounded batch before entering poll. One task per poll serialized
|
|
819
|
+
ready requests and repeatedly paid the event-loop wake-up cost. */
|
|
820
|
+
bool progressed = false;
|
|
821
|
+
for (unsigned i = 0; i < 32 && aug_scheduler_step(); i++) progressed = true;
|
|
822
|
+
if (progressed || io_wakeup_pending) lws_cancel_service(server_context);
|
|
823
|
+
io_wakeup_pending=false;
|
|
824
|
+
if (lws_service(server_context, 100) < 0) break;
|
|
825
|
+
}
|
|
826
|
+
lws_context_destroy(server_context); server_context = NULL;
|
|
827
|
+
while (aug_scheduler_step()) {}
|
|
828
|
+
sigaction(SIGTERM, &previous_term, NULL); sigaction(SIGINT, &previous_int, NULL);
|
|
829
|
+
}
|