diff --git a/Dockerfile b/Dockerfile index 5c0b66e..14a14e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,28 +3,32 @@ FROM golang:alpine AS build-env ENV GOPATH=/gopath ENV PATH=$GOPATH/bin:$PATH -ADD . /gopath/src/github.com/dfeyer/flow-debugproxy +WORKDIR /app RUN apk update && \ apk upgrade && \ apk add git -RUN cd /gopath/src/github.com/dfeyer/flow-debugproxy \ - && go get \ - && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o flow-debugproxy +ADD go.mod . +ADD go.sum . +RUN go mod download +ADD . . +RUN CGO_ENABLED=0 go build -o flow-debugproxy # # Build step FROM alpine WORKDIR /app -COPY --from=build-env /gopath/src/github.com/dfeyer/flow-debugproxy/flow-debugproxy /app/ +COPY --from=build-env /app/flow-debugproxy /app/ ENV ADDITIONAL_ARGS "" -ENV XDEBUG_PORT 9010 +ENV XDEBUG 9003:Development -ENV IDE_IP 127.0.0.1 -ENV IDE_PORT 9000 +ENV IDE_IP host.docker.internal +ENV IDE_PORT 9010 ENV FRAMEWORK "flow" -ENTRYPOINT ["sh", "-c", "./flow-debugproxy --xdebug 0.0.0.0:${XDEBUG_PORT} --framework ${FRAMEWORK} --ide ${IDE_IP}:${IDE_PORT} ${ADDITIONAL_ARGS}"] +ENV LOCAL_ROOT "" + +ENTRYPOINT ["sh", "-c", "./flow-debugproxy --xdebug ${XDEBUG} --framework ${FRAMEWORK} --ide ${IDE_IP}:${IDE_PORT} --localroot \"${LOCAL_ROOT}\" ${ADDITIONAL_ARGS}"] diff --git a/README.md b/README.md index ed5cd6e..4ee81e0 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ between your PHP file and the proxy class. Build your own -------------- +When you just want to use this inside Docker you probably want to scroll further down and skip until you reach the **Use with Docker** part. No need to build it on your host machine yourself. + # Get the dependecies go get # Build @@ -43,57 +45,65 @@ Show help Use with Docker --------------- -Use the [official docker image](https://hub.docker.com/r/dfeyer/flow-debugproxy/) and follow the instruction for the configuration. - -##### PHP configuration +Make sure xdebug is installed in your dev container. You can use drydock for this or add the following to your development Dockerfile ``` -[Xdebug] -zend_extension=/.../xdebug.so -xdebug.remote_enable=1 -xdebug.idekey=PHPSTORM -; The IP or name of the proxy container -xdebug.remote_host=debugproxy -; The proxy port (9010 by default, to not have issue is you use PHP FPM, already on port 9000) -xdebug.remote_port=9010 -;xdebug.remote_log=/tmp/xdebug.log -``` +# install xdebug +RUN yes | pecl install xdebug +RUN echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini -You can use the `xdebug.remote_log` to debug the protocol between your container and the proxy, it's useful to catch network issues. - -##### Docker Compose +# ensure /app/Data/Temporary exists (change this if neos is not in /app) +RUN mkdir -p /app/Data/Temporary +``` +#### Docker Compose -This is an incomplete Docker Compose configuration: +This is an incomplete Docker Compose configuration showing all relevant settings for the debugproxy: ``` services: debugproxy: - image: dfeyer/flow-debugproxy:latest + image: ghcr.io/sachera/flow-debugproxy:latest volumes: - - .:/data + - neos_data_tempory:/neos/Data/Temporary:ro environment: # This MUST be the IP address of the IDE (your computer) - - "IDE_IP=192.168.1.130" - # This is the default value, need to match the xdebug.remote_port on your php.ini - - "XDEBUG_PORT=9010" + IDE_PORT: 9003 + # This is the default value, need to match the xdebug.client_port and FLOW_CONTEXT environment variable + XDEBUG: Docker/Testing:9001,Docker/Development:9002 + # must match the path used in the volume + LOCAL_ROOT: /neos # Use this to enable verbose debugging on the proxy - # - "ADDITIONAL_ARGS=-vv --debug" + #ADDITIONAL_ARGS: "-vv --debug" networks: - backend # This is your application containers, you need to link it to the proxy app: # The proxy need an access to the project files, to be able to do the path mapping + # make sure the path is the one actually used by neos and exists in the container + # if it is created by this command neos won't be able to write any temporary cache data due to right issues! volumes: - - .:/data - links: - - debugproxy + neos_data_tempory:/app/Data/Temporary:rw,cached + +volumes: + neos_data_temporary: {} ``` +When running the debugger make sure to include interpreter options for xdebug to tell it about your debug proxy. For example using the above compose file when debugging a test in the Docker/Testing context you would use: + +``` +-dxdebug.client_host=debugproxy -dxdebug.client_port=9001 +``` + +**Depending on your IDE set this setting as late as possible!** + +IntelliJ/PHPStorm for example set these command line options on their own, overriding them if you set them in your xdebug.ini. You will need to add them to the configuration of each actual test to make sure these settings have precedence! + **Options summary:** -* `IDE_IP` The primary local W-/LAN IP of your machine where your IDE runs on +* `IDE_IP` The primary local W-/LAN IP of your machine where your IDE runs on, defaults to docker.host.internal * `IDE_PORT` The Port your IDE is listening for incoming xdebug connections. (The port the debug proxy will try to connect to) -* `XDEBUG_PORT` The port on which xdebug will try to establish a connection (to this container) +* `XDEBUG` The flow context to port mapping. Based on the port xdebug connects to the context will be determined by this container +* `LOCAL_ROOT` The path where the Data/Temporary directory will be mounted to. Used to determine the path mapping * `FRAMEWORK` Currently supported values: `flow` and `dummy` * `ADDITIONAL_ARGS` For any additional argument like verbosity flags (`-vv`) or debug mode (`--debug`) (or both) diff --git a/dummypathmapper/dummypathmapper.go b/dummypathmapper/dummypathmapper.go index 0b4ecdd..5ba1dea 100644 --- a/dummypathmapper/dummypathmapper.go +++ b/dummypathmapper/dummypathmapper.go @@ -9,15 +9,26 @@ import ( "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapperfactory" "github.com/dfeyer/flow-debugproxy/pathmapping" + "github.com/dfeyer/flow-debugproxy/xdebugproxy" ) const framework = "dummy" func init() { - p := &PathMapper{} + p := &PathMapperFactory{} pathmapperfactory.Register(framework, p) } +type PathMapperFactory struct{} + +func (p *PathMapperFactory) Create(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) xdebugproxy.XDebugProcessorPlugin { + return &PathMapper{ + config: c, + logger: l, + pathMapping: m, + } +} + // PathMapper handle the mapping between real code and proxy type PathMapper struct { config *config.Config @@ -25,19 +36,12 @@ type PathMapper struct { pathMapping *pathmapping.PathMapping } -// Initialize the path mapper dependencies -func (p *PathMapper) Initialize(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) { - p.config = c - p.logger = l - p.pathMapping = m -} - // ApplyMappingToTextProtocol change file path in xDebug text protocol -func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) []byte { - return message +func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) ([]byte, error) { + return message, nil } // ApplyMappingToXML change file path in xDebug XML protocol -func (p *PathMapper) ApplyMappingToXML(message []byte) []byte { - return message +func (p *PathMapper) ApplyMappingToXML(message []byte) ([]byte, error) { + return message, nil } diff --git a/errorhandler/errorhanlder.go b/errorhandler/errorhanlder.go deleted file mode 100644 index 5d52f70..0000000 --- a/errorhandler/errorhanlder.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2015 Dominique Feyer . All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package errorhandler - -import ( - "github.com/dfeyer/flow-debugproxy/logger" - "os" -) - -// PanicHandling handle error and output log message -func PanicHandling(err error, logger *logger.Logger) { - if err != nil { - logger.Warn(err.Error()) - os.Exit(1) - } -} diff --git a/flowpathmapper/flowpathmapper.go b/flowpathmapper/flowpathmapper.go index 9754040..6bcf8ff 100644 --- a/flowpathmapper/flowpathmapper.go +++ b/flowpathmapper/flowpathmapper.go @@ -6,19 +6,19 @@ package flowpathmapper import ( "github.com/dfeyer/flow-debugproxy/config" - "github.com/dfeyer/flow-debugproxy/errorhandler" "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapperfactory" "github.com/dfeyer/flow-debugproxy/pathmapping" + "github.com/dfeyer/flow-debugproxy/xdebugproxy" "bytes" "fmt" "io/ioutil" "os" "regexp" + "runtime" "strconv" "strings" - "runtime" ) const ( @@ -28,16 +28,17 @@ const ( ) var ( - regexpPhpFile = regexp.MustCompile(`(?://)?(/[^ ]*\.php)`) - regexpFilename__Win = regexp.MustCompile(`filename=["]?file:///(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) - regexpFilename__Unix = regexp.MustCompile(`filename=["]?file://(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) - regexpPathAndFilename = regexp.MustCompile(`(?m)^# PathAndFilename: (.*)$`) - regexpPackageClass = regexp.MustCompile(`(.*?)/Packages/[^/]*/(.*?)/Classes/(.*).php`) - regexpDot = regexp.MustCompile(`[\./]`) + regexpPhpFile = regexp.MustCompile(`(?://)?(/[^ ]*\.php)`) + regexpFilename__Win = regexp.MustCompile(`filename=["]?file:///(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) + regexpFilename__Unix = regexp.MustCompile(`filename=["]?file://(\S+)/Data/Temporary/.+?/Cache/Code/Flow_Object_Classes/([^"]*)\.php`) + regexpPathAndFilename = regexp.MustCompile(`(?m)^# PathAndFilename: (.*)$`) + regexpPackageClass = regexp.MustCompile(`(.*?)/Packages/[^/]*/(.*?)/Classes/(.*).php`) + regexpDistributionPackageClass = regexp.MustCompile(`(.*?)/DistributionPackages/(.*?)/Classes/(.*).php`) + regexpDot = regexp.MustCompile(`[\./]`) ) func init() { - p := &PathMapper{} + p := &PathMapperFactory{} pathmapperfactory.Register(framework, p) } @@ -48,6 +49,16 @@ func regexpFilename() *regexp.Regexp { return regexpFilename__Unix } +type PathMapperFactory struct{} + +func (p *PathMapperFactory) Create(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) xdebugproxy.XDebugProcessorPlugin { + return &PathMapper{ + config: c, + logger: l, + pathMapping: m, + } +} + // PathMapper handle the mapping between real code and proxy type PathMapper struct { config *config.Config @@ -55,32 +66,27 @@ type PathMapper struct { pathMapping *pathmapping.PathMapping } -// Initialize the path mapper dependencies -func (p *PathMapper) Initialize(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) { - p.config = c - p.logger = l - p.pathMapping = m -} - // ApplyMappingToTextProtocol change file path in xDebug text protocol -func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) []byte { - return p.doTextPathMapping(message) +func (p *PathMapper) ApplyMappingToTextProtocol(message []byte) ([]byte, error) { + return p.doTextPathMapping(message), nil } // ApplyMappingToXML change file path in xDebug XML protocol -func (p *PathMapper) ApplyMappingToXML(message []byte) []byte { +func (p *PathMapper) ApplyMappingToXML(message []byte) ([]byte, error) { message = p.doXMLPathMapping(message) // update xml length count s := strings.Split(string(message), "\x00") i, err := strconv.Atoi(s[0]) - errorhandler.PanicHandling(err, p.logger) + if err != nil { + return nil, err + } l := len(s[1]) if i != l { message = bytes.Replace(message, []byte(strconv.Itoa(i)), []byte(strconv.Itoa(l)), 1) } - return message + return message, nil } func (p *PathMapper) doTextPathMapping(message []byte) []byte { @@ -88,7 +94,7 @@ func (p *PathMapper) doTextPathMapping(message []byte) []byte { for _, match := range regexpPhpFile.FindAllStringSubmatch(string(message), -1) { originalPath := match[1] if runtime.GOOS == "windows" { - originalPath = strings.Replace(originalPath,"//","", 1) + originalPath = strings.Replace(originalPath, "//", "", 1) } path := p.mapPath(originalPath) p.logger.Debug("doTextPathMapping %s >>> %s", path, originalPath) @@ -107,7 +113,9 @@ func (p *PathMapper) getCachePath(base, filename string) string { context := p.config.Context if strings.Contains(context, "/") { contextParts := strings.Split(context, "/") - contextParts[1] = "SubContext" + contextParts[1] + for i := 1; i < len(contextParts); i++ { + contextParts[i] = "SubContext" + contextParts[i] + } context = strings.Join(contextParts, "/") } cachePath = strings.Replace(cachePath, "@context@", context, 1) @@ -148,16 +156,23 @@ func (p *PathMapper) getRealFilename(path string) string { } func (p *PathMapper) mapPath(originalPath string) string { - if strings.Contains(originalPath, "/Packages/") { + if strings.Contains(originalPath, "/Packages/") || + strings.Contains(originalPath, "/DistributionPackages/") { p.logger.Debug("Path %s is a Flow Package file", originalPath) - cachePath := p.getCachePath(p.buildClassNameFromPath(originalPath)) + basePath, className, err := pathToClassPath(originalPath) + if err != nil { + p.logger.Warn(err.Error()) + return originalPath + } + cachePath := p.getCachePath(basePath, className) realPath := p.getRealFilename(cachePath) - var err error if len(p.config.LocalRoot) == 0 { _, err = os.Stat(realPath) } if err == nil { return p.setPathMapping(realPath, originalPath) + } else if p.config.Debug { + p.logger.Debug("Path %s not found on disk, return original path", realPath) } } @@ -183,7 +198,10 @@ func (p *PathMapper) readOriginalPathFromCache(path, basePath string) string { } p.logger.Debug("readOriginalPathFromCache %s", localPath) dat, err := ioutil.ReadFile(localPath) - errorhandler.PanicHandling(err, p.logger) + if err != nil { + p.logger.Warn(err.Error()) + return localPath + } match := regexpPathAndFilename.FindStringSubmatch(string(dat)) if len(match) == 2 { originalPath := match[1] @@ -200,23 +218,16 @@ func (p *PathMapper) readOriginalPathFromCache(path, basePath string) string { return path } -func (p *PathMapper) buildClassNameFromPath(path string) (string, string) { - basePath, className := pathToClassPath(path) - if className == "" { - // Other (vendor) packages, todo add support for vendor package with Flow proxy class - p.logger.Warn(h, "Vendor package detected") - p.logger.Warn("Class mapping not supported currently for path: %s, \n", path) - } - return basePath, className -} - // Convert absolute path to class path (internal use only) -func pathToClassPath(path string) (string, string) { +func pathToClassPath(path string) (string, string, error) { var ( basePath string classPath string ) match := regexpPackageClass.FindStringSubmatch(path) + if len(match) != 4 { + match = regexpDistributionPackageClass.FindStringSubmatch(path) + } if len(match) == 4 { // Flow standard packages packagePath := regexpDot.ReplaceAllString(match[2], "/") @@ -228,9 +239,7 @@ func pathToClassPath(path string) (string, string) { basePath = match[1] classPath = regexpDot.ReplaceAllString(classPath, "_") } else { - // Other (vendor) packages, todo add support for vendor package with Flow proxy class - basePath = path - classPath = "" + return "", "", fmt.Errorf("path %s does not match known class path patterns, may be an (unsupported) vendor package", path) } - return basePath, classPath + return basePath, classPath, nil } diff --git a/flowpathmapper/flowpathmapper_test.go b/flowpathmapper/flowpathmapper_test.go index 334c069..00a0fae 100644 --- a/flowpathmapper/flowpathmapper_test.go +++ b/flowpathmapper/flowpathmapper_test.go @@ -1,18 +1,21 @@ package flowpathmapper import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestBuildClassNameFromPathSupportPSR2(t *testing.T) { - basePath, className := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/Ttree/FlowDebugProxyHelper/ProxyClassMapperComponent.php") + basePath, className, err := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/Ttree/FlowDebugProxyHelper/ProxyClassMapperComponent.php") assert.Equal(t, "/your/path/sites/dev/master-dev.neos-workplace.dev", basePath, "they should be equal") assert.Equal(t, "Ttree_FlowDebugProxyHelper_ProxyClassMapperComponent", className, "they should be equal") + assert.Equal(t, nil, err, "there should not be an error") } func TestBuildClassNameFromPathSupportPSR4(t *testing.T) { - basePath, className := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/ProxyClassMapperComponent.php") + basePath, className, err := pathToClassPath("/your/path/sites/dev/master-dev.neos-workplace.dev/Packages/Application/Ttree.FlowDebugProxyHelper/Classes/ProxyClassMapperComponent.php") assert.Equal(t, "/your/path/sites/dev/master-dev.neos-workplace.dev", basePath, "they should be equal") assert.Equal(t, "Ttree_FlowDebugProxyHelper_ProxyClassMapperComponent", className, "they should be equal") + assert.Equal(t, nil, err, "there should not be an error") } diff --git a/main.go b/main.go index f6c0035..bc432ea 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,11 @@ package main import ( + "fmt" + golog "log" + "github.com/dfeyer/flow-debugproxy/config" - "github.com/dfeyer/flow-debugproxy/errorhandler" "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapperfactory" "github.com/dfeyer/flow-debugproxy/pathmapping" @@ -33,7 +35,7 @@ func main() { app.Flags = []cli.Flag{ &cli.StringFlag{ Name: "xdebug, l", - Value: "127.0.0.1:9000", + Value: "Development:9003", Usage: "Listen address IP and port number", }, &cli.StringFlag{ @@ -41,11 +43,6 @@ func main() { Value: "127.0.0.1:9010", Usage: "Bind address IP and port number", }, - &cli.StringFlag{ - Name: "context, c", - Value: "Development", - Usage: "The context to run as", - }, &cli.StringFlag{ Name: "localroot, r", Value: "", @@ -72,7 +69,7 @@ func main() { app.Action = func(cli *cli.Context) error { c := &config.Config{ - Context: cli.String("context"), + Context: "", Framework: cli.String("framework"), LocalRoot: strings.TrimRight(cli.String("localroot"), "/"), Verbose: cli.Bool("verbose") || cli.Bool("vv"), @@ -84,43 +81,112 @@ func main() { Config: c, } - laddr, raddr, listener := setupNetworkConnection(cli.String("xdebug"), cli.String("ide"), log) + listener, raddr, err := setupNetworkConnection(strings.Split(cli.String("xdebug"), ","), cli.String("ide")) + if err != nil { + log.Warn(err.Error()) + os.Exit(1) + } - log.Info("Debugger from %v\nIDE from %v\n", laddr, raddr) + for _, listenerWithContext := range listener { + log.Info("Debugger from %v for context %v", listenerWithContext.addr, listenerWithContext.context) + } + log.Info("IDE from %v", raddr) + if c.Verbose { + log.Info("Framework %v", c.Framework) + log.Info("Local Root %v", c.LocalRoot) + log.Info("Verbose %v", c.Verbose) + log.Info("Very Verbose %v", c.VeryVerbose) + log.Info("Debug %v", c.Debug) + } - pathMapping := &pathmapping.PathMapping{} - pathMapper, err := pathmapperfactory.Create(c, pathMapping, log) - errorhandler.PanicHandling(err, log) + connections := make(chan *xdebugproxy.Proxy) + for _, listenerWithContext := range listener { + listenerWithContext := listenerWithContext + originalConfig := *c + proxyConfig := originalConfig // copy config + proxyConfig.Context = listenerWithContext.context - for { - conn, err := listener.AcceptTCP() + pathMapping := &pathmapping.PathMapping{} + pathMapper, err := pathmapperfactory.Create(&proxyConfig, pathMapping, log) if err != nil { - log.Warn("Failed to accept connection '%s'\n", err) - continue + log.Warn(err.Error()) + os.Exit(1) } - proxy := &xdebugproxy.Proxy{ - Lconn: conn, - Raddr: raddr, - PathMapper: pathMapper, - Config: c, - } + go func() { + for { + conn, err := listenerWithContext.listener.AcceptTCP() + if err != nil { + log.Warn("Failed to accept connection '%s'\n", err) + continue + } + + connections <- &xdebugproxy.Proxy{ + Lconn: conn, + Raddr: raddr, + PathMapper: pathMapper, + Config: &proxyConfig, + } + } + }() + } + + for { + proxy := <-connections go proxy.Start() } } - app.Run(os.Args) + err := app.Run(os.Args) + if err != nil { + golog.Fatal(err) + return + } +} + +type listenerWithContext struct { + addr *net.TCPAddr + listener *net.TCPListener + context string } -func setupNetworkConnection(xdebugAddr string, ideAddr string, log *logger.Logger) (*net.TCPAddr, *net.TCPAddr, *net.TCPListener) { - laddr, err := net.ResolveTCPAddr("tcp", xdebugAddr) - errorhandler.PanicHandling(err, log) +func splitContextAndPort(contextWithPort string) (string, string, error) { + contextAndPort := strings.Split(contextWithPort, ":") + if len(contextAndPort) != 2 { + return "", "", fmt.Errorf("could not parse port and context information '%s'", contextWithPort) + } + return contextAndPort[0], contextAndPort[1], nil +} - raddr, err := net.ResolveTCPAddr("tcp", ideAddr) - errorhandler.PanicHandling(err, log) +func setupNetworkConnection(xdebugAddr []string, ideAddr string) ([]*listenerWithContext, *net.TCPAddr, error) { + listener := make([]*listenerWithContext, 0, len(xdebugAddr)) + for _, portWithContext := range xdebugAddr { + context, portStr, err := splitContextAndPort(portWithContext) + if err != nil { + return nil, nil, err + } + + addr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+portStr) + if err != nil { + return nil, nil, err + } - listener, err := net.ListenTCP("tcp", laddr) - errorhandler.PanicHandling(err, log) + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return nil, nil, err + } + + listener = append(listener, &listenerWithContext{ + addr: addr, + listener: l, + context: context, + }) + } + + raddr, err := net.ResolveTCPAddr("tcp", ideAddr) + if err != nil { + return nil, nil, err + } - return laddr, raddr, listener + return listener, raddr, nil } diff --git a/pathmapperfactory/pathmapperfactory.go b/pathmapperfactory/pathmapperfactory.go index a504c3a..64b3820 100644 --- a/pathmapperfactory/pathmapperfactory.go +++ b/pathmapperfactory/pathmapperfactory.go @@ -13,20 +13,19 @@ import ( "errors" ) -var pathMapperRegistry = map[string]xdebugproxy.XDebugProcessorPlugin{} +var pathMapperRegistry = map[string]xdebugproxy.XDebugProcessorPluginFactory{} // Register a path mapper -func Register(f string, p xdebugproxy.XDebugProcessorPlugin) { +func Register(f string, p xdebugproxy.XDebugProcessorPluginFactory) { pathMapperRegistry[f] = p } // Create return a pathmapper for the given framework func Create(c *config.Config, p *pathmapping.PathMapping, l *logger.Logger) (xdebugproxy.XDebugProcessorPlugin, error) { if _, exist := pathMapperRegistry[c.Framework]; exist { - pathmapper := pathMapperRegistry[c.Framework] - pathmapper.Initialize(c, l, p) - return pathmapper, nil + pathmapperFactory := pathMapperRegistry[c.Framework] + return pathmapperFactory.Create(c, l, p), nil } - return nil, errors.New("Unsupported framework") + return nil, errors.New("unsupported framework") } diff --git a/xdebugproxy/xdebugproxy.go b/xdebugproxy/xdebugproxy.go index 2560219..ceddbaa 100644 --- a/xdebugproxy/xdebugproxy.go +++ b/xdebugproxy/xdebugproxy.go @@ -9,20 +9,23 @@ import ( "github.com/dfeyer/flow-debugproxy/logger" "github.com/dfeyer/flow-debugproxy/pathmapping" + "bytes" "fmt" "io" "net" - "bytes" "strconv" ) const h = "%s" +type XDebugProcessorPluginFactory interface { + Create(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) XDebugProcessorPlugin +} + // XDebugProcessorPlugin process message in xDebug protocol type XDebugProcessorPlugin interface { - Initialize(c *config.Config, l *logger.Logger, m *pathmapping.PathMapping) - ApplyMappingToTextProtocol(message []byte) []byte - ApplyMappingToXML(message []byte) []byte + ApplyMappingToTextProtocol(message []byte) ([]byte, error) + ApplyMappingToXML(message []byte) ([]byte, error) } // Proxy represents a pair of connections and their state @@ -60,7 +63,7 @@ func (p *Proxy) Start() { defer close(p.pipeErrors) // display both ends - p.log("Opened %s >>> %s", p.Lconn.RemoteAddr().String(), p.rconn.RemoteAddr().String()) + p.log("Opened %s >>> %s -- context %s", p.Lconn.RemoteAddr().String(), p.rconn.RemoteAddr().String(), p.Config.Context) // bidirectional copy go p.pipe(p.Lconn, p.rconn) go p.pipe(p.rconn, p.Lconn) @@ -135,18 +138,30 @@ func (p *Proxy) pipe(src, dst *net.TCPConn) { } // extract command name if isFromDebugger { - b = p.PathMapper.ApplyMappingToXML(b) + b, err = p.PathMapper.ApplyMappingToXML(b) + if p.handleError(err, dst) { + return + } // post processors for _, d := range p.postProcessors { processor = d - b = processor.ApplyMappingToXML(b) + b, err = processor.ApplyMappingToXML(b) + if p.handleError(err, dst) { + return + } } } else { - b = p.PathMapper.ApplyMappingToTextProtocol(b) + b, err = p.PathMapper.ApplyMappingToTextProtocol(b) + if p.handleError(err, dst) { + return + } // post processors for _, d := range p.postProcessors { processor = d - b = processor.ApplyMappingToTextProtocol(b) + b, err = processor.ApplyMappingToTextProtocol(b) + if p.handleError(err, dst) { + return + } } } @@ -184,4 +199,4 @@ func (p *Proxy) handleError(err error, ch *net.TCPConn) bool { } return false -} \ No newline at end of file +}