SagerNet/cronet-go

[Need help] How to do chunked body http request with cronet-go?

diyism opened this issue · 1 comments

I can do chunked body http request with net/http.Client,
my server.go can process the received chunk one by one:

$ go run server.go
2024/05/16 17:06:50 Starting server on port 1081...
2024/05/16 17:06:53 received: aaaaaaaaaaaaaa
2024/05/16 17:06:56 received: bbbbbbbbbbbbbb

but if I enable cronet by uncommenting this line of "Transport: &cronet.RoundTripper{}" in my client.go, it will block there forever,
I can't figure out how to do chunked body request with cronet-go,
any hint?

my server.go:

package main

import (
    "bufio"
    "encoding/base64"
    "fmt"
    "io"
    "log"
    "net"
    "net/http"
)

const (
    udpIP   = "127.0.0.1"
    udpPort = 1082
)

func handlePost(w http.ResponseWriter, r *http.Request) {
    var data []byte
    reader := bufio.NewReader(r.Body)

    for {
        chunkSizeStr, err := reader.ReadString('\n')
        if err != nil {
            if err == io.EOF {
                break
            }
            log.Printf("Error reading chunk size: %v", err)
            http.Error(w, "Error reading chunk size", http.StatusInternalServerError)
            return
        }

        var chunkSize int
        if _, err := fmt.Sscanf(chunkSizeStr, "%x", &chunkSize); err != nil {
            log.Printf("Invalid chunk size: %v", err)
            http.Error(w, "Invalid chunk size", http.StatusBadRequest)
            return
        }

        if chunkSize == 0 {
            break
        }

        chunk := make([]byte, chunkSize)
        if _, err := io.ReadFull(reader, chunk); err != nil {
            log.Printf("Error reading chunk data: %v", err)
            http.Error(w, "Error reading chunk data", http.StatusInternalServerError)
            return
        }

        if _, err := reader.Discard(2); err != nil { // Discard the trailing "\r\n"
            log.Printf("Error discarding CRLF: %v", err)
            http.Error(w, "Error discarding CRLF", http.StatusInternalServerError)
            return
        }

        decodedChunk, err := base64.StdEncoding.DecodeString(string(chunk))
        if err != nil {
            log.Printf("Base64 decoding error: %v", err)
            continue
        }

        data = append(data, decodedChunk...)
        log.Println("received: "+string(decodedChunk))
    }

    udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", udpIP, udpPort))
    if err != nil {
        log.Printf("Error resolving UDP address: %v", err)
        return
    }

    conn, err := net.DialUDP("udp", nil, udpAddr)
    if err != nil {
        log.Printf("Error dialing UDP: %v", err)
        return
    }
    defer conn.Close()

    _, err = conn.Write(data)
    if err != nil {
        log.Printf("Error sending UDP data: %v", err)
        return
    }

    w.Write([]byte("POST request processed"))
}

func main() {
    http.HandleFunc("/", handlePost)

    log.Println("Starting server on port 1081...")
    err := http.ListenAndServe(":1081", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

my client.go:

package main

import (
    "fmt"
    "io"
    "log"
    "net/http"
    "strconv"
    "time"

    //"github.com/SagerNet/cronet-go"
)

type ChunkedReader struct {
    chunks [][]byte
    delay  time.Duration
    index  int
}

func (r *ChunkedReader) Read(p []byte) (n int, err error) {
    log.Printf("Entering read function, current index: %d", r.index)
    if r.index >= len(r.chunks) {
        return 0, io.EOF
    }
    if r.index > 0 {
        time.Sleep(r.delay)
    }

    chunk := r.chunks[r.index]
    n = copy(p, chunk)
    log.Printf("Copied chunk %d, length: %d", r.index, n)
    r.index++
    return n, nil
}

func formatChunk(data []byte) []byte {
    size := strconv.FormatInt(int64(len(data)), 16)
    return []byte(fmt.Sprintf("%s\r\n%s\r\n", size, data))
}

func main() {
    client := &http.Client{
        //Transport: &cronet.RoundTripper{},
    }

    data1 := []byte("YWFhYWFhYWFhYWFhYWE=")
    data2 := []byte("YmJiYmJiYmJiYmJiYmI=")

    chunk1 := formatChunk(data1)
    chunk2 := formatChunk(data2)
    finalChunk := []byte("0\r\n\r\n")

    chunkedReader := &ChunkedReader{
        chunks: [][]byte{chunk1, chunk2, finalChunk},
        delay:  3 * time.Second,
    }

    headers := map[string]string{
        "Host":              "127.0.0.1",
        "Content-Type":      "application/octet-stream",
        "Transfer-Encoding": "chunked",
    }

    req, err := http.NewRequest("POST", "http://127.0.0.1:1081", chunkedReader)
    if err != nil {
        log.Fatalf("Failed to create request: %v", err)
    }

    req.Proto = "HTTP/1.1"
    req.ProtoMajor = 1
    req.ProtoMinor = 1

    for key, value := range headers {
        req.Header.Set(key, value)
    }

    log.Print("Before client.Do")
    resp, err := client.Do(req)
    if err != nil {
        log.Fatalf("Request failed: %v", err)
    }
    defer func() {
        if cerr := resp.Body.Close(); cerr != nil {
            log.Printf("Failed to close response body: %v", cerr)
        }
    }()

    log.Print("Waiting for request to complete...")
    fmt.Printf("Response status: %s\n", resp.Status)
}
    log.Print("Setting up upload provider and request parameters")
    uploadDataProvider := cronet.NewUploadDataProvider(uploadProvider)
    requestParams := cronet.NewURLRequestParams()
    requestParams.SetMethod("POST")
    requestParams.SetUploadDataProvider(uploadDataProvider)
    requestParams.SetUploadDataExecutor(executor)

    callback := cronet.NewURLRequestCallback(nil)
    urlRequest := cronet.NewURLRequest()
    urlRequest.InitWithParams(rt.Engine, "http://127.0.0.1:1081", requestParams, callback, rt.Executor)
    requestParams.Destroy()
    log.Print("Executing Cronet URL request")
    urlRequest.Start()