Repository: cloudwego/hertz
Pull request #1349 (2025-05-13 09:09:42)

feat(sse): SetLastEventID
#### What type of PR is this?
feat

<!--
Add one of the following kinds:

build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
docs: Documentation only changes
feat: A new feature
optimize: A new optimization
fix: A bug fix
perf: A code change that improves performance
refactor: A code change that neither fixes a bug nor adds a feature
style: Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc)
test: Adding missing tests or correcting existing tests
chore: Changes to the build process or auxiliary tools and libraries such as documentation generation
-->

#### Check the PR title.
<!--
The description of the title will be attached in Release Notes, 
so please describe it from user-oriented, what this PR does / why we need it.
Please check your PR title with the below requirements:
-->
- [ ] This PR title match the format: \<type\>(optional scope): \<description\>
- [ ] The description of this PR title is user-oriented and clear enough for others to understand.
- [ ] Attach the PR updating the user documentation if the current PR requires user awareness at the usage level. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)


#### (Optional) Translate the PR title into Chinese.


#### (Optional) More detailed description for this PR(en: English/zh: Chinese).
<!--
Provide more detailed info for review(e.g., it's recommended to provide perf data if this is a perf type PR).
-->
en: add `SetLastEventID`
zh(optional):

#### (Optional) Which issue(s) this PR fixes:
<!--
Automatically closes linked issue when PR is merged.
Eg: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->

#### (Optional) The PR that updates user documentation:
<!--
If the current PR requires user awareness at the usage level, please submit a PR to update user docs. [User docs repo](https://github.com/cloudwego/cloudwego.github.io)
-->

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cloudwego/hertz/pull/1349?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 73.50%. Comparing base [(`3952e95`)](https://app.codecov.io/gh/cloudwego/hertz/commit/3952e9565e7d3795bdff6210d3c546eb8af60274?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) to head [(`e19949a`)](https://app.codecov.io/gh/cloudwego/hertz/commit/e19949a85f1d87f7bede4f959834d3a7cd11469c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop    #1349      +/-   ##
===========================================
+ Coverage    73.48%   73.50%   +0.02%     
===========================================
  Files          144      143       -1     
  Lines        16654    16656       +2     
===========================================
+ Hits         12238    12243       +5     
+ Misses        3830     3829       -1     
+ Partials       586      584       -2     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cloudwego/hertz/pull/1349?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/pkg/protocol/sse/request.go b/pkg/protocol/sse/request.go
index 84a5ac8..6e9082d 100644
--- a/pkg/protocol/sse/request.go
+++ b/pkg/protocol/sse/request.go
@@ -15,28 +15,3 @@
  */
 
 package sse
-
-import (
-	"bytes"
-
-	"github.com/cloudwego/hertz/internal/bytestr"
-	"github.com/cloudwego/hertz/pkg/protocol"
-)
-
-// AddAcceptMIME adds `text/event-stream` to http `Accept` header.
-//
-// This is NOT required as per spec:
-// * User agents MAY set (`Accept`, `text/event-stream`) in request's header list.
-func AddAcceptMIME(req *protocol.Request) {
-	v := req.Header.Peek("Accept")
-	if len(v) > 0 {
-		if bytes.Contains(v, bytestr.MIMETextEventStream) {
-			return
-		}
-		// for better compatibility, only use one Accept header value
-		// append `text/event-stream` to the end of the value
-		req.Header.Set("Accept", string(v)+", "+string(bytestr.MIMETextEventStream))
-	} else {
-		req.Header.Set("Accept", string(bytestr.MIMETextEventStream))
-	}
-}
diff --git a/pkg/protocol/sse/utils.go b/pkg/protocol/sse/utils.go
index 5d69751..74062b8 100644
--- a/pkg/protocol/sse/utils.go
+++ b/pkg/protocol/sse/utils.go
@@ -17,14 +17,40 @@
 package sse
 
 import (
-	"github.com/cloudwego/hertz/pkg/app"
+	"bytes"
+
+	"github.com/cloudwego/hertz/internal/bytestr"
+	"github.com/cloudwego/hertz/pkg/protocol"
 )
 
 const LastEventIDHeader = "Last-Event-ID"
 
 // GetLastEventID returns the value of the Last-Event-ID header.
-func GetLastEventID(c *app.RequestContext) string {
-	return string(c.GetHeader(LastEventIDHeader))
+func GetLastEventID(req *protocol.Request) string {
+	return string(req.Header.Peek(LastEventIDHeader))
+}
+
+// SetLastEventID sets the Last-Event-ID header.
+func SetLastEventID(req *protocol.Request, id string) {
+	req.Header.Set(LastEventIDHeader, id)
+}
+
+// AddAcceptMIME adds `text/event-stream` to http `Accept` header.
+//
+// This is NOT required as per spec:
+// * User agents MAY set (`Accept`, `text/event-stream`) in request's header list.
+func AddAcceptMIME(req *protocol.Request) {
+	v := req.Header.Peek("Accept")
+	if len(v) > 0 {
+		if bytes.Contains(v, bytestr.MIMETextEventStream) {
+			return
+		}
+		// for better compatibility, only use one Accept header value
+		// append `text/event-stream` to the end of the value
+		req.Header.Set("Accept", string(v)+", "+string(bytestr.MIMETextEventStream))
+	} else {
+		req.Header.Set("Accept", string(bytestr.MIMETextEventStream))
+	}
 }
 
 func sseEventType(v []byte) string {


Test patch:
diff --git a/pkg/protocol/sse/example_test.go b/pkg/protocol/sse/example_test.go
index 44a58aa..6b1a143 100644
--- a/pkg/protocol/sse/example_test.go
+++ b/pkg/protocol/sse/example_test.go
@@ -36,7 +36,7 @@ func Example() {
 	opt.Addr = "127.0.0.1:0"
 	engine := route.NewEngine(opt)
 	engine.GET("/", func(ctx context.Context, c *app.RequestContext) {
-		println("Server Got LastEventID", GetLastEventID(c))
+		println("Server Got LastEventID", GetLastEventID(&c.Request))
 		w := NewWriter(c)
 		for i := 0; i < 5; i++ {
 			w.WriteEvent(fmt.Sprintf("id-%d", i), "message", []byte("hello\n\nworld"))
diff --git a/pkg/protocol/sse/request_test.go b/pkg/protocol/sse/request_test.go
index c04d231..6e9082d 100644
--- a/pkg/protocol/sse/request_test.go
+++ b/pkg/protocol/sse/request_test.go
@@ -15,40 +15,3 @@
  */
 
 package sse
-
-import (
-	"testing"
-
-	"github.com/cloudwego/hertz/internal/bytestr"
-	"github.com/cloudwego/hertz/pkg/common/test/assert"
-	"github.com/cloudwego/hertz/pkg/protocol"
-)
-
-func TestAddAcceptMIME(t *testing.T) {
-	// Test case 1: Empty Accept header
-	req := protocol.AcquireRequest()
-	defer protocol.ReleaseRequest(req)
-
-	AddAcceptMIME(req)
-
-	acceptHeader := req.Header.Peek("Accept")
-	assert.DeepEqual(t, string(bytestr.MIMETextEventStream), string(acceptHeader))
-
-	// Test case 2: Existing Accept header without text/event-stream
-	req.Reset()
-	req.Header.Set("Accept", "text/html, application/json")
-
-	AddAcceptMIME(req)
-
-	acceptHeader = req.Header.Peek("Accept")
-	assert.DeepEqual(t, "text/html, application/json, text/event-stream", string(acceptHeader))
-
-	// Test case 3: Existing Accept header already containing text/event-stream
-	req.Reset()
-	req.Header.Set("Accept", "text/html, text/event-stream, application/json")
-
-	AddAcceptMIME(req)
-
-	acceptHeader = req.Header.Peek("Accept")
-	assert.DeepEqual(t, "text/html, text/event-stream, application/json", string(acceptHeader))
-}
diff --git a/pkg/protocol/sse/utils_test.go b/pkg/protocol/sse/utils_test.go
index e9169f3..4c73a3f 100644
--- a/pkg/protocol/sse/utils_test.go
+++ b/pkg/protocol/sse/utils_test.go
@@ -19,9 +19,48 @@ package sse
 import (
 	"testing"
 
+	"github.com/cloudwego/hertz/internal/bytestr"
 	"github.com/cloudwego/hertz/pkg/common/test/assert"
+	"github.com/cloudwego/hertz/pkg/protocol"
 )
 
+func TestSetGetLastEventID(t *testing.T) {
+	req := protocol.AcquireRequest()
+	defer protocol.ReleaseRequest(req)
+
+	SetLastEventID(req, "123")
+	assert.DeepEqual(t, "123", GetLastEventID(req))
+}
+
+func TestAddAcceptMIME(t *testing.T) {
+	// Test case 1: Empty Accept header
+	req := protocol.AcquireRequest()
+	defer protocol.ReleaseRequest(req)
+
+	AddAcceptMIME(req)
+
+	acceptHeader := req.Header.Peek("Accept")
+	assert.DeepEqual(t, string(bytestr.MIMETextEventStream), string(acceptHeader))
+
+	// Test case 2: Existing Accept header without text/event-stream
+	req.Reset()
+	req.Header.Set("Accept", "text/html, application/json")
+
+	AddAcceptMIME(req)
+
+	acceptHeader = req.Header.Peek("Accept")
+	assert.DeepEqual(t, "text/html, application/json, text/event-stream", string(acceptHeader))
+
+	// Test case 3: Existing Accept header already containing text/event-stream
+	req.Reset()
+	req.Header.Set("Accept", "text/html, text/event-stream, application/json")
+
+	AddAcceptMIME(req)
+
+	acceptHeader = req.Header.Peek("Accept")
+	assert.DeepEqual(t, "text/html, text/event-stream, application/json", string(acceptHeader))
+}
+
 func TestHasCRLF(t *testing.T) {
 	assert.Assert(t, hasCRLF("\nThis is a test string"))
 	assert.Assert(t, hasCRLF("This is \na test string"))