http: return more than data from server app
Server app could return headers in front of body/data buffer.
Offers apis for building and serialization of headers section.
HTTP layer now only add Date, Server and Content-Lengths headers,
rest is up to app. Well known header names are predefined.
Type: improvement
Change-Id: If778bdfc9acf6b0d11a48f0a745a3a56c96c2436
Signed-off-by: Matus Fabian <matfabia@cisco.com>
diff --git a/extras/hs-test/http_test.go b/extras/hs-test/http_test.go
index a5694bf..e20efd6 100644
--- a/extras/hs-test/http_test.go
+++ b/extras/hs-test/http_test.go
@@ -9,7 +9,6 @@
"time"
. "fd.io/hs-test/infra"
- . "github.com/onsi/ginkgo/v2"
)
func init() {
@@ -21,7 +20,7 @@
HttpContentLengthTest, HttpStaticBuildInUrlGetIfListTest, HttpStaticBuildInUrlGetVersionTest,
HttpStaticMacTimeTest, HttpStaticBuildInUrlGetVersionVerboseTest, HttpVersionNotSupportedTest,
HttpInvalidContentLengthTest, HttpInvalidTargetSyntaxTest, HttpStaticPathTraversalTest, HttpUriDecodeTest,
- HttpHeadersTest)
+ HttpHeadersTest, HttpStaticFileHandler)
RegisterNoTopoSoloTests(HttpStaticPromTest, HttpTpsTest, HttpTpsInterruptModeTest)
}
@@ -89,19 +88,81 @@
}
func HttpStaticPromTest(s *NoTopoSuite) {
- finished := make(chan error, 1)
query := "stats.prom"
vpp := s.GetContainerByName("vpp").VppInstance
serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
s.Log(vpp.Vppctl("http static server uri tcp://" + serverAddress + "/80 url-handlers"))
s.Log(vpp.Vppctl("prom enable"))
time.Sleep(time.Second * 5)
- go func() {
- defer GinkgoRecover()
- s.StartWget(finished, serverAddress, "80", query, "")
- }()
- err := <-finished
- s.AssertNil(err)
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/"+query, nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, false))
+ s.AssertEqual(200, resp.StatusCode)
+ s.AssertContains(resp.Header.Get("Content-Type"), "text")
+ s.AssertContains(resp.Header.Get("Content-Type"), "plain")
+ s.AssertNotEqual(int64(0), resp.ContentLength)
+ _, err = io.ReadAll(resp.Body)
+}
+
+func HttpStaticFileHandler(s *NoTopoSuite) {
+ content := "<http><body><p>Hello</p></body></http>"
+ content2 := "<http><body><p>Page</p></body></http>"
+ vpp := s.GetContainerByName("vpp").VppInstance
+ vpp.Container.Exec("mkdir -p " + wwwRootPath)
+ vpp.Container.CreateFile(wwwRootPath+"/index.html", content)
+ vpp.Container.CreateFile(wwwRootPath+"/page.html", content2)
+ serverAddress := s.GetInterfaceByName(TapInterfaceName).Peer.Ip4AddressString()
+ s.Log(vpp.Vppctl("http static server www-root " + wwwRootPath + " uri tcp://" + serverAddress + "/80 debug cache-size 2m"))
+
+ client := NewHttpClient()
+ req, err := http.NewRequest("GET", "http://"+serverAddress+":80/index.html", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err := client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertEqual(200, resp.StatusCode)
+ s.AssertContains(resp.Header.Get("Content-Type"), "html")
+ s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
+ s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
+ body, err := io.ReadAll(resp.Body)
+ s.AssertEqual(string(body), content)
+ o := vpp.Vppctl("show http static server cache verbose")
+ s.Log(o)
+ s.AssertContains(o, "index.html")
+ s.AssertNotContains(o, "page.html")
+
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertEqual(200, resp.StatusCode)
+ s.AssertContains(resp.Header.Get("Content-Type"), "html")
+ s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
+ s.AssertEqual(int64(len([]rune(content))), resp.ContentLength)
+ body, err = io.ReadAll(resp.Body)
+ s.AssertEqual(string(body), content)
+
+ req, err = http.NewRequest("GET", "http://"+serverAddress+":80/page.html", nil)
+ s.AssertNil(err, fmt.Sprint(err))
+ resp, err = client.Do(req)
+ s.AssertNil(err, fmt.Sprint(err))
+ defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertEqual(200, resp.StatusCode)
+ s.AssertContains(resp.Header.Get("Content-Type"), "html")
+ s.AssertContains(resp.Header.Get("Cache-Control"), "max-age=")
+ s.AssertEqual(int64(len([]rune(content2))), resp.ContentLength)
+ body, err = io.ReadAll(resp.Body)
+ s.AssertEqual(string(body), content2)
+ o = vpp.Vppctl("show http static server cache verbose")
+ s.Log(o)
+ s.AssertContains(o, "index.html")
+ s.AssertContains(o, "page.html")
}
func HttpStaticPathTraversalTest(s *NoTopoSuite) {
@@ -118,7 +179,11 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(404, resp.StatusCode)
+ s.AssertEmpty(resp.Header.Get("Content-Type"))
+ s.AssertEmpty(resp.Header.Get("Cache-Control"))
+ s.AssertEqual(int64(0), resp.ContentLength)
}
func HttpStaticMovedTest(s *NoTopoSuite) {
@@ -134,8 +199,12 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(301, resp.StatusCode)
- s.AssertNotEqual("", resp.Header.Get("Location"))
+ s.AssertEqual("http://"+serverAddress+"/tmp.aaa/index.html", resp.Header.Get("Location"))
+ s.AssertEmpty(resp.Header.Get("Content-Type"))
+ s.AssertEmpty(resp.Header.Get("Cache-Control"))
+ s.AssertEqual(int64(0), resp.ContentLength)
}
func HttpStaticNotFoundTest(s *NoTopoSuite) {
@@ -150,7 +219,11 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(404, resp.StatusCode)
+ s.AssertEmpty(resp.Header.Get("Content-Type"))
+ s.AssertEmpty(resp.Header.Get("Cache-Control"))
+ s.AssertEqual(int64(0), resp.ContentLength)
}
func HttpCliMethodNotAllowedTest(s *NoTopoSuite) {
@@ -164,9 +237,11 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(405, resp.StatusCode)
- // TODO: need to be fixed in http code
- //s.AssertNotEqual("", resp.Header.Get("Allow"))
+ s.AssertNotEqual("", resp.Header.Get("Allow"), "server MUST generate an Allow header")
+ s.AssertEmpty(resp.Header.Get("Content-Type"))
+ s.AssertEqual(int64(0), resp.ContentLength)
}
func HttpCliBadRequestTest(s *NoTopoSuite) {
@@ -180,7 +255,10 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(400, resp.StatusCode)
+ s.AssertEmpty(resp.Header.Get("Content-Type"))
+ s.AssertEqual(int64(0), resp.ContentLength)
}
func HttpStaticBuildInUrlGetVersionTest(s *NoTopoSuite) {
@@ -194,6 +272,7 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(200, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
@@ -203,6 +282,7 @@
s.AssertNotContains(string(data), "build_by")
s.AssertNotContains(string(data), "build_host")
s.AssertNotContains(string(data), "build_dir")
+ s.AssertContains(resp.Header.Get("Content-Type"), "json")
}
func HttpStaticBuildInUrlGetVersionVerboseTest(s *NoTopoSuite) {
@@ -216,6 +296,7 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(200, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
@@ -225,6 +306,7 @@
s.AssertContains(string(data), "build_by")
s.AssertContains(string(data), "build_host")
s.AssertContains(string(data), "build_dir")
+ s.AssertContains(resp.Header.Get("Content-Type"), "json")
}
func HttpStaticBuildInUrlGetIfListTest(s *NoTopoSuite) {
@@ -238,11 +320,13 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(200, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "interface_list")
s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+ s.AssertContains(resp.Header.Get("Content-Type"), "json")
}
func HttpStaticBuildInUrlGetIfStatsTest(s *NoTopoSuite) {
@@ -256,12 +340,14 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(200, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "interface_stats")
s.AssertContains(string(data), "local0")
s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Peer.Name())
+ s.AssertContains(resp.Header.Get("Content-Type"), "json")
}
func validatePostInterfaceStats(s *NoTopoSuite, data string) {
@@ -284,10 +370,12 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(200, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
validatePostInterfaceStats(s, string(data))
+ s.AssertContains(resp.Header.Get("Content-Type"), "json")
}
func HttpStaticMacTimeTest(s *NoTopoSuite) {
@@ -302,12 +390,14 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(200, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
s.AssertContains(string(data), "mactime")
s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).Ip4AddressString())
s.AssertContains(string(data), s.GetInterfaceByName(TapInterfaceName).HwAddress.String())
+ s.AssertContains(resp.Header.Get("Content-Type"), "json")
}
func HttpInvalidRequestLineTest(s *NoTopoSuite) {
@@ -444,7 +534,10 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(501, resp.StatusCode)
+ s.AssertEmpty(resp.Header.Get("Content-Type"))
+ s.AssertEqual(int64(0), resp.ContentLength)
}
func HttpVersionNotSupportedTest(s *NoTopoSuite) {
@@ -468,12 +561,13 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
s.AssertEqual(200, resp.StatusCode)
data, err := io.ReadAll(resp.Body)
s.AssertNil(err, fmt.Sprint(err))
- s.Log(string(data))
s.AssertNotContains(string(data), "unknown input")
s.AssertContains(string(data), "Compiler")
+ s.AssertContains(resp.Header.Get("Content-Type"), "html")
}
func HttpHeadersTest(s *NoTopoSuite) {
@@ -539,5 +633,8 @@
resp, err := client.Do(req)
s.AssertNil(err, fmt.Sprint(err))
defer resp.Body.Close()
+ s.Log(DumpHttpResp(resp, true))
+ s.AssertEqual(200, resp.StatusCode)
s.AssertEqual("http_cli_server", resp.Header.Get("Server"))
+ s.AssertContains(resp.Header.Get("Content-Type"), "html")
}
diff --git a/extras/hs-test/infra/utils.go b/extras/hs-test/infra/utils.go
index 9619efb..05b7b36 100644
--- a/extras/hs-test/infra/utils.go
+++ b/extras/hs-test/infra/utils.go
@@ -5,6 +5,7 @@
"io"
"net"
"net/http"
+ "net/http/httputil"
"os"
"strings"
"time"
@@ -96,6 +97,14 @@
return client
}
+func DumpHttpResp(resp *http.Response, body bool) string {
+ dump, err := httputil.DumpResponse(resp, body)
+ if err != nil {
+ return ""
+ }
+ return string(dump)
+}
+
func TcpSendReceive(address, data string) (string, error) {
conn, err := net.DialTimeout("tcp", address, time.Second*30)
if err != nil {