Integrating with Golang Apps

In some cases, it can be useful to embed a RoadRunner server inside another GO program. This is often the case in microservice architectures where you may have a mandated GO framework for all the apps. In such cases it might not be possible to run a stock roadrunner instance, and the only choice is to run roadrunner inside the main app framework / program.

Here's an example of how to embed RoadRunner into a Go program with an HTTP handler:

Import RoadRunner library via go get command into your Go project:

main.go
go get -u github.com/roadrunner-server/roadrunner/v2024.2.0/lib
main.go
func handleRequest(w http.ResponseWriter, request *http.Request) {
    // Find a way to pass that to RoadRunner so PHP handles the request
}

Create an RR instance

main.go

import (
	"github.com/roadrunner-server/roadrunner/v2024/lib"
)

overrides := []string{} // List of configuration overrides
plugins := lib.DefaultPluginsList() // List of RR plugins to enable
rr, err := lib.NewRR(".rr.yaml", overrides, plugins)

Here we use the default list of plugins. The same list of plugin you would get if you were to run rr serve with a stock roadrunner binary.

You can, however, choose only the plugins you want and add your own private plugins as well:

main.go
overrides := []string{
    "http.address=127.0.0.1:4444", // example override to set the http address
    "http.pool.num_workers=4", // example override of how to set the number of php workers
} // List of configuration overrides
plugins := []interface{}{
    &informer.Plugin{},
    &resetter.Plugin{},
    // ...
    &httpPlugin.Plugin{},
    // ...
    &coolCompany.Plugin{},
}
rr, err := roadrunner.NewRR(".rr.yaml", overrides, plugins)

Passing requests to RoadRunner

Roadrunner can respond to HTTP requests, but also gRPC ones or many more. Because this is all done via plugins that each listen to different types of requests, ports, etc...

So when we talk about passing a request to roadrunner, we're actually talking about passing the request to roadrunner's HTTP plugin. To do this, we need to keep a handle on the http plugin.

main.go
overrides := []string{} // List of configuration overrides
httpPlugin := &httpPlugin.Plugin{},
plugins := []interface{}{
    &informer.Plugin{},
    &resetter.Plugin{},
    // ...
    httpPlugin,
    // ...
    &coolCompany.Plugin{},
}
rr, err := roadrunner.NewRR(".rr.yaml", overrides, plugins)

The HTTP plugin is itself an http.Handler so it's now very easy to use it to let roadrunner and PHP handle the request:

main.go
overrides := []string{
    // override the http plugin's address value for the current run of the program
    "http.address=127.0.0.1:4444",
} // List of configuration overrides
httpPlugin := &httpPlugin.Plugin{},
plugins := []interface{}{
    &informer.Plugin{},
    &resetter.Plugin{},
    // ...
    httpPlugin,
    // ...
    &coolCompany.Plugin{},
}
rr, err := roadrunner.NewRR(".rr.yaml", overrides, plugins)
if err != nil {
    return err
}

func handleRequest(w http.ResponseWriter, request *http.Request) {
    return httpPlugin.ServeHTTP(w, request)
}

Starting & Stopping Embedded Roadrunner

Once everything is ready, we can start the roadrunner instance:

main.go
errCh := make(chan error, 1)
go func() {
    errCh <- rr.Serve()
}()

rr.Serve() will block until it returns an error or nil if it was stopped gracefully.

To gracefully stop the server, we simply call rr.Stop()

Roadrunner State

When you run roadrunner, it goes through multiple phases of initialization, running, stopping etc... Sometimes it is useful to know about those, be it for debugging, to know if you're ready to accept requests, or if you can gracefully shutdown the main program.

You can call rr.CurrentState() on your roadrunner instance to retrieve one of the following states:

main.go
package fsm
// github.com/roadrunner-server/endure/pkg/fsm

type State uint32

const (
    Uninitialized State = iota
    Initializing
    Initialized
    Starting
    Started
    Stopping
    Stopped
    Error
)

Additionally, the actual status name can be obtained via rr.CurrentState().String().

Last updated