google / go-cloud

The Go Cloud Development Kit (Go CDK): A library and tools for open cloud development in Go.

Home Page:https://gocloud.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

server/requestlog: support assert `http.ResponseWriter` to `http.Flusher`

ashertz-dev opened this issue · comments

commented

Please use a title starting with the name of the affected package, or "all",
followed by a colon, followed by a short summary of the feature request.
Example: blob/gcsblob: add support for more blobbing.

Is your feature request related to a problem? Please describe.

When I try a http server, and transfer message by Transfer-Encoding: "chunked", it panic by interface conversion: *requestlog.responseStats is not http.Flusher: missing method Flush

Describe the solution you'd like

I want to add method Flush for type responseStats struct

Describe alternatives you've considered

Ues net/http rather than gocloud.dev to run http server

Additional context

Do you have a stack trace for the panic?

commented
  • Panic message
2023/06/02 10:23:01 http: panic serving 127.0.0.1:63023: interface conversion: *requestlog.responseStats is not http.Flusher: missing method Flush

  • This code is a simplified version of the code that causes a panic.
package main

import (
	"fmt"
	"net/http"
	"os"
	"time"

	restful "github.com/emicklei/go-restful/v3"
	"gocloud.dev/server"
	"gocloud.dev/server/requestlog"
	"golang.org/x/exp/slog"
)

type SlogLogger struct {
	slogger *slog.Logger
}

func NewSlogLogger(logger *slog.Logger) *SlogLogger {
	return &SlogLogger{
		slogger: logger,
	}
}

func (l *SlogLogger) Log(ent *requestlog.Entry) {
	l.slogger.Info("",
		"method", ent.Request.Method,
		"url", ent.Request.URL,
		"proto", ent.Request.Proto,
		"user-agent", ent.Request.UserAgent(),
		"remote", ent.Request.RemoteAddr,
		"referer", ent.Request.Referer(),
		"status", ent.Status,
		"size", ent.ResponseBodySize,
		"lat-ms", ent.Latency.Milliseconds(),
	)
}

func main() {
	c := restful.NewContainer()
	c.Add(webServiceV1beta2())
	restful.EnableTracing(true)

	slogger := slog.New(slog.NewTextHandler(os.Stdout))
	srvOpts := &server.Options{
		RequestLogger: NewSlogLogger(slogger),
	}
	srv := server.New(c, srvOpts)
	if err := srv.ListenAndServe(":8084"); err != nil {
		panic(fmt.Errorf("http listen and serve: %v", err))
	}
}
func webServiceV1beta2() *restful.WebService {
	ws := new(restful.WebService)
	ws.
		Path("/api/v1beta1").
		Consumes(restful.MIME_JSON).
		Produces(restful.MIME_JSON)

	ws.Route(ws.GET("/log").To(Log))

	return ws
}

func Log(req *restful.Request, res *restful.Response) {
	flusher := res.ResponseWriter.(http.Flusher)
	fmt.Printf("flusher: %+v\n", flusher)

	res.Header().Set("X-Content-Type-Options", "nosniff")
	res.Header().Set("Connection", "Keep-Alive")
	res.Header().Set("Transfer-Encoding", "chunked")

	ticker := time.NewTicker(time.Second)
	go func() {
		for t := range ticker.C {
			_, err := res.Write([]byte("Chunk\r\n"))
			if err != nil {
				fmt.Printf("err: %+v\n", err)
				return
			}
			flusher.Flush()
			fmt.Println("Tick at", t)
		}
	}()
	time.Sleep(time.Second * 5)
	ticker.Stop()
	fmt.Println("Finished: should return Content-Length: 0 here")
	res.Header().Set("Content-Length", "0")

}