Skip to content

Conversation

@ryan-gang
Copy link
Contributor

@ryan-gang ryan-gang commented Apr 8, 2025

Summary by CodeRabbit

  • New Features

    • Upgraded the HTTP server with additional endpoints for echo responses, user-agent handling, and file operations.
    • Introduced containerization with enhanced deployment configurations for simplified setup.
  • Documentation

    • Added comprehensive guides and logs to support persistent connection usage and challenge instructions.
  • Chores

    • Updated release pipelines and dependency versions to improve overall performance and stability.
  • Tests

    • Refined testing scripts and scenarios to ensure robust connection persistence and reliable server behavior.

@ryan-gang ryan-gang self-assigned this Apr 8, 2025
@coderabbitai
Copy link

coderabbitai bot commented Apr 8, 2025

Walkthrough

A comprehensive set of changes has been applied across multiple parts of the project. The updates include upgrading CI/CD workflows (GitHub Actions and Go versions), introducing new Docker and Caddy configurations, and adding several new test targets and utilities. Significant refactoring has been performed in the internal packages to streamline logger usage, improve error handling via idiomatic patterns, and add helper functions for HTTP request/response generation and persistent connection testing. New documentation and test scenarios for HTTP persistence have also been added alongside enhanced Python server scripts.

Changes

File(s) Change Summary
.github/workflows/publish.yml
.github/workflows/test.yml
Upgraded GitHub Actions (checkout, setup-go, setup-python, goreleaser), updated Go version from 1.21.x to 1.22.x, and modified release command arguments.
Dockerfile
docker-compose.yml
Makefile
curl-logs.md
flakiness.sh
go.mod
Introduced new Dockerfile and docker-compose configuration for the Caddy server; updated Makefile test targets; added detailed curl log documentation and a persistence flakiness script; updated the module’s Go version.
Caddyfile
file_writer.py
Added a new Caddy server configuration with multiple endpoint handlers and a Python file writer to manage POST requests.
internal/(anti_cheat_basic.go, files.go, http/connection/*, parser/errors.go, test_cases/send_request_test_case.go, http_server_binary_helper.go, log_friendly_error.go, new_http_client.go) Refactored logger imports (removed aliasing) and variable names; enhanced error handling using errors.As; added the new IsOpen method for HTTP connections; and updated connection and HTTP request logic.
internal/http_messages.go Introduced utilities for generating HTTP request–response pairs for various endpoints.
internal/stage_*
internal/stages_test.go
internal/test_helpers/**/*
internal/tester_definition.go
Added multiple persistence test cases (pe1, pe2, pe3) and refactored test cases to use helper functions for HTTP messages; updated scenario scripts/configurations for both Go and Python server challenges.
internal/utils.go
persistence.md
Added utility functions to set up data directories and manage persistent connections; introduced documentation detailing persistent connection and pipelining behaviors.

Sequence Diagram(s)

sequenceDiagram
    participant TH as Test Harness
    participant PC as Persistent Connection
    participant Srv as HTTP Server
    TH->>PC: assertConnectionIsOpen()
    alt Connection Open
      TH->>Srv: Send HTTP Request
      Srv-->>PC: Process Request
      PC-->>TH: Return Response
    else Connection Closed
      TH->>TH: Log error and abort
    end
    TH->>PC: Close Connection
Loading

Poem

I’m a little rabbit with code to cheer,
Hopping through updates, my ears full of gear.
Workflows upgraded and servers so bright,
Persistence tests bloom in the soft moonlight.
Every new function is a crunchy delight,
Hoppy changes abound—what a wonderful night!

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@ryan-gang ryan-gang changed the base branch from main to persistence April 8, 2025 07:12
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 19

🧹 Nitpick comments (27)
flakiness.sh (1)

3-11: Improve Error Handling and Readability in Loop

Consider refactoring the error checking to simplify the logic. For example, combining the command execution and its error check improves readability. Additionally, adding a newline at the end of the file is a best practice.
Suggested diff:

- for i in $(seq 1 100)
- do
-     echo "Running iteration $i"
-     make test_persistence_pass > /tmp/test
-     if [ $? -ne 0 ]; then
-         echo "make test_persistence_pass failed on iteration $i"
-         exit 1
-     fi
- done    
+ for i in $(seq 1 100); do
+     echo "Running iteration $i"
+     if ! make test_persistence_pass > /tmp/test; then
+         echo "make test_persistence_pass failed on iteration $i"
+         exit 1
+     fi
+ done
internal/test_helpers/course_definition.yml (1)

752-754: New Stage Entries and Newline at EOF

The new stage entries (pe1, pe2, pe3) are added as minimal identifiers, which appears intentional for future expansion and testing integration. Please ensure these entries align with the expected schema in the overall course configuration.
Additionally, YAMLlint reports a missing newline at the end of file. Adding a newline will help avoid trivial parsing issues in some environments.
Suggested fix for the newline issue:

-  - slug: "pe3"
\ No newline at end of file
+  - slug: "pe3"
+
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 754-754: no new line character at the end of file

(new-line-at-end-of-file)

internal/http/connection/curl_string.go (1)

27-36: Function naming convention could be improved

According to Go's naming conventions, acronyms in function names should be capitalized. Consider renaming HttpKeepAliveRequestToCurlString to HTTPKeepAliveRequestToCurlString.

-func HttpKeepAliveRequestToCurlString(requests []*http.Request) string {
+func HTTPKeepAliveRequestToCurlString(requests []*http.Request) string {
internal/test_helpers/fixtures/persistence/pass_all (1)

1-1: Consider making debug logging configurable.
Currently, "Debug = true" is hard-coded at line 1, which might lead to excessive logging in production. Use an environment variable or a configuration setting to control debug logs.

-Debug = true
+# Debug logging can be toggled via an environment variable or configuration
+Debug = os.getenv("DEBUG_MODE", "false").lower() == "true"
persistence.md (1)

83-83: Use more precise language instead of "very."
Remove the intensifier "very" and opt for clearer phrasing to maintain concise technical documentation.

-... RFCs disagree and make it very clear ...
+... RFCs disagree and clarify it ...
🧰 Tools
🪛 LanguageTool

[style] ~83-~83: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...in pipelines, RFCs disagree and make it very clear) [3](https://www.rfc-editor.org/rfc/rfc...

(EN_WEAK_ADJECTIVE)

internal/test_helpers/scenarios/pass_all/app/main.py (2)

135-135: Allow a configurable timeout.
Hardcoding the timeout to 5 seconds can cause issues for long-running requests or large file uploads. Consider making this timeout configurable via an environment variable or command-line argument to accommodate different usage scenarios.

-connection.settimeout(5)
+import os

+timeout = int(os.getenv("SERVER_TIMEOUT", "5"))
+connection.settimeout(timeout)

197-200: Handle unknown errors by returning an HTTP 500 response.
Instead of just printing the error and closing the connection, consider sending a 500 Internal Server Error response to inform the client that the server encountered an unexpected exception.

-except Exception as e:
-    print(f"Error handling request: {e}")
-    break
+except Exception as e:
+    print(f"Error handling request: {e}")
+    error_response = Response(
+        status=HTTPStatus.INTERNAL_SERVER_ERROR,
+        data="Server encountered an internal error",
+        headers={"Connection": "close"}
+    )
+    connection.send(bytes(error_response))
+    break
internal/utils.go (1)

15-20: Consider returning an error instead of panicking

The setupDataDirectory function currently panics if it encounters an error when creating the directory. This approach might interrupt the normal flow of the program and make error recovery difficult. Consider returning an error instead, allowing the caller to decide how to handle the failure.

-func setupDataDirectory() {
+func setupDataDirectory() error {
	err := os.MkdirAll(DATA_DIR, 0755)
	if err != nil {
-		panic(err)
+		return fmt.Errorf("failed to create data directory: %w", err)
	}
+	return nil
}
internal/test_helpers/scenarios/python_builtin/codecrafters.yml (1)

1-11: Consider setting debug to false by default

While having debug enabled is helpful during development, it might be better to set it to false by default to avoid verbose logs in normal operation, as mentioned in your own comments. Users can still enable it when needed.

# Set this to true if you want debug logs.
#
# These can be VERY verbose, so we suggest turning them off
# unless you really need them.
-debug: true
+debug: false

# Use this to change the Python version used to run your code
# on Codecrafters.
#
# Available versions: python-3.12
language_pack: python-3.12
docker-compose.yml (2)

1-12: Compose file looks good, with minor formatting issues

The Docker Compose file is well-structured, defining a Caddy service with appropriate port mappings and volume mounts. However, there are two minor issues to fix:

  1. There's a trailing space at the end of line 12
  2. The file is missing a newline character at the end
version: '3.8'

services:
  caddy:
    build: .
    ports:
      - "4221:4221"
      - "4222:4222"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - /tmp/data/codecrafters.io/http-server-tester/:/tmp/data/codecrafters.io/http-server-tester
-    restart: unless-stopped 
+    restart: unless-stopped
+
🧰 Tools
🪛 YAMLlint (1.35.1)

[error] 12-12: no new line character at the end of file

(new-line-at-end-of-file)


[error] 12-12: trailing spaces

(trailing-spaces)


11-11: Consider using named volumes for persistence

The current setup uses a host path volume for data. For better portability and to follow Docker best practices, consider using named volumes instead.

    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
-      - /tmp/data/codecrafters.io/http-server-tester/:/tmp/data/codecrafters.io/http-server-tester
+      - http_server_data:/tmp/data/codecrafters.io/http-server-tester

+volumes:
+  http_server_data:
Dockerfile (1)

17-17: Consider Robust Process Management for Concurrent Services

Currently, the Dockerfile’s CMD runs the Python file_writer and the Caddy server concurrently using a background process. This approach may hide failures if the Python process crashes. It is recommended to use a dedicated startup script (or process manager) that properly manages both processes (e.g., capturing exit codes, logging errors) and ensures reliable service startup.

For example, you might create a start.sh script:

- CMD sh -c "python3 /file_writer.py & caddy run --config /etc/caddy/Caddyfile" 
+ COPY start.sh /start.sh
+ RUN chmod +x /start.sh
+ CMD ["/start.sh"]

And a sample start.sh:

#!/bin/sh
python3 /file_writer.py &
caddy run --config /etc/caddy/Caddyfile
wait
curl-logs.md (2)

1-1: Verify Duplicate URL in Curl Command

The curl invocation on line 1 shows the URL repeated:

curl --http1.1 -v http://localhost:4221/ http://localhost:4221/

If the duplicate URL is not intentional, consider removing the repetition. Please confirm if this is intended for testing or if it should be corrected.

- curl --http1.1 -v http://localhost:4221/ http://localhost:4221/
+ curl --http1.1 -v http://localhost:4221/
🧰 Tools
🪛 LanguageTool

[duplication] ~1-~1: Possible typo: you repeated a word.
Context: curl --http1.1 -v http://localhost:4221/ http://localhost:4221/ * Host localhost:4221 was resolved. * I...

(ENGLISH_WORD_REPEAT_RULE)

🪛 markdownlint-cli2 (0.17.2)

1-1: Bare URL used
null

(MD034, no-bare-urls)


1-1: Bare URL used
null

(MD034, no-bare-urls)


41-44: Consider Using Markdown Link Syntax for URLs

The endpoints listed (lines 41–44) are bare URLs, which trigger markdownlint MD034 warnings. For clarity and to conform with best markdown practices, consider wrapping these URLs in angle brackets or using markdown link syntax.

For example:

-$ curl -v http://localhost:4221/echo/orange
+$ curl -v <http://localhost:4221/echo/orange>
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

41-41: Bare URL used
null

(MD034, no-bare-urls)


43-43: Bare URL used
null

(MD034, no-bare-urls)


44-44: Bare URL used
null

(MD034, no-bare-urls)

internal/test_helpers/scenarios/pass_base/README.md (1)

7-7: Grammar correction needed: "an HTTP/1.1" instead of "a HTTP/1.1"

Since "HTTP" is pronounced with a vowel sound at the beginning (aitch-tee-tee-pee), it should be preceded by "an" rather than "a".

- protocol that powers the web. In this challenge, you'll build a HTTP/1.1 server
+ protocol that powers the web. In this challenge, you'll build an HTTP/1.1 server
🧰 Tools
🪛 LanguageTool

[misspelling] ~7-~7: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...he web. In this challenge, you'll build a HTTP/1.1 server that is capable of serv...

(EN_A_VS_AN)

internal/test_helpers/scenarios/python_builtin/README.md (1)

7-7: Grammar correction needed: "an HTTP/1.1" instead of "a HTTP/1.1"

Since "HTTP" is pronounced with a vowel sound at the beginning (aitch-tee-tee-pee), it should be preceded by "an" rather than "a".

- protocol that powers the web. In this challenge, you'll build a HTTP/1.1 server
+ protocol that powers the web. In this challenge, you'll build an HTTP/1.1 server
🧰 Tools
🪛 LanguageTool

[misspelling] ~7-~7: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...he web. In this challenge, you'll build a HTTP/1.1 server that is capable of serv...

(EN_A_VS_AN)

internal/stages_test.go (1)

54-56: Consider implementing the TODO soon

The TODO comment suggests a valuable refactoring to organize tests into categories (base, compression, persistence). This would improve test organization and maintainability.

Consider implementing this refactoring in a follow-up PR to keep the test structure consistent with the new categorization you're introducing.

file_writer.py (1)

36-39: Consider adding signal handling for graceful shutdown

For a server process that might run for a long time, it's good practice to add signal handling for graceful shutdown.

+ import signal
+
 if __name__ == '__main__':
     server = HTTPServer(('localhost', 4222), FileWriterHandler)
     print("Starting file writer server on port 4222...")
-    server.serve_forever() 
+    try:
+        server.serve_forever()
+    except KeyboardInterrupt:
+        print("Shutting down file writer server...")
+        server.server_close()
internal/stage_14.go (1)

69-77: Immediate closure checks may cause race conditions.
You’re checking connections[i].IsOpen() immediately after sending requests. If the server closes the connection slightly later (e.g., asynchronously), this check might fail intermittently. Consider introducing a brief wait or a retry to handle delayed closure.

internal/stage_12.go (5)

13-13: Make sure this TODO is addressed before finalizing.

The comment indicates a need for better emulated curl logs. Consider adding this to your planned follow-up tasks.


47-49: Use more descriptive logging for request sets.

The current logging only mentions "first set of requests" without additional context. This could make debugging difficult.

-logger.Debugf("Sending first set of requests")
+logger.Debugf("Sending first set of requests (total: %d) to test HTTP persistence", uniqueRequestCount)

55-57: Use more descriptive logging for request sets.

Similar to the first request set, add more context to the logging.

-logger.Debugf("Sending second set of requests")
+logger.Debugf("Sending second set of requests on the same connection to verify persistence")

58-58: Move the connection reuse message outside the loop.

This debug message is printed for every request in the loop, but it would be more appropriate as a one-time message before the loop starts.

-	for _, testCase := range testCases {
-		logger.Debugf("* Re-using existing connection with host localhost")
+	logger.Debugf("* Re-using existing connection with host localhost")
+	for _, testCase := range testCases {

64-68: Use more idiomatic Go error handling.

The current implementation checks for an error, logs it, and then returns it. This can be simplified.

-	err = connection.Close()
-	if err != nil {
-		logFriendlyError(logger, err)
-		return err
-	}
+	if err = connection.Close(); err != nil {
+		logFriendlyError(logger, err)
+		return err
+	}
internal/http/connection/connection.go (1)

201-208: Consider returning the error for diagnostic purposes.

The current implementation discards the error information when a connection is closed. It might be helpful to return both the boolean status and the error for better diagnostics.

You could modify the method signature to func (c *HttpConnection) IsOpen() (bool, error) and return the error along with the boolean status. This would provide more context when diagnosing connection issues.

internal/test_helpers/scenarios/pass_base/app/main.py (1)

175-184: Verify thread-based concurrency approach.

The server spawns a new thread for each client connection without restricting or managing the thread count. For a large number of incoming connections, this can lead to resource exhaustion. Consider using a thread pool or an asynchronous I/O approach to handle concurrent requests more efficiently.

internal/http_messages.go (1)

181-181: Re-evaluate commented-out code for files endpoint testing.

The function call to GetFilesRequestResponsePair is commented out but might be useful to expand coverage tests. If this endpoint should be tested randomly, consider uncommenting and verifying interface consistency.

🛑 Comments failed to post (19)
internal/test_helpers/scenarios/pass_base/your_server.sh (1)

8-9: 💡 Verification agent

❓ Verification inconclusive

Enhance CD Command Robustness

Shellcheck has flagged the cd command on line 8. To prevent potential issues (such as a failing cd or word splitting), please update it to handle errors and quote the directory path.
Suggested change:

- cd $(dirname $0)
+ cd "$(dirname "$0")" || exit

Enhance CD Command Robustness

The current use of:

cd $(dirname $0)

is prone to word splitting and does not handle failure if the directory change fails. Please update it to:

- cd $(dirname $0)
+ cd "$(dirname "$0")" || exit

This ensures that the directory path is properly quoted, and the script exits if the cd command fails.

🧰 Tools
🪛 Shellcheck (0.10.0)

[warning] 8-8: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

(SC2164)


[warning] 8-8: Quote this to prevent word splitting.

(SC2046)

internal/test_helpers/scenarios/pass_all/your_server.sh (1)

8-13: ⚠️ Potential issue

Fix the Docker Compose pipeline failure

The Docker Compose approach is causing a pipeline failure because the command is not available in the CI environment. According to the error logs: "docker-compose: not found. Failed to create connection: dial tcp [::1]:4221: connect: connection refused."

You have a few options to fix this:

  1. Ensure Docker Compose is installed in your CI environment
  2. Add error handling to handle the case when Docker Compose isn't available
  3. Provide an alternative fallback mechanism to start the server
-docker-compose up --build
+if command -v docker-compose &> /dev/null; then
+  docker-compose up --build
+else
+  echo "docker-compose not found, falling back to Python server"
+  cd $(dirname $0)
+  exec python3 -m app.main "$@"
+fi
📝 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.

# cd $(dirname $0)
# exec python3 -m app.main "$@"

if command -v docker-compose &> /dev/null; then
  docker-compose up --build
else
  echo "docker-compose not found, falling back to Python server"
  cd $(dirname $0)
  exec python3 -m app.main "$@"
fi
# And then run make test.
# This script would do nothing, just start and end tests.
🧰 Tools
🪛 GitHub Actions: Test

[error] 11-11: docker-compose: not found. Failed to create connection: dial tcp [::1]:4221: connect: connection refused.

Caddyfile (1)

32-39: 💡 Verification agent

❓ Verification inconclusive

Consider documenting the service on port 4222

The configuration forwards POST requests to port 4222, but it's not clear what service is expected to run on this port. Consider adding documentation about this service requirement, either in the Caddyfile comments or in a README.


🏁 Script executed:

#!/bin/bash
# Check if there's any mention of the port 4222 service in the repository
grep -r "4222" --include="*.{md,py,go,yml,yaml}" .

Length of output: 50


Documentation Required for the Service on Port 4222
Our search for “4222” across documentation files (e.g. Markdown, YAML) returned no results. This indicates that the expected service (the local file-writing server forwarded to on port 4222) isn’t documented anywhere in the repository. Please add a clear note—either as an inline comment in the Caddyfile or in the project’s README—explaining what service is expected on port 4222, its responsibilities, and how it integrates with the rest of the system.

  • Caddyfile (lines 32–39):
    • The config forwards POST requests to localhost:4222.
  • Action: Document the service requirements and its purpose to avoid future confusion.
internal/test_helpers/scenarios/pass_all/app/main.py (1)

195-196: 🛠️ Refactor suggestion

Return an HTTP 408 response on socket timeout.
Currently, the code silently closes the connection on timeout. Returning a 408 (Request Timeout) provides more informative feedback to the client.

-except socket.timeout:
-    break
+except socket.timeout:
+    timeout_response = Response(
+        status=HTTPStatus.REQUEST_TIMEOUT,
+        data="Request timed out",
+        headers={"Connection": "close"},
+    )
+    connection.send(bytes(timeout_response))
+    break
📝 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.

            except socket.timeout:
-                # Connection timed out, break the loop
-                break
+                timeout_response = Response(
+                    status=HTTPStatus.REQUEST_TIMEOUT,
+                    data="Request timed out",
+                    headers={"Connection": "close"},
+                )
+                connection.send(bytes(timeout_response))
+                break
internal/utils.go (7)

60-66: 🛠️ Refactor suggestion

Avoid capturing loop variables in closures

The callback function captures the loop variable i in a closure. This can lead to unexpected behavior in Go as all closures will see the same final value of i. Use a local variable inside the loop to capture the correct value.

-		conn.Callbacks.AfterReadResponse = func(response http_parser.HTTPResponse) {
+		connIndex := i  // Capture the current value of i
+		conn.Callbacks.AfterReadResponse = func(response http_parser.HTTPResponse) {
			for _, line := range strings.Split(strings.TrimSpace(response.FormattedString()), "\r\n") {
				stageHarness.Logger.Debugf("%s%s", "< ", line)
			}
			stageHarness.Logger.Debugf("%s%s", "< ", "")
-			stageHarness.Logger.Debugf("* Connection #%d to host localhost left intact", i)
+			stageHarness.Logger.Debugf("* Connection #%d to host localhost left intact", connIndex)
		}
📝 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.

		connIndex := i  // Capture the current value of i
		conn.Callbacks.AfterReadResponse = func(response http_parser.HTTPResponse) {
			for _, line := range strings.Split(strings.TrimSpace(response.FormattedString()), "\r\n") {
				stageHarness.Logger.Debugf("%s%s", "< ", line)
			}
			stageHarness.Logger.Debugf("%s%s", "< ", "")
			stageHarness.Logger.Debugf("* Connection #%d to host localhost left intact", connIndex)
		}

22-44: ⚠️ Potential issue

Fix nil pointer dereference by checking error immediately after connection creation

There's a critical issue in your function logic. You're modifying conn.Callbacks before checking if conn is nil. This is causing the nil pointer dereference observed in the pipeline failure. Move the error checking right after creating the connection.

func spawnPersistentConnection(stageHarness *test_case_harness.TestCaseHarness, logger *logger.Logger) (*http_connection.HttpConnection, error) {
	logger.Debugf("Creating connection")

	conn, err := http_connection.NewInstrumentedHttpConnection(stageHarness, TCP_DEST, "")
+	if err != nil {
+		logFriendlyError(logger, err)
+		return nil, err
+	}

	// We want to log all the requests at once, not one by one
	// Note: No support for logPrefix here
	conn.Callbacks.BeforeSendRequest = nil
	conn.Callbacks.AfterReadResponse = func(response http_parser.HTTPResponse) {
		for _, line := range strings.Split(strings.TrimSpace(response.FormattedString()), "\r\n") {
			stageHarness.Logger.Debugf("%s%s", "< ", line)
		}
		stageHarness.Logger.Debugf("%s%s", "< ", "")
		stageHarness.Logger.Debugf("* Connection #0 to host localhost left intact")
	}

-	if err != nil {
-		logFriendlyError(logger, err)
-		return nil, err
-	}

	return conn, nil
}
📝 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.

func spawnPersistentConnection(stageHarness *test_case_harness.TestCaseHarness, logger *logger.Logger) (*http_connection.HttpConnection, error) {
	logger.Debugf("Creating connection")

	conn, err := http_connection.NewInstrumentedHttpConnection(stageHarness, TCP_DEST, "")
	if err != nil {
		logFriendlyError(logger, err)
		return nil, err
	}

	// We want to log all the requests at once, not one by one
	// Note: No support for logPrefix here
	conn.Callbacks.BeforeSendRequest = nil
	conn.Callbacks.AfterReadResponse = func(response http_parser.HTTPResponse) {
		for _, line := range strings.Split(strings.TrimSpace(response.FormattedString()), "\r\n") {
			stageHarness.Logger.Debugf("%s%s", "< ", line)
		}
		stageHarness.Logger.Debugf("%s%s", "< ", "")
		stageHarness.Logger.Debugf("* Connection #0 to host localhost left intact")
	}

	return conn, nil
}
🧰 Tools
🪛 GitHub Actions: Test

[error] 29-29: panic: runtime error: invalid memory address or nil pointer dereference.


78-78: ⚠️ Potential issue

Fix incorrect range loop syntax

Similar to the issue in spawnPersistentConnections, using range with an integer is incorrect. Change to a traditional for loop.

-	for i := range connectionCount {
+	for i := 0; i < connectionCount; i++ {
📝 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.

	for i := 0; i < connectionCount; i++ {

46-72: 🛠️ Refactor suggestion

Implement proper resource cleanup on error

If an error occurs while creating connections, the function should close all previously created connections before returning. Otherwise, this could lead to resource leaks.

func spawnPersistentConnections(stageHarness *test_case_harness.TestCaseHarness, connectionCount int, logger *logger.Logger) ([]*http_connection.HttpConnection, error) {
	logger.Debugf("Creating %d persistent connections", connectionCount)
	connections := make([]*http_connection.HttpConnection, connectionCount)

	for i := 0; i < connectionCount; i++ {
		conn, err := http_connection.NewInstrumentedHttpConnection(stageHarness, TCP_DEST, fmt.Sprintf("client-%d", i+1))
		if err != nil {
			logFriendlyError(logger, err)
+			// Close all previously created connections
+			for j := 0; j < i; j++ {
+				if connections[j] != nil {
+					connections[j].Conn.Close()
+				}
+			}
			return nil, err
		}

		// We want to log all the requests at once, not one by one
		// Note: No support for logPrefix here
		conn.Callbacks.BeforeSendRequest = nil
		conn.Callbacks.AfterReadResponse = func(response http_parser.HTTPResponse) {
			for _, line := range strings.Split(strings.TrimSpace(response.FormattedString()), "\r\n") {
				stageHarness.Logger.Debugf("%s%s", "< ", line)
			}
			stageHarness.Logger.Debugf("%s%s", "< ", "")
			stageHarness.Logger.Debugf("* Connection #%d to host localhost left intact", i)
		}

		connections[i] = conn
	}

	return connections, nil
}
📝 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.

func spawnPersistentConnections(stageHarness *test_case_harness.TestCaseHarness, connectionCount int, logger *logger.Logger) ([]*http_connection.HttpConnection, error) {
	logger.Debugf("Creating %d persistent connections", connectionCount)
	connections := make([]*http_connection.HttpConnection, connectionCount)

	for i := 0; i < connectionCount; i++ {
		conn, err := http_connection.NewInstrumentedHttpConnection(stageHarness, TCP_DEST, fmt.Sprintf("client-%d", i+1))
		if err != nil {
			logFriendlyError(logger, err)
			// Close all previously created connections
			for j := 0; j < i; j++ {
				if connections[j] != nil {
					connections[j].Conn.Close()
				}
			}
			return nil, err
		}

		// We want to log all the requests at once, not one by one
		// Note: No support for logPrefix here
		conn.Callbacks.BeforeSendRequest = nil
		conn.Callbacks.AfterReadResponse = func(response http_parser.HTTPResponse) {
			for _, line := range strings.Split(strings.TrimSpace(response.FormattedString()), "\r\n") {
				stageHarness.Logger.Debugf("%s%s", "< ", line)
			}
			stageHarness.Logger.Debugf("%s%s", "< ", "")
			stageHarness.Logger.Debugf("* Connection #%d to host localhost left intact", i)
		}

		connections[i] = conn
	}

	return connections, nil
}

50-50: ⚠️ Potential issue

Fix incorrect range loop syntax

The current loop syntax is incorrect. When using range with an integer, Go expects a slice, array, map, string, or channel to iterate over, not a single integer value. You should use a traditional for loop here.

-	for i := range connectionCount {
+	for i := 0; i < connectionCount; i++ {
📝 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.

	for i := 0; i < connectionCount; i++ {

38-41: 💡 Verification agent

🧩 Analysis chain

Consider adding a reference to logFriendlyError

The function logFriendlyError is called but not defined in this file. Make sure this function is properly defined elsewhere or add a comment to indicate where it's defined to improve code readability.


🏁 Script executed:

#!/bin/bash
# Check if logFriendlyError function is defined somewhere in the codebase
rg -A 2 -B 2 "func logFriendlyError" --type go

Length of output: 408


Action Required: Add a Clarifying Comment for logFriendlyError Reference

The function logFriendlyError is indeed defined in internal/log_friendly_error.go. To improve code readability, please add an inline comment at its call site in internal/utils.go (around lines 38–41) indicating that its definition can be found in internal/log_friendly_error.go.

  • Location: internal/utils.go, lines 38–41
  • Suggestion: Add a comment such as:
    // logFriendlyError is defined in internal/log_friendly_error.go
    if err != nil {
        logFriendlyError(logger, err)
        return nil, err
    }

74-88: 🛠️ Refactor suggestion

Implement proper resource cleanup on error

Similar to the issue in spawnPersistentConnections, you should close any previously created connections when an error occurs.

func spawnConnections(stageHarness *test_case_harness.TestCaseHarness, connectionCount int, logger *logger.Logger) ([]*http_connection.HttpConnection, error) {
	logger.Infof("Creating %d parallel connections", connectionCount)
	connections := make([]*http_connection.HttpConnection, connectionCount)

	for i := 0; i < connectionCount; i++ {
		logger.Debugf("Creating connection %d", i+1)
		conn, err := http_connection.NewInstrumentedHttpConnection(stageHarness, TCP_DEST, fmt.Sprintf("client-%d", i+1))
		if err != nil {
			logFriendlyError(logger, err)
+			// Close all previously created connections
+			for j := 0; j < i; j++ {
+				if connections[j] != nil {
+					connections[j].Conn.Close()
+				}
+			}
			return nil, err
		}
		connections[i] = conn
	}
	return connections, nil
}
📝 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.

func spawnConnections(stageHarness *test_case_harness.TestCaseHarness, connectionCount int, logger *logger.Logger) ([]*http_connection.HttpConnection, error) {
	logger.Infof("Creating %d parallel connections", connectionCount)
	connections := make([]*http_connection.HttpConnection, connectionCount)

	for i := 0; i < connectionCount; i++ {
		logger.Debugf("Creating connection %d", i+1)
		conn, err := http_connection.NewInstrumentedHttpConnection(stageHarness, TCP_DEST, fmt.Sprintf("client-%d", i+1))
		if err != nil {
			logFriendlyError(logger, err)
			// Close all previously created connections
			for j := 0; j < i; j++ {
				if connections[j] != nil {
					connections[j].Conn.Close()
				}
			}
			return nil, err
		}
		connections[i] = conn
	}
	return connections, nil
}
internal/test_helpers/scenarios/python_builtin/your_server.sh (1)

8-8: 🛠️ Refactor suggestion

Enhance Directory Change Robustness

The current cd $(dirname $0) may fail silently if the directory change is unsuccessful. It is recommended to quote the command and add error handling to prevent unexpected behavior.

-cd $(dirname $0)
+cd "$(dirname "$0")" || { echo "Failed to change directory"; exit 1; }
📝 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.

cd "$(dirname "$0")" || { echo "Failed to change directory"; exit 1; }
🧰 Tools
🪛 Shellcheck (0.10.0)

[warning] 8-8: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

(SC2164)


[warning] 8-8: Quote this to prevent word splitting.

(SC2046)

file_writer.py (1)

5-35: ⚠️ Potential issue

Add path traversal protection for the file handler

The current implementation accepts any filename from the path without validation, which could lead to path traversal vulnerabilities if a request contains paths like ../../../etc/passwd.

Add validation to ensure the filename doesn't contain path traversal sequences:

- filename = self.path.lstrip('/')
+ filename = self.path.lstrip('/')
+ # Prevent path traversal attacks
+ if '..' in filename or filename.startswith('/'):
+     self.send_response(400)
+     self.end_headers()
+     self.wfile.write(b"Invalid filename")
+     return
📝 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.

class FileWriterHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        try:
            # Get the filename from the path
            filename = self.path.lstrip('/')
            # Prevent path traversal attacks
            if '..' in filename or filename.startswith('/'):
                self.send_response(400)
                self.end_headers()
                self.wfile.write(b"Invalid filename")
                return
            if not filename:
                self.send_response(400)
                self.end_headers()
                return

            # Get the content length
            content_length = int(self.headers['Content-Length'])
            
            # Read the request body
            data = self.rfile.read(content_length)
            
            # Write to file
            filepath = os.path.join('/tmp/data/codecrafters.io/http-server-tester', filename)
            os.makedirs(os.path.dirname(filepath), exist_ok=True)
            
            with open(filepath, 'wb') as f:
                f.write(data)
            
            self.send_response(201)
            self.end_headers()
            
        except Exception as e:
            print(f"Error: {e}", file=sys.stderr)
            self.send_response(500)
            self.end_headers()
internal/stage_13.go (1)

62-67: ⚠️ Potential issue

Fix the loop logic for sending the second set of requests.
The loop for i := range connectionCount { ... } will not compile because range cannot iterate over an integer in Go. Replace it with a standard for loop.

Apply the following diff to fix this bug:

-for i := range connectionCount {
+for i := 0; i < connectionCount; i++ {
📝 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.

	for i := 0; i < connectionCount; i++ {
		logger.Debugf("* Re-using existing connection with host localhost")
		if err := testCases[i].RunWithConn(connections[i], logger); err != nil {
			return err
		}
	}
internal/stage_14.go (1)

58-67: ⚠️ Potential issue

Fix the integer iteration in the range loop.
Similar to testPersistence2, the loop for i := range uniqueRequestCount { ... } will not compile. Replace with a standard for loop.

-for i := range uniqueRequestCount {
+for i := 0; i < uniqueRequestCount; i++ {
📝 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.

	logger.Debugf("Sending first set of requests to connection #0")
	for i := 0; i < uniqueRequestCount; i++ {
		if err := testCases[i].RunWithConn(connections[0], logger); err != nil {
			return err
		}
	}

	if connections[0].IsOpen() {
		return fmt.Errorf("connection is still open")
	}
internal/stage_12.go (1)

42-42: ⚠️ Potential issue

Fix the panic in spawnPersistentConnection call.

The pipeline failure indicates a panic at this line. The spawnPersistentConnection function appears to be missing or improperly implemented, which is causing the test to fail.

Ensure the spawnPersistentConnection function is properly defined in this package or imported from another package. The function should establish and return a persistent HTTP connection for testing purposes.

🧰 Tools
🪛 GitHub Actions: Test

[error] 42-42: Test failed due to panic in testPersistence1 function.

internal/stage_6.go (1)

59-59: ⚠️ Potential issue

Invalid range loop syntax

The loop syntax is incorrect. In Go, you can only use range with slices, arrays, strings, maps, or channels, but connectionCount is an integer (defined on line 18).

-for i := range connectionCount {
+for i := connectionCount - 1; i >= 0; i-- {

Note: This should maintain the original reverse order as noted in the comments on lines 37-38.

📝 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.

	for i := connectionCount - 1; i >= 0; i-- {
internal/test_helpers/scenarios/pass_base/app/main.py (1)

57-63: 🛠️ Refactor suggestion

Avoid mutable default argument for headers.

Using a mutable default ({}) can lead to subtle bugs if the argument is modified and reused. It's better to default to None and initialize a new dictionary inside the function body.

Here's a recommended fix:

 def __init__(
     self,
     status: HTTPStatus = HTTPStatus.OK,
-    headers: dict[str, str] = {},
+    headers: dict[str, str] | None = None,
     data: str = "",
     bytes_data: bytes = b"",
 ) -> None:
     if headers is None:
         headers = {}
     self.status = f"{status} {status.phrase}"
     ...
📝 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.

    def __init__(
        self,
        status: HTTPStatus = HTTPStatus.OK,
        headers: dict[str, str] | None = None,
        data: str = "",
        bytes_data: bytes = b"",
    ) -> None:
        if headers is None:
            headers = {}
        self.status = f"{status} {status.phrase}"
        ...
🧰 Tools
🪛 Ruff (0.8.2)

60-60: Do not use mutable data structures for argument defaults

Replace with None; initialize within function

(B006)

internal/http_messages.go (1)

210-210: ⚠️ Potential issue

Fix invalid loop range usage.

In Go, for i := range count is invalid when count is an integer. Use a classic for loop to iterate from 0 to count.

-func GetRandomRequestResponsePairs(count int, logger *logger.Logger) ([]*RequestResponsePair, error) {
-    requestResponsePairs := make([]*RequestResponsePair, count)
-    for i := range count {
-        requestResponsePair, err := getRandomRequestResponsePair(logger)
-        ...
-    }
-    return requestResponsePairs, nil
-}

+func GetRandomRequestResponsePairs(count int, logger *logger.Logger) ([]*RequestResponsePair, error) {
+    requestResponsePairs := make([]*RequestResponsePair, count)
+    for i := 0; i < count; i++ {
+        requestResponsePair, err := getRandomRequestResponsePair(logger)
+        if err != nil {
+            return nil, err
+        }
+        requestResponsePairs[i] = requestResponsePair
+    }
+    return requestResponsePairs, nil
+}
📝 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.

func GetRandomRequestResponsePairs(count int, logger *logger.Logger) ([]*RequestResponsePair, error) {
    requestResponsePairs := make([]*RequestResponsePair, count)
    for i := 0; i < count; i++ {
        requestResponsePair, err := getRandomRequestResponsePair(logger)
        if err != nil {
            return nil, err
        }
        requestResponsePairs[i] = requestResponsePair
    }
    return requestResponsePairs, nil
}

Base automatically changed from persistence to main April 8, 2025 14:15
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.

2 participants