|
|
|
@ -73,11 +73,40 @@ func isHTTPMethod(s string) bool { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getResourceHost(bufConn *BufConn, maxHeaderBytes, methodLen int) (resource string, host string, err error) { |
|
|
|
|
func getPlainText(bufConn *BufConn) (bool, error) { |
|
|
|
|
defer bufConn.setReadTimeout() |
|
|
|
|
|
|
|
|
|
if bufConn.canSetReadDeadline() { |
|
|
|
|
// Set deadline such that we close the connection quickly
|
|
|
|
|
// of no data was received from the Peek()
|
|
|
|
|
bufConn.SetReadDeadline(time.Now().UTC().Add(time.Second * 3)) |
|
|
|
|
} |
|
|
|
|
b, err := bufConn.Peek(1) |
|
|
|
|
if err != nil { |
|
|
|
|
return false, err |
|
|
|
|
} |
|
|
|
|
for _, method := range methods { |
|
|
|
|
if b[0] == method[0] { |
|
|
|
|
return true, nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return false, nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func getResourceHost(bufConn *BufConn, maxHeaderBytes int) (resource string, method string, host string, err error) { |
|
|
|
|
defer bufConn.setReadTimeout() |
|
|
|
|
|
|
|
|
|
var data []byte |
|
|
|
|
for dataLen := 0; methodLen+dataLen < maxHeaderBytes; dataLen += 8 { |
|
|
|
|
if data, err = bufConn.Peek(methodLen + dataLen); err != nil { |
|
|
|
|
return "", "", err |
|
|
|
|
for dataLen := 1; dataLen < maxHeaderBytes; dataLen++ { |
|
|
|
|
if bufConn.canSetReadDeadline() { |
|
|
|
|
// Set deadline such that we close the connection quickly
|
|
|
|
|
// of no data was received from the Peek()
|
|
|
|
|
bufConn.SetReadDeadline(time.Now().UTC().Add(time.Second * 3)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
data, err = bufConn.bufReader.Peek(dataLen) |
|
|
|
|
if err != nil { |
|
|
|
|
return "", "", "", err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tokens := strings.Split(string(data), "\n") |
|
|
|
@ -85,8 +114,17 @@ func getResourceHost(bufConn *BufConn, maxHeaderBytes, methodLen int) (resource |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if resource == "" { |
|
|
|
|
resource = strings.SplitN(tokens[0][methodLen:], " ", 2)[0] |
|
|
|
|
if method == "" && resource == "" { |
|
|
|
|
if i := strings.IndexByte(tokens[0], ' '); i == -1 { |
|
|
|
|
return "", "", "", fmt.Errorf("malformed HTTP request %s, from %s", tokens[0], bufConn.LocalAddr()) |
|
|
|
|
} |
|
|
|
|
httpTokens := strings.SplitN(tokens[0], " ", 3) |
|
|
|
|
if len(httpTokens) < 3 { |
|
|
|
|
return "", "", "", fmt.Errorf("malformed HTTP request %s, from %s", tokens[0], bufConn.LocalAddr()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
method = httpTokens[0] |
|
|
|
|
resource = httpTokens[1] |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, token := range tokens[1:] { |
|
|
|
@ -99,7 +137,7 @@ func getResourceHost(bufConn *BufConn, maxHeaderBytes, methodLen int) (resource |
|
|
|
|
token = strings.ToLower(token) |
|
|
|
|
if strings.HasPrefix(token, "host: ") { |
|
|
|
|
host = strings.TrimPrefix(strings.TrimSuffix(token, "\r"), "host: ") |
|
|
|
|
return resource, host, nil |
|
|
|
|
return resource, method, host, nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -108,7 +146,7 @@ func getResourceHost(bufConn *BufConn, maxHeaderBytes, methodLen int) (resource |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return resource, host, nil |
|
|
|
|
return "", "", "", fmt.Errorf("malformed HTTP request from %s", bufConn.LocalAddr()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type acceptResult struct { |
|
|
|
@ -175,38 +213,14 @@ func (listener *httpListener) start() { |
|
|
|
|
tcpConn.SetKeepAlivePeriod(listener.tcpKeepAliveTimeout) |
|
|
|
|
|
|
|
|
|
bufconn := newBufConn(tcpConn, listener.readTimeout, listener.writeTimeout) |
|
|
|
|
|
|
|
|
|
// Peek bytes of maximum length of all HTTP methods.
|
|
|
|
|
data, err := bufconn.Peek(methodMaxLen) |
|
|
|
|
if err != nil { |
|
|
|
|
// Peek could fail legitimately when clients abruptly close
|
|
|
|
|
// connection. E.g. Chrome browser opens connections speculatively to
|
|
|
|
|
// speed up loading of a web page. Peek may also fail due to network
|
|
|
|
|
// saturation on a transport with read timeout set. All other kind of
|
|
|
|
|
// errors should be logged for further investigation. Thanks @brendanashworth.
|
|
|
|
|
if !isRoutineNetErr(err) { |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
|
ctx := logger.SetReqInfo(context.Background(), reqInfo) |
|
|
|
|
logger.LogIf(ctx, err) |
|
|
|
|
} |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Return bufconn if read data is a valid HTTP method.
|
|
|
|
|
tokens := strings.SplitN(string(data), " ", 2) |
|
|
|
|
if isHTTPMethod(tokens[0]) { |
|
|
|
|
if listener.tlsConfig != nil { |
|
|
|
|
// As TLS is configured and we got plain text HTTP request,
|
|
|
|
|
// return 403 (forbidden) error.
|
|
|
|
|
bufconn.Write(sslRequiredErrMsg) |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var resource, host string |
|
|
|
|
if resource, host, err = getResourceHost(bufconn, listener.maxHeaderBytes, len(tokens[0])+1); err != nil { |
|
|
|
|
if listener.tlsConfig != nil { |
|
|
|
|
ok, err := getPlainText(bufconn) |
|
|
|
|
if err != nil { |
|
|
|
|
// Peek could fail legitimately when clients abruptly close
|
|
|
|
|
// connection. E.g. Chrome browser opens connections speculatively to
|
|
|
|
|
// speed up loading of a web page. Peek may also fail due to network
|
|
|
|
|
// saturation on a transport with read timeout set. All other kind of
|
|
|
|
|
// errors should be logged for further investigation. Thanks @brendanashworth.
|
|
|
|
|
if !isRoutineNetErr(err) { |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
@ -216,27 +230,16 @@ func (listener *httpListener) start() { |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
header := make(http.Header) |
|
|
|
|
if host != "" { |
|
|
|
|
header.Add("Host", host) |
|
|
|
|
if ok { |
|
|
|
|
// As TLS is configured and we got plain text HTTP request,
|
|
|
|
|
// return 403 (forbidden) error.
|
|
|
|
|
bufconn.Write(sslRequiredErrMsg) |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
bufconn.setRequest(&http.Request{ |
|
|
|
|
Method: tokens[0], |
|
|
|
|
URL: &url.URL{Path: resource}, |
|
|
|
|
Host: bufconn.LocalAddr().String(), |
|
|
|
|
Header: header, |
|
|
|
|
}) |
|
|
|
|
bufconn.setUpdateFuncs(listener.updateBytesReadFunc, listener.updateBytesWrittenFunc) |
|
|
|
|
|
|
|
|
|
send(acceptResult{bufconn, nil}, doneCh) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if listener.tlsConfig != nil { |
|
|
|
|
// As the listener is configured with TLS, try to do TLS handshake, drop the connection if it fails.
|
|
|
|
|
tlsConn := tls.Server(bufconn, listener.tlsConfig) |
|
|
|
|
if err = tlsConn.Handshake(); err != nil { |
|
|
|
|
if err := tlsConn.Handshake(); err != nil { |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
|
ctx := logger.SetReqInfo(context.Background(), reqInfo) |
|
|
|
@ -244,61 +247,50 @@ func (listener *httpListener) start() { |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Check whether the connection contains HTTP request or not.
|
|
|
|
|
bufconn = newBufConn(tlsConn, listener.readTimeout, listener.writeTimeout) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Peek bytes of maximum length of all HTTP methods.
|
|
|
|
|
data, err = bufconn.Peek(methodMaxLen) |
|
|
|
|
if err != nil { |
|
|
|
|
if !isRoutineNetErr(err) { |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
|
ctx := logger.SetReqInfo(context.Background(), reqInfo) |
|
|
|
|
logger.LogIf(ctx, err) |
|
|
|
|
} |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
resource, method, host, err := getResourceHost(bufconn, listener.maxHeaderBytes) |
|
|
|
|
if err != nil { |
|
|
|
|
// Peek could fail legitimately when clients abruptly close
|
|
|
|
|
// connection. E.g. Chrome browser opens connections speculatively to
|
|
|
|
|
// speed up loading of a web page. Peek may also fail due to network
|
|
|
|
|
// saturation on a transport with read timeout set. All other kind of
|
|
|
|
|
// errors should be logged for further investigation. Thanks @brendanashworth.
|
|
|
|
|
if !isRoutineNetErr(err) { |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
|
ctx := logger.SetReqInfo(context.Background(), reqInfo) |
|
|
|
|
logger.LogIf(ctx, err) |
|
|
|
|
} |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Return bufconn if read data is a valid HTTP method.
|
|
|
|
|
tokens := strings.SplitN(string(data), " ", 2) |
|
|
|
|
if isHTTPMethod(tokens[0]) { |
|
|
|
|
var resource, host string |
|
|
|
|
if resource, host, err = getResourceHost(bufconn, listener.maxHeaderBytes, len(tokens[0])+1); err != nil { |
|
|
|
|
if !isRoutineNetErr(err) { |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
|
ctx := logger.SetReqInfo(context.Background(), reqInfo) |
|
|
|
|
logger.LogIf(ctx, err) |
|
|
|
|
} |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
// Return bufconn if read data is a valid HTTP method.
|
|
|
|
|
if !isHTTPMethod(method) { |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
|
ctx := logger.SetReqInfo(context.Background(), reqInfo) |
|
|
|
|
logger.LogIf(ctx, fmt.Errorf("malformed HTTP invalid HTTP method %s", method)) |
|
|
|
|
|
|
|
|
|
header := make(http.Header) |
|
|
|
|
if host != "" { |
|
|
|
|
header.Add("Host", host) |
|
|
|
|
} |
|
|
|
|
bufconn.setRequest(&http.Request{ |
|
|
|
|
Method: tokens[0], |
|
|
|
|
URL: &url.URL{Path: resource}, |
|
|
|
|
Host: bufconn.LocalAddr().String(), |
|
|
|
|
Header: header, |
|
|
|
|
}) |
|
|
|
|
bufconn.setUpdateFuncs(listener.updateBytesReadFunc, listener.updateBytesWrittenFunc) |
|
|
|
|
|
|
|
|
|
send(acceptResult{bufconn, nil}, doneCh) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
reqInfo := (&logger.ReqInfo{}).AppendTags("remoteAddr", bufconn.RemoteAddr().String()) |
|
|
|
|
reqInfo.AppendTags("localAddr", bufconn.LocalAddr().String()) |
|
|
|
|
ctx := logger.SetReqInfo(context.Background(), reqInfo) |
|
|
|
|
logger.LogIf(ctx, err) |
|
|
|
|
|
|
|
|
|
bufconn.Close() |
|
|
|
|
return |
|
|
|
|
header := make(http.Header) |
|
|
|
|
if host != "" { |
|
|
|
|
header.Add("Host", host) |
|
|
|
|
} |
|
|
|
|
bufconn.setRequest(&http.Request{ |
|
|
|
|
Method: method, |
|
|
|
|
URL: &url.URL{Path: resource}, |
|
|
|
|
Host: bufconn.LocalAddr().String(), |
|
|
|
|
Header: header, |
|
|
|
|
}) |
|
|
|
|
bufconn.setUpdateFuncs(listener.updateBytesReadFunc, listener.updateBytesWrittenFunc) |
|
|
|
|
|
|
|
|
|
send(acceptResult{bufconn, nil}, doneCh) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Closure to handle TCPListener until done channel is closed.
|
|
|
|
|