Разные Content-Type с httptest и curl
Я пытаюсь этот код Go
package main
import (
"github.com/gorilla/mux"
"io"
"log"
"net/http"
)
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"alive": true}`)
}
func main() {
router := mux.NewRouter()
router.HandleFunc("/health", HealthCheckHandler).Methods("GET")
log.Printf("running server ...")
log.Fatal(http.ListenAndServe(":8000", router))
}
с этим тестом
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHealthCheckHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(HealthCheckHandler)
handler.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
t.Logf("%v", rr.Header())
if ctype := rr.Header().Get("Content-Type"); ctype != "application/json" {
t.Errorf("content type header does not match: got %v want %v",
ctype, "application/json")
}
}
когда я запускаю тест, все в порядке
go test -v
=== RUN TestHealthCheckHandler
--- PASS: TestHealthCheckHandler (0.00s)
handlers_test.go:24: map[Content-Type:[application/json]]
PASS
ok 0.012s
Content-Type
является application/json
, но когда я запускаю сервис и вызываю его, Content-Type
является text/plain
curl -v localhost:8000/health
* Trying ::1...
* Connected to localhost (::1) port 8000 (#0)
> GET /health HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 14 Feb 2019 01:37:15 GMT
< Content-Length: 15
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
Почему поведение отличается от теста и выполнения?
(пример основан на https://github.com/gorilla/mux)
РЕДАКТИРОВАТЬ 1
Когда я изменил порядок между строками, из
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
в
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
с завитком я получил ожидаемое поведение, Content-Type: application/json
curl -v localhost:8000/health
* Trying ::1...
* Connected to localhost (::1) port 8000 (#0)
> GET /health HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Date: Thu, 14 Feb 2019 01:43:18 GMT
< Content-Length: 15
<
* Connection #0 to host localhost left intact
но в любом случае, в оригинальном случае, почему тест и выполнение показывают разные Content-Type
?
РЕДАКТИРОВАТЬ 2
Я скопировал tcpdump, он содержит Content-Type: text/plain
,
11:51:05.686149 IP localhost.47368 > localhost.32000: Flags [P.], seq 1:92, ack 1, win 342, options [nop,nop,TS val 23544153 ecr 23544153], length 91
E...-L@.@.............}..Vl..WAu...V.......
.gAY.gAYGET /health-check HTTP/1.1
Host: localhost:32000
User-Agent: curl/7.47.0
Accept: */*
11:51:05.686847 IP localhost.32000 > localhost.47368: Flags [P.], seq 1:133, ack 92, win 342, options [nop,nop,TS val 23544153 ecr 23544153], length 132
E....-@.@.<.........}....WAu.Vln...V.......
.gAY.gAYHTTP/1.1 200 OK
Date: Thu, 14 Feb 2019 14:51:05 GMT
Content-Length: 15
Content-Type: text/plain; charset=utf-8
{"alive": true}
2 ответа
Просто измените свою функцию на:
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
// this will cause a duplicate status header to be written
// w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
io.WriteString(w, `{"alive": true}`)
}
Проблема заключается в том, что, как заявил @colminator, вы отправляете свое тело перед заголовком. Это не работает ни на одном языке - это факт HTTP, а не Go.
Причина, по которой ваш тест не был обнаружен, заключается в том, что ваш тест на самом деле неправильно ResponseRecorder
; Вы устанавливаете поля на карте, а затем читаете поля с этой карты напрямую. Тесты должны проверять только ResponseRecorder.Result
, который разработан, чтобы дать вам результат, который клиент фактически получит, включая блокировку заголовков при отправке тела:
if ctype := rr.Response().Header.Get("Content-Type"); ctype != "application/json" {
t.Errorf("content type header does not match: got %v want %v",
ctype, "application/json")
}