summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml154
-rwxr-xr-x.github/workflows/ci-build.yml119
-rw-r--r--.github/workflows/codeql-analysis.yml2
-rw-r--r--.github/workflows/release.yml139
-rwxr-xr-x.gitignore7
-rw-r--r--CHANGELOG.md4
-rw-r--r--Dockerfile42
-rwxr-xr-x[-rw-r--r--]Makefile67
-rwxr-xr-xbin/rr312
-rwxr-xr-xbors.toml21
-rw-r--r--cmd/rr/cmd/root.go158
-rwxr-xr-xcomposer.json9
-rw-r--r--phpstan.neon.dist1
-rw-r--r--src/Diactoros/ServerRequestFactory.php4
-rw-r--r--src/Diactoros/StreamFactory.php2
-rw-r--r--src/Diactoros/UploadedFileFactory.php2
-rw-r--r--src/HttpClient.php9
17 files changed, 880 insertions, 172 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..92b55666
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,154 @@
+name: build
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ php:
+ name: Build (PHP ${{ matrix.php }}, ${{ matrix.setup }} setup)
+ runs-on: ubuntu-20.04
+ timeout-minutes: 6
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['7.3', '7.4', '8.0']
+ setup: [basic, lowest]
+ steps:
+ - name: Set up PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2 # action page: <https://github.com/shivammathur/setup-php>
+ with:
+ php-version: ${{ matrix.php }}
+
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Syntax check only (lint)
+ run: find ./src/ ./tests/ -name "*.php" -print0 | xargs -0 -n1 -P8 php -l
+
+ - name: Get Composer Cache Directory
+ id: composer-cache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+
+ - name: Init Composer Cache # Docs: <https://git.io/JfAKn#php---composer>
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ matrix.setup }}-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install lowest Composer dependencies
+ if: matrix.setup == 'lowest'
+ run: composer update --prefer-dist --no-progress --prefer-lowest --ansi
+
+ - name: Install basic Composer dependencies
+ if: matrix.setup == 'basic'
+ run: composer update --prefer-dist --no-progress --ansi
+
+ - name: Analyze PHP sources
+ run: composer analyze
+
+ # TODO write phpunit tests
+ #- name: Analyze PHP sources
+ # run: composer test
+
+ golang:
+ name: Build (Go ${{ matrix.go }}, PHP ${{ matrix.php }})
+ runs-on: ubuntu-20.04
+ timeout-minutes: 10
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['7.3', '7.4', '8.0']
+ go: ['1.14', '1.15']
+ steps:
+ - name: Set up Go ${{ matrix.go }}
+ uses: actions/setup-go@v2 # action page: <https://github.com/actions/setup-go>
+ with:
+ go-version: ${{ matrix.go }}
+
+ - name: Set up PHP ${{ matrix.php }}
+ uses: shivammathur/setup-php@v2 # action page: <https://github.com/shivammathur/setup-php>
+ with:
+ php-version: ${{ matrix.php }}
+
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Get Composer Cache Directory
+ id: composer-cache
+ run: echo "::set-output name=dir::$(composer config cache-files-dir)"
+
+ - name: Init Composer Cache # Docs: <https://git.io/JfAKn#php---composer>
+ uses: actions/cache@v2
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install Composer dependencies
+ run: composer update --prefer-dist --no-progress --ansi
+
+ - name: Init Go modules Cache # Docs: <https://git.io/JfAKn#go---modules>
+ uses: actions/cache@v2
+ with:
+ path: ~/go/pkg/mod
+ key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
+ restore-keys: ${{ runner.os }}-go-
+
+ - name: Install Go dependencies
+ run: go mod download
+
+ - name: Run golang tests
+ run: |
+ mkdir ./coverage-ci
+ go test -race -v -covermode=atomic -coverprofile=./coverage-ci/lib.txt
+ go test ./util -race -v -covermode=atomic -coverprofile=./coverage-ci/util.txt
+ go test ./service -race -v -covermode=atomic -coverprofile=./coverage-ci/service.txt
+ go test ./service/env -race -v -covermode=atomic -coverprofile=./coverage-ci/env.txt
+ go test ./service/rpc -race -v -covermode=atomic -coverprofile=./coverage-ci/rpc.txt
+ go test ./service/http -race -v -covermode=atomic -coverprofile=./coverage-ci/http.txt
+ go test ./service/static -race -v -covermode=atomic -coverprofile=./coverage-ci/static.txt
+ go test ./service/limit -race -v -covermode=atomic -coverprofile=./coverage-ci/limit.txt
+ go test ./service/headers -race -v -covermode=atomic -coverprofile=./coverage-ci/headers.txt
+ go test ./service/metrics -race -v -covermode=atomic -coverprofile=./coverage-ci/metrics.txt
+ go test ./service/health -race -v -covermode=atomic -coverprofile=./coverage-ci/health.txt
+ go test ./service/gzip -race -v -covermode=atomic -coverprofile=./coverage-ci/gzip.txt
+ go test ./service/reload -race -v -covermode=atomic -coverprofile=./coverage-ci/reload.txt
+ cat ./coverage-ci/*.txt > ./coverage-ci/summary.txt
+
+ - uses: codecov/codecov-action@v1 # Docs: <https://github.com/codecov/codecov-action>
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ file: ./coverage-ci/summary.txt
+ fail_ci_if_error: false
+
+ golangci-check:
+ name: Golang-CI (lint)
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v1
+
+ - name: golangci-lint
+ uses: reviewdog/action-golangci-lint@v1 # action page: <https://github.com/reviewdog/action-golangci-lint>
+ with:
+ github_token: ${{ secrets.github_token }}
+
+ image:
+ name: Build docker image
+ runs-on: ubuntu-20.04
+ timeout-minutes: 10
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Build image
+ run: docker build -t roadrunner:local -f Dockerfile .
+
+ - name: Scan image
+ uses: anchore/scan-action@v2 # action page: <https://github.com/anchore/scan-action>
+ with:
+ image: roadrunner:local
+ fail-build: true
+ severity-cutoff: low # negligible, low, medium, high or critical
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
deleted file mode 100755
index 8ec3eec9..00000000
--- a/.github/workflows/ci-build.yml
+++ /dev/null
@@ -1,119 +0,0 @@
-name: CI
-
-on: [ push, pull_request ]
-
-jobs:
- build:
- name: Build (PHP ${{ matrix.php }}, Go ${{ matrix.go }}, OS ${{ matrix.os }})
- runs-on: ${{ matrix.os }}
- strategy:
- fail-fast: false
- matrix:
- php: [ 7.4, 8.0 ]
- go: [ 1.14, 1.15 ]
- os: [ ubuntu-20.04 ]
- env:
- GO111MODULE: on
- steps:
- - name: Set up Go ${{ matrix.go }}
- uses: actions/setup-go@v1
- with:
- go-version: ${{ matrix.go }}
-
- - name: Set up PHP ${{ matrix.php }}
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: dom
- coverage: xdebug
-
- - name: Check out code
- uses: actions/checkout@v2
- with:
- fetch-depth: 1
-
- - name: Show versions
- run: php -v ; composer -V ; go version
-
- - name: Debug if needed
- env:
- DEBUG: ${{ secrets.DEBUG }}
- run: if [[ "$DEBUG" == "true" ]]; then env && go env; fi
-
- - name: Syntax check only (lint)
- run: find ./src/ -name "*.php" -print0 | xargs -0 -n1 -P8 php -l
-
- - name: Get Composer Cache Directory # Docs: <https://github.com/actions/cache/blob/master/examples.md#php---composer>
- id: composer-cache
- run: echo "::set-output name=dir::$(composer config cache-files-dir)"
-
- - name: Cache dependencies # Docs: <https://github.com/actions/cache/blob/master/examples.md#php---composer>
- uses: actions/cache@v1
- with:
- path: ${{ steps.composer-cache.outputs.dir }}
- key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
- restore-keys: ${{ runner.os }}-composer-
-
- - name: Install Composer dependencies
- run: composer install --prefer-dist --no-interaction
-
- # - name: Analyze PHP sources
- # run: composer analyze
-
- - name: Install Go dependencies
- run: go mod download
-
- - name: Run golang tests
- run: |
- mkdir ./coverage-ci
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/lib.txt -covermode=atomic .
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/rpc_config.txt -covermode=atomic ./plugins/rpc
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/rpc.txt -covermode=atomic ./plugins/rpc/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/plugin_config.txt -covermode=atomic ./plugins/config/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/logger.txt -covermode=atomic ./plugins/logger/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/server.txt -covermode=atomic ./plugins/server/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/metrics.txt -covermode=atomic ./plugins/metrics/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/informer.txt -covermode=atomic ./plugins/informer/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/informer.txt -covermode=atomic ./plugins/resetter/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/attributes.txt -covermode=atomic ./plugins/http/attributes
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/http_tests.txt -covermode=atomic ./plugins/http/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/gzip.txt -covermode=atomic ./plugins/gzip/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/static.txt -covermode=atomic ./plugins/static/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/static_root.txt -covermode=atomic ./plugins/static
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/headers.txt -covermode=atomic ./plugins/headers/tests
- go test -v -race -cover -tags=debug -coverprofile=./coverage-ci/checker.txt -covermode=atomic ./plugins/checker/tests
- cat ./coverage-ci/*.txt > ./coverage-ci/summary.txt
-
- - name: Run code coverage
- uses: codecov/codecov-action@v1
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
- files: summary.txt
- flags: unittests
- name: codecov-umbrella
- fail_ci_if_error: false
- verbose: true
-
-
- golangci-check:
- name: runner / golangci-lint
- runs-on: ubuntu-latest
- steps:
- - name: Check out code into the Go module directory
- uses: actions/checkout@v1
- - name: golangci-lint
- uses: reviewdog/action-golangci-lint@v1
- with:
- github_token: ${{ secrets.github_token }}
-
-# image:
-# name: Build docker image
-# runs-on: ubuntu-latest
-# steps:
-# - name: Check out code
-# uses: actions/checkout@v2
-# with:
-# fetch-depth: 1
-#
-# - name: Build image
-# run: docker build -t rr:local -f Dockerfile .
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1c90e4a4..75e40110 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -47,7 +47,7 @@ jobs:
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
+ # By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..b1cd83ae
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,139 @@
+name: release
+
+on:
+ release: # Docs: <https://help.github.com/en/articles/events-that-trigger-workflows#release-event-release>
+ types: [published]
+
+jobs:
+ build:
+ name: Build for ${{ matrix.os }} (${{ matrix.arch }}, ${{ matrix.compiler }})
+ runs-on: ubuntu-20.04
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [windows, freebsd, darwin] # linux, freebsd, darwin, windows
+ compiler: [gcc] # gcc, musl-gcc
+ archiver: [zip] # tar, zip
+ arch: [amd64] # amd64, 386
+ include:
+ - os: linux
+ compiler: gcc
+ archiver: tar
+ arch: amd64
+ - os: ''
+ compiler: musl-gcc # more info: <https://musl.libc.org/>
+ archiver: zip
+ arch: amd64
+ steps:
+ - name: Set up Go
+ uses: actions/setup-go@v2
+ with:
+ go-version: 1.15.5
+
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Install musl
+ if: matrix.compiler == 'musl-gcc'
+ run: sudo apt-get install -y musl-tools
+
+ - name: Download dependencies
+ run: go mod download # `-x` means "verbose" mode
+
+ - name: Generate builder values
+ id: values
+ run: |
+ echo "::set-output name=version::`echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//'`"
+ echo "::set-output name=timestamp::`date +%FT%T%z`"
+ echo "::set-output name=binary-name::rr`[ ${{ matrix.os }} = 'windows' ] && echo '.exe'`"
+
+ - name: Compile binary file
+ env:
+ GOOS: ${{ matrix.os }}
+ GOARCH: ${{ matrix.arch }}
+ CC: ${{ matrix.compiler }}
+ CGO_ENABLED: 0
+ LDFLAGS: >-
+ -s
+ -X github.com/spiral/roadrunner/cmd/rr/cmd.Version=${{ steps.values.outputs.version }}
+ -X github.com/spiral/roadrunner/cmd/rr/cmd.BuildTime=${{ steps.values.outputs.timestamp }}
+ run: |
+ go build -trimpath -ldflags "$LDFLAGS" -o "./${{ steps.values.outputs.binary-name }}" ./cmd/rr/main.go
+ stat "./${{ steps.values.outputs.binary-name }}"
+
+ - name: Generate distributive directory name
+ id: dist-dir
+ run: >
+ echo "::set-output name=name::roadrunner-${{ steps.values.outputs.version }}-$(
+ [ ${{ matrix.os }} != '' ] && echo '${{ matrix.os }}' || echo 'unknown'
+ )$(
+ [ ${{ matrix.compiler }} = 'musl-gcc' ] && echo '-musl'
+ )-${{ matrix.arch }}"
+
+ - name: Generate distributive archive name
+ id: dist-arch
+ run: >
+ echo "::set-output name=name::${{ steps.dist-dir.outputs.name }}.$(
+ case ${{ matrix.archiver }} in
+ zip) echo 'zip';;
+ tar) echo 'tar.gz';;
+ *) exit 10;
+ esac
+ )"
+
+ - name: Create distributive
+ run: |
+ mkdir ${{ steps.dist-dir.outputs.name }}
+ mv "./${{ steps.values.outputs.binary-name }}" ./${{ steps.dist-dir.outputs.name }}/
+ cp ./README.md ./CHANGELOG.md ./LICENSE ./${{ steps.dist-dir.outputs.name }}/
+
+ - name: Pack distributive using tar
+ if: matrix.archiver == 'tar'
+ run: tar -zcf "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}"
+
+ - name: Pack distributive using zip
+ if: matrix.archiver == 'zip'
+ run: zip -r -q "${{ steps.dist-arch.outputs.name }}" "${{ steps.dist-dir.outputs.name }}"
+
+ - name: Upload artifact
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{ steps.dist-dir.outputs.name }}
+ path: ${{ steps.dist-arch.outputs.name }}
+ if-no-files-found: error
+ retention-days: 30
+
+ - name: Upload binaries to release
+ uses: svenstaro/upload-release-action@v2
+ with:
+ repo_token: ${{ secrets.GITHUB_TOKEN }}
+ file: ${{ steps.dist-arch.outputs.name }}
+ asset_name: ${{ steps.dist-arch.outputs.name }}
+ tag: ${{ github.ref }}
+
+ docker:
+ name: Build docker image
+ runs-on: ubuntu-20.04
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v2
+
+ - name: Make docker login
+ run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_LOGIN }}" --password-stdin
+
+ - name: Generate builder values
+ id: values
+ run: |
+ echo "::set-output name=version::`echo ${GITHUB_REF##*/} | sed -e 's/^[vV ]*//'`"
+ echo "::set-output name=timestamp::`date +%FT%T%z`"
+
+ - name: Build image
+ run: |
+ docker build \
+ --tag "spiralscout/roadrunner:${{ steps.values.outputs.version }}" \
+ --build-arg "APP_VERSION=${{ steps.values.outputs.version }}" \
+ --build-arg "BUILD_TIME=${{ steps.values.outputs.timestamp }}" \
+ .
+
+ - name: Push image into registry
+ run: docker push "spiralscout/roadrunner:${{ steps.values.outputs.version }}"
diff --git a/.gitignore b/.gitignore
index b2a71f90..fb4afc29 100755
--- a/.gitignore
+++ b/.gitignore
@@ -16,5 +16,10 @@
# Dependency directories (remove the comment below to include it)
# vendor/
.idea
+composer.lock
+vendor
vendor_php
-builds \ No newline at end of file
+builds/
+tests/vendor/
+.rr-sample.yaml
+psr-worker.php
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db8ad552..030b8c06 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
CHANGELOG
=========
+UNRELEASED
+----------
+- Add `rr --version` flag support
+
v1.9.0 (02.12.2020)
-------------------
- Update PHP minimal supported version to 7.3
diff --git a/Dockerfile b/Dockerfile
index 09cf7933..3c9ce76a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,17 +1,39 @@
-FROM golang:latest as builder
+# Image page: <https://hub.docker.com/_/golang>
+FROM golang:1.15.5 as builder
-COPY . /src
+# app version and build date must be passed during image building (version without any prefix).
+# e.g.: `docker build --build-arg "APP_VERSION=1.2.3" --build-arg "BUILD_TIME=$(date +%FT%T%z)" .`
+ARG APP_VERSION="undefined"
+ARG BUILD_TIME="undefined"
+
+# arguments to pass on each go tool link invocation
+ENV LDFLAGS="-s \
+-X github.com/spiral/roadrunner/cmd/rr/cmd.Version=$APP_VERSION \
+-X github.com/spiral/roadrunner/cmd/rr/cmd.BuildTime=$BUILD_TIME"
+
+RUN mkdir /src
WORKDIR /src
+COPY ./go.mod ./go.sum ./
+
+# Burn modules cache
RUN set -x \
- && apt-get update -y \
- && apt-get install -y bash git \
&& go version \
- && bash ./build.sh \
- && test -f ./.rr.yaml
+ && go mod download \
+ && go mod verify
+
+COPY . .
+
+# compile binary file
+RUN CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" -o ./rr ./cmd/rr/main.go
+
+# Image page: <https://hub.docker.com/_/alpine>
+FROM alpine:3.12
-FROM alpine:latest
+# use same build arguments for image labels
+ARG APP_VERSION
+ARG BUILD_TIME
LABEL \
org.opencontainers.image.title="roadrunner" \
@@ -19,9 +41,13 @@ LABEL \
org.opencontainers.image.url="https://github.com/spiral/roadrunner" \
org.opencontainers.image.source="https://github.com/spiral/roadrunner" \
org.opencontainers.image.vendor="SpiralScout" \
+ org.opencontainers.image.version="$APP_VERSION" \
+ org.opencontainers.image.created="$BUILD_TIME" \
org.opencontainers.image.licenses="MIT"
+# copy required files from builder image
COPY --from=builder /src/rr /usr/bin/rr
COPY --from=builder /src/.rr.yaml /etc/rr.yaml
-ENTRYPOINT ["/usr/bin/rr"] \ No newline at end of file
+# use roadrunner binary as image entrypoint
+ENTRYPOINT ["/usr/bin/rr"]
diff --git a/Makefile b/Makefile
index 383b7699..9ad158ba 100644..100755
--- a/Makefile
+++ b/Makefile
@@ -1,23 +1,44 @@
-test:
- go clean -testcache
- go test -v -race -cover . -tags=debug
- go test -v -race -cover ./plugins/rpc -tags=debug
- go test -v -race -cover ./plugins/rpc/tests -tags=debug
- go test -v -race -cover ./plugins/config/tests -tags=debug
- go test -v -race -cover ./plugins/server/tests -tags=debug
- go test -v -race -cover ./plugins/logger/tests -tags=debug
- go test -v -race -cover ./plugins/metrics/tests -tags=debug
- go test -v -race -cover ./plugins/informer/tests -tags=debug
- go test -v -race -cover ./plugins/resetter/tests -tags=debug
- go test -v -race -cover ./plugins/http/attributes -tags=debug
- go test -v -race -cover ./plugins/http/tests -tags=debug
- go test -v -race -cover ./plugins/gzip/tests -tags=debug
- go test -v -race -cover ./plugins/static/tests -tags=debug
- go test -v -race -cover ./plugins/static -tags=debug
- go test -v -race -cover ./plugins/headers/tests -tags=debug
- go test -v -race -cover ./plugins/checker/tests -tags=debug
-
-test_headers:
- go test -v -race -cover ./plugins/headers/tests -tags=debug
-test_checker:
- go test -v -race -cover ./plugins/checker/tests -tags=debug \ No newline at end of file
+#!/usr/bin/make
+# Makefile readme (ru): <http://linux.yaroslavl.ru/docs/prog/gnu_make_3-79_russian_manual.html>
+# Makefile readme (en): <https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents>
+
+SHELL = /bin/sh
+
+.DEFAULT_GOAL := build
+
+# This will output the help for each task. thanks to https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
+help: ## Show this help
+ @printf "\033[33m%s:\033[0m\n" 'Available commands'
+ @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_-]+:.*?## / {printf " \033[32m%-14s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
+
+build: ## Build RR binary file for local os/arch
+ CGO_ENABLED=0 go build -trimpath -ldflags "-s" -o ./rr ./cmd/rr/main.go
+
+clean: ## Make some clean
+ rm ./rr
+
+install: build ## Build and install RR locally
+ cp rr /usr/local/bin/rr
+
+uninstall: ## Uninstall locally installed RR
+ rm -f /usr/local/bin/rr
+
+test: ## Run application tests
+ test -d ./vendor_php || composer update --prefer-dist --ansi
+ go test -v -race -cover
+ go test -v -race -cover ./util
+ go test -v -race -cover ./service
+ go test -v -race -cover ./service/env
+ go test -v -race -cover ./service/rpc
+ go test -v -race -cover ./service/http
+ go test -v -race -cover ./service/static
+ go test -v -race -cover ./service/limit
+ go test -v -race -cover ./service/headers
+ go test -v -race -cover ./service/metrics
+ go test -v -race -cover ./service/health
+ go test -v -race -cover ./service/gzip
+ go test -v -race -cover ./service/reload
+
+lint: ## Run application linters
+ go fmt ./...
+ golint ./...
diff --git a/bin/rr b/bin/rr
new file mode 100755
index 00000000..d7d0b4f2
--- /dev/null
+++ b/bin/rr
@@ -0,0 +1,312 @@
+#!/usr/bin/env php
+<?php
+/**
+ * RoadRunner
+ * High-performance PHP process supervisor and load balancer written in Go
+ *
+ * This file responsive for cli commands
+ */
+declare(strict_types=1);
+
+foreach ([
+ __DIR__ . '/../../../autoload.php',
+ __DIR__ . '/../vendor/autoload.php',
+ __DIR__ . '/vendor/autoload.php',
+ __DIR__ . '/../vendor_php/autoload.php'
+ ] as $file) {
+ if (file_exists($file)) {
+ define('RR_COMPOSER_INSTALL', $file);
+
+ break;
+ }
+}
+
+unset($file);
+
+if (!defined('RR_COMPOSER_INSTALL')) {
+ fwrite(
+ STDERR,
+ 'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL .
+ ' composer install' . PHP_EOL . PHP_EOL .
+ 'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL
+ );
+
+ die(1);
+}
+
+if (RRHelper::getOSType() !== 'linux' && !class_exists('ZipArchive')) {
+ fwrite(STDERR, 'Extension `php-zip` is required.' . PHP_EOL);
+ die(1);
+}
+
+if (!function_exists('curl_init')) {
+ fwrite(STDERR, 'Extension `php-curl` is required.' . PHP_EOL);
+ die(1);
+}
+
+require RR_COMPOSER_INSTALL;
+
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Question\ConfirmationQuestion;
+use function Couchbase\defaultDecoder;
+
+class RRHelper
+{
+ /**
+ * @var string
+ */
+ public const SELF_PACKAGE_NAME = 'spiral/roadrunner';
+
+ /**
+ * Returns version of RoadRunner based on current package version.
+ *
+ * @return string Version of RoadRunner (eg.: `1.8.0`)
+ */
+ public static function getVersion(): string
+ {
+ $version = \PackageVersions\Versions::getVersion(self::SELF_PACKAGE_NAME);
+
+ if (\is_int($delimiter_position = \mb_strpos($version, '@'))) {
+ $version = \mb_substr($version, 0, (int) $delimiter_position);
+ }
+
+ return \ltrim($version, 'vV');
+ }
+
+ /**
+ * Returns OS Type for filename
+ *
+ * @return string OS Type
+ */
+ public static function getOSType(): string
+ {
+ switch (PHP_OS) {
+ case 'Darwin':
+ return 'darwin';
+ case 'Linux':
+ return 'linux';
+ case 'FreeBSD':
+ return 'freebsd';
+ case 'WIN32':
+ case 'WINNT':
+ case 'Windows':
+ return 'windows';
+ default:
+ return 'linux';
+ }
+ }
+
+ /**
+ * @return string
+ * @throws Exception
+ */
+ public static function getSignature(): string
+ {
+ return 'roadrunner-' . self::getVersion() . '-' . self::getOSType() . '-amd64';
+ }
+
+ /**
+ * Returns generated URL to zip file on GitHub with binary file
+ *
+ * @return string URL
+ * @throws Exception
+ */
+ public static function getBinaryDownloadUrl(): string
+ {
+ $ext = '.zip';
+ if (self::getOSType() == 'linux') {
+ $ext = '.tar.gz';
+ }
+
+ return 'https://github.com/spiral/roadrunner/releases/download/v'
+ . static::getVersion() . '/' . self::getSignature()
+ . $ext;
+ }
+
+ /**
+ * Extracts the roadrunner RR binary into given location.
+ *
+ * @param string $archive
+ * @param string $target
+ * @throws Exception
+ */
+ public static function extractBinary(string $archive, string $target)
+ {
+ if (self::getOSType() !== 'linux') {
+ self::extractZIP($archive, $target);
+ } else {
+ self::extractTAR($archive, $target);
+ }
+ }
+
+ /**
+ * @param string $archive
+ * @param string $target
+ * @throws Exception
+ */
+ protected static function extractZIP(string $archive, string $target)
+ {
+ $zip = new ZipArchive();
+ $zip->open($archive);
+
+ $name = self::getSignature() . '/rr';
+ if (self::getOSType() == 'windows') {
+ $name .= '.exe';
+ }
+
+ $stream = $zip->getStream($name);
+ if (!is_resource($stream)) {
+ return;
+ }
+
+ $to = fopen($target, 'w');
+ stream_copy_to_stream($stream, $to);
+ fclose($to);
+
+ $zip->close();
+ }
+
+ /**
+ * @param string $archive
+ * @param string $target
+ * @throws Exception
+ */
+ protected static function extractTAR(string $archive, string $target)
+ {
+ $arch = new PharData($archive);
+ $arch->extractTo('./', self::getSignature() . '/rr');
+
+ copy('./' . self::getSignature() . '/rr', $target);
+ unlink('./' . self::getSignature() . '/rr');
+ rmdir('./' . self::getSignature());
+ }
+}
+
+(new Application('RoadRunner', RRHelper::getVersion()))
+ ->register('get-binary')
+ ->setDescription("Install or update RoadRunner binaries in specified folder (current folder by default)")
+ ->addOption('location', 'l', InputArgument::OPTIONAL, 'destination folder', '.')
+ ->setCode(function (InputInterface $input, OutputInterface $output) {
+ $output->writeln('<info>Updating binary file of RoadRunner</info>');
+
+ $finalFile = $input->getOption('location') . DIRECTORY_SEPARATOR . 'rr';
+ if (RRHelper::getOSType() == 'windows') {
+ $finalFile .= '.exe';
+ }
+
+ if (is_file($finalFile)) {
+ $version = RRHelper::getVersion();
+
+ $previousVersion = preg_match(
+ '#Version:.+(\d+\.\d+\.\d+)#',
+ (string)shell_exec($finalFile),
+ $matches
+ ) ? $matches[1] : "";
+
+ $output->writeln('<error>RoadRunner binary file already exists!</error>');
+ $helper = $this->getHelper('question');
+
+ if (version_compare($previousVersion, $version) === 0) {
+ $output->writeln(sprintf('<info>Current version: %s</info>', $previousVersion));
+ $question = new ConfirmationQuestion(
+ sprintf('Skip update to the same version: %s ? [Y/n]', $version)
+ );
+ if ($helper->ask($input, $output, $question)) {
+ return;
+ }
+ } else {
+ $question = new ConfirmationQuestion('Do you want overwrite it? [Y/n]');
+ if (!$helper->ask($input, $output, $question)) {
+ return;
+ }
+ }
+ }
+
+ $output->writeln('<info>Downloading RoadRunner archive for <fg=cyan>' . ucfirst(RRHelper::getOSType()) . '</fg=cyan></info>');
+
+ $progressBar = new ProgressBar($output);
+ $progressBar->setFormat('verbose');
+
+ $zipFileName = 'rr_zip_'.random_int(0, 10000);
+ if (RRHelper::getOSType() == 'linux') {
+ $zipFileName .= '.tar.gz';
+ }
+
+ $zipFile = fopen($zipFileName, "w+");
+ $curlResource = curl_init();
+
+ curl_setopt($curlResource, CURLOPT_URL, RRHelper::getBinaryDownloadUrl());
+ curl_setopt($curlResource, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($curlResource, CURLOPT_BINARYTRANSFER, true);
+ curl_setopt($curlResource, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($curlResource, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($curlResource, CURLOPT_FILE, $zipFile);
+ curl_setopt($curlResource, CURLOPT_PROGRESSFUNCTION,
+ function ($resource, $download_size, $downloaded, $upload_size, $uploaded) use (&$progressBar, $output) {
+ if ($download_size == 0) {
+ return;
+ }
+
+ if ($progressBar->getStartTime() === 0) {
+ $progressBar->start();
+ }
+
+ if ($progressBar->getMaxSteps() != $download_size) {
+ /**
+ * Workaround for symfony < 4.1.x, for example PHP 7.0 will use 3.x
+ * feature #26449 Make ProgressBar::setMaxSteps public (ostrolucky)
+ */
+ $progressBar = new ProgressBar($output, $download_size);
+ }
+
+ $progressBar->setFormat('[%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% ' . intval($download_size / 1024) . 'KB');
+ $progressBar->setProgress($downloaded);
+ });
+ curl_setopt($curlResource, CURLOPT_NOPROGRESS, false); // needed to make progress function work
+ curl_setopt($curlResource, CURLOPT_HEADER, 0);
+ curl_exec($curlResource);
+ curl_close($curlResource);
+ fclose($zipFile);
+
+ $progressBar->finish();
+ $output->writeln("");
+
+ $output->writeln('<info>Unpacking <comment>' . basename(RRHelper::getBinaryDownloadUrl()) . '</comment></info>');
+
+ RRHelper::extractBinary($zipFileName, $finalFile);
+ unlink($zipFileName);
+
+ if (!file_exists($finalFile) || filesize($finalFile) === 0) {
+ throw new Exception('Unable to extract the file.');
+ }
+
+ chmod($finalFile, 0755);
+ $output->writeln('<info>Binary file updated!</info>');
+ })
+ ->getApplication()
+ ->register("init-config")
+ ->setDescription("Inits default .rr.yaml config in specified folder (current folder by default)")
+ ->addOption('location', 'l', InputArgument::OPTIONAL, 'destination folder', '.')
+ ->setCode(function (InputInterface $input, OutputInterface $output) {
+ if (is_file($input->getOption('location') . DIRECTORY_SEPARATOR . '.rr.yaml')) {
+ $output->writeln('<error>Config file already exists!</error>');
+ $helper = $this->getHelper('question');
+ $question = new ConfirmationQuestion('Do you want overwrite it? [Y/n] ');
+
+ if (!$helper->ask($input, $output, $question)) {
+ return;
+ }
+ }
+
+ copy(
+ __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '.rr.yaml',
+ $input->getOption('location') . DIRECTORY_SEPARATOR . '.rr.yaml'
+ );
+ $output->writeln('<info>Config file created!</info>');
+ })
+ ->getApplication()
+ ->run();
diff --git a/bors.toml b/bors.toml
index fbbfa9db..e56a268a 100755
--- a/bors.toml
+++ b/bors.toml
@@ -1,11 +1,18 @@
status = [
- 'Build (PHP 7.4, Go 1.15, OS ubuntu-latest)',
- 'Build (PHP 7.4, Go 1.14, OS ubuntu-latest)',
- 'Build (PHP 8, Go 1.15, OS ubuntu-latest)',
- 'Build (PHP 8, Go 1.14, OS ubuntu-latest)',
- 'runner / golangci-lint', ]
-
+'Build (PHP 7.3, basic setup)',
+'Build (PHP 7.3, lowest setup)',
+'Build (PHP 8.0, basic setup)',
+'Build (PHP 8.0, lowest setup)',
+'Build (Go 1.14, PHP 7.3)',
+'Build (Go 1.15, PHP 7.3)',
+'Build (Go 1.14, PHP 7.4)',
+'Build (Go 1.15, PHP 7.4)',
+'Build (Go 1.14, PHP 8.0)',
+'Build (Go 1.15, PHP 8.0)',
+'Golang-CI (lint)',
+'Build docker image',
+]
required_approvals = 1
delete_merged_branches = true
-timeout-sec = 600 \ No newline at end of file
+timeout-sec = 1800
diff --git a/cmd/rr/cmd/root.go b/cmd/rr/cmd/root.go
new file mode 100644
index 00000000..e67a4e62
--- /dev/null
+++ b/cmd/rr/cmd/root.go
@@ -0,0 +1,158 @@
+// Copyright (c) 2018 SpiralScout
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+package cmd
+
+import (
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+ "github.com/spiral/roadrunner/cmd/util"
+ "github.com/spiral/roadrunner/service"
+ "github.com/spiral/roadrunner/service/limit"
+ "log"
+ "net/http"
+ "net/http/pprof"
+ "os"
+)
+
+// Services bus for all the commands.
+var (
+ cfgFile, workDir, logFormat string
+ override []string
+ mergeJson string
+
+ // Verbose enables verbosity mode (container specific).
+ Verbose bool
+
+ // Debug enables debug mode (service specific).
+ Debug bool
+
+ // Logger - shared logger.
+ Logger = logrus.New()
+
+ // Container - shared service bus.
+ Container = service.NewContainer(Logger)
+
+ // CLI is application endpoint.
+ CLI = &cobra.Command{
+ Use: "rr",
+ SilenceErrors: true,
+ SilenceUsage: true,
+ Version: Version, // allows to use `--version` flag
+ Short: util.Sprintf(
+ "<green>RoadRunner</reset>, PHP Application Server\nVersion: <yellow+hb>%s</reset>, %s",
+ Version,
+ BuildTime,
+ ),
+ }
+)
+
+// Execute adds all child commands to the CLI command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the CLI.
+func Execute() {
+ if err := CLI.Execute(); err != nil {
+ util.ExitWithError(err)
+ }
+}
+
+func init() {
+ CLI.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
+ CLI.PersistentFlags().BoolVarP(&Debug, "debug", "d", false, "debug mode")
+ CLI.PersistentFlags().StringVarP(&logFormat, "logFormat", "l", "color", "select log formatter (color, json, plain)")
+ CLI.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is .rr.yaml)")
+ CLI.PersistentFlags().StringVarP(&workDir, "workDir", "w", "", "work directory")
+ CLI.PersistentFlags().StringVarP(&mergeJson, "jsonConfig", "j", "", "merge json configuration")
+
+ CLI.PersistentFlags().StringArrayVarP(
+ &override,
+ "override",
+ "o",
+ nil,
+ "override config value (dot.notation=value)",
+ )
+
+ cobra.OnInitialize(func() {
+ if Verbose {
+ Logger.SetLevel(logrus.DebugLevel)
+ }
+
+ configureLogger(logFormat)
+
+ cfg, err := util.LoadConfig(cfgFile, []string{"."}, ".rr", override, mergeJson)
+ if err != nil {
+ Logger.Warnf("config: %s", err)
+ return
+ }
+
+ if workDir != "" {
+ if err := os.Chdir(workDir); err != nil {
+ util.ExitWithError(err)
+ }
+ }
+
+ if err := Container.Init(cfg); err != nil {
+ util.ExitWithError(err)
+ }
+
+ // global watcher config
+ if Verbose {
+ wcv, _ := Container.Get(limit.ID)
+ if wcv, ok := wcv.(*limit.Service); ok {
+ wcv.AddListener(func(event int, ctx interface{}) {
+ util.LogEvent(Logger, event, ctx)
+ })
+ }
+ }
+
+ // if debug --> also run pprof service
+ if Debug {
+ go runDebugServer()
+ }
+ })
+}
+func runDebugServer() {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/debug/pprof/", pprof.Index)
+ mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
+ mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
+ mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
+ mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
+ srv := http.Server{
+ Addr: ":6061",
+ Handler: mux,
+ }
+
+ if err := srv.ListenAndServe(); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func configureLogger(format string) {
+ util.Colorize = false
+ switch format {
+ case "color", "default":
+ util.Colorize = true
+ Logger.Formatter = &logrus.TextFormatter{ForceColors: true}
+ case "plain":
+ Logger.Formatter = &logrus.TextFormatter{DisableColors: true}
+ case "json":
+ Logger.Formatter = &logrus.JSONFormatter{}
+ }
+}
diff --git a/composer.json b/composer.json
index 4b68d498..e3017b97 100755
--- a/composer.json
+++ b/composer.json
@@ -18,16 +18,17 @@
"ext-json": "*",
"ext-curl": "*",
"spiral/goridge": "^2.4.2",
- "psr/http-factory": "^1.0",
- "psr/http-message": "^1.0",
+ "psr/http-factory": "^1.0.1",
+ "psr/http-message": "^1.0.1",
"symfony/console": "^2.5.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
- "laminas/laminas-diactoros": "^1.3 || ^2.0"
+ "laminas/laminas-diactoros": "^1.3.6 || ^2.0",
+ "composer/package-versions-deprecated": "^1.8"
},
"config": {
"vendor-dir": "vendor_php"
},
"require-dev": {
- "phpstan/phpstan": "~0.12"
+ "phpstan/phpstan": "~0.12.34"
},
"scripts": {
"analyze": "phpstan analyze -c ./phpstan.neon.dist --no-progress --ansi"
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 50c2a587..b5fec74d 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -1,5 +1,4 @@
parameters:
level: 'max'
- checkMissingIterableValueType: false
paths:
- src \ No newline at end of file
diff --git a/src/Diactoros/ServerRequestFactory.php b/src/Diactoros/ServerRequestFactory.php
index 3fcf8e29..6a42f207 100644
--- a/src/Diactoros/ServerRequestFactory.php
+++ b/src/Diactoros/ServerRequestFactory.php
@@ -11,12 +11,14 @@ namespace Spiral\RoadRunner\Diactoros;
use Psr\Http\Message\ServerRequestFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;
-use Zend\Diactoros\ServerRequest;
+use Laminas\Diactoros\ServerRequest;
final class ServerRequestFactory implements ServerRequestFactoryInterface
{
/**
* @inheritdoc
+ *
+ * @param array<mixed> $serverParams Array of SAPI parameters with which to seed the generated request instance.
*/
public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
{
diff --git a/src/Diactoros/StreamFactory.php b/src/Diactoros/StreamFactory.php
index cc0a5306..68a77e92 100644
--- a/src/Diactoros/StreamFactory.php
+++ b/src/Diactoros/StreamFactory.php
@@ -12,7 +12,7 @@ namespace Spiral\RoadRunner\Diactoros;
use RuntimeException;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
-use Zend\Diactoros\Stream;
+use Laminas\Diactoros\Stream;
final class StreamFactory implements StreamFactoryInterface
{
diff --git a/src/Diactoros/UploadedFileFactory.php b/src/Diactoros/UploadedFileFactory.php
index 45773287..daa475c1 100644
--- a/src/Diactoros/UploadedFileFactory.php
+++ b/src/Diactoros/UploadedFileFactory.php
@@ -12,7 +12,7 @@ namespace Spiral\RoadRunner\Diactoros;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
-use Zend\Diactoros\UploadedFile;
+use Laminas\Diactoros\UploadedFile;
final class UploadedFileFactory implements UploadedFileFactoryInterface
{
diff --git a/src/HttpClient.php b/src/HttpClient.php
index 4ca152c8..9b9048ca 100644
--- a/src/HttpClient.php
+++ b/src/HttpClient.php
@@ -62,14 +62,13 @@ final class HttpClient
*/
public function respond(int $status, string $body, array $headers = []): void
{
- if (empty($headers)) {
- // this is required to represent empty header set as map and not as array
- $headers = new \stdClass();
- }
+ $sendHeaders = empty($headers)
+ ? new \stdClass() // this is required to represent empty header set as map and not as array
+ : $headers;
$this->getWorker()->send(
$body,
- (string) json_encode(['status' => $status, 'headers' => $headers])
+ (string) json_encode(['status' => $status, 'headers' => $sendHeaders])
);
}
}