diff --git a/p2p/http/libp2phttp.go b/p2p/http/libp2phttp.go index 31b6fe33d3..9da308c4d1 100644 --- a/p2p/http/libp2phttp.go +++ b/p2p/http/libp2phttp.go @@ -153,20 +153,6 @@ func (h *HTTPHost) SetHttpHandlerAtPath(p protocol.ID, path string, handler http h.rootHandler.Handle(path, handler) } -// TODO do we need this? Kind of complicated. We could the same with http.Serve and a custom libp2p listener. -// SetCustomHTTPHandler sets a custom HTTP handler for all HTTP over libp2p -// streams. It is up to the user to make sure the well-known mapping is set up -// correctly (NewWellKnownHandler could be helpful). This is useful if you're -// bringing in an existing HTTP ServeMux (or similar) to be used on top of -// libp2p streams. -// Use host.RemoveStreamHandler(libp2phttp.ProtocolIDForMultistreamSelect) to remove this handler. -func SetCustomHTTPHandler(streamHost host.Host, handler http.Handler) { - streamHost.SetStreamHandler(ProtocolIDForMultistreamSelect, func(s network.Stream) { - defer s.Close() - ServeReadWriter(s, handler) - }) -} - type roundTripperOpts struct { // todo SkipClientAuth bool preferHTTPTransport bool diff --git a/p2p/http/responsewriter.go b/p2p/http/responsewriter.go deleted file mode 100644 index ad037038f1..0000000000 --- a/p2p/http/responsewriter.go +++ /dev/null @@ -1,138 +0,0 @@ -package libp2phttp - -import ( - "bufio" - "fmt" - "io" - "net/http" - "strconv" - "sync" - - logging "github.com/ipfs/go-log/v2" -) - -var bufWriterPool = sync.Pool{ - New: func() any { - return bufio.NewWriterSize(nil, 4<<10) - }, -} - -var bufReaderPool = sync.Pool{ - New: func() any { - return bufio.NewReaderSize(nil, 4<<10) - }, -} - -var log = logging.Logger("p2phttp") - -var _ http.ResponseWriter = (*httpResponseWriter)(nil) - -type httpResponseWriter struct { - w *bufio.Writer - directWriter io.Writer - header http.Header - wroteHeader bool - inferredContentLength bool -} - -// Header implements http.ResponseWriter -func (w *httpResponseWriter) Header() http.Header { - return w.header -} - -// Write implements http.ResponseWriter -func (w *httpResponseWriter) Write(b []byte) (int, error) { - if !w.wroteHeader { - if w.header.Get("Content-Type") == "" { - contentType := http.DetectContentType(b) - w.header.Set("Content-Type", contentType) - } - - if w.w.Available() > len(b) { - return w.w.Write(b) - } - } - - // Ran out of buffered space, We should check if we need to write the headers. - if !w.wroteHeader { - if w.header.Get("Content-Length") == "" && w.header.Get("Transfer-Encoding") == "" { - log.Error("Handler did not set the Content-Length or Transfer-Encoding header. Clients may fail to parse this response") - } - w.WriteHeader(http.StatusOK) - } else if w.inferredContentLength { - log.Error("Tried to infer content length, but another write happened, so content length is wrong and headers are already written. This response may fail to parse by clients") - } - - return w.w.Write(b) -} - -func (w *httpResponseWriter) flush() { - if !w.wroteHeader { - // Be nice for small things - if w.header.Get("Content-Length") == "" { - w.inferredContentLength = true - w.header.Set("Content-Length", strconv.Itoa(w.w.Buffered())) - } - - // If WriteHeader has not yet been called, Write calls - // WriteHeader(http.StatusOK) before writing the data. - w.WriteHeader(http.StatusOK) - } - w.w.Flush() -} - -// WriteHeader implements http.ResponseWriter -func (w *httpResponseWriter) WriteHeader(statusCode int) { - if w.wroteHeader { - log.Errorf("multiple WriteHeader calls dropping %d", statusCode) - return - } - w.wroteHeader = true - w.writeStatusLine(statusCode) - w.header.Write(w.directWriter) - w.directWriter.Write([]byte("\r\n")) -} - -// Copied from Go stdlib https://cs.opensource.google/go/go/+/refs/tags/go1.20.2:src/net/http/server.go;drc=ea4631cc0cf301c824bd665a7980c13289ab5c9d;l=1533 -func (w *httpResponseWriter) writeStatusLine(code int) { - // Stack allocated - scratch := [4]byte{} - // Always HTTP/1.1 - w.directWriter.Write([]byte("HTTP/1.1 ")) - - if text := http.StatusText(code); text != "" { - w.directWriter.Write(strconv.AppendInt(scratch[:0], int64(code), 10)) - w.directWriter.Write([]byte(" ")) - w.directWriter.Write([]byte(text)) - w.directWriter.Write([]byte("\r\n")) - } else { - // don't worry about performance - fmt.Fprintf(w.directWriter, "%03d status code %d\r\n", code, code) - } -} - -func ServeReadWriter(rw io.ReadWriter, handler http.Handler) { - r := bufReaderPool.Get().(*bufio.Reader) - r.Reset(rw) - defer bufReaderPool.Put(r) - - buffedWriter := bufWriterPool.Get().(*bufio.Writer) - buffedWriter.Reset(rw) - defer bufWriterPool.Put(buffedWriter) - w := httpResponseWriter{ - w: buffedWriter, - directWriter: rw, - header: make(http.Header), - } - defer w.flush() - - req, err := http.ReadRequest(r) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.w.Flush() - log.Errorf("error reading request: %s", err) - return - } - - handler.ServeHTTP(&w, req) -} diff --git a/p2p/http/responsewriter_test.go b/p2p/http/responsewriter_test.go deleted file mode 100644 index 2eb4007d0a..0000000000 --- a/p2p/http/responsewriter_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package libp2phttp - -import ( - "bufio" - "bytes" - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestResponseLooksCorrect(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost/", bytes.NewReader([]byte(""))) - require.NoError(t, err) - reqBuf := bytes.Buffer{} - req.Write(&reqBuf) - - resp := bytes.Buffer{} - respWriter := bufio.NewWriter(&resp) - s := bufio.NewReadWriter(bufio.NewReader(&reqBuf), respWriter) - - ServeReadWriter(s, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello world")) - })) - - respWriter.Flush() - parsedResponse, err := http.ReadResponse(bufio.NewReader(&resp), nil) - require.NoError(t, err) - respBody, err := io.ReadAll(parsedResponse.Body) - require.NoError(t, err) - require.Equal(t, "Hello world", string(respBody)) - require.Equal(t, len("Hello world"), int(parsedResponse.ContentLength)) -} - -func TestMultipleWritesButSmallResponseLooksCorrect(t *testing.T) { - req, err := http.NewRequest("GET", "http://localhost/", bytes.NewReader([]byte(""))) - require.NoError(t, err) - reqBuf := bytes.Buffer{} - req.Write(&reqBuf) - - resp := bytes.Buffer{} - respWriter := bufio.NewWriter(&resp) - s := bufio.NewReadWriter(bufio.NewReader(&reqBuf), respWriter) - - ServeReadWriter(s, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Hello world 1 ")) - w.Write([]byte("2 ")) - w.Write([]byte("3 ")) - w.Write([]byte("4 ")) - w.Write([]byte("5 ")) - w.Write([]byte("6 ")) - })) - - respWriter.Flush() - parsedResponse, err := http.ReadResponse(bufio.NewReader(&resp), nil) - require.NoError(t, err) - respBody, err := io.ReadAll(parsedResponse.Body) - require.NoError(t, err) - require.Equal(t, "Hello world 1 2 3 4 5 6 ", string(respBody)) - require.Equal(t, len("Hello world 1 2 3 4 5 6 "), int(parsedResponse.ContentLength)) -}