PageRenderTime 33ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/caddytest/integration/stream_test.go

https://gitlab.com/caddy/caddy
Go | 436 lines | 384 code | 34 blank | 18 comment | 54 complexity | 70680f265208bfd1c014447c8d3d2cd2 MD5 | raw file
  1. package integration
  2. import (
  3. "compress/gzip"
  4. "context"
  5. "crypto/rand"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "net/http/httputil"
  10. "net/url"
  11. "strings"
  12. "testing"
  13. "time"
  14. "github.com/caddyserver/caddy/v2/caddytest"
  15. "golang.org/x/net/http2"
  16. "golang.org/x/net/http2/h2c"
  17. )
  18. // (see https://github.com/caddyserver/caddy/issues/3556 for use case)
  19. func TestH2ToH2CStream(t *testing.T) {
  20. tester := caddytest.NewTester(t)
  21. tester.InitServer(`
  22. {
  23. "apps": {
  24. "http": {
  25. "http_port": 9080,
  26. "https_port": 9443,
  27. "servers": {
  28. "srv0": {
  29. "listen": [
  30. ":9443"
  31. ],
  32. "routes": [
  33. {
  34. "handle": [
  35. {
  36. "handler": "reverse_proxy",
  37. "transport": {
  38. "protocol": "http",
  39. "compression": false,
  40. "versions": [
  41. "h2c",
  42. "2"
  43. ]
  44. },
  45. "upstreams": [
  46. {
  47. "dial": "localhost:54321"
  48. }
  49. ]
  50. }
  51. ],
  52. "match": [
  53. {
  54. "path": [
  55. "/tov2ray"
  56. ]
  57. }
  58. ]
  59. }
  60. ],
  61. "tls_connection_policies": [
  62. {
  63. "certificate_selection": {
  64. "any_tag": ["cert0"]
  65. },
  66. "default_sni": "a.caddy.localhost"
  67. }
  68. ]
  69. }
  70. }
  71. },
  72. "tls": {
  73. "certificates": {
  74. "load_files": [
  75. {
  76. "certificate": "/a.caddy.localhost.crt",
  77. "key": "/a.caddy.localhost.key",
  78. "tags": [
  79. "cert0"
  80. ]
  81. }
  82. ]
  83. }
  84. },
  85. "pki": {
  86. "certificate_authorities" : {
  87. "local" : {
  88. "install_trust": false
  89. }
  90. }
  91. }
  92. }
  93. }
  94. `, "json")
  95. expectedBody := "some data to be echoed"
  96. // start the server
  97. server := testH2ToH2CStreamServeH2C(t)
  98. go server.ListenAndServe()
  99. defer func() {
  100. ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
  101. defer cancel()
  102. server.Shutdown(ctx)
  103. }()
  104. r, w := io.Pipe()
  105. req := &http.Request{
  106. Method: "PUT",
  107. Body: io.NopCloser(r),
  108. URL: &url.URL{
  109. Scheme: "https",
  110. Host: "127.0.0.1:9443",
  111. Path: "/tov2ray",
  112. },
  113. Proto: "HTTP/2",
  114. ProtoMajor: 2,
  115. ProtoMinor: 0,
  116. Header: make(http.Header),
  117. }
  118. // Disable any compression method from server.
  119. req.Header.Set("Accept-Encoding", "identity")
  120. resp := tester.AssertResponseCode(req, 200)
  121. if 200 != resp.StatusCode {
  122. return
  123. }
  124. go func() {
  125. fmt.Fprint(w, expectedBody)
  126. w.Close()
  127. }()
  128. defer resp.Body.Close()
  129. bytes, err := io.ReadAll(resp.Body)
  130. if err != nil {
  131. t.Fatalf("unable to read the response body %s", err)
  132. }
  133. body := string(bytes)
  134. if !strings.Contains(body, expectedBody) {
  135. t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
  136. }
  137. return
  138. }
  139. func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
  140. h2s := &http2.Server{}
  141. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  142. rstring, err := httputil.DumpRequest(r, false)
  143. if err == nil {
  144. t.Logf("h2c server received req: %s", rstring)
  145. }
  146. // We only accept HTTP/2!
  147. if r.ProtoMajor != 2 {
  148. t.Error("Not a HTTP/2 request, rejected!")
  149. w.WriteHeader(http.StatusInternalServerError)
  150. return
  151. }
  152. if r.Host != "127.0.0.1:9443" {
  153. t.Errorf("r.Host doesn't match, %v!", r.Host)
  154. w.WriteHeader(http.StatusNotFound)
  155. return
  156. }
  157. if !strings.HasPrefix(r.URL.Path, "/tov2ray") {
  158. w.WriteHeader(http.StatusNotFound)
  159. return
  160. }
  161. w.Header().Set("Cache-Control", "no-store")
  162. w.WriteHeader(200)
  163. if f, ok := w.(http.Flusher); ok {
  164. f.Flush()
  165. }
  166. buf := make([]byte, 4*1024)
  167. for {
  168. n, err := r.Body.Read(buf)
  169. if n > 0 {
  170. w.Write(buf[:n])
  171. }
  172. if err != nil {
  173. if err == io.EOF {
  174. r.Body.Close()
  175. }
  176. break
  177. }
  178. }
  179. })
  180. server := &http.Server{
  181. Addr: "127.0.0.1:54321",
  182. Handler: h2c.NewHandler(handler, h2s),
  183. }
  184. return server
  185. }
  186. // (see https://github.com/caddyserver/caddy/issues/3606 for use case)
  187. func TestH2ToH1ChunkedResponse(t *testing.T) {
  188. tester := caddytest.NewTester(t)
  189. tester.InitServer(`
  190. {
  191. "logging": {
  192. "logs": {
  193. "default": {
  194. "level": "DEBUG"
  195. }
  196. }
  197. },
  198. "apps": {
  199. "http": {
  200. "http_port": 9080,
  201. "https_port": 9443,
  202. "servers": {
  203. "srv0": {
  204. "listen": [
  205. ":9443"
  206. ],
  207. "routes": [
  208. {
  209. "handle": [
  210. {
  211. "handler": "subroute",
  212. "routes": [
  213. {
  214. "handle": [
  215. {
  216. "encodings": {
  217. "gzip": {}
  218. },
  219. "handler": "encode"
  220. }
  221. ]
  222. },
  223. {
  224. "handle": [
  225. {
  226. "handler": "reverse_proxy",
  227. "upstreams": [
  228. {
  229. "dial": "localhost:54321"
  230. }
  231. ]
  232. }
  233. ],
  234. "match": [
  235. {
  236. "path": [
  237. "/tov2ray"
  238. ]
  239. }
  240. ]
  241. }
  242. ]
  243. }
  244. ],
  245. "terminal": true
  246. }
  247. ],
  248. "tls_connection_policies": [
  249. {
  250. "certificate_selection": {
  251. "any_tag": [
  252. "cert0"
  253. ]
  254. },
  255. "default_sni": "a.caddy.localhost"
  256. }
  257. ]
  258. }
  259. }
  260. },
  261. "tls": {
  262. "certificates": {
  263. "load_files": [
  264. {
  265. "certificate": "/a.caddy.localhost.crt",
  266. "key": "/a.caddy.localhost.key",
  267. "tags": [
  268. "cert0"
  269. ]
  270. }
  271. ]
  272. }
  273. },
  274. "pki": {
  275. "certificate_authorities": {
  276. "local": {
  277. "install_trust": false
  278. }
  279. }
  280. }
  281. }
  282. }
  283. `, "json")
  284. // need a large body here to trigger caddy's compression, larger than gzip.miniLength
  285. expectedBody, err := GenerateRandomString(1024)
  286. if err != nil {
  287. t.Fatalf("generate expected body failed, err: %s", err)
  288. }
  289. // start the server
  290. server := testH2ToH1ChunkedResponseServeH1(t)
  291. go server.ListenAndServe()
  292. defer func() {
  293. ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
  294. defer cancel()
  295. server.Shutdown(ctx)
  296. }()
  297. r, w := io.Pipe()
  298. req := &http.Request{
  299. Method: "PUT",
  300. Body: io.NopCloser(r),
  301. URL: &url.URL{
  302. Scheme: "https",
  303. Host: "127.0.0.1:9443",
  304. Path: "/tov2ray",
  305. },
  306. Proto: "HTTP/2",
  307. ProtoMajor: 2,
  308. ProtoMinor: 0,
  309. Header: make(http.Header),
  310. }
  311. // underlying transport will automaticlly add gzip
  312. // req.Header.Set("Accept-Encoding", "gzip")
  313. go func() {
  314. fmt.Fprint(w, expectedBody)
  315. w.Close()
  316. }()
  317. resp := tester.AssertResponseCode(req, 200)
  318. if 200 != resp.StatusCode {
  319. return
  320. }
  321. defer resp.Body.Close()
  322. bytes, err := io.ReadAll(resp.Body)
  323. if err != nil {
  324. t.Fatalf("unable to read the response body %s", err)
  325. }
  326. body := string(bytes)
  327. if body != expectedBody {
  328. t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
  329. }
  330. return
  331. }
  332. func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
  333. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  334. if r.Host != "127.0.0.1:9443" {
  335. t.Errorf("r.Host doesn't match, %v!", r.Host)
  336. w.WriteHeader(http.StatusNotFound)
  337. return
  338. }
  339. if !strings.HasPrefix(r.URL.Path, "/tov2ray") {
  340. w.WriteHeader(http.StatusNotFound)
  341. return
  342. }
  343. defer r.Body.Close()
  344. bytes, err := io.ReadAll(r.Body)
  345. if err != nil {
  346. t.Fatalf("unable to read the response body %s", err)
  347. }
  348. n := len(bytes)
  349. var writer io.Writer
  350. if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  351. gw, err := gzip.NewWriterLevel(w, 5)
  352. if err != nil {
  353. t.Error("can't return gzip data")
  354. w.WriteHeader(http.StatusInternalServerError)
  355. return
  356. }
  357. defer gw.Close()
  358. writer = gw
  359. w.Header().Set("Content-Encoding", "gzip")
  360. w.Header().Del("Content-Length")
  361. w.WriteHeader(200)
  362. } else {
  363. writer = w
  364. }
  365. if n > 0 {
  366. writer.Write(bytes[:])
  367. }
  368. })
  369. server := &http.Server{
  370. Addr: "127.0.0.1:54321",
  371. Handler: handler,
  372. }
  373. return server
  374. }
  375. // GenerateRandomBytes returns securely generated random bytes.
  376. // It will return an error if the system's secure random
  377. // number generator fails to function correctly, in which
  378. // case the caller should not continue.
  379. func GenerateRandomBytes(n int) ([]byte, error) {
  380. b := make([]byte, n)
  381. _, err := rand.Read(b)
  382. // Note that err == nil only if we read len(b) bytes.
  383. if err != nil {
  384. return nil, err
  385. }
  386. return b, nil
  387. }
  388. // GenerateRandomString returns a securely generated random string.
  389. // It will return an error if the system's secure random
  390. // number generator fails to function correctly, in which
  391. // case the caller should not continue.
  392. func GenerateRandomString(n int) (string, error) {
  393. const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
  394. bytes, err := GenerateRandomBytes(n)
  395. if err != nil {
  396. return "", err
  397. }
  398. for i, b := range bytes {
  399. bytes[i] = letters[b%byte(len(letters))]
  400. }
  401. return string(bytes), nil
  402. }