diff --git a/README.md b/README.md index c6d86f8..4aa4ebe 100644 --- a/README.md +++ b/README.md @@ -94,4 +94,14 @@ You can also redirect to a 'file://' protol address: ```bash curl -v 'localhost:9527/redirect/301?url=file:///etc/passwd' -``` \ No newline at end of file +``` +### :arrows_counterclockwise: Chunked http response + +You can simulate chunked http response. Use the following format, `$COUNT` is the count of chunked message. + +`http://localhost:9527/chunk/$COUNT` + +Examples: + +* http://localhost:9527/chunk/3 +* http://localhost:9527/chunk/999 diff --git a/main.go b/main.go index 48bc7d3..fc9250e 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "fmt" "html/template" "log" + "math/rand" "mime" "net/http" "net/http/httputil" @@ -178,6 +179,36 @@ func sizeHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, strings.Repeat("o", size)) } +func chunkHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + count, _ := strconv.Atoi(vars["count"]) + + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Transfer-Encoding", "chunked") + + //time.Sleep(time.Duration(count) * time.Second) + for i := 0; i < count; i++ { + randomStr := randomString(10) + currentTime := time.Now().Format(time.RFC3339) + fmt.Fprintf(w, "Time: %s, Msg: %s\n", currentTime, randomStr) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + time.Sleep(1 * time.Second) + } +} + +func randomString(n int) string { + var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + + s := make([]rune, n) + for i := range s { + s[i] = letters[rand.Intn(len(letters))] + } + return string(s) +} + func appRouter() http.Handler { r := mux.NewRouter() r.HandleFunc("/", indexHandler) @@ -187,6 +218,7 @@ func appRouter() http.Handler { r.HandleFunc("/slow/{time:[0-9]+}", slowHandler) r.HandleFunc("/redirect/{method}", redirectHandler) r.HandleFunc("/size/{size:[0-9]+}{measure:[k|m]?}{ext:.*}", sizeHandler) + r.HandleFunc("/chunk/{count:[0-9]+}", chunkHandler) return r } diff --git a/main_test.go b/main_test.go index 252fb62..aa2789b 100644 --- a/main_test.go +++ b/main_test.go @@ -1,12 +1,15 @@ package main import ( + "bufio" "fmt" "net/http" "net/http/httptest" "strings" "testing" "time" + + "github.com/gorilla/mux" ) func TestIndexHandler(t *testing.T) { @@ -21,7 +24,7 @@ func TestIndexHandler(t *testing.T) { if w.Code != http.StatusOK { t.Errorf("Got HTTP status code %d, expect 200", w.Code) } - if !strings.Contains(w.Body.String(), "SERVER-ID") { + if !strings.Contains(w.Body.String(), "Server id:") { t.Errorf("Got HTTP status code %d, expect 200", w.Code) } } @@ -134,3 +137,55 @@ func TestSlowHandler(t *testing.T) { } } + +// generate test for chunkHandler +func TestChunkHandler(t *testing.T) { + // 创建一个路由,模拟实际的路由行为 + r := mux.NewRouter() + r.HandleFunc("/chunk/{count}", chunkHandler) + + // 创建一个测试服务器 + server := httptest.NewServer(r) + defer server.Close() + + expectedCount := 5 + url := fmt.Sprintf("%s/chunk/%d", server.URL, expectedCount) + // 发送请求到测试服务器 + resp, err := http.Get(url) + if err != nil { + t.Fatalf("Failed to send request: %v", err) + } + defer resp.Body.Close() + + // 检查Transfer-Encoding是否为chunked + if resp.TransferEncoding == nil || resp.TransferEncoding[0] != "chunked" { + t.Errorf("Expected chunked transfer encoding, got %v", resp.TransferEncoding) + } + + // 读取响应体,确保它是分多次接收的 + scanner := bufio.NewScanner(resp.Body) + lineCount := 0 + + last_time := time.Now() + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "Time:") && strings.Contains(line, "Msg:") { + lineCount++ + // 检查每个分块之间的时间间隔是否大于1秒 + if lineCount > 1 { + current_time := time.Now() + if current_time.Sub(last_time) < 1*time.Second { + t.Errorf("Chunk interval is less than 1 second") + } + } + } + } + + if err := scanner.Err(); err != nil { + t.Fatalf("Error reading stream: %v", err) + } + // 确保我们接收到了预期数量的分块 + if lineCount != expectedCount { + t.Errorf("Expected %d chunks, got %d", expectedCount, lineCount) + } +}