mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-15 11:52:14 +01:00
5.0.0 (#66)
This commit is contained in:
@@ -2,30 +2,36 @@ package pm2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/mafredri/cdp/devtool"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/notify"
|
||||
)
|
||||
|
||||
// Chrome facilitates starting or shutting down
|
||||
// Chrome headless with PM2.
|
||||
type Chrome struct{}
|
||||
|
||||
// Launch starts Chrome headless with PM2.
|
||||
func (c *Chrome) Launch() error {
|
||||
return launch(c)
|
||||
type chrome struct {
|
||||
manager *processManager
|
||||
}
|
||||
|
||||
// Shutdown stops Chrome headless and
|
||||
// removes it from the list of PM2
|
||||
// processes.
|
||||
func (c *Chrome) Shutdown() error {
|
||||
return shutdown(c)
|
||||
// NewChrome retruns a Google Chrome
|
||||
// headless process.
|
||||
func NewChrome() Process {
|
||||
return &chrome{
|
||||
manager: &processManager{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chrome) getArgs() []string {
|
||||
func (p *chrome) Fullname() string {
|
||||
return "Google Chrome headless"
|
||||
}
|
||||
|
||||
func (p *chrome) Start() error {
|
||||
return p.manager.start(p)
|
||||
}
|
||||
|
||||
func (p *chrome) Shutdown() error {
|
||||
return p.manager.shutdown(p)
|
||||
}
|
||||
|
||||
func (p *chrome) args() []string {
|
||||
return []string{
|
||||
"--no-sandbox",
|
||||
"--headless",
|
||||
@@ -44,28 +50,23 @@ func (c *Chrome) getArgs() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Chrome) getName() string {
|
||||
func (p *chrome) name() string {
|
||||
return "google-chrome-stable"
|
||||
}
|
||||
|
||||
func (c *Chrome) getFullname() string {
|
||||
return "Chrome headless"
|
||||
}
|
||||
|
||||
func (c *Chrome) isViable() bool {
|
||||
// check if Chrome is correctly running.
|
||||
func (p *chrome) viable() bool {
|
||||
// check if Google Chrome is correctly running.
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
_, err := devtool.New("http://localhost:9222").Version(ctx)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (c *Chrome) warmup() {
|
||||
notify.Println(fmt.Sprintf("warming-up %s", c.getFullname()))
|
||||
func (p *chrome) warmup() {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure type implements desired interfaces.
|
||||
var (
|
||||
_ = Process(new(Chrome))
|
||||
_ = Process(new(chrome))
|
||||
)
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChromeLaunch(t *testing.T) {
|
||||
p := &Chrome{}
|
||||
err := p.Launch()
|
||||
func TestChromeStart(t *testing.T) {
|
||||
p := NewChrome()
|
||||
err := p.Start()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestChromeShutdown(t *testing.T) {
|
||||
p := &Chrome{}
|
||||
p := NewChrome()
|
||||
err := p.Shutdown()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Package pm2 facilitates starting external
|
||||
processes on which our API depends.
|
||||
|
||||
For instance, it starts Chrome headless and
|
||||
For instance, it may start Google Chrome headless and
|
||||
unoconv listener with PM2.
|
||||
|
||||
The PM2 process manager launch those processes and keep
|
||||
|
||||
@@ -3,69 +3,81 @@ package pm2
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/notify"
|
||||
)
|
||||
|
||||
// Process is a type that can launch or
|
||||
const (
|
||||
stoppedState = iota
|
||||
runningState
|
||||
errorState
|
||||
)
|
||||
|
||||
// Process is a type that can start or
|
||||
// shutdown a process with PM2.
|
||||
type Process interface {
|
||||
Launch() error
|
||||
Fullname() string
|
||||
Start() error
|
||||
Shutdown() error
|
||||
getArgs() []string
|
||||
getName() string
|
||||
getFullname() string
|
||||
isViable() bool
|
||||
args() []string
|
||||
name() string
|
||||
viable() bool
|
||||
warmup()
|
||||
}
|
||||
|
||||
const maxRestartAttempts int = 5
|
||||
|
||||
var humanNames = map[string]string{
|
||||
"start": "started",
|
||||
"restart": "restarted",
|
||||
"stop": "stopped",
|
||||
type processManager struct {
|
||||
heuristicState int32
|
||||
}
|
||||
|
||||
func launch(p Process) error {
|
||||
if err := run(p, "start"); err != nil {
|
||||
func (m *processManager) start(p Process) error {
|
||||
if err := m.pm2(p, "start"); err != nil {
|
||||
return err
|
||||
}
|
||||
p.warmup()
|
||||
if !p.isViable() {
|
||||
if !p.viable() {
|
||||
attempts := 0
|
||||
for attempts < maxRestartAttempts && !p.isViable() {
|
||||
run(p, "restart")
|
||||
for attempts < 5 && !p.viable() {
|
||||
if err := m.pm2(p, "restart"); err != nil {
|
||||
m.heuristicState = errorState
|
||||
return err
|
||||
}
|
||||
p.warmup()
|
||||
attempts++
|
||||
}
|
||||
if !p.isViable() {
|
||||
return fmt.Errorf("failed to launch %s", p.getFullname())
|
||||
if !p.viable() {
|
||||
m.heuristicState = errorState
|
||||
return fmt.Errorf("failed to launch %s", p.Fullname())
|
||||
}
|
||||
}
|
||||
m.heuristicState = runningState
|
||||
return nil
|
||||
}
|
||||
|
||||
func shutdown(p Process) error {
|
||||
return run(p, "stop")
|
||||
func (m *processManager) shutdown(p Process) error {
|
||||
if m.heuristicState != runningState {
|
||||
return nil
|
||||
}
|
||||
if err := m.pm2(p, "stop"); err != nil {
|
||||
m.heuristicState = errorState
|
||||
return err
|
||||
}
|
||||
m.heuristicState = stoppedState
|
||||
return nil
|
||||
}
|
||||
|
||||
func run(p Process, cmdName string) error {
|
||||
func (m *processManager) pm2(p Process, cmdName string) error {
|
||||
cmdArgs := []string{
|
||||
cmdName,
|
||||
p.getName(),
|
||||
p.name(),
|
||||
}
|
||||
if cmdName == "start" {
|
||||
cmdArgs = append(cmdArgs, "--interpreter none", "--")
|
||||
cmdArgs = append(cmdArgs, p.getArgs()...)
|
||||
cmdArgs = append(cmdArgs, p.args()...)
|
||||
}
|
||||
cmd := exec.Command(
|
||||
"pm2",
|
||||
cmdArgs...,
|
||||
)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("%s %s with PM2: %v", cmdName, p.getFullname(), err)
|
||||
return fmt.Errorf("%s %s with PM2: %v", cmdName, p.Fullname(), err)
|
||||
}
|
||||
notify.Println(fmt.Sprintf("%s %s with PM2", p.getFullname(), humanNames[cmdName]))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,47 +1,52 @@
|
||||
package pm2
|
||||
|
||||
// Unoconv facilitates starting or shutting down
|
||||
// unoconv listener with PM2.
|
||||
type Unoconv struct{}
|
||||
|
||||
// Launch starts unoconv listener with PM2.
|
||||
func (u *Unoconv) Launch() error {
|
||||
return launch(u)
|
||||
type unoconv struct {
|
||||
manager *processManager
|
||||
}
|
||||
|
||||
// Shutdown stops unoconv listener and
|
||||
// removes it from the list of PM2
|
||||
// processes.
|
||||
func (u *Unoconv) Shutdown() error {
|
||||
return shutdown(u)
|
||||
// NewUnoconv retruns a unoconv listener
|
||||
// process.
|
||||
func NewUnoconv() Process {
|
||||
return &unoconv{
|
||||
manager: &processManager{},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unoconv) getArgs() []string {
|
||||
func (p *unoconv) Fullname() string {
|
||||
return "unoconv listener"
|
||||
}
|
||||
|
||||
func (p *unoconv) Start() error {
|
||||
return p.manager.start(p)
|
||||
}
|
||||
|
||||
func (p *unoconv) Shutdown() error {
|
||||
return p.manager.shutdown(p)
|
||||
}
|
||||
|
||||
func (p *unoconv) args() []string {
|
||||
return []string{
|
||||
"--listener",
|
||||
"--verbose",
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Unoconv) getName() string {
|
||||
func (p *unoconv) name() string {
|
||||
return "unoconv"
|
||||
}
|
||||
|
||||
func (u *Unoconv) getFullname() string {
|
||||
return "unoconv listener"
|
||||
}
|
||||
|
||||
func (u *Unoconv) isViable() bool {
|
||||
func (p *unoconv) viable() bool {
|
||||
// TODO find a way to check if
|
||||
// unoconv is correctly started?
|
||||
// the unoconv listener
|
||||
// is correctly started?
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *Unoconv) warmup() {
|
||||
func (p *unoconv) warmup() {
|
||||
// let's do nothing.
|
||||
}
|
||||
|
||||
// Compile-time checks to ensure type implements desired interfaces.
|
||||
var (
|
||||
_ = Process(new(Unoconv))
|
||||
_ = Process(new(unoconv))
|
||||
)
|
||||
|
||||
@@ -6,14 +6,14 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnoconvLaunch(t *testing.T) {
|
||||
p := &Unoconv{}
|
||||
err := p.Launch()
|
||||
func TestUnoconvStart(t *testing.T) {
|
||||
p := NewUnoconv()
|
||||
err := p.Start()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestUnoconvShutdown(t *testing.T) {
|
||||
p := &Unoconv{}
|
||||
p := NewUnoconv()
|
||||
err := p.Shutdown()
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user