From 0c683704f4a1683b327f271959bfda0d1f128379 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:05:57 -1000 Subject: [PATCH 1/4] feature: set ResponseEmitter encoding type Add the ability to explicitly set the response emitter encoding type. This is used to set the encoding type of the response emitter to something other than the default. For example, when setting the encoding type to `Gzip` when compressed output is being emitted. Needed for: https://github.com/ipfs/kubo/issues/2376 --- chan.go | 4 ++++ cli/responseemitter.go | 4 ++++ encoding.go | 4 ++++ http/responseemitter.go | 21 +++++++++++++++------ responseemitter.go | 3 +++ writer.go | 4 ++++ 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/chan.go b/chan.go index 2b6fb36d..6bdd75eb 100644 --- a/chan.go +++ b/chan.go @@ -181,6 +181,10 @@ func (re *chanResponseEmitter) SetLength(l uint64) { } } +func (re *chanResponseEmitter) SetEncodingType(encType EncodingType) { + return +} + func (re *chanResponseEmitter) CloseWithError(err error) error { re.wl.Lock() defer re.wl.Unlock() diff --git a/cli/responseemitter.go b/cli/responseemitter.go index 0e69e7fc..0b86d4cf 100644 --- a/cli/responseemitter.go +++ b/cli/responseemitter.go @@ -59,6 +59,10 @@ func (re *responseEmitter) SetLength(l uint64) { re.length = l } +func (re *responseEmitter) SetEncodingType(encType cmds.EncodingType) { + re.encType = encType +} + func (re *responseEmitter) isClosed() bool { re.l.Lock() defer re.l.Unlock() diff --git a/encoding.go b/encoding.go index 61209f02..a4b14bab 100644 --- a/encoding.go +++ b/encoding.go @@ -35,6 +35,7 @@ const ( Protobuf = "protobuf" Text = "text" TextNewline = "textnl" + Gzip = "gzip" // PostRunTypes CLI = "cli" @@ -56,6 +57,9 @@ var Encoders = EncoderMap{ XML: func(req *Request) func(io.Writer) Encoder { return func(w io.Writer) Encoder { return xml.NewEncoder(w) } }, + Gzip: func(req *Request) func(io.Writer) Encoder { + return func(w io.Writer) Encoder { return TextEncoder{w: w} } + }, JSON: func(req *Request) func(io.Writer) Encoder { return func(w io.Writer) Encoder { return json.NewEncoder(w) } }, diff --git a/http/responseemitter.go b/http/responseemitter.go index e5d424e7..3f2edabe 100644 --- a/http/responseemitter.go +++ b/http/responseemitter.go @@ -22,6 +22,7 @@ var ( cmds.JSON: "application/json", cmds.XML: "application/xml", cmds.Text: "text/plain", + cmds.Gzip: "application/x-gzip; charset=binary", } ) @@ -143,6 +144,13 @@ func (re *responseEmitter) Emit(value interface{}) error { return err } +func (re *responseEmitter) SetEncodingType(encType cmds.EncodingType) { + re.l.Lock() + defer re.l.Unlock() + + re.encType = encType +} + func (re *responseEmitter) SetLength(l uint64) { re.l.Lock() defer re.l.Unlock() @@ -252,10 +260,7 @@ func (re *responseEmitter) sendErr(err *cmds.Error) { } func (re *responseEmitter) doPreamble(value interface{}) { - var ( - h = re.w.Header() - mime string - ) + h := re.w.Header() // Common Headers @@ -283,6 +288,8 @@ func (re *responseEmitter) doPreamble(value interface{}) { } } + var mime string + switch v := value.(type) { case *cmds.Error: re.sendErr(v) @@ -293,7 +300,10 @@ func (re *responseEmitter) doPreamble(value interface{}) { h.Set(streamHeader, "1") re.streaming = true - mime = "text/plain" + if re.encType == cmds.JSON { + mime = "text/plain" + } + case cmds.Single: // don't set stream/channel header default: @@ -302,7 +312,6 @@ func (re *responseEmitter) doPreamble(value interface{}) { if mime == "" { var ok bool - // lookup mime type from map mime, ok = mimeTypes[re.encType] if !ok { diff --git a/responseemitter.go b/responseemitter.go index 97d4438c..753e1b1e 100644 --- a/responseemitter.go +++ b/responseemitter.go @@ -43,6 +43,9 @@ type ResponseEmitter interface { // SetLength sets the length of the output SetLength(length uint64) + // SetEncodingType sets the encoding type of the output. + SetEncodingType(encType EncodingType) + // Emit sends a value. // If value is io.Reader we just copy that to the connection // other values are marshalled. diff --git a/writer.go b/writer.go index 23b24608..a06b98a9 100644 --- a/writer.go +++ b/writer.go @@ -135,6 +135,10 @@ func (re *writerResponseEmitter) SetLength(length uint64) { re.length = &length } +func (re *writerResponseEmitter) SetEncodingType(encType EncodingType) { + return +} + func (re *writerResponseEmitter) Close() error { if re.closed { return ErrClosingClosedEmitter From 3bf315310c0353fc32b2d8cffdb79835a0ca2571 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:39:12 -1000 Subject: [PATCH 2/4] handle decoding content type "application/x-gzip" --- http/parse.go | 2 +- http/response.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/http/parse.go b/http/parse.go index b19c3a51..c23bd88a 100644 --- a/http/parse.go +++ b/http/parse.go @@ -201,7 +201,7 @@ func parseResponse(httpRes *http.Response, req *cmds.Request) (cmds.Response, er makeDec, ok := cmds.Decoders[encType] if ok { res.dec = makeDec(res.rr) - } else if encType != "text" { + } else if encType != "text" && encType != "gzip" { log.Errorf("could not find decoder for encoding %q", encType) } // else we have an io.Reader, which is okay } else { diff --git a/http/response.go b/http/response.go index e29287f3..a739072a 100644 --- a/http/response.go +++ b/http/response.go @@ -12,9 +12,10 @@ import ( var ( MIMEEncodings = map[string]cmds.EncodingType{ - "application/json": cmds.JSON, - "application/xml": cmds.XML, - "text/plain": cmds.Text, + "application/json": cmds.JSON, + "application/x-gzip": cmds.Gzip, + "application/xml": cmds.XML, + "text/plain": cmds.Text, } ) From 4b544b5c78b0e327f2a1464dd1e4ad6aea22a06d Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:49:58 -1000 Subject: [PATCH 3/4] update test types --- command_test.go | 2 ++ helpers_test.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/command_test.go b/command_test.go index 5e834122..e97749d6 100644 --- a/command_test.go +++ b/command_test.go @@ -344,6 +344,8 @@ func (s *testEmitterWithError) Close() error { return nil } +func (s *testEmitterWithError) SetEncodingType(EncodingType) {} + func (s *testEmitterWithError) SetLength(_ uint64) {} func (s *testEmitterWithError) CloseWithError(err error) error { diff --git a/helpers_test.go b/helpers_test.go index 3c976f2a..04e7f92f 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -17,6 +17,9 @@ func (s *testEmitter) Close() error { } func (s *testEmitter) SetLength(_ uint64) {} + +func (s *testEmitter) SetEncodingType(EncodingType) {} + func (s *testEmitter) CloseWithError(err error) error { if err != nil { (*testing.T)(s).Error(err) From abbc0fe66598bab47c053f4e89475cb53cbd1537 Mon Sep 17 00:00:00 2001 From: gammazero <11790789+gammazero@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:57:50 -1000 Subject: [PATCH 4/4] fix lint warn --- chan.go | 4 +--- writer.go | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/chan.go b/chan.go index 6bdd75eb..6d82ce6d 100644 --- a/chan.go +++ b/chan.go @@ -181,9 +181,7 @@ func (re *chanResponseEmitter) SetLength(l uint64) { } } -func (re *chanResponseEmitter) SetEncodingType(encType EncodingType) { - return -} +func (re *chanResponseEmitter) SetEncodingType(encType EncodingType) {} func (re *chanResponseEmitter) CloseWithError(err error) error { re.wl.Lock() diff --git a/writer.go b/writer.go index a06b98a9..9ead8fe7 100644 --- a/writer.go +++ b/writer.go @@ -135,9 +135,7 @@ func (re *writerResponseEmitter) SetLength(length uint64) { re.length = &length } -func (re *writerResponseEmitter) SetEncodingType(encType EncodingType) { - return -} +func (re *writerResponseEmitter) SetEncodingType(encType EncodingType) {} func (re *writerResponseEmitter) Close() error { if re.closed {