Repository: cloudwego/hertz
Pull request #928 (2023-08-25 14:07:31)

fix(hz): shebang position
#### What type of PR is this?
<!--
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
-->

fix: A bug fix

#### 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:
-->
- [x] This PR title match the format: \<type\>(optional scope): \<description\>
- [x] The description of this PR title is user-oriented and clear enough for others to understand.
- [x] 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) 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: Make shebang be the first line
zh(optional): 将 shebang 作为生成文件的第一行

Current Version:
![image](https://github.com/cloudwego/hertz/assets/87760338/8ff4efa0-7c7f-4e69-847d-49ff0c112370)
![image](https://github.com/cloudwego/hertz/assets/87760338/252c4556-72df-4daf-82f2-b139ad0ad4aa)

Fixed Version:
![image](https://github.com/cloudwego/hertz/assets/87760338/d9b4cb70-3814-4aff-b344-2a8ec25a2512)
![image](https://github.com/cloudwego/hertz/assets/87760338/ab3ad7e2-088b-4907-b188-941e6d28b057)

#### (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)`.
-->
null

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cloudwego/hertz/pull/928?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) Report
Patch coverage has no change and project coverage change: **`-1.68%`** :warning:
> Comparison is base [(`74cedc7`)](https://app.codecov.io/gh/cloudwego/hertz/commit/74cedc74ad2804f9eb88a70ad2565893cb260edf?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 81.95% compared to head [(`54763c2`)](https://app.codecov.io/gh/cloudwego/hertz/pull/928?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego) 80.28%.

> :exclamation: Current head 54763c2 differs from pull request most recent head dd62d23. Consider uploading reports for the commit dd62d23 to get more accurate results

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


```diff
@@             Coverage Diff             @@
##           develop     #928      +/-   ##
===========================================
- Coverage    81.95%   80.28%   -1.68%     
===========================================
  Files           97       97              
  Lines         9623     9604      -19     
===========================================
- Hits          7887     7711     -176     
- Misses        1262     1409     +147     
- Partials       474      484      +10     
```


[see 14 files with indirect coverage changes](https://app.codecov.io/gh/cloudwego/hertz/pull/928/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cloudwego)


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cloudwego/hertz/pull/928?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).


Patch:
diff --git a/cmd/hz/generator/layout_tpl.go b/cmd/hz/generator/layout_tpl.go
index 6f2b4c3..68a6d63 100644
--- a/cmd/hz/generator/layout_tpl.go
+++ b/cmd/hz/generator/layout_tpl.go
@@ -201,24 +201,20 @@ func GeneratedRegister(r *server.Hertz){
 		},
 		{
 			Path: "build.sh",
-			Body: `
-#!/bin/bash
+			Body: `#!/bin/bash
 RUN_NAME={{.ServiceName}}
 mkdir -p output/bin
 cp script/* output 2>/dev/null
 chmod +x output/bootstrap.sh
-go build -o output/bin/${RUN_NAME}
-`,
+go build -o output/bin/${RUN_NAME}`,
 		},
 		{
 			Path: defaultScriptDir + sp + "bootstrap.sh",
-			Body: `
-#!/bin/bash
+			Body: `#!/bin/bash
 CURDIR=$(cd $(dirname $0); pwd)
 BinaryName={{.ServiceName}}
 echo "$CURDIR/bin/${BinaryName}"
-exec $CURDIR/bin/${BinaryName}
-`,
+exec $CURDIR/bin/${BinaryName}`,
 		},
 	},
 }
diff --git a/pkg/protocol/http1/client.go b/pkg/protocol/http1/client.go
index c2b7cf5..09fbc10 100644
--- a/pkg/protocol/http1/client.go
+++ b/pkg/protocol/http1/client.go
@@ -72,9 +72,12 @@ import (
 	respI "github.com/cloudwego/hertz/pkg/protocol/http1/resp"
 )
 
-var errConnectionClosed = errs.NewPublic("the server closed connection before returning the first response byte. " +
-	"Make sure the server returns 'Connection: close' response header before closing the connection")
-var errTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, "host client")
+var (
+	errConnectionClosed = errs.NewPublic("the server closed connection before returning the first response byte. " +
+		"Make sure the server returns 'Connection: close' response header before closing the connection")
+
+	errTimeout = errs.New(errs.ErrTimeout, errs.ErrorTypePublic, "host client")
+)
 
 // HostClient balances http requests among hosts listed in Addr.
 //
@@ -699,7 +702,8 @@ func (c *HostClient) doNonNilReqResp(req *protocol.Request, resp *protocol.Respo
 
 	shouldCloseConn = resetConnection || req.ConnectionClose() || resp.ConnectionClose()
 
-	if c.ResponseBodyStream {
+	// In stream mode, we still can close/release the connection immediately if there is no content on the wire.
+	if c.ResponseBodyStream && resp.BodyStream() != protocol.NoResponseBody {
 		return false, err
 	}
 
diff --git a/pkg/protocol/request.go b/pkg/protocol/request.go
index ef70f92..d047318 100644
--- a/pkg/protocol/request.go
+++ b/pkg/protocol/request.go
@@ -76,7 +76,6 @@ var (
 // NoBody is an io.ReadCloser with no bytes. Read always returns EOF
 // and Close always returns nil. It can be used in an outgoing client
 // request to explicitly signal that a request has zero bytes.
-// An alternative, however, is to simply set Request.Body to nil.
 var NoBody = noBody{}
 
 type noBody struct{}
diff --git a/pkg/protocol/response.go b/pkg/protocol/response.go
index 016af92..8beb385 100644
--- a/pkg/protocol/response.go
+++ b/pkg/protocol/response.go
@@ -54,7 +54,13 @@ import (
 	"github.com/cloudwego/hertz/pkg/network"
 )
 
-var responsePool sync.Pool
+var (
+	responsePool sync.Pool
+	// NoResponseBody is an io.ReadCloser with no bytes. Read always returns EOF
+	// and Close always returns nil. It can be used in an ingoing client
+	// response to explicitly signal that a response has zero bytes.
+	NoResponseBody = noBody{}
+)
 
 // Response represents HTTP response.
 //
@@ -334,6 +340,9 @@ func (resp *Response) SetBody(body []byte) {
 }
 
 func (resp *Response) BodyStream() io.Reader {
+	if resp.bodyStream == nil {
+		resp.bodyStream = NoResponseBody
+	}
 	return resp.bodyStream
 }
 


Test patch:
diff --git a/pkg/protocol/http1/client_test.go b/pkg/protocol/http1/client_test.go
index 7bc389b..7c45d14 100644
--- a/pkg/protocol/http1/client_test.go
+++ b/pkg/protocol/http1/client_test.go
@@ -536,3 +536,43 @@ func TestConnNotRetry(t *testing.T) {
 	assert.True(t, logbuf.String() == "")
 	protocol.ReleaseResponse(resp)
 }
+
+type countCloseConn struct {
+	network.Conn
+	isClose bool
+}
+
+func (c *countCloseConn) Close() error {
+	c.isClose = true
+	return nil
+}
+
+func newCountCloseConn(s string) *countCloseConn {
+	return &countCloseConn{
+		Conn: mock.NewConn(s),
+	}
+}
+
+func TestStreamNoContent(t *testing.T) {
+	conn := newCountCloseConn("HTTP/1.1 204 Foo Bar\r\nContent-Type: aab\r\nTrailer: Foo\r\nContent-Encoding: deflate\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nFoo: bar\r\n\r\nHTTP/1.2")
+
+	c := &HostClient{
+		ClientOptions: &ClientOptions{
+			Dialer: newSlowConnDialer(func(network, addr string) (network.Conn, error) {
+				return conn, nil
+			}),
+		},
+		Addr: "foobar",
+	}
+
+	c.ResponseBodyStream = true
+
+	req := protocol.AcquireRequest()
+	req.SetRequestURI("http://foobar/baz")
+	req.Header.SetConnectionClose(true)
+	resp := protocol.AcquireResponse()
+
+	c.Do(context.Background(), req, resp)
+
+	assert.True(t, conn.isClose)
+}