Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: unwrap resp for better deadline/flush SSE support #613

Merged
merged 2 commits into from
Oct 18, 2024

Conversation

danielgtaylor
Copy link
Owner

@danielgtaylor danielgtaylor commented Oct 17, 2024

This PR adds support for unwrapping HTTP response writers similar to how Go's built-in http.ResponseController handles it to enable better support for setting write deadlines and flushing during Server Sent Events handling in various routers. This fixes some issues with Chi and Gin, and updates the SSE example to be able to run with either router to show that it works.

Fixes #491. Related to go-chi/chi#947.

Summary by CodeRabbit

  • New Features

    • Enhanced server functionality to support multiple router options for Server-Sent Events (SSE).
    • Introduced new interfaces for improved handling of HTTP response writers.
  • Bug Fixes

    • Improved error handling for write deadlines in SSE requests.
  • Tests

    • Expanded test cases for the DummyWriter to cover new error scenarios related to write deadlines.

Copy link

coderabbitai bot commented Oct 17, 2024

Walkthrough

The changes in this pull request enhance the Server-Sent Events (SSE) functionality by introducing multiple router options and improving HTTP response writer handling. The Options struct now includes a Router field to specify either "chi" or "gin" routers, allowing for flexible server configuration. Additionally, two new interfaces, unwrapper and writeDeadliner, are introduced to streamline the handling of response writers. The DummyWriter struct in the test file is updated to simulate write deadline errors, ensuring robust error handling during SSE operations.

Changes

File Change Summary
examples/sse/main.go Added Router field to Options struct; refactored router initialization to support "chi" or "gin". Updated import statements.
sse/sse.go Introduced unwrapper and writeDeadliner interfaces; modified send function to utilize new interfaces for response writer handling.
sse/sse_test.go Updated DummyWriter struct to include deadlineErr; added Unwrap method; defined WrappedDeadliner struct with SetWriteDeadline method; expanded tests for error handling.

Poem

🐇 In the meadow where routers play,
A choice of paths brightens the day.
With unwinding tales and deadlines set,
Our SSE dances, no need to fret!
So hop along, let the events flow,
With chi and gin, we’re ready to go! 🌼


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 144c8d0 and 4d1a046.

📒 Files selected for processing (1)
  • sse/sse.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • sse/sse.go

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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 resolve resolve all the CodeRabbit review comments.
  • @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.

Copy link

codecov bot commented Oct 17, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 92.88%. Comparing base (1d5dce7) to head (4d1a046).
Report is 5 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #613      +/-   ##
==========================================
+ Coverage   92.84%   92.88%   +0.03%     
==========================================
  Files          22       22              
  Lines        4810     4834      +24     
==========================================
+ Hits         4466     4490      +24     
  Misses        299      299              
  Partials       45       45              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

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.

Actionable comments posted: 3

🧹 Outside diff range and nitpick comments (1)
sse/sse_test.go (1)

119-122: LGTM: Good addition to error handling tests

The new test case for write deadline errors is a valuable addition. It ensures that the system handles these errors gracefully without panicking, which is crucial for robust SSE implementation.

Consider adding a comment explaining the purpose of this test case for better clarity:

 	// Test inability to set write deadline due to error doesn't panic
+	// This simulates a scenario where setting a write deadline fails
 	w = &DummyWriter{deadlineErr: errors.New("whoops")}
 	req, _ = http.NewRequest(http.MethodGet, "/sse", nil)
 	api.Adapter().ServeHTTP(w, req)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 1d5dce7 and 144c8d0.

📒 Files selected for processing (3)
  • examples/sse/main.go (2 hunks)
  • sse/sse.go (3 hunks)
  • sse/sse_test.go (3 hunks)
🧰 Additional context used
🔇 Additional comments (5)
sse/sse_test.go (5)

31-32: LGTM: Enhanced error simulation capability

The addition of the deadlineErr field to the DummyWriter struct is a good improvement. It allows for more comprehensive testing by simulating errors related to setting write deadlines, which is crucial for robust SSE implementation.


45-47: LGTM: Proper implementation of unwrapping

The Unwrap method is a great addition. It follows Go's conventions for unwrapping and allows access to the underlying WrappedDeadliner. This change supports better handling of HTTP response writers, which is one of the main objectives of this PR.


49-52: LGTM: Well-designed wrapper for deadline handling

The WrappedDeadliner struct is a well-thought-out addition. By embedding http.ResponseWriter and including a deadlineErr field, it provides a clean way to handle deadline errors while maintaining compatibility with the standard library. This design supports the PR's goal of improving SSE support and error handling.


54-55: LGTM: Proper implementation of SetWriteDeadline

The SetWriteDeadline method is correctly implemented. It allows for simulating deadline setting errors, which is crucial for testing error handling in SSE implementations. This method aligns well with the PR's objective of improving deadline handling and flush support for SSE across various routers.


Line range hint 31-122: Overall assessment: Excellent improvements to SSE testing

The changes in this file significantly enhance the testing capabilities for SSE functionality, particularly in error handling scenarios. The additions to the DummyWriter, the new WrappedDeadliner, and the extended test cases all contribute to a more robust and comprehensive test suite.

These improvements align perfectly with the PR objectives of enhancing SSE support and improving HTTP response writer handling. They provide better coverage for potential edge cases and error scenarios, which will lead to more reliable SSE implementations across different routers.

Great job on these enhancements!

examples/sse/main.go Show resolved Hide resolved
Comment on lines +132 to +158
var flusher http.Flusher
flushCheck := bw
for {
if f, ok := flushCheck.(http.Flusher); ok {
flusher = f
break
}
if u, ok := flushCheck.(unwrapper); ok {
flushCheck = u.Unwrap()
} else {
break
}
}

var deadliner writeDeadliner
deadlineCheck := bw
for {
if d, ok := deadlineCheck.(writeDeadliner); ok {
deadliner = d
break
}
if u, ok := deadlineCheck.(unwrapper); ok {
deadlineCheck = u.Unwrap()
} else {
break
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor duplicated unwrapping logic into a helper function

The unwrapping logic to obtain http.Flusher and writeDeadliner interfaces from the response writer is duplicated in two places. This duplication can make the code harder to maintain and more error-prone. Consider refactoring this into a reusable helper function to improve code maintainability and reduce duplication.

You could create a generic helper function like this:

// Helper function to unwrap the response writer until the desired interface is found.
func getInterface[T any](bw http.ResponseWriter) T {
	check := bw
	for {
		if res, ok := check.(T); ok {
			return res
		}
		if u, ok := check.(unwrapper); ok {
			check = u.Unwrap()
		} else {
			var zero T
			return zero
		}
	}
}

Then replace the unwrapping loops with calls to this helper function:

- var flusher http.Flusher
- flushCheck := bw
- for {
-     if f, ok := flushCheck.(http.Flusher); ok {
-         flusher = f
-         break
-     }
-     if u, ok := flushCheck.(unwrapper); ok {
-         flushCheck = u.Unwrap()
-     } else {
-         break
-     }
- }
+ flusher := getInterface[http.Flusher](bw)

And similarly for deadliner:

- var deadliner writeDeadliner
- deadlineCheck := bw
- for {
-     if d, ok := deadlineCheck.(writeDeadliner); ok {
-         deadliner = d
-         break
-     }
-     if u, ok := deadlineCheck.(unwrapper); ok {
-         deadlineCheck = u.Unwrap()
-     } else {
-         break
-     }
- }
+ deadliner := getInterface[writeDeadliner](bw)

sse/sse.go Outdated Show resolved Hide resolved
@danielgtaylor danielgtaylor merged commit c6191e3 into main Oct 18, 2024
7 checks passed
@danielgtaylor danielgtaylor deleted the unwrap-response-writer branch October 18, 2024 16:11
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.

SSE Error with gin adapter: "warning: unable to set write deadline"
1 participant