Skip to content
This repository was archived by the owner on Apr 30, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/http/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const (
VcapTraceHeader = "X-Vcap-Trace"
CfInstanceIdHeader = "X-CF-InstanceID"
CfAppInstance = "X-CF-APP-INSTANCE"
CfProcessInstance = "X-CF-PROCESS-INSTANCE"
CfRouterError = "X-Cf-RouterError"
)

Expand Down
96 changes: 76 additions & 20 deletions handlers/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"log/slog"
"net/http"
"regexp"
"strings"

"github.com/urfave/negroni/v3"

Expand All @@ -19,14 +18,22 @@ import (

const CfAppInstance = "X-CF-APP-INSTANCE"

type InvalidInstanceHeaderError struct {
type InvalidAppInstanceHeaderError struct {
headerValue string
}

func (err InvalidInstanceHeaderError) Error() string {
func (err InvalidAppInstanceHeaderError) Error() string {
return fmt.Sprintf("invalid-app-instance-header: %s", err.headerValue)
}

type InvalidProcessInstanceHeaderError struct {
headerValue string
}

func (err InvalidProcessInstanceHeaderError) Error() string {
return fmt.Sprintf("invalid-process-instance-header: %s", err.headerValue)
}

type lookupHandler struct {
registry registry.Registry
reporter metrics.ProxyReporter
Expand Down Expand Up @@ -75,8 +82,13 @@ func (l *lookupHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request, next
}

pool, err := l.lookup(r, logger)
if _, ok := err.(InvalidInstanceHeaderError); ok {
l.handleInvalidInstanceHeader(rw, r, logger)
if _, ok := err.(InvalidAppInstanceHeaderError); ok {
l.handleInvalidAppInstanceHeader(rw, r, logger)
return
}

if _, ok := err.(InvalidProcessInstanceHeaderError); ok {
l.handleInvalidProcessInstanceHeader(rw, r, logger)
return
}

Expand Down Expand Up @@ -109,7 +121,7 @@ func (l *lookupHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request, next
next(rw, r)
}

func (l *lookupHandler) handleInvalidInstanceHeader(rw http.ResponseWriter, r *http.Request, logger *slog.Logger) {
func (l *lookupHandler) handleInvalidAppInstanceHeader(rw http.ResponseWriter, r *http.Request, logger *slog.Logger) {
l.reporter.CaptureBadRequest()

AddRouterErrorHeader(rw, "invalid_cf_app_instance_header")
Expand All @@ -123,6 +135,20 @@ func (l *lookupHandler) handleInvalidInstanceHeader(rw http.ResponseWriter, r *h
)
}

func (l *lookupHandler) handleInvalidProcessInstanceHeader(rw http.ResponseWriter, r *http.Request, logger *slog.Logger) {
l.reporter.CaptureBadRequest()

AddRouterErrorHeader(rw, "invalid_cf_process_instance_header")
addNoCacheControlHeader(rw)

l.errorWriter.WriteError(
rw,
http.StatusBadRequest,
"Invalid X-CF-Process-Instance Header",
logger,
)
}

func (l *lookupHandler) handleMissingHost(rw http.ResponseWriter, r *http.Request, logger *slog.Logger) {
l.reporter.CaptureBadRequest()

Expand All @@ -147,11 +173,23 @@ func (l *lookupHandler) handleMissingRoute(rw http.ResponseWriter, r *http.Reque
returnStatus := http.StatusNotFound

if appInstanceHeader := r.Header.Get(router_http.CfAppInstance); appInstanceHeader != "" {
guid, idx := splitInstanceHeader(appInstanceHeader)
// parseAppInstanceHeader had already been called. So the error has already been checked.
guid, idx, _ := parseAppInstanceHeader(appInstanceHeader)
errorMsg = fmt.Sprintf("Requested instance ('%s') with guid ('%s') does not exist for route ('%s')", idx, guid, r.Host)
returnStatus = http.StatusBadRequest
}

if processInstanceHeader := r.Header.Get(router_http.CfProcessInstance); processInstanceHeader != "" {
// parseProcessInstanceHeader had already been called. So the error has already been checked.
guid, idx, _ := parseProcessInstanceHeader(processInstanceHeader)
if idx == "" {
errorMsg = fmt.Sprintf("Requested instance with process guid ('%s') does not exist for route ('%s')", guid, r.Host)
} else {
errorMsg = fmt.Sprintf("Requested instance ('%s') with process guid ('%s') does not exist for route ('%s')", idx, guid, r.Host)
}
returnStatus = http.StatusBadRequest
}

l.errorWriter.WriteError(
rw,
returnStatus,
Expand Down Expand Up @@ -193,29 +231,47 @@ func (l *lookupHandler) lookup(r *http.Request, logger *slog.Logger) (*route.End
appInstanceHeader := r.Header.Get(router_http.CfAppInstance)

if appInstanceHeader != "" {
err := validateInstanceHeader(appInstanceHeader)
appID, appIndex, err := parseAppInstanceHeader(appInstanceHeader)
if err != nil {
logger.Error("invalid-app-instance-header", log.ErrAttr(err))
return nil, InvalidInstanceHeaderError{headerValue: appInstanceHeader}
return nil, InvalidAppInstanceHeaderError{headerValue: appInstanceHeader}
}

appID, appIndex := splitInstanceHeader(appInstanceHeader)
return l.registry.LookupWithInstance(uri, appID, appIndex), nil
return l.registry.LookupWithAppInstance(uri, appID, appIndex), nil
}

processInstanceHeader := r.Header.Get(router_http.CfProcessInstance)
if processInstanceHeader != "" {
processID, processIndex, err := parseProcessInstanceHeader(processInstanceHeader)
if err != nil {
logger.Error("invalid-process-instance-header", log.ErrAttr(err))
return nil, InvalidProcessInstanceHeaderError{headerValue: processInstanceHeader}
}

return l.registry.LookupWithProcessInstance(uri, processID, processIndex), nil
}

return l.registry.Lookup(uri), nil
}

func validateInstanceHeader(appInstanceHeader string) error {
// Regex to match format of `APP_GUID:INSTANCE_ID`
r := regexp.MustCompile(`^[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}:\d+$`)
if !r.MatchString(appInstanceHeader) {
return fmt.Errorf("Incorrect %s header : %s", CfAppInstance, appInstanceHeader)
// Regex to match format of `APP_GUID:INSTANCE_ID`
var aReg = regexp.MustCompile(`^(?P<id>[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}):(?P<idx>\d+)$`)

func parseAppInstanceHeader(appInstanceHeader string) (string, string, error) {
matches := aReg.FindStringSubmatch(appInstanceHeader)
if len(matches) == 0 {
return "", "", fmt.Errorf("Incorrect %s header : %s", router_http.CfAppInstance, appInstanceHeader)
}
return nil
return matches[aReg.SubexpIndex("id")], matches[aReg.SubexpIndex("idx")], nil
}

func splitInstanceHeader(appInstanceHeader string) (string, string) {
appDetails := strings.Split(appInstanceHeader, ":")
return appDetails[0], appDetails[1]
// Regex to match format of `PROCESS_GUID:INSTANCE_ID` and `PROCESS_GUID`
var pReg = regexp.MustCompile(`^(?P<id>[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12})(:(?P<idx>\d+))?$`)

func parseProcessInstanceHeader(processInstanceHeader string) (string, string, error) {
matches := pReg.FindStringSubmatch(processInstanceHeader)
if len(matches) == 0 {
return "", "", fmt.Errorf("Incorrect %s header : %s", router_http.CfProcessInstance, processInstanceHeader)
}
return matches[pReg.SubexpIndex("id")], matches[pReg.SubexpIndex("idx")], nil
}
Loading