Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: removes context on the C side #1404

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 7 additions & 8 deletions cgi.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import "C"
import (
"crypto/tls"
"net"
"net/http"
"path/filepath"
"strings"
"unsafe"
Expand Down Expand Up @@ -48,7 +47,8 @@ var knownServerKeys = []string{
//
// TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
func addKnownVariablesToServer(thread *phpThread, request *http.Request, fc *FrankenPHPContext, trackVarsArray *C.zval) {
func addKnownVariablesToServer(thread *phpThread, fc *FrankenPHPContext, trackVarsArray *C.zval) {
request := fc.request
keys := mainThread.knownServerKeys
// Separate remote IP and port; more lenient than net.SplitHostPort
var ip, port string
Expand Down Expand Up @@ -160,8 +160,8 @@ func packCgiVariable(key *C.zend_string, value string) C.ht_key_value_pair {
return C.ht_key_value_pair{key, toUnsafeChar(value), C.size_t(len(value))}
}

func addHeadersToServer(request *http.Request, thread *phpThread, fc *FrankenPHPContext, trackVarsArray *C.zval) {
for field, val := range request.Header {
func addHeadersToServer(thread *phpThread, fc *FrankenPHPContext, trackVarsArray *C.zval) {
for field, val := range fc.request.Header {
if k := mainThread.commonHeaders[field]; k != nil {
v := strings.Join(val, ", ")
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
Expand All @@ -186,11 +186,10 @@ func addPreparedEnvToServer(fc *FrankenPHPContext, trackVarsArray *C.zval) {
//export go_register_variables
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
thread := phpThreads[threadIndex]
r := thread.getActiveRequest()
fc := r.Context().Value(contextKey).(*FrankenPHPContext)
fc := thread.getRequestContext()

addKnownVariablesToServer(thread, r, fc, trackVarsArray)
addHeadersToServer(r, thread, fc, trackVarsArray)
addKnownVariablesToServer(thread, fc, trackVarsArray)
addHeadersToServer(thread, fc, trackVarsArray)

// The Prepared Environment is registered last and can overwrite any previous values
addPreparedEnvToServer(fc, trackVarsArray)
Expand Down
161 changes: 161 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package frankenphp

import (
"context"
"net/http"
"os"
"strings"
"time"

"go.uber.org/zap"
)

// FrankenPHPContext provides contextual information about the Request to handle.
type FrankenPHPContext struct {
documentRoot string
splitPath []string
env PreparedEnv
logger *zap.Logger
request *http.Request
originalRequest *http.Request

docURI string
pathInfo string
scriptName string
scriptFilename string

// Whether the request is already closed by us
isDone bool

responseWriter http.ResponseWriter
exitStatus int

done chan interface{}
startedAt time.Time
}

// FromContext extracts the FrankenPHPContext from a context.
func FromContext(ctx context.Context) (fctx *FrankenPHPContext, ok bool) {
fctx, ok = ctx.Value(contextKey).(*FrankenPHPContext)
return
}

// NewRequestWithContext creates a new FrankenPHP request context.
func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error) {
fc := &FrankenPHPContext{
done: make(chan interface{}),
startedAt: time.Now(),
request: r,
}
for _, o := range opts {
if err := o(fc); err != nil {
return nil, err
}
}

if fc.logger == nil {
fc.logger = logger
}

if fc.documentRoot == "" {
if EmbeddedAppPath != "" {
fc.documentRoot = EmbeddedAppPath
} else {
var err error
if fc.documentRoot, err = os.Getwd(); err != nil {
return nil, err
}
}
}

if fc.splitPath == nil {
fc.splitPath = []string{".php"}
}

if fc.env == nil {
fc.env = make(map[string]string)
}

if splitPos := splitPos(fc, r.URL.Path); splitPos > -1 {
fc.docURI = r.URL.Path[:splitPos]
fc.pathInfo = r.URL.Path[splitPos:]

// Strip PATH_INFO from SCRIPT_NAME
fc.scriptName = strings.TrimSuffix(r.URL.Path, fc.pathInfo)

// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
if fc.scriptName != "" && !strings.HasPrefix(fc.scriptName, "/") {
fc.scriptName = "/" + fc.scriptName
}
}

// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName)

c := context.WithValue(r.Context(), contextKey, fc)

return r.WithContext(c), nil
}

func newFakeContext(requestPath string, opts ...RequestOption) (*FrankenPHPContext, error) {
r, err := http.NewRequest(http.MethodGet, requestPath, nil)
if err != nil {
return nil, err
}

fr, err := NewRequestWithContext(r, opts...)
if err != nil {
return nil, err
}

fc, _ := FromContext(fr.Context())

return fc, nil
}

// closeContext sends the response to the client
func (fc *FrankenPHPContext) closeContext() {
if !fc.isDone {
close(fc.done)
fc.isDone = true
}
}

// validate checks if the request should be outright rejected
func (fc *FrankenPHPContext) validate() bool {
if !strings.Contains(fc.request.URL.Path, "\x00") {
return true
}
fc.rejectBadRequest("Invalid request path")
return false
}

func (fc *FrankenPHPContext) clientHasClosed() bool {
select {
case <-fc.request.Context().Done():
return true
default:
return false
}
}

// reject sends a response with the given status code and message
func (fc *FrankenPHPContext) reject(statusCode int, message string) {
if fc.isDone {
return
}

rw := fc.responseWriter
if rw != nil {
rw.WriteHeader(statusCode)
_, _ = rw.Write([]byte(message))
rw.(http.Flusher).Flush()
}

fc.closeContext()
}

func (fc *FrankenPHPContext) rejectBadRequest(message string) {
fc.reject(http.StatusBadRequest, message)
}
Loading
Loading