Skip to content

feat(content-type): implement content type handling - #4

Draft
mwillbanks wants to merge 3 commits into
roadrunner-server:masterfrom
mwillbanks:feat/form-support
Draft

mwillbanks wants to merge 3 commits into
roadrunner-server:masterfrom
mwillbanks:feat/form-support

Conversation

@mwillbanks

@mwillbanks mwillbanks commented Dec 10, 2025 •

Copy link
Copy Markdown
  • fixes lambda support for forms, uploads and other base64-encoded content

Summary by CodeRabbit

  • New Features

    • RoadRunner AWS Lambda integration and richer HTTP handling (base64 body support; urlencoded & multipart form parsing)
  • Documentation

    • Expanded README with overview, request flow, Docker rationale, and local build/testing guidance
  • Tests

    • Added comprehensive tests covering request handling, body transformations, uploads, and response mapping
  • Chores

    • .gitignore updated to ignore vendor/ and .cache; runtime env and log mode adjusted for development
  • Refactor

    • Startup environment setup encapsulated and covered by unit tests

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 10, 2025 •

Copy link
Copy Markdown

Walkthrough

Adds request body base64 decoding, multipart and urlencoded form parsing with file-backed uploads, header normalization and URI construction; introduces a parser library, tests, a Docker runtime image for Go+PHP AWS Lambda, small main refactor extracting environment setup, and .gitignore/.rr.yaml updates.

Changes

Cohort / File(s) Summary
Infrastructure & Docs
Dockerfile, README.md, .gitignore, .rr.yaml
Adds multi-stage Dockerfile for a Go+PHP AWS Lambda provided runtime; expands README with RoadRunner AWS Lambda details and local build/test instructions; uncomments vendor/ and adds .cache to .gitignore; updates .rr.yaml env and logging settings.
Entry Point & Test
main.go, main_test.go
Extracts inline environment setup into new unexported configureEnvironment() called from main; adds TestConfigureEnvironment validating PATH and LD_LIBRARY_PATH behavior.
Request Handling & Plugin
plugin.go, plugin_test.go
Adds base64 body decoding, body transformation pipeline (transformBody), header normalization (normalizeHeaders), URI builder (buildURI), and wiring of parsed JSON fields and Uploads into proto requests; adds comprehensive tests covering methods, content types, base64 payloads, multipart uploads, cookies, and response mapping.
Form Parser Library & Tests
request_parser.go, request_parser_test.go
New parser providing Uploads and FileUpload types, multipart and urlencoded parsing, temporary-file materialization and cleanup (Clear), JSON marshaling of parsed fields, and tests for content-type classification, tree building, multipart uploads, and empty-tree packing.

Sequence Diagram(s)

sequenceDiagram
    actor API as API Gateway
    participant Handler as Plugin Handler
    participant Parser as Request Parser
    participant FS as File System
    participant Proto as Proto Request

    API->>Handler: APIGatewayV2HTTPRequest (may include base64 body)
    Handler->>Handler: decode base64 if flagged
    Handler->>Parser: transformBody(body, content-type)
    alt multipart/form-data
        Parser->>FS: create temp file(s) for uploads
        FS-->>Parser: temp file metadata
        Parser-->>Handler: JSON fields + Uploads object
    else application/x-www-form-urlencoded
        Parser-->>Handler: JSON fields (no uploads)
    else other
        Parser-->>Handler: passthrough body
    end
    Handler->>Handler: normalizeHeaders()
    Handler->>Handler: buildURI(path, rawQuery)
    Handler->>Proto: populate request (body, headers, URI, uploads)
    Proto-->>Handler: proto response context
    Handler-->>API: API Gateway response (status, headers, body)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to focus on:
    • request_parser.go: multipart parsing logic, nested data-tree correctness, temporary-file creation, and Clear() lifecycle.
    • plugin.go: base64 decoding branches, transformBody integration, header normalization, buildURI correctness, and deferred upload cleanup.
    • plugin_test.go: validity of fakes/mocks (fakePool, reflection-based PExec) and coverage of edge cases.
    • Dockerfile: multi-stage build correctness and runtime layout assumptions.

"I'm a rabbit in the code-burrow bright,
Peeling Base64 carrots late at night.
Multipart crumbs tucked in a heap,
Temp files napping, uploads sleep—
RoadRunner hums, and tests take flight." 🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(content-type): implement content type handling' is concise and directly reflects the main technical focus of the changeset—comprehensive content type and form data handling improvements. However, the PR also introduces Docker/Lambda runtime configuration, environment setup, and substantial documentation updates that are not captured by the title. The title addresses a primary aspect but omits equally significant changes like the Dockerfile, .rr.yaml, and .gitignore modifications.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f46fd5e and a0526a1.

📒 Files selected for processing (1)
  • request_parser.go (1 hunks)
🧰 Additional context used
🪛 golangci-lint (2.5.0)
request_parser.go

[error] 121-121: net/http.NewRequest must not be called. use net/http.NewRequestWithContext

(noctx)


[error] 143-143: net/http.NewRequest must not be called. use net/http.NewRequestWithContext

(noctx)

🔇 Additional comments (7)
request_parser.go (7)

36-52: LGTM! Clean resource management.

The Uploads struct is well-designed with proper JSON marshaling of the tree structure and safe cleanup of temporary files.


54-96: LGTM! Robust file upload handling.

The FileUpload implementation properly handles errors at each stage, sets appropriate error codes, and ensures resources are closed.


120-140: Static analysis warning is a false positive here.

The linter flags line 121 for using NewRequest without context. However, this request is used solely for parsing form data via the standard library's ParseForm method—it's never sent over the network. A context would serve no purpose in this scenario.


160-162: Verify intended behavior for failed file uploads.

The error returned by f.Open() is silently discarded. While FileUpload.Open does set the Error field on failure, this means failed uploads are included in the results without propagating the error. Please confirm this is the intended behavior—reporting all uploads with their error status rather than failing the entire request.


177-236: LGTM! Clean data extraction logic.

The parsing functions properly handle nil checks, error propagation, and the empty-tree edge case in packDataTree.


322-326: Good fix for the type assertion safety.

The safe type assertion with error handling properly addresses the previous review concern about potential panics from inconsistent tree structure.


238-412: Complex but well-structured tree building logic.

The tree mounting implementation handles various edge cases including:

  • PHP-style array notation parsing (field[0], field[])
  • Nested structure creation
  • Conflict detection between leaf and branch nodes
  • Empty value handling

The logic mirrors the proven roadrunner-server-http implementation, which provides confidence in its correctness.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mwillbanks

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Dec 10, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin.go (1)

253-276: Handle url.QueryUnescape error on line 268.

Line 268 silently ignores the error from url.QueryUnescape(v.Value) with decoded, _ := url.QueryUnescape(v.Value). This can mask malformed percent-encoding in cookie values. Check the error and either reject the cookie or log the issue.

Note: http.ParseCookie does return a slice of *http.Cookie, so the iteration pattern is correct.

🧹 Nitpick comments (13)
main.go (1)

82-85: Consider handling or logging os.Setenv errors.

While os.Setenv rarely fails in practice, silently ignoring errors could mask critical initialization issues, especially since this environment setup is essential for the Lambda runtime.

Apply this diff to log errors:

 func configureEnvironment() {
-	_ = os.Setenv("PATH", os.Getenv("PATH")+":"+os.Getenv("LAMBDA_TASK_ROOT"))
-	_ = os.Setenv("LD_LIBRARY_PATH", "./lib:/lib64:/usr/lib64")
+	if err := os.Setenv("PATH", os.Getenv("PATH")+":"+os.Getenv("LAMBDA_TASK_ROOT")); err != nil {
+		log.Printf("Warning: failed to set PATH: %v", err)
+	}
+	if err := os.Setenv("LD_LIBRARY_PATH", "./lib:/lib64:/usr/lib64"); err != nil {
+		log.Printf("Warning: failed to set LD_LIBRARY_PATH: %v", err)
+	}
 }
php-conf/10-app.ini (1)

9-9: Consider Lambda timeout interaction.

Setting max_execution_time = 0 disables PHP's execution time limit. While Lambda enforces its own timeout, this setting means PHP won't fail gracefully before Lambda terminates. Consider setting this to slightly less than your Lambda timeout to allow proper error handling.

symfony/assets/controllers/csrf_protection_controller.js (1)

37-37: Consider removing the msCrypto fallback.

The window.msCrypto fallback was for Internet Explorer 11, which is no longer supported by Microsoft. Since this project targets modern browsers (evidenced by the use of Turbo/Hotwire), you can simplify to just window.crypto.

-        csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
+        csrfField.defaultValue = csrfToken = btoa(String.fromCharCode.apply(null, window.crypto.getRandomValues(new Uint8Array(18))));
plugin_test.go (2)

54-55: Consider checking proto unmarshal errors in tests.

Ignoring the unmarshal error could mask test setup issues. While this is test code, failed unmarshalling would cause subsequent assertions to fail with confusing errors.

 	req := &httpV1proto.Request{}
-	_ = proto.Unmarshal(pld.Context, req)
+	if err := proto.Unmarshal(pld.Context, req); err != nil {
+		// Log or handle - helps debug test failures
+	}

362-366: Simplify nil check before len().

The nil check is redundant since len() on a nil map returns 0.

 		if len(tt.cookies) > 0 {
-			if gotReq.Cookies == nil || len(gotReq.Cookies) == 0 {
+			if len(gotReq.Cookies) == 0 {
 				t.Fatalf("expected cookies to be parsed")
 			}
 		}
request_parser.go (3)

120-124: Use http.NewRequestWithContext for proper context propagation.

Per static analysis, http.NewRequest should be replaced with http.NewRequestWithContext to support request cancellation and timeouts.

-func parseURLEncoded(body []byte, headers map[string]string) ([]byte, error) {
-	req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(body))
+func parseURLEncoded(ctx context.Context, body []byte, headers map[string]string) ([]byte, error) {
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/", bytes.NewReader(body))

The same change should be applied to parseMultipart at line 143.


98-103: exists() may return true for permission errors.

os.Stat can fail for reasons other than the file not existing (e.g., permission denied). In those cases, the function returns true, which could cause os.Remove to be called unnecessarily.

 func exists(path string) bool {
-	if _, err := os.Stat(path); os.IsNotExist(err) {
-		return false
-	}
-	return true
+	_, err := os.Stat(path)
+	return err == nil
 }

160-162: Silently ignoring f.Open() errors may obscure upload failures.

While the FileUpload.Error field captures failure reasons, consider logging or aggregating errors to aid debugging when uploads fail.

request_parser_test.go (2)

11-30: Consider subtests for clearer classification failures

Table-driven test is fine, but wrapping each case in t.Run(tt.name, func(t *testing.T) { ... }) would isolate failures and show the failing case in go test output more clearly.


62-129: Multipart test nicely validates uploads and cleanup; consider guaranteed cleanup on failure

The test thoroughly checks fields, upload metadata, and uploads.Clear() removing the temp file. To avoid leaking temp files when an earlier assertion fails, you could add a t.Cleanup(uploads.Clear) (or defer uploads.Clear()) right after parseMultipart returns, while still keeping the explicit Clear/assertion at the end.

symfony/tests/bootstrap.php (1)

11-13: Guard against undefined APP_DEBUG in bootstrap

If APP_DEBUG isn’t set in the environment, $_SERVER['APP_DEBUG'] can trigger a notice. Using a default keeps the bootstrap robust:

-if ($_SERVER['APP_DEBUG']) {
+if (($_SERVER['APP_DEBUG'] ?? false)) {
     umask(0000);
 }
symfony/templates/index.html.twig (1)

32-47: Consider using Promise.all or for...of instead of forEach with async.

While the current implementation works, forEach doesn't await async callbacks. If you need sequential execution or want to handle errors collectively, consider:

-            targets.forEach(async ({ id, url }) => {
+            for (const { id, url } of targets) {
                 const container = document.getElementById(id);
                 if (!container) {
-                    return;
+                    continue;
                 }
 
                 try {
                     const response = await fetch(url, { headers: { 'X-Debug': 'ajax-check' } });
                     const contentType = response.headers.get('content-type') || 'unknown';
                     const bodyText = await response.text();
 
                     container.textContent = `Content-Type: ${contentType} | Body: ${bodyText}`;
                 } catch (error) {
                     container.textContent = `Failed to load: ${error}`;
                 }
-            });
+            }

Alternatively, if parallel execution is desired (current behavior), make the intent explicit with Promise.all:

await Promise.all(targets.map(async ({ id, url }) => {
    // ... existing logic
}));
symfony/templates/base.html.twig (1)

8-139: Consider externalizing CSS for production.

While inline styles work well for this demo, consider moving the CSS to an external stylesheet (e.g., assets/styles/app.css) for better maintainability, caching, and separation of concerns in a production environment.

This is a minor maintainability improvement and can be deferred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6ca7ea and 5b51886.

⛔ Files ignored due to path filters (2)
  • symfony/composer.lock is excluded by !**/*.lock
  • symfony/symfony.lock is excluded by !**/*.lock
📒 Files selected for processing (63)
  • .gitignore (1 hunks)
  • .rr.yaml (1 hunks)
  • Dockerfile (1 hunks)
  • README.md (1 hunks)
  • main.go (2 hunks)
  • main_test.go (1 hunks)
  • php-conf/10-app.ini (1 hunks)
  • plugin.go (5 hunks)
  • plugin_test.go (1 hunks)
  • request_parser.go (1 hunks)
  • request_parser_test.go (1 hunks)
  • symfony/.editorconfig (1 hunks)
  • symfony/.env (1 hunks)
  • symfony/.gitignore (1 hunks)
  • symfony/assets/app.js (1 hunks)
  • symfony/assets/controllers.json (1 hunks)
  • symfony/assets/controllers/csrf_protection_controller.js (1 hunks)
  • symfony/assets/controllers/hello_controller.js (1 hunks)
  • symfony/assets/stimulus_bootstrap.js (1 hunks)
  • symfony/assets/styles/app.css (1 hunks)
  • symfony/bin/console (1 hunks)
  • symfony/bin/phpunit (1 hunks)
  • symfony/composer.json (1 hunks)
  • symfony/config/bundles.php (1 hunks)
  • symfony/config/packages/asset_mapper.yaml (1 hunks)
  • symfony/config/packages/cache.yaml (1 hunks)
  • symfony/config/packages/csrf.yaml (1 hunks)
  • symfony/config/packages/debug.yaml (1 hunks)
  • symfony/config/packages/doctrine.yaml (1 hunks)
  • symfony/config/packages/doctrine_migrations.yaml (1 hunks)
  • symfony/config/packages/framework.yaml (1 hunks)
  • symfony/config/packages/mailer.yaml (1 hunks)
  • symfony/config/packages/messenger.yaml (1 hunks)
  • symfony/config/packages/monolog.yaml (1 hunks)
  • symfony/config/packages/notifier.yaml (1 hunks)
  • symfony/config/packages/nyholm_psr7.yaml (1 hunks)
  • symfony/config/packages/property_info.yaml (1 hunks)
  • symfony/config/packages/routing.yaml (1 hunks)
  • symfony/config/packages/security.yaml (1 hunks)
  • symfony/config/packages/translation.yaml (1 hunks)
  • symfony/config/packages/twig.yaml (1 hunks)
  • symfony/config/packages/ux_turbo.yaml (1 hunks)
  • symfony/config/packages/validator.yaml (1 hunks)
  • symfony/config/packages/web_profiler.yaml (1 hunks)
  • symfony/config/preload.php (1 hunks)
  • symfony/config/reference.php (1 hunks)
  • symfony/config/routes.yaml (1 hunks)
  • symfony/config/routes/framework.yaml (1 hunks)
  • symfony/config/routes/security.yaml (1 hunks)
  • symfony/config/routes/web_profiler.yaml (1 hunks)
  • symfony/config/services.yaml (1 hunks)
  • symfony/importmap.php (1 hunks)
  • symfony/phpunit.dist.xml (1 hunks)
  • symfony/public/index.php (1 hunks)
  • symfony/src/Controller/IndexController.php (1 hunks)
  • symfony/src/Kernel.php (1 hunks)
  • symfony/templates/base.html.twig (1 hunks)
  • symfony/templates/form_multipart.html.twig (1 hunks)
  • symfony/templates/form_multipart_result.html.twig (1 hunks)
  • symfony/templates/form_urlencoded.html.twig (1 hunks)
  • symfony/templates/form_urlencoded_result.html.twig (1 hunks)
  • symfony/templates/index.html.twig (1 hunks)
  • symfony/tests/bootstrap.php (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
symfony/public/index.php (1)
symfony/src/Kernel.php (1)
  • Kernel (8-31)
plugin.go (1)
request_parser.go (1)
  • Uploads (36-39)
plugin_test.go (2)
plugin.go (2)
  • Logger (42-44)
  • Plugin (31-39)
request_parser.go (1)
  • Uploads (36-39)
🪛 dotenv-linter (4.0.0)
symfony/.env

[warning] 33-33: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 golangci-lint (2.5.0)
request_parser.go

[error] 121-121: net/http.NewRequest must not be called. use net/http.NewRequestWithContext

(noctx)


[error] 143-143: net/http.NewRequest must not be called. use net/http.NewRequestWithContext

(noctx)

plugin_test.go

[warning] 24-24: unused-parameter: parameter 'name' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 50-50: unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 80-80: unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 82-82: unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _

(revive)


[error] 363-363: S1009: should omit nil check; len() for nil maps is defined as zero

(staticcheck)

🪛 LanguageTool
README.md

[grammar] ~8-~8: Ensure spelling is correct
Context: ...Symfony app to validate HTML responses, urlencoded forms, multipart uploads, and assorted ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (62)
symfony/config/routes/security.yaml (1)

1-3: Verify the referenced service is properly configured.

The route configuration references the service security.route_loader.logout which should be defined in your Symfony service container. Ensure this service is properly configured elsewhere in your application's service definitions (typically in services.yaml or similar configuration files).

symfony/config/packages/translation.yaml (1)

1-5: Symfony translation configuration is correct and follows best practices.

The configuration properly sets up the translation framework with a sensible default locale and path structure using Symfony's standard parameter syntax. The empty providers mapping is acceptable since translation providers are optional.

symfony/config/packages/doctrine_migrations.yaml (1)

1-6: Configuration looks good for Lambda deployment.

This is a minimal and correct Doctrine Migrations setup. Notably, enable_profiler: false is an appropriate optimization for Lambda environments where memory and execution time are constrained. The namespace convention is correctly documented.

symfony/config/packages/validator.yaml (1)

1-11: This appears to be a Symfony sample app config disconnected from the stated PR objective.

The validator configuration is syntactically valid, but this file seems orthogonal to the PR's stated goal of "content type handling for forms, uploads, and base64-encoded content." This appears to be part of a Symfony sample application included alongside the core Lambda feature implementation.

The commented auto_mapping section and test-environment override are reasonable defaults. However, if the sample app uses Doctrine entities with validation constraints, consider uncommenting and configuring auto_mapping to leverage automatic constraint inference.

symfony/config/packages/asset_mapper.yaml (1)

1-11: Well-configured asset mapper setup with environment-specific behavior.

The configuration is syntactically correct and follows Symfony best practices: strict mode in development catches missing imports early, while lenient warn mode in production prevents site breakage. The assets/ directory path is a standard convention.

Verify that the assets/ directory is present in the project structure and properly included in the Lambda deployment package, as this configuration depends on that directory being accessible at runtime.

symfony/config/reference.php (2)

1534-1545: Inconsistency between AI summary and actual code.

The AI-generated summary mentions a method named App::coderabbit_config, but the actual code implements App::config. This discrepancy suggests the summary may be inaccurate or based on outdated information.


1618-1621: Pass-through method provides only type checking.

The Routes::config() method simply returns the input array without any transformation or validation. While this is likely intentional to provide type safety through Psalm annotations, it adds no runtime logic or validation.

symfony/config/packages/security.yaml (2)

1-28: Security configuration looks solid for a sample application.

The firewall setup follows Symfony best practices:

  • Dev firewall correctly excludes profiler, debugging tools, and static assets with security: false
  • Main firewall uses lazy loading and an in-memory provider, appropriate for this sample app
  • Password hashing uses the 'auto' strategy for compatibility

For production deployments, consider moving away from in-memory user providers and adding explicit access control rules beyond the commented examples.


30-39: The password hasher configuration values are appropriate for testing and align with Symfony's documented examples. The configured time_cost: 3 and memory_cost: 10 appear in Symfony's own security documentation as acceptable low-cost values for test environments. The actual minimum values for argon2 depend on the underlying PHP provider and can vary; some implementations accept time_cost ≥ 1 and memory_cost ≥ 8 KiB, while others require higher minimums. The comments claiming "lowest possible value" are overstated without platform/provider context, but the configuration itself does not require changes.

Likely an incorrect or invalid review comment.

.gitignore (1)

18-18: LGTM!

Adding vendor/ and .cache to .gitignore is appropriate for keeping dependency artifacts and build outputs out of version control.

Also applies to: 23-23

symfony/config/packages/property_info.yaml (1)

1-3: LGTM!

Enabling the constructor extractor in Symfony's property info component is a standard configuration that allows property metadata extraction from constructor parameters.

symfony/config/routes.yaml (1)

1-11: LGTM!

The routing configuration is well-documented and follows Symfony conventions. The schema reference on Line 1 provides helpful IDE support.

symfony/public/index.php (1)

1-9: LGTM!

The front controller follows the Symfony Runtime component pattern correctly. The factory function creates a Kernel instance with environment and debug settings from the context, which integrates well with the Lambda-compatible Kernel implementation that uses SYMFONY_CACHE_DIR and SYMFONY_LOG_DIR environment variables.

symfony/assets/controllers.json (1)

1-15: LGTM!

The Symfony UX Turbo controller configuration is properly structured. Enabling turbo-core with eager fetch and disabling mercure-turbo-stream are reasonable defaults.

symfony/config/packages/ux_turbo.yaml (1)

1-4: LGTM!

Enabling header-based CSRF protection is appropriate for stateless environments like AWS Lambda. This aligns well with the serverless deployment model where session-based CSRF tokens would be problematic.

symfony/config/packages/twig.yaml (1)

1-6: LGTM!

The Twig configuration is well-structured. Enabling strict variables in the test environment (Lines 4-6) is a best practice that helps catch undefined variable bugs during testing.

.rr.yaml (2)

6-9: LGTM!

The environment variables align well with the Symfony Kernel implementation. Setting SYMFONY_CACHE_DIR and SYMFONY_LOG_DIR to /tmp directories is appropriate for Lambda's read-only filesystem, and the APP_RUNTIME configures the RoadRunner-Symfony integration correctly.


12-13: Verify log configuration for production deployment.

The logging mode has been changed from production/error to development/debug. Debug-level logging can expose sensitive information and impact performance in production environments.

Confirm whether:

  • This configuration is intended only for development/testing
  • There's a separate production configuration that overrides these settings
  • The Lambda deployment uses environment-specific RoadRunner configs

If this file is used in production, consider reverting to production log levels or using environment variables to control log verbosity.

symfony/config/packages/csrf.yaml (1)

1-11: LGTM! Stateless CSRF configuration is appropriate for Lambda environments.

The stateless CSRF protection configuration aligns well with the serverless/Lambda deployment model where traditional session-based CSRF tokens wouldn't persist across invocations.

main.go (1)

23-23: LGTM! Environment setup refactored into a dedicated function.

Extracting environment configuration into configureEnvironment() improves code organization and testability.

main_test.go (1)

9-24: LGTM! Test coverage for environment configuration is thorough.

The test properly uses t.Setenv for automatic cleanup and validates both PATH appending and LD_LIBRARY_PATH setting.

symfony/config/packages/cache.yaml (1)

1-19: LGTM! Configuration scaffolding provides helpful examples.

This file provides commented configuration examples for various cache backends without enabling any by default, which is appropriate for a template/example setup.

symfony/assets/stimulus_bootstrap.js (1)

1-5: LGTM! Standard Stimulus bootstrap implementation.

The Stimulus initialization follows Symfony UX conventions and includes a helpful placeholder comment for registering custom controllers.

symfony/config/routes/framework.yaml (1)

1-4: LGTM! Dev-only error routing is properly scoped.

The error route configuration is correctly limited to the dev environment with the when@dev directive, preventing it from being active in production.

symfony/.editorconfig (1)

1-17: LGTM! EditorConfig settings follow standard conventions.

The configuration provides sensible defaults with appropriate overrides for YAML compose files (2-space indent) and Markdown files (preserve trailing whitespace).

plugin.go (3)

137-154: LGTM: Base64 decoding and cleanup handling.

The base64 decoding logic correctly handles encoded request bodies, and the deferred cleanup pattern ensures uploaded files are properly removed even if errors occur. Good defensive programming.


328-363: LGTM: Content type handling with proper cleanup.

The transformBody method correctly handles different content types (none, URL-encoded, multipart) and returns appropriate cleanup functions for file uploads. The error handling is comprehensive.


365-373: LGTM: URI construction helper.

The buildURI helper correctly constructs URIs with optional query strings and handles the empty path case by defaulting to "/".

php-conf/10-app.ini (1)

19-19: Verify preload file exists.

The OPcache preload references /var/task/config/preload.php. Ensure this file exists in the Symfony application.

Dockerfile (2)

107-111: Verify the lambda directory structure.

The COPY --exclude="*.new" ./lambda/* . command copies files from a lambda/ directory that isn't visible in this PR. Please ensure this directory exists and contains the necessary main.go and plugin.go files.


4-4: Go version 1.25.5 is valid and current. This is the latest stable Go release as of December 2025 (released December 2, 2025), so no change is needed.

symfony/composer.json (1)

1-108: Well-structured Symfony project configuration.

The composer.json follows Symfony best practices with appropriate dependency declarations, autoload configuration, and polyfill replacements for PHP 8.2+. The RoadRunner integration via runtime/roadrunner-symfony-nyholm aligns with the PR's Lambda support objective.

symfony/assets/controllers/csrf_protection_controller.js (1)

1-81: Solid CSRF protection implementation for Turbo integration.

The double-submit cookie pattern with SameSite=strict and conditional Secure flag is correctly implemented. Token validation via regex and proper cookie cleanup on submission complete the security model.

plugin_test.go (2)

102-369: Comprehensive test coverage for Lambda request handling.

The table-driven tests cover a wide range of content types including JSON, URL-encoded forms, multipart uploads, base64-encoded bodies, and binary data. The assertions validate method, URI, query string, headers, body transformation, and upload metadata propagation.


253-255: Loop variable capture is no longer needed in Go 1.22+.

The tt := tt pattern was necessary before Go 1.22 to capture the loop variable for parallel subtests. If the project uses Go 1.22+, this line can be removed.

symfony/config/packages/nyholm_psr7.yaml (1)

1-11: Correct PSR-17 factory registration.

The configuration properly maps all six PSR-17 HTTP factory interfaces to the Nyholm implementation, enabling autowiring throughout the Symfony application.

request_parser.go (1)

1-408: Well-structured request parsing implementation.

The parser correctly handles URL-encoded and multipart form data with proper tree-building logic for nested PHP-style field names. The maxLevel constant protects against deep nesting attacks, and temporary file cleanup via Uploads.Clear() prevents resource leaks.

request_parser_test.go (2)

32-60: URL-encoded parsing test covers nested keys and arrays well

This test exercises plain fields, nested bracket syntax, and repeated keys into arrays; good coverage and assertions look correct.


131-139: Empty dataTree packing behavior is well specified

Asserting that an empty dataTree{} packs to {} makes the intended wire format explicit; no issues here.

symfony/assets/styles/app.css (1)

1-3: Simple demo styling is fine

Minimal body background color is appropriate for a sample app; nothing to change here.

symfony/config/packages/mailer.yaml (1)

1-3: Mailer DSN wiring looks standard

Using framework.mailer.dsn: '%env(MAILER_DSN)%' is the usual Symfony pattern and keeps transport fully env-driven; looks good.

symfony/config/routes/web_profiler.yaml (1)

1-8: Dev-only web profiler routing matches standard setup

when@dev with /_wdt and /_profiler routes mirrors the usual Symfony skeleton; no issues.

symfony/config/packages/debug.yaml (1)

1-5: VarDumper server config is conventional and well-documented

Dev-only dump_destination pointing to tcp://%env(VAR_DUMPER_SERVER)% with comments matches common Symfony debug setups; no changes needed.

symfony/config/packages/routing.yaml (1)

1-10: Routing defaults and prod override look correct

Using default_uri from DEFAULT_URI and relaxing strict_requirements only in prod is consistent with Symfony’s recommended patterns.

symfony/bin/phpunit (1)

1-4: Add a guard for missing PHPUnit binary for clearer failures

Right now, if vendor/phpunit/phpunit/phpunit is missing (e.g., before composer install), this will emit a fatal error without context. A small guard makes failures clearer:

-#!/usr/bin/env php
-<?php
-
-require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
+#!/usr/bin/env php
+<?php
+
+$phpunit = dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
+
+if (!is_file($phpunit)) {
+    fwrite(STDERR, "PHPUnit binary not found at {$phpunit}. Did you run 'composer install'?\n");
+    exit(1);
+}
+
+require $phpunit;
symfony/config/packages/framework.yaml (1)

1-15: LGTM!

The framework configuration follows Symfony best practices with appropriate environment variable usage for secrets and lazy session initialization. The test-specific mock session storage is correctly configured for the test environment.

symfony/config/packages/web_profiler.yaml (1)

1-13: LGTM!

The environment-specific web profiler configuration is appropriate, enabling the toolbar for development while disabling profiler collection in tests for better performance.

symfony/config/bundles.php (1)

3-16: LGTM!

The bundle configuration follows Symfony best practices with appropriate environment restrictions. Development and debugging tools are correctly limited to dev/test environments, while core application bundles are enabled for all environments.

symfony/templates/form_urlencoded_result.html.twig (1)

1-23: LGTM!

The template is well-structured with proper Twig inheritance, automatic escaping for XSS protection, and appropriate handling of both scalar and iterable values. The user experience is enhanced with an empty state message and navigation back to the form.

symfony/phpunit.dist.xml (1)

1-44: LGTM!

The PHPUnit configuration is comprehensive and follows best practices with strict error handling, proper test environment setup, and appropriate deprecation trigger mappings for Doctrine. The configuration will help catch issues early during development.

symfony/config/packages/doctrine.yaml (3)

5-7: Ensure database server version is configured.

The commented server_version must be configured either here or in the DATABASE_URL environment variable. Without this, Doctrine may make incorrect assumptions about database features and compatibility.

Verify that your DATABASE_URL includes the server version (e.g., postgresql://user:pass@host/db?serverVersion=16) or uncomment and set the server_version here:

         # IMPORTANT: You MUST configure your server version,
         # either here or in the DATABASE_URL env var (see .env file)
-        #server_version: '16'
+        server_version: '16'

13-14: Verify PostgreSQL as the intended database platform.

The configuration explicitly sets identity generation preferences for PostgreSQL. If PostgreSQL is your intended production database, this is fine. However, if you plan to support multiple database platforms, you may want to either remove this platform-specific configuration or add preferences for other platforms.

If PostgreSQL is the only intended database, consider adding a comment to document this decision:

         naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
+        # This application requires PostgreSQL
         identity_generation_preferences:
             Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity

32-48: LGTM!

The production cache configuration follows Doctrine best practices with separate cache pools for query and result caching. This will provide appropriate performance optimization in production while keeping development environments uncached for easier debugging.

symfony/assets/controllers/hello_controller.js (1)

1-16: LGTM! Example Stimulus controller.

This is a standard Stimulus controller example. The implementation is correct and serves well as a starting point for custom controllers.

symfony/templates/form_multipart_result.html.twig (1)

1-35: LGTM! Well-structured result template.

The template properly handles both iterable and scalar field values and displays file metadata appropriately. Twig's auto-escaping protects against XSS.

README.md (1)

1-109: Excellent documentation!

The README provides comprehensive guidance on building, testing, and deploying the Lambda integration. The request flow diagram and step-by-step instructions are particularly helpful.

Note: The static analysis hint about "urlencoded" is a false positive—this is standard technical terminology for URL-encoded content.

symfony/templates/base.html.twig (1)

1-162: Well-structured base template with inline styles.

The template provides a solid foundation with proper block structure and responsive navigation. The inline CSS is acceptable for a demo application.

symfony/config/packages/messenger.yaml (2)

15-15: Verify Doctrine configuration for failed transport.

The failed transport uses doctrine://default, which requires Doctrine DBAL to be installed and configured with a valid database connection.


7-14: Verify PostgreSQL usage and consider transport portability.

The use_notify: true option requires PostgreSQL's LISTEN/NOTIFY feature and will fail with MySQL, MariaDB, or SQLite. Ensure your deployment uses PostgreSQL, or remove this option for database portability.

Run the following script to verify the database configuration and messenger transport setup:

#!/bin/bash
# Check DATABASE_URL and MESSENGER_TRANSPORT_DSN configuration

# Look for database configuration in .env files
echo "=== Checking database configuration ==="
fd -t f '^\.env' -X cat {} \; | grep -E '(DATABASE_URL|MESSENGER_TRANSPORT_DSN)'

# Check if Doctrine is configured
echo -e "\n=== Checking Doctrine configuration ==="
cat symfony/config/packages/doctrine.yaml 2>/dev/null || echo "doctrine.yaml not found"

# Search for doctrine/dbal in composer files
echo -e "\n=== Checking Doctrine DBAL dependency ==="
fd 'composer.json' symfony -X cat {} \; | jq -r '.require["doctrine/doctrine-bundle"], .require["doctrine/dbal"]' 2>/dev/null || echo "No Doctrine dependencies found in composer.json"
symfony/src/Controller/IndexController.php (3)

13-17: LGTM!

The index route correctly renders the template with proper attribute routing.


19-23: LGTM!

The ping endpoint correctly returns a plain text response with appropriate headers.


25-29: LGTM!

The health check endpoint correctly returns a JSON response with appropriate status.

Comment thread Dockerfile Resolved
Comment thread plugin.go
Comment on lines +268 to +270
decoded, _ := url.QueryUnescape(v.Value)
resp[v.Name] = &httpV1proto.HeaderValue{
Value: [][]byte{[]byte(v.Value)},
Value: [][]byte{[]byte(decoded)},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle QueryUnescape errors properly.

The error from url.QueryUnescape(v.Value) is silently ignored. If unescaping fails, you should fall back to the original value or log the error.

Apply this diff to handle the error:

-        decoded, _ := url.QueryUnescape(v.Value)
+        decoded, err := url.QueryUnescape(v.Value)
+        if err != nil {
+            log.Warn("failed to unescape cookie value", zap.String("name", v.Name), zap.Error(err))
+            decoded = v.Value
+        }
         resp[v.Name] = &httpV1proto.HeaderValue{
             Value: [][]byte{[]byte(decoded)},
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
decoded, _ := url.QueryUnescape(v.Value)
resp[v.Name] = &httpV1proto.HeaderValue{
Value: [][]byte{[]byte(v.Value)},
Value: [][]byte{[]byte(decoded)},
decoded, err := url.QueryUnescape(v.Value)
if err != nil {
log.Warn("failed to unescape cookie value", zap.String("name", v.Name), zap.Error(err))
decoded = v.Value
}
resp[v.Name] = &httpV1proto.HeaderValue{
Value: [][]byte{[]byte(decoded)},
🤖 Prompt for AI Agents
In plugin.go around lines 268 to 270, the call to url.QueryUnescape ignores the
returned error; change it to capture the error (decoded, err :=
url.QueryUnescape(v.Value)) and handle it by falling back to the original
v.Value when err != nil (i.e., use v.Value as the value instead of decoded), and
optionally log the error via the package's logger if available; construct
resp[v.Name] with the chosen value bytes (either decoded on success or v.Value
on error).

Comment thread request_parser.go Resolved
Comment thread symfony/.env Resolved
Comment thread symfony/.env Resolved
Comment thread symfony/src/Kernel.php Resolved
Comment thread symfony/src/Kernel.php Resolved
* fixes lambda support for forms, uploads and other base64-encoded content
@mwillbanks
mwillbanks marked this pull request as ready for review December 11, 2025 15:25

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
request_parser.go (1)

320-323: Potential panic from unchecked type assertion remains unaddressed.

This unchecked type assertion can panic if the tree structure is inconsistent. Use a safe type assertion as previously suggested.

-	return ft[i[0]].(fileTree).mount(i[1:], v)
+	sub, ok := ft[i[0]].(fileTree)
+	if !ok {
+		return errors.New("invalid tree structure: expected fileTree")
+	}
+	return sub.mount(i[1:], v)
🧹 Nitpick comments (6)
main.go (1)

86-93: Hardcoded Symfony directories reduce reusability.

This AWS Lambda plugin appears to be a generic RoadRunner integration, but configureEnvironment() creates Symfony-specific directories. Consider making these paths configurable via environment variables or removing them from this generic plugin, delegating directory creation to the PHP application bootstrap.

plugin_test.go (2)

85-100: Unsafe reflection to set unexported fields is fragile.

Using unsafe.Pointer to set unexported fields in poolImp.PExec creates a maintenance burden—any field rename or reorder in the upstream library will silently break these tests. Consider checking if the library provides a constructor or test helper, or document this fragility with a comment explaining the workaround.


362-366: Simplify nil check before len().

The nil check is redundant since len() on a nil slice returns 0.

 if len(tt.cookies) > 0 {
-	if gotReq.Cookies == nil || len(gotReq.Cookies) == 0 {
+	if len(gotReq.Cookies) == 0 {
 		t.Fatalf("expected cookies to be parsed")
 	}
 }
request_parser.go (3)

120-124: Use NewRequestWithContext for proper context propagation.

Static analysis correctly flags that http.NewRequest should be replaced with http.NewRequestWithContext to support cancellation and timeouts. Since this is called from a Lambda handler context, propagating the context is important.

-func parseURLEncoded(body []byte, headers map[string]string) ([]byte, error) {
-	req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(body))
+func parseURLEncoded(ctx context.Context, body []byte, headers map[string]string) ([]byte, error) {
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/", bytes.NewReader(body))

142-146: Same context propagation issue here.

Apply the same fix as parseURLEncoded to accept a context parameter and use http.NewRequestWithContext.

-func parseMultipart(body []byte, headers map[string]string) ([]byte, *Uploads, error) {
-	req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(body))
+func parseMultipart(ctx context.Context, body []byte, headers map[string]string) ([]byte, *Uploads, error) {
+	req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/", bytes.NewReader(body))

160-162: Consider logging upload open failures.

While f.Error captures the error code for the PHP side, silently discarding the Go error makes debugging harder. Consider logging the error at debug level.

 for _, f := range uploads.list {
-	_ = f.Open()
+	if err := f.Open(); err != nil {
+		// Error code already set in f.Error for PHP side
+		// Log for debugging purposes (if logger available)
+	}
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5b51886 and bdee797.

📒 Files selected for processing (10)
  • .gitignore (1 hunks)
  • .rr.yaml (1 hunks)
  • Dockerfile (1 hunks)
  • README.md (1 hunks)
  • main.go (2 hunks)
  • main_test.go (1 hunks)
  • plugin.go (6 hunks)
  • plugin_test.go (1 hunks)
  • request_parser.go (1 hunks)
  • request_parser_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • Dockerfile
  • request_parser_test.go
  • .rr.yaml
  • main_test.go
  • plugin.go
🧰 Additional context used
🧬 Code graph analysis (1)
plugin_test.go (2)
plugin.go (2)
  • Logger (42-44)
  • Plugin (31-39)
request_parser.go (1)
  • Uploads (36-39)
🪛 golangci-lint (2.5.0)
plugin_test.go

[warning] 24-24: unused-parameter: parameter 'name' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 50-50: unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 80-80: unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _

(revive)


[warning] 82-82: unused-parameter: parameter 'ctx' seems to be unused, consider removing or renaming it as _

(revive)


[error] 363-363: S1009: should omit nil check; len() for nil maps is defined as zero

(staticcheck)

request_parser.go

[error] 121-121: net/http.NewRequest must not be called. use net/http.NewRequestWithContext

(noctx)


[error] 143-143: net/http.NewRequest must not be called. use net/http.NewRequestWithContext

(noctx)

🪛 LanguageTool
README.md

[grammar] ~8-~8: Ensure spelling is correct
Context: ...Symfony app to validate HTML responses, urlencoded forms, multipart uploads, and assorted ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🔇 Additional comments (9)
.gitignore (2)

18-18: Uncomment vendor/ to ignore Go dependencies directory.

This change aligns with typical Go project patterns where the vendor/ directory is not committed to source control. Projects rely on go.mod and go.sum for reproducibility instead.


23-23: Add .cache to ignore build and test artifacts.

The addition of .cache aligns well with the PR's introduction of Docker containerization and test infrastructure, which commonly generate temporary cache files.

main.go (1)

82-85: LGTM!

The environment variable configuration follows standard AWS Lambda bootstrap patterns. Ignoring os.Setenv errors is acceptable here as these calls rarely fail on Linux.

README.md (1)

1-59: LGTM!

Documentation is clear, well-structured, and provides a good overview of the project with helpful diagrams and build instructions. The term "urlencoded" on line 8 is standard technical terminology for application/x-www-form-urlencoded.

plugin_test.go (2)

102-251: Good test coverage with comprehensive scenarios.

The table-driven test structure covers a wide range of HTTP scenarios including base64 decoding, multipart uploads, URL-encoded forms, and various content types. The test cases properly verify method, URI, query string, headers, body transformation, and upload handling.


371-407: LGTM!

The proto response handling test properly validates status code, headers, and body extraction from the protobuf response structure.

request_parser.go (3)

46-52: LGTM!

The cleanup method properly iterates over uploads and removes temporary files. The exists() check before removal is a minor TOCTOU race but benign since os.Remove on a non-existent file is safe.


73-96: LGTM!

The Open method properly handles the file upload workflow with appropriate error codes, deferred cleanup of file handles, and size tracking from io.Copy.


356-395: LGTM!

The generic prepareTreeNode function properly handles edge cases in tree building with clear error messages. The complexity reflects the PHP form parsing semantics being mirrored.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
plugin_test.go (2)

102-369: Strong request-translation coverage; consider a few extra assertions

The table-driven TestHandlerBuildsLambdaRequests exercises a wide matrix of methods, content-types, base64 handling, and uploads, and validates URI, raw query, content-type, decoded/normalized bodies, uploads metadata, and cookie presence. To tighten coverage further without adding much noise, you might:

  • For some non‑hasExpectedBody cases (e.g., postJSON, graphql, htmlBody, pngBody), also assert on the forwarded body where semantics are simple (e.g., unchanged passthrough), to catch regressions in transformBody.
  • In the graphql case, assert that gotReq.Cookies contains the expected key/value pairs, not just that the slice is non‑empty, to validate cookie parsing more thoroughly.

These are optional, but they’d make the tests even more resilient to subtle regressions in request normalization.


371-407: Also assert propagation of non-Content-Type headers in proto response test

TestHandleProtoResponse validates status, Content-Type, and body mapping from httpV1proto.Response to events.APIGatewayV2HTTPResponse. Since you already include an extra "X-Test" header in rspProto, it would be valuable to assert that it appears in response.Headers to cover generic header propagation, not just content-type. For example:

   if response.Body != `{"ok":true}` {
     t.Fatalf("body mismatch: got %s", response.Body)
   }
+
+  if response.Headers["X-Test"] != "value" {
+    t.Fatalf("X-Test header mismatch: got %s", response.Headers["X-Test"])
+  }

This keeps the test focused while ensuring handlePROTOresponse doesn’t accidentally drop arbitrary headers.

request_parser.go (1)

73-96: Ignoring FileUpload.Open errors may hide upload failures

FileUpload.Open correctly sets Error to uploadErrorNoFile, uploadErrorNoTmpDir, or uploadErrorCantWrite on failure, but parseMultipart currently invokes it as:

for _, f := range uploads.list {
	_ = f.Open()
}

This means any failure to materialize an upload to disk is silently ignored at the parser level; downstream code must remember to consult the Error field on each FileUpload to detect problems.

If the intent is to fail the whole request when any upload cannot be persisted, you might instead aggregate and propagate those errors (or at least log them). If per-file error reporting is desired and the overall parse should still succeed, the current approach is fine but could benefit from a brief comment to document that design choice.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdee797 and f46fd5e.

📒 Files selected for processing (3)
  • main.go (2 hunks)
  • plugin_test.go (1 hunks)
  • request_parser.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • main.go
🧰 Additional context used
🧬 Code graph analysis (1)
plugin_test.go (2)
plugin.go (2)
  • Logger (42-44)
  • Plugin (31-39)
request_parser.go (1)
  • Uploads (36-39)
🪛 golangci-lint (2.5.0)
request_parser.go

[error] 120-120: undefined: context

(typecheck)


[error] 142-142: undefined: context

(typecheck)

plugin_test.go

[error] 120-120: : # github.com/roadrunner-server/aws-lambda [github.com/roadrunner-server/aws-lambda.test]
./request_parser.go:120:26: undefined: context
./request_parser.go:142:25: undefined: context
./plugin.go:412:35: not enough arguments in call to parseURLEncoded
have ([]byte, map[string]string)
want (unknown type, []byte, map[string]string)
./plugin.go:418:43: not enough arguments in call to parseMultipart
have ([]byte, map[string]string)
want (unknown type, []byte, map[string]string)
./request_parser_test.go:35:40: not enough arguments in call to parseURLEncoded
have ([]byte, map[string]string)
want (unknown type, []byte, map[string]string)
./request_parser_test.go:90:55: not enough arguments in call to parseMultipart
have ([]byte, map[string]string)
want (unknown type, []byte, map[string]string)

(typecheck)

🔇 Additional comments (3)
plugin_test.go (1)

22-100: Test helper scaffolding is robust and appropriate for isolation

The namedLoggerStub, fakePool.Exec, and buildPExec/setUnexportedField helpers cleanly decouple tests from real pool behavior while still exercising the protobuf payload and uploads paths. The use of append([]byte(nil), ...) to copy bodies/uploads and the mutex in fakePool avoid accidental aliasing and races. The reliance on unexported PExec fields is brittle but acceptable in tests, since breakage will surface immediately if upstream internals change.

request_parser.go (2)

240-327: Tree-building logic mirrors roadrunner-http and now handles invalid structures more safely

The dataTree.push/mount and fileTree.push/mount functions, together with fetchIndexes and prepareTreeNode, provide a faithful port of the nested form-key handling from roadrunner HTTP. The updated fileTree.mount implementation that uses a safe type assertion:

sub, ok := ft[i[0]].(fileTree)
if !ok {
	return errors.New("invalid tree structure: expected fileTree")
}

avoids the panic risk highlighted in the earlier review while still surfacing structural inconsistencies as errors. The combination of maxLevel, prepareTreeNode invariants, and isDataEmpty covers a good range of edge cases for conflicting or malformed keys. No changes needed here.


3-12: The referenced functions parseURLEncoded and parseMultipart do not exist in the current request_parser.go codebase.

This review comment references functions and modifications that were reverted in commit a0526a1 ("fix: revert to original arguments vs. requestwithcontext"). The file's current state does not contain parseURLEncoded or parseMultipart functions with context.Context parameters, and the context import is correctly absent since it is not used.

If the intent is to re-apply the context.Context parameter changes that were previously reverted, this review comment should be updated to clarify that it represents a reversal of the revert, or the code changes need to be provided for evaluation.

Comment thread request_parser.go Outdated
Comment on lines +142 to +175
func parseMultipart(ctx context.Context, body []byte, headers map[string]string) ([]byte, *Uploads, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/", bytes.NewReader(body))
if err != nil {
return nil, nil, err
}
for k, v := range headers {
req.Header.Add(k, v)
}
req.ContentLength = int64(len(body))

if err := req.ParseMultipartForm(defaultMaxMemory); err != nil {
return nil, nil, err
}

uploads, err := parseUploads(req)
if err != nil {
return nil, nil, err
}
for _, f := range uploads.list {
_ = f.Open()
}

data, err := parseMultipartData(req)
if err != nil {
return nil, nil, err
}

encoded, err := packDataTree(data)
if err != nil {
return nil, nil, err
}

return encoded, uploads, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "request_parser.go"

Repository: roadrunner-server/aws-lambda

Length of output: 89


🏁 Script executed:

cat -n request_parser.go | head -200

Repository: roadrunner-server/aws-lambda

Length of output: 5256


🏁 Script executed:

wc -l request_parser.go

Repository: roadrunner-server/aws-lambda

Length of output: 93


🏁 Script executed:

rg "func parseMultipart" request_parser.go -A 5

Repository: roadrunner-server/aws-lambda

Length of output: 513


🏁 Script executed:

git log --oneline request_parser.go | head -20

Repository: roadrunner-server/aws-lambda

Length of output: 136


🏁 Script executed:

git show a0526a1 --stat

Repository: roadrunner-server/aws-lambda

Length of output: 957


🏁 Script executed:

rg "MultipartForm|RemoveAll|defer|Close" request_parser.go

Repository: roadrunner-server/aws-lambda

Length of output: 320


🌐 Web query:

Go http.Request ParseMultipartForm RemoveAll temp files cleanup

💡 Result:

Yes — ParseMultipartForm may write large file parts to disk, and you (the handler) should remove those temp files when done.

Recommended pattern:

  • After parsing, defer removal (check for nil):
    • if err := r.ParseMultipartForm(maxMemory); err == nil {
      defer func() {
      if r.MultipartForm != nil {
      _ = r.MultipartForm.RemoveAll()
      }
      }()
      }
  • Or call r.MultipartForm.RemoveAll() explicitly once you’ve finished using uploaded data.

Notes:

  • Memory buffers become GC‑eligible; temp files are removed by RemoveAll. The server may attempt cleanup, but it’s the caller’s safe practice to call RemoveAll (defer) to guarantee cleanup. [1][2]

Sources:
[1] net/http ParseMultipartForm docs & tests (shows use of MultipartForm.RemoveAll).
[2] Discussion / Q&A on when temporary files are removed (Stack Overflow / Go issues).


🏁 Script executed:

cat -n request_parser.go | sed -n '142,175p'

Repository: roadrunner-server/aws-lambda

Length of output: 1026


Call MultipartForm.RemoveAll to avoid leaking net/http temp files

parseMultipart calls ParseMultipartForm at line 152 and then builds your own Uploads structure and data tree. However, it never calls req.MultipartForm.RemoveAll(), so the temporary files created by ParseMultipartForm are left on disk for the lifetime of the process. Since FileUpload.Open creates additional temp files that you manage via Uploads.Clear, this results in unnecessary and potentially unbounded temp-file accumulation.

Add a defer statement after the successful ParseMultipartForm call to clean up the net/http-managed files:

 func parseMultipart(body []byte, headers map[string]string) ([]byte, *Uploads, error) {
 	req, err := http.NewRequest(http.MethodPost, "/", bytes.NewReader(body))
 	if err != nil {
 		return nil, nil, err
 	}
 	for k, v := range headers {
 		req.Header.Add(k, v)
 	}
 	req.ContentLength = int64(len(body))
 
 	if err := req.ParseMultipartForm(defaultMaxMemory); err != nil {
 		return nil, nil, err
 	}
+	defer req.MultipartForm.RemoveAll()

This keeps only your own temp copies (referenced by TempFilename) around, which Uploads.Clear can later delete, and prevents /tmp from filling up under sustained traffic.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 golangci-lint (2.5.0)

[error] 142-142: undefined: context

(typecheck)

🤖 Prompt for AI Agents
In request_parser.go around lines 142 to 175, after the successful call to
req.ParseMultipartForm(defaultMaxMemory) add a deferred cleanup of the
net/http-managed temp files so they don’t leak; specifically, immediately after
ParseMultipartForm returns nil, defer a safe call that checks req.MultipartForm
for nil and calls req.MultipartForm.RemoveAll() (e.g. defer func(){ if
req.MultipartForm != nil { _ = req.MultipartForm.RemoveAll() } }()), leaving
your existing Uploads.Clear logic to manage your own temp copies.

@rustatian
rustatian marked this pull request as draft December 31, 2025 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant