server/requestlog: support assert `http.ResponseWriter` to `http.Flusher`
ashertz-dev opened this issue · comments
Asher 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
Robert van Gent commented
Do you have a stack trace for the panic?
Asher 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")
}