From eb330a597e46b3a6f157a262e286552a7d6d982d Mon Sep 17 00:00:00 2001 From: Mgrdich Date: Wed, 4 Dec 2024 18:11:43 +0400 Subject: [PATCH] DEV: finalize the readRequest and HTTP/1.x support functionality --- pkg/request.go | 40 +++++++++++++++++++ pkg/server.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++++- pkg/util.go | 25 ------------ 3 files changed, 142 insertions(+), 27 deletions(-) diff --git a/pkg/request.go b/pkg/request.go index b3de4cc..3345be4 100644 --- a/pkg/request.go +++ b/pkg/request.go @@ -128,3 +128,43 @@ func readRequest(b *bufio.Reader) (req *http.Request, err error) { return req, nil } + +// isH2Upgrade reports whether r represents the http2 "client preface" +// magic string. +func isH2Upgrade(r *http.Request) bool { + return r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0" +} + +func ExpectsContinue(r *http.Request) bool { + return hasToken(GetHeader(r.Header, "Expect"), "100-continue") +} + +func GetHeader(h http.Header, key string) string { + if v := h[key]; len(v) > 0 { + return v[0] + } + + return "" +} + +func HasHeader(h http.Header, key string) bool { + _, ok := h[key] + + return ok +} + +func wantsHttp10KeepAlive(r *http.Request) bool { + if r.ProtoMajor != 1 || r.ProtoMinor != 0 { + return false + } + + return hasToken(GetHeader(r.Header, "Connection"), "keep-alive") +} + +func wantsClose(r *http.Request) bool { + if r.Close { + return true + } + + return hasToken(GetHeader(r.Header, "Connection"), "close") +} diff --git a/pkg/server.go b/pkg/server.go index d551cef..32e80f9 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -80,6 +80,10 @@ const closeStr = "close" // This timeout is somewhat arbitrary (~latency around the planet). const rstAvoidanceDelay = 500 * time.Millisecond +// This should be >= 512 bytes for DetectContentType, +// but otherwise it's somewhat arbitrary. +const bufferBeforeChunkingSize = 2048 + type conn struct { // server is the server on which the connection arrived. // Immutable; never nil. @@ -110,6 +114,10 @@ type conn struct { // bufw writes to checkConnErrorWriter{c}, which populates werr on error. bufw *bufio.Writer + // tlsState is the TLS connection state when using TLS. + // nil means not TLS. + tlsState *tls.ConnectionState + // lastMethod is the method of the most recent request // on this connection, if any. lastMethod string @@ -1416,6 +1424,7 @@ func (c *conn) serve(ctx context.Context) { }() if tlsConn, ok := c.rwc.(*tls.Conn); ok { + // TODO tls and h2 support fmt.Println(tlsConn) } @@ -1583,6 +1592,25 @@ func (c *conn) serve(ctx context.Context) { } } +// http1ServerSupportsRequest reports whether Go's HTTP/1.x server +// supports the given request. +func http1ServerSupportsRequest(req *http.Request) bool { + if req.ProtoMajor == 1 { + return true + } + + // Accept "PRI * HTTP/2.0" upgrade requests, so Handlers can + // wire up their own HTTP/2 upgrades. + if req.ProtoMajor == 2 && req.ProtoMinor == 0 && + req.Method == "PRI" && req.RequestURI == "*" { + return true + } + + // Reject HTTP/0.x, and all other HTTP/2+ requests (which + // aren't encoded in ASCII anyway). + return false +} + var textprotoReaderPool sync.Pool func newTextprotoReader(br *bufio.Reader) *textproto.Reader { @@ -1648,9 +1676,81 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) { return nil, err } - fmt.Println(req, wholeReqDeadline) + if !http1ServerSupportsRequest(req) { + return nil, statusError{http.StatusHTTPVersionNotSupported, "unsupported protocol version"} + } + + c.lastMethod = req.Method + c.r.setInfiniteReadLimit() + + hosts, haveHost := req.Header["Host"] + isH2Upgraded := isH2Upgrade(req) + + if req.ProtoAtLeast(1, 1) && (!haveHost || len(hosts) == 0) && !isH2Upgraded && req.Method != http.MethodConnect { + return nil, badRequestError("missing required Host header") + } + + if len(hosts) == 1 && !httpguts.ValidHostHeader(hosts[0]) { + return nil, badRequestError("malformed Host header") + } + + for k, vv := range req.Header { + if !httpguts.ValidHeaderFieldName(k) { + return nil, badRequestError("invalid header name") + } + + for _, v := range vv { + if !httpguts.ValidHeaderFieldValue(v) { + return nil, badRequestError("invalid header value") + } + } + } + + delete(req.Header, "Host") + + ctx, cancelCtx := context.WithCancel(ctx) + + // override context since ctx is a private variable + // remove this line if it does not work + req = req.WithContext(ctx) + + req.RemoteAddr = c.remoteAddr + req.TLS = c.tlsState + + if body, ok := req.Body.(*body); ok { + body.doEarlyClose = true + } + + // Adjust the read deadline if necessary. + if !hdrDeadline.Equal(wholeReqDeadline) { + //nolint:errcheck + c.rwc.SetReadDeadline(wholeReqDeadline) + } + + w = &response{ + conn: c, + cancelCtx: cancelCtx, + req: req, + reqBody: req.Body, + handlerHeader: make(http.Header), + contentLength: -1, + closeNotifyCh: make(chan bool, 1), + + // We populate these ahead of time so we're not + // reading from req.Header after their Handler starts + // and maybe mutates it (Issue 14940) + wants10KeepAlive: wantsHttp10KeepAlive(req), + wantsClose: wantsClose(req), + } + + if isH2Upgraded { + w.closeAfterReply = true + } + + w.cw.res = w + w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize) - return nil, nil + return w, nil } func (c *conn) close() { diff --git a/pkg/util.go b/pkg/util.go index c16c749..2f7b5ca 100644 --- a/pkg/util.go +++ b/pkg/util.go @@ -2,27 +2,12 @@ package pkg import ( "fmt" - "net/http" "strings" "golang.org/x/net/http/httpguts" "myHttpServer/internal" ) -func GetHeader(h http.Header, key string) string { - if v := h[key]; len(v) > 0 { - return v[0] - } - - return "" -} - -func HasHeader(h http.Header, key string) bool { - _, ok := h[key] - - return ok -} - // hasToken reports whether token appears with v, ASCII // case-insensitive, with space or comma boundaries. // token must be all lowercase. @@ -69,10 +54,6 @@ func isTokenBoundary(b byte) bool { return b == ' ' || b == ',' || b == '\t' } -func ExpectsContinue(r *http.Request) bool { - return hasToken(GetHeader(r.Header, "Expect"), "100-continue") -} - func NumLeadingCRorLF(v []byte) (n int) { for _, b := range v { if b == '\r' || b == '\n' { @@ -110,9 +91,3 @@ func ValidMethod(method string) bool { */ return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1 } - -// isH2Upgrade reports whether r represents the http2 "client preface" -// magic string. -func isH2Upgrade(r *http.Request) bool { - return r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0" -}