Skip to content

Commit 93ad74f

Browse files
mmatczukChoraden
authored andcommitted
martian: fix Excess found: excess = 2 url = / (zero-length body) in HEAD response
This works around golang/go#62015 by manually writing response to HEAD requests. Fixes #357
1 parent 3821a21 commit 93ad74f

File tree

2 files changed

+90
-6
lines changed

2 files changed

+90
-6
lines changed

internal/martian/head.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2023 Sauce Labs Inc. All rights reserved.
2+
//
3+
// Copyright 2015 Google Inc. All rights reserved.
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
package martian
18+
19+
import (
20+
"fmt"
21+
"io"
22+
"net/http"
23+
"strconv"
24+
"strings"
25+
26+
"golang.org/x/exp/maps"
27+
)
28+
29+
// writeHeadResponse writes the status line and header of r to w.
30+
func writeHeadResponse(w io.Writer, res *http.Response) error {
31+
// Status line
32+
text := res.Status
33+
if text == "" {
34+
text = http.StatusText(res.StatusCode)
35+
if text == "" {
36+
text = "status code " + strconv.Itoa(res.StatusCode)
37+
}
38+
} else {
39+
// Just to reduce stutter, if user set res.Status to "200 OK" and StatusCode to 200.
40+
// Not important.
41+
text = strings.TrimPrefix(text, strconv.Itoa(res.StatusCode)+" ")
42+
}
43+
44+
if _, err := fmt.Fprintf(w, "HTTP/%d.%d %03d %s\r\n", res.ProtoMajor, res.ProtoMinor, res.StatusCode, text); err != nil {
45+
return err
46+
}
47+
48+
// Header
49+
if err := res.Header.Write(w); err != nil {
50+
return err
51+
}
52+
53+
// Add Trailer header if needed
54+
if len(res.Trailer) > 0 {
55+
if _, err := io.WriteString(w, "Trailer: "); err != nil {
56+
return err
57+
}
58+
59+
for i, k := range maps.Keys(res.Trailer) {
60+
if i > 0 {
61+
if _, err := io.WriteString(w, ", "); err != nil {
62+
return err
63+
}
64+
}
65+
if _, err := io.WriteString(w, k); err != nil {
66+
return err
67+
}
68+
}
69+
}
70+
71+
// End-of-header
72+
if _, err := io.WriteString(w, "\r\n"); err != nil {
73+
return err
74+
}
75+
76+
return nil
77+
}

internal/martian/proxy.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -705,13 +705,20 @@ func (p *Proxy) handle(ctx *Context, conn net.Conn, brw *bufio.ReadWriter) error
705705
}
706706
}
707707

708-
// Add support for Server Sent Events - relay HTTP chunks and flush after each chunk.
709-
// This is safe for events that are smaller than the buffer io.Copy uses (32KB).
710-
// If the event is larger than the buffer, the event will be split into multiple chunks.
711-
if shouldFlush(res) {
712-
err = res.Write(flushAfterChunkWriter{brw.Writer})
708+
if req.Method == "HEAD" && res.Body == http.NoBody {
709+
// The http package is misbehaving when writing a HEAD response.
710+
// See https://github.com/golang/go/issues/62015 for details.
711+
// This works around the issue by writing the response manually.
712+
err = writeHeadResponse(brw.Writer, res)
713713
} else {
714-
err = res.Write(brw)
714+
// Add support for Server Sent Events - relay HTTP chunks and flush after each chunk.
715+
// This is safe for events that are smaller than the buffer io.Copy uses (32KB).
716+
// If the event is larger than the buffer, the event will be split into multiple chunks.
717+
if shouldFlush(res) {
718+
err = res.Write(flushAfterChunkWriter{brw.Writer})
719+
} else {
720+
err = res.Write(brw)
721+
}
715722
}
716723
if err != nil {
717724
tracedLog.Errorf("martian: got error while writing response back to client: %v", err)

0 commit comments

Comments
 (0)