diff --git a/README.md b/README.md index 079a7ac..a640612 100644 --- a/README.md +++ b/README.md @@ -97,19 +97,24 @@ Health check endpoint (for Kubernetes, e.g.). Returns empty 200. ### Run commands ```bash - -cache-size uint - amount of cached queries. 0 means no caching (default 100) - -chrome-ip string - Google Chrome remote IP address (default "127.0.0.1") - -chrome-port uint - Google Chrome remote debugging port (default 9222) - -log-level string - log level (default "debug") - -port uint - port to listen (default 8080) - -version - show version - -help - show this list - + -log-level="debug" + log level + -port=8080 + port to listen + -body-limit=1000 + maximum size of request body in kb. 0 means no limit. + -request-limit=20 + amount of requests per second for each IP. 0 means no limit. + -cache-size=100 + amount of cached queries. 0 means no caching. + -chrome-ip="127.0.0.1" + Google Chrome remote IP address + -chrome-port=9222 + Google Chrome remote debugging port + -no-chrome=false + disable Chrome driver + -version=false + show version + -help=false + show this list ``` diff --git a/internal/controllers/health.go b/internal/controllers/health.go index ab51615..ffa6c65 100644 --- a/internal/controllers/health.go +++ b/internal/controllers/health.go @@ -23,6 +23,10 @@ func (c *Health) Use(e *echo.Echo) { } func (c *Health) healthCheck(ctx echo.Context) error { + if c.settings.Disabled { + return ctx.NoContent(http.StatusOK) + } + out, err := http.Get(c.settings.VersionURL()) if err != nil { diff --git a/internal/controllers/info.go b/internal/controllers/info.go index 3971925..e1772be 100644 --- a/internal/controllers/info.go +++ b/internal/controllers/info.go @@ -87,32 +87,38 @@ func (c *Info) Use(e *echo.Echo) { } func (c *Info) version(_ context.Context) (VersionDto, error) { - chromeVersionResp, err := http.Get(c.settings.CDP.VersionURL()) + var chromeVersion ChromeVersionDto - if err != nil { - return VersionDto{}, errors.Wrap(err, "call Chrome") - } + if !c.settings.CDP.Disabled { + chromeVersionResp, err := http.Get(c.settings.CDP.VersionURL()) - defer chromeVersionResp.Body.Close() + if err != nil { + return VersionDto{}, errors.Wrap(err, "call Chrome") + } - chromeVersionBlob, err := io.ReadAll(chromeVersionResp.Body) + defer chromeVersionResp.Body.Close() - if err != nil { - return VersionDto{}, errors.Wrap(err, "read response from Chrome") - } + chromeVersionBlob, err := io.ReadAll(chromeVersionResp.Body) - chromeVersion := chromeVersionInternal{} + if err != nil { + return VersionDto{}, errors.Wrap(err, "read response from Chrome") + } - err = json.Unmarshal(chromeVersionBlob, &chromeVersion) + chromeVersionInternal := chromeVersionInternal{} - if err != nil { - return VersionDto{}, errors.Wrap(err, "parse response from Chrome") + err = json.Unmarshal(chromeVersionBlob, &chromeVersionInternal) + + if err != nil { + return VersionDto{}, errors.Wrap(err, "parse response from Chrome") + } + + chromeVersion = ChromeVersionDto(chromeVersionInternal) } return VersionDto{ Worker: c.settings.Version, Ferret: c.settings.FerretVersion, - Chrome: ChromeVersionDto(chromeVersion), + Chrome: chromeVersion, }, nil } diff --git a/internal/server/server.go b/internal/server/server.go index b2af72b..3811d4b 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -4,28 +4,51 @@ import ( "fmt" "net/http" + "golang.org/x/time/rate" + "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/ziflex/lecho/v3" ) -// Server is HTTP server that wraps Ferret worker. -type Server struct { - router *echo.Echo -} +type ( + // Options is a set of options for server. + Options struct { + // RequestLimit is a number of requests per second for each IP. + // If value is 0, rate limit is disabled. + RequestLimit uint64 + + // BodyLimit is a maximum size of request body. + // If value is 0, body limit is disabled. + BodyLimit uint64 + } + + // Server is HTTP server that wraps Ferret worker. + Server struct { + router *echo.Echo + } +) -func New(logger *lecho.Logger) (*Server, error) { +func New(logger *lecho.Logger, opts Options) (*Server, error) { router := echo.New() router.Logger = logger router.HideBanner = true + if opts.RequestLimit > 0 { + router.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(opts.RequestLimit)))) + } + router.Use(middleware.CORSWithConfig(middleware.CORSConfig{ Skipper: middleware.DefaultSkipper, AllowOrigins: []string{"*"}, AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept}, })) - router.Use(middleware.BodyLimit("1M")) + + if opts.BodyLimit > 0 { + router.Use(middleware.BodyLimit(fmt.Sprintf("%d", opts.BodyLimit))) + } + router.Use(middleware.RequestID()) router.Use(lecho.Middleware(lecho.Config{ Logger: logger, diff --git a/main.go b/main.go index 4b2f67c..52af5ed 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,8 @@ var ( port = flag.Uint64("port", 8080, "port to listen") + noChrome = flag.Bool("no-chrome", false, "disable Chrome driver") + chromeIP = flag.String("chrome-ip", "127.0.0.1", "Google Chrome remote IP address") chromeDebuggingPort = flag.Uint64("chrome-port", 9222, "Google Chrome remote debugging port") @@ -42,6 +44,18 @@ var ( "amount of cached queries. 0 means no caching.", ) + requestLimit = flag.Uint64( + "request-limit", + 20, + "amount of requests per second for each IP. 0 means no limit.", + ) + + bodyLimit = flag.Uint64( + "body-limit", + 1000, + "maximum size of request body in kb. 0 means no limit.", + ) + showVersion = flag.Bool( "version", false, @@ -84,15 +98,19 @@ func main() { ) cdp := worker.CDPSettings{ - Host: *chromeIP, - Port: *chromeDebuggingPort, + Host: *chromeIP, + Port: *chromeDebuggingPort, + Disabled: *noChrome, } if err := waitForChrome(cdp); err != nil { logger.Fatalf("wait for Chrome: %s", err) } - srv, err := server.New(logger) + srv, err := server.New(logger, server.Options{ + RequestLimit: *requestLimit, + BodyLimit: *bodyLimit, + }) if err != nil { logger.Fatal(err) @@ -104,10 +122,14 @@ func main() { logger.Fatal(errors.Wrap(err, "create cache storage")) } - wkr, err := worker.New( - worker.WithCustomCDP(cdp), - worker.WithCache(cache), - ) + opts := make([]worker.Option, 0, 2) + opts = append(opts, worker.WithCache(cache)) + + if !cdp.Disabled { + opts = append(opts, worker.WithCustomCDP(cdp)) + } + + wkr, err := worker.New(opts...) if err != nil { logger.Fatal(errors.Wrap(err, "create a worker instance")) @@ -123,6 +145,10 @@ func main() { } func waitForChrome(cdp worker.CDPSettings) error { + if cdp.Disabled { + return nil + } + runner := waitfor.New(http.Use()) return runner.Test(context.Background(), []string{ diff --git a/pkg/worker/options.go b/pkg/worker/options.go index a76ba9f..8957b0a 100644 --- a/pkg/worker/options.go +++ b/pkg/worker/options.go @@ -10,8 +10,9 @@ import ( type ( CDPSettings struct { - Host string `json:"host"` - Port uint64 `json:"port"` + Host string `json:"host"` + Port uint64 `json:"port"` + Disabled bool `json:"disabled"` } Options struct {