quic-go / webtransport-go

WebTransport implementation based on quic-go (https://datatracker.ietf.org/doc/draft-ietf-webtrans-http3/)
https://quic-go.net
MIT License
346 stars 55 forks source link

Help with echo server #117

Closed anderspitman closed 8 months ago

anderspitman commented 8 months ago

I think I have some fundamental misunderstandings about how to use this library. I'm trying to get a simple echo server running. The following is a self-contained example (uses certmagic to automatically get Let's Encrypt certs):

package main

import (
    "context"
    "crypto/tls"
    "fmt"
    "io"
    "log"
    "net/http"

    "github.com/caddyserver/certmagic"
    "github.com/quic-go/quic-go"
    "github.com/quic-go/quic-go/http3"
    "github.com/quic-go/webtransport-go"
)

func main() {
    go runServer()
    runClient()
}

func runServer() {

    tlsConfig := getTlsConfig()

    ctx := context.Background()

    wtServer := webtransport.Server{
        H3: http3.Server{
            Addr:       ":443",
            TLSConfig:  tlsConfig,
            QuicConfig: &quic.Config{},
        },
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {

        wtSession, err := wtServer.Upgrade(w, r)
        if err != nil {
            fmt.Println(err)
            return
        }

        stream, err := wtSession.AcceptStream(ctx)
        if err != nil {
            fmt.Println(err)
            return
        }

        bytes, err := io.ReadAll(stream)
        if err != nil {
            fmt.Println(err)
            return
        }

        fmt.Println(string(bytes))

        stream.Write(bytes)

        err = stream.Close()
        if err != nil {
            fmt.Println(err)
            return
        }
    })

    log.Fatal(wtServer.ListenAndServe())
}

func runClient() {
    ctx := context.Background()

    var d webtransport.Dialer
    _, wtSession, err := d.Dial(ctx, "https://example.com", nil)
    if err != nil {
        panic(err)
    }

    stream, err := wtSession.OpenStreamSync(ctx)
    if err != nil {
        panic(err)
    }

    stream.Write([]byte("Hi there"))
    stream.CancelWrite(0)

    bytes, err := io.ReadAll(stream)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(bytes))
}

func getTlsConfig() *tls.Config {
    certmagic.DefaultACME.DisableHTTPChallenge = true
    certmagic.DefaultACME.Agreed = true

    certmagic.Default.OnDemand = &certmagic.OnDemandConfig{
        DecisionFunc: func(ctx context.Context, name string) error {
            return nil
        },
    }
    certConfig := certmagic.NewDefault()

    tlsConfig := &tls.Config{
        GetCertificate: certConfig.GetCertificate,
        NextProtos:     []string{"h3", "http/1.1", "acme-tls/1"},
    }

    return tlsConfig
}

I'm never seeing anything printed, either on the client or server side. Interestingly, if I sleep for a second before stream.CancelWrite(0) then I get an error log: "stream canceled with error code 0". I think I'm incorrectly assuming that EOF will be sent after a call to CancelWrite. Is that not the case? If not, is there a correct way to signal end of stream from the writer side?

anderspitman commented 8 months ago

I believe I've solved the problem. My misunderstanding is that I was assuming CancelWrite() was the correct way to close the send side of a stream. But according to the go-quic docs here, calling Close is the way to do it. I had assumed that would also close the receive side, but that's not the case. After sawpping CancelWrite for Close my code appears to be working.

anderspitman commented 8 months ago

@marten-seemann would you be interested in a PR that adds an examples directory and a simple echo server example, or is that not something you want in the repo?

Looks like I'm not the first to struggle with this: https://github.com/quic-go/webtransport-go/issues/103

marten-seemann commented 8 months ago

Sure, sounds like a good idea! Want to create a PR?

anderspitman commented 8 months ago

Submitted #122