Repository: goadesign/goa
Pull request #3695 (2025-04-11 04:36:18)

Do not write HTTP headers with SkipResponseBodyEncodeDecode
Make it possible for user code to write HTTP headers.

Prior to this fix using `SkipResponseBodyEncodeDecode` would generate the following encoder code:

```go
// EncodeStreamResponse returns an encoder for responses returned by the events
// stream endpoint.
func EncodeStreamResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error {
	return func(ctx context.Context, w http.ResponseWriter, v any) error {
		w.WriteHeader(http.StatusOK)
		return nil
	}
}
```

With this change the function is now:

```go
// EncodeStreamResponse returns an encoder for responses returned by the events
// stream endpoint.
func EncodeStreamResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error {
	return func(ctx context.Context, w http.ResponseWriter, v any) error {
		return nil
	}
}
```

This makes it possible for user code to write custom headers, for example to support SSE.


Patch:
diff --git a/http/codegen/templates/response_encoder.go.tpl b/http/codegen/templates/response_encoder.go.tpl
index 1500adf4..5b5ab0b5 100644
--- a/http/codegen/templates/response_encoder.go.tpl
+++ b/http/codegen/templates/response_encoder.go.tpl
@@ -33,7 +33,9 @@ func {{ .ResponseEncoder }}(encoder func(context.Context, http.ResponseWriter) g
 		{{- end }}
 	{{- else }}
 		{{- with (index .Result.Responses 0) }}
-			w.WriteHeader({{ .StatusCode }})
+			{{- if not $.Method.SkipResponseBodyEncodeDecode }}
+				w.WriteHeader({{ .StatusCode }})
+			{{- end }}
 			return nil
 		{{- end }}
 	{{- end }}


Test patch:
diff --git a/http/codegen/server_encode_test.go b/http/codegen/server_encode_test.go
index c9c3c2d9..80d8fb0b 100644
--- a/http/codegen/server_encode_test.go
+++ b/http/codegen/server_encode_test.go
@@ -85,6 +85,8 @@ func TestEncode(t *testing.T) {
 		{"empty-server-response", testdata.EmptyServerResponseDSL, testdata.EmptyServerResponseEncodeCode},
 		{"empty-server-response-with-tags", testdata.EmptyServerResponseWithTagsDSL, testdata.EmptyServerResponseWithTagsEncodeCode},
 
+		{"skip-response-body-encode-decode", testdata.ResponseEncoderSkipResponseBodyEncodeDecodeDSL, testdata.ResponseEncoderSkipResponseBodyEncodeDecodeCode},
+
 		{"result-with-custom-pkg-type", testdata.ResultWithCustomPkgTypeDSL, testdata.ResultWithCustomPkgTypeEncodeCode},
 		{"result-with-embedded-custom-pkg-type", testdata.EmbeddedCustomPkgTypeDSL, testdata.ResultWithEmbeddedCustomPkgTypeEncodeCode},
 	}
diff --git a/http/codegen/testdata/result_encode_functions.go b/http/codegen/testdata/result_encode_functions.go
index 02d402c1..449fee8a 100644
--- a/http/codegen/testdata/result_encode_functions.go
+++ b/http/codegen/testdata/result_encode_functions.go
@@ -1036,6 +1036,16 @@ func EncodeMethodEmptyServerResponseWithTagsResponse(encoder func(context.Contex
 }
 `
 
+var ResponseEncoderSkipResponseBodyEncodeDecodeCode = `// EncodeMethodResponseEncoderSkipResponse returns an encoder for responses
+// returned by the ServiceResponseEncoderSkip MethodResponseEncoderSkip
+// endpoint.
+func EncodeMethodResponseEncoderSkipResponse(encoder func(context.Context, http.ResponseWriter) goahttp.Encoder) func(context.Context, http.ResponseWriter, any) error {
+	return func(ctx context.Context, w http.ResponseWriter, v any) error {
+		return nil
+	}
+}
+`
+
 var ResultWithCustomPkgTypeEncodeCode = `// EncodeMethodResultWithCustomPkgTypeDSLResponse returns an encoder for
 // responses returned by the ServiceResultWithCustomPkgTypeDSL
 // MethodResultWithCustomPkgTypeDSL endpoint.
diff --git a/http/codegen/testdata/server_dsls.go b/http/codegen/testdata/server_dsls.go
index 8f9a3a5f..b50ea913 100644
--- a/http/codegen/testdata/server_dsls.go
+++ b/http/codegen/testdata/server_dsls.go
@@ -294,3 +294,15 @@ var ServerSkipResponseBodyEncodeDecodeDSL = func() {
 		})
 	})
 }
+
+var ResponseEncoderSkipResponseBodyEncodeDecodeDSL = func() {
+	Service("ServiceResponseEncoderSkip", func() {
+		Method("MethodResponseEncoderSkip", func() {
+			HTTP(func() {
+				GET("/")
+				SkipResponseBodyEncodeDecode()
+				Response(StatusOK)
+			})
+		})
+	})
+}