Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fortio for openSUSE:Factory checked in at 2022-11-15 13:19:10 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fortio (Old) and /work/SRC/openSUSE:Factory/.fortio.new.1597 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fortio" Tue Nov 15 13:19:10 2022 rev:24 rq:1035795 version:1.38.4 Changes: -------- --- /work/SRC/openSUSE:Factory/fortio/fortio.changes 2022-10-26 12:32:01.772342940 +0200 +++ /work/SRC/openSUSE:Factory/.fortio.new.1597/fortio.changes 2022-11-15 13:21:50.916931824 +0100 @@ -1,0 +2,19 @@ +Tue Nov 15 09:21:44 UTC 2022 - ka...@b1-systems.de + +- Update to version 1.38.4: + * added test for #652, stepped on #653 while testing the test, added test for that too (#654) + +------------------------------------------------------------------- +Tue Nov 15 09:19:08 UTC 2022 - ka...@b1-systems.de + +- Update to version 1.38.3: + * support User-Agent change and deletion from -H, simplify UI for headers (#649) + * codeql v2 and a lot of github actions update, sha pinning in dockerfile etc (#647) + * Bump docker/setup-buildx-action from 1.7.0 to 2.2.1 (#646) + * Bump docker/setup-qemu-action from 1.2.0 to 2.1.0 (#645) + * Bump docker/login-action from 1.10.0 to 2.1.0 (#644) + * Bump docker/build-push-action from 2.5.0 to 3.2.0 (#643) + * bump build image (go 1.19.3), added govulncheck, fix permissions in /go - also stop using /go/src (#641) + * README formatting and links correction (#636) + +------------------------------------------------------------------- Old: ---- fortio-1.38.2.tar.gz New: ---- fortio-1.38.4.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fortio.spec ++++++ --- /var/tmp/diff_new_pack.8lgUdC/_old 2022-11-15 13:21:51.468934673 +0100 +++ /var/tmp/diff_new_pack.8lgUdC/_new 2022-11-15 13:21:51.476934714 +0100 @@ -19,7 +19,7 @@ %define __arch_install_post export NO_BRP_STRIP_DEBUG=true Name: fortio -Version: 1.38.2 +Version: 1.38.4 Release: 0 Summary: Load testing library, command line tool, advanced echo server and web UI License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.8lgUdC/_old 2022-11-15 13:21:51.508934880 +0100 +++ /var/tmp/diff_new_pack.8lgUdC/_new 2022-11-15 13:21:51.512934900 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/fortio/fortio</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.38.2</param> + <param name="revision">v1.38.4</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> @@ -17,7 +17,7 @@ <param name="compression">gz</param> </service> <service name="go_modules" mode="disabled"> - <param name="archive">fortio-1.38.2.tar.gz</param> + <param name="archive">fortio-1.38.4.tar.gz</param> </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.8lgUdC/_old 2022-11-15 13:21:51.532935004 +0100 +++ /var/tmp/diff_new_pack.8lgUdC/_new 2022-11-15 13:21:51.536935024 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/fortio/fortio</param> - <param name="changesrevision">d2a2d42f4f17df4c09e608353546ebd0d443747d</param></service></servicedata> + <param name="changesrevision">860b2f915597a1c48e6d74e6140ff94998093593</param></service></servicedata> (No newline at EOF) ++++++ fortio-1.38.2.tar.gz -> fortio-1.38.4.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/.circleci/config.yml new/fortio-1.38.4/.circleci/config.yml --- old/fortio-1.38.2/.circleci/config.yml 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/.circleci/config.yml 2022-11-13 00:06:32.000000000 +0100 @@ -8,8 +8,8 @@ &defaultEnv docker: # specify the version - - image: docker.io/fortio/fortio.build:v47 - working_directory: /go/src/fortio.org/fortio + - image: docker.io/fortio/fortio.build:v50@sha256:fe69c193d8ad40eb0d791984881f3678aead02660b8e3468c757f717892ada4c + working_directory: /build/fortio jobs: unit-tests: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/.github/dependabot.yml new/fortio-1.38.4/.github/dependabot.yml --- old/fortio-1.38.2/.github/dependabot.yml 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/.github/dependabot.yml 2022-11-13 00:06:32.000000000 +0100 @@ -4,3 +4,7 @@ directory: / schedule: interval: daily + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/.github/workflows/codeql-analysis.yml new/fortio-1.38.4/.github/workflows/codeql-analysis.yml --- old/fortio-1.38.2/.github/workflows/codeql-analysis.yml 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/.github/workflows/codeql-analysis.yml 2022-11-13 00:06:32.000000000 +0100 @@ -40,11 +40,11 @@ steps: - name: Checkout repository - uses: actions/checkout@7884fcad6b5d53d10323aee724dc68d8b9096a2e # pin@v2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@a6611b86918424d4588efe7d6dbe18fe52d42518 # pin@v1 + uses: github/codeql-action/init@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # pin@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,10 +52,10 @@ # 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 - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@a6611b86918424d4588efe7d6dbe18fe52d42518 # pin@v1 + uses: github/codeql-action/autobuild@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # pin@v2 # â¹ï¸ Command-line programs to run using the OS shell. # ð https://git.io/JvXDl @@ -66,4 +66,4 @@ # make bootstrap # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@a6611b86918424d4588efe7d6dbe18fe52d42518 # pin@v1 + uses: github/codeql-action/analyze@c3b6fce4ee2ca25bc1066aa3bf73962fda0e8898 # pin@v2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/.github/workflows/main.yml new/fortio-1.38.4/.github/workflows/main.yml --- old/fortio-1.38.2/.github/workflows/main.yml 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/.github/workflows/main.yml 2022-11-13 00:06:32.000000000 +0100 @@ -22,20 +22,20 @@ # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@7884fcad6b5d53d10323aee724dc68d8b9096a2e # pin@v2 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 - name: Set up QEMU - uses: docker/setup-qemu-action@27d0a4f181a40b142cce983c5393082c365d1480 # pin@v1 + uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@f211e3e9ded2d9377c8cadc4489a4e38014bc4c9 # pin@v1 + uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # pin@v2 - name: Available platforms run: echo ${{ steps.buildx.outputs.platforms }} - name: Log in to Docker Hub - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 # pin@f054a8b539a109f9f41c372932f1ae047eff08c9 + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a # pin@v2 with: username: ${{ secrets.DOCKER_USER }} password: ${{ secrets.DOCKER_TOKEN }} @@ -46,27 +46,27 @@ make info make release VERSION=$(make echo-version) - echo ::set-output name=VERSION::${VERSION} + echo "VERSION=${VERSION}" >> $GITHUB_ENV PACKAGE_VERSION=$(make echo-package-version) - echo ::set-output name=PACKAGE_VERSION::${PACKAGE_VERSION} echo "Version $VERSION, Package version $PACKAGE_VERSION" - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc # pin@ad44023a93711e3deb337508980b4b5e9bcdc5dc + uses: docker/build-push-action@c56af957549030174b10d6867f20e78cfd7debc5 # pin@v3 with: context: . platforms: linux/amd64,linux/arm64,linux/ppc64le,linux/s390x push: true - tags: fortio/fortio:${{ steps.build.outputs.VERSION }}, fortio/fortio:latest + tags: fortio/fortio:${{ env.VERSION }}, fortio/fortio:latest - name: Create Release id: create_release + # Need to find a replacement not using set-output uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # pin@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token with: tag_name: ${{ github.ref }} - release_name: Fortio ${{ steps.build.outputs.VERSION }} + release_name: Fortio ${{ env.VERSION }} draft: true - name: Upload release artifacts diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/.github/workflows/manual-build.yml new/fortio-1.38.4/.github/workflows/manual-build.yml --- old/fortio-1.38.2/.github/workflows/manual-build.yml 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/.github/workflows/manual-build.yml 2022-11-13 00:06:32.000000000 +0100 @@ -21,16 +21,16 @@ run: | echo "tag is ${{ github.event.inputs.tag }}" - - uses: actions/checkout@7884fcad6b5d53d10323aee724dc68d8b9096a2e # pin@v2 + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # pin@v3 with: ref: ${{ github.event.inputs.tag }} - name: Set up QEMU - uses: docker/setup-qemu-action@27d0a4f181a40b142cce983c5393082c365d1480 # pin@v1 + uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # pin@v2 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@f211e3e9ded2d9377c8cadc4489a4e38014bc4c9 # pin@v1 + uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # pin@v2 - name: Build id: build @@ -38,9 +38,7 @@ make info make release VERSION=$(make echo-version) - echo ::set-output name=VERSION::${VERSION} PACKAGE_VERSION=$(make echo-package-version) - echo ::set-output name=PACKAGE_VERSION::${PACKAGE_VERSION} echo "Version $VERSION, Package version $PACKAGE_VERSION" - name: Upload release artifacts diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/Dockerfile new/fortio-1.38.4/Dockerfile --- old/fortio-1.38.2/Dockerfile 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/Dockerfile 2022-11-13 00:06:32.000000000 +0100 @@ -1,6 +1,6 @@ # Build the binaries in larger image -FROM docker.io/fortio/fortio.build:v47 as build -WORKDIR /go/src/fortio.org +FROM docker.io/fortio/fortio.build:v50@sha256:fe69c193d8ad40eb0d791984881f3678aead02660b8e3468c757f717892ada4c as build +WORKDIR /build COPY . fortio ARG MODE=install # We moved a lot of the logic into the Makefile so it can be reused in brew @@ -12,8 +12,8 @@ # NOTE: the list of files here, if updated, must be changed in release/Dockerfile.in too COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ # TODO: get rid of *.bak, *~ and other spurious non source files -#COPY --from=build /go/src/fortio.org/fortio/ui/static /usr/share/fortio/static -#COPY --from=build /go/src/fortio.org/fortio/ui/templates /usr/share/fortio/templates +#COPY --from=build /build/fortio/ui/static /usr/share/fortio/static +#COPY --from=build /build/fortio/ui/templates /usr/share/fortio/templates COPY --from=build /build/result/fortio /usr/bin/fortio EXPOSE 8078 EXPOSE 8079 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/Dockerfile.build new/fortio-1.38.4/Dockerfile.build --- old/fortio-1.38.2/Dockerfile.build 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/Dockerfile.build 2022-11-13 00:06:32.000000000 +0100 @@ -1,5 +1,5 @@ # Dependencies and linters for build: -FROM golang:1.19.2 +FROM golang:1.19.3 # Need gcc for -race test (and some linters though those work with CGO_ENABLED=0) RUN apt-get -y update && \ apt-get --no-install-recommends -y upgrade && \ @@ -21,9 +21,12 @@ apt-get -y update && apt-get install --no-install-recommends -y docker-ce; \ fi +# govulncheck +RUN go install golang.org/x/vuln/cmd/govulncheck@latest WORKDIR /build COPY .golangci.yml . VOLUME /build RUN useradd -m build -d /build RUN chown -R build:build /build +RUN chown -R build:build /go USER build diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/Dockerfile.echosrv new/fortio-1.38.4/Dockerfile.echosrv --- old/fortio-1.38.2/Dockerfile.echosrv 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/Dockerfile.echosrv 2022-11-13 00:06:32.000000000 +0100 @@ -1,10 +1,10 @@ # Build the binaries in larger image -FROM docker.io/fortio/fortio.build:v47 as build -WORKDIR /go/src/fortio.org +FROM docker.io/fortio/fortio.build:v50@sha256:fe69c193d8ad40eb0d791984881f3678aead02660b8e3468c757f717892ada4c as build +WORKDIR /build COPY . fortio -RUN make -C fortio official-build-version BUILD_DIR=/build OFFICIAL_TARGET=fortio.org/fortio/echosrv OFFICIAL_BIN=../echosrv.bin +RUN make -C fortio official-build-version BUILD_DIR=/build OFFICIAL_TARGET=fortio.org/fortio/echosrv # Minimal image with just the binary FROM scratch -COPY --from=build /go/src/fortio.org/echosrv.bin /usr/bin/echosrv +COPY --from=build /build/result/echosrv /usr/bin/echosrv EXPOSE 8080 ENTRYPOINT ["/usr/bin/echosrv"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/Dockerfile.fcurl new/fortio-1.38.4/Dockerfile.fcurl --- old/fortio-1.38.2/Dockerfile.fcurl 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/Dockerfile.fcurl 2022-11-13 00:06:32.000000000 +0100 @@ -1,11 +1,10 @@ # Build the binaries in larger image -FROM docker.io/fortio/fortio.build:v47 as build -WORKDIR /go/src/fortio.org +FROM docker.io/fortio/fortio.build:v50@sha256:fe69c193d8ad40eb0d791984881f3678aead02660b8e3468c757f717892ada4c as build +WORKDIR /build COPY . fortio -# fcurl should not need vendor/no dependencies -RUN make -C fortio official-build-version BUILD_DIR=/build OFFICIAL_TARGET=fortio.org/fortio/fcurl OFFICIAL_BIN=../fcurl.bin +RUN make -C fortio official-build-version BUILD_DIR=/build OFFICIAL_TARGET=fortio.org/fortio/fcurl # Minimal image with just the binary and certs FROM scratch COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY --from=build /go/src/fortio.org/fcurl.bin /usr/bin/fcurl +COPY --from=build /build/result/fcurl /usr/bin/fcurl ENTRYPOINT ["/usr/bin/fcurl"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/Makefile new/fortio-1.38.4/Makefile --- old/fortio-1.38.2/Makefile 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/Makefile 2022-11-13 00:06:32.000000000 +0100 @@ -7,7 +7,7 @@ IMAGES=echosrv fcurl # plus the combo image / Dockerfile without ext. DOCKER_PREFIX := docker.io/fortio/fortio -BUILD_IMAGE_TAG := v47 +BUILD_IMAGE_TAG := v50@sha256:fe69c193d8ad40eb0d791984881f3678aead02660b8e3468c757f717892ada4c BUILDX_PLATFORMS := linux/amd64,linux/arm64,linux/ppc64le,linux/s390x BUILDX_POSTFIX := ifeq '$(shell echo $(BUILDX_PLATFORMS) | awk -F "," "{print NF-1}")' '0' @@ -57,23 +57,24 @@ # DEBUG_LINTERS="--debug" local-lint: + govulncheck $(LINT_PACKAGES) golangci-lint version golangci-lint --timeout 120s $(DEBUG_LINTERS) run $(LINT_PACKAGES) # Lint everything by default but ok to "make lint LINT_PACKAGES=./fhttp" LINT_PACKAGES:=./... lint: - docker run -v $(CURDIR):/go/src/fortio.org/fortio $(BUILD_IMAGE) bash -c \ - "cd /go/src/fortio.org/fortio \ + docker run -v $(CURDIR):/build/fortio $(BUILD_IMAGE) bash -c \ + "cd /build/fortio \ && time make local-lint DEBUG_LINTERS=\"$(DEBUG_LINTERS)\" LINT_PACKAGES=\"$(LINT_PACKAGES)\"" docker-test: - docker run -v $(CURDIR):/go/src/fortio.org/fortio $(BUILD_IMAGE) bash -c \ - "cd /go/src/fortio.org/fortio \ + docker run -v $(CURDIR):/build/fortio $(BUILD_IMAGE) bash -c \ + "cd /build/fortio \ && time make test" shell: - docker run -ti -v $(CURDIR):/go/src/fortio.org/fortio $(BUILD_IMAGE) + docker run -ti -v $(CURDIR):/build/fortio $(BUILD_IMAGE) # This really also tests the release process and build on windows,mac,linux # and the docker images, not just "web" (ui) stuff that it also exercises. @@ -108,10 +109,14 @@ docker buildx create --use $(MAKE) docker-push-internal IMAGE=.build TAG=$(BUILD_IMAGE_TAG) +# Get the sha (use after newly building a new build image) to put it back in BUILD_IMAGE_TAG +build-image-sha: + docker inspect $(BUILD_IMAGE) | jq -r '.[0].RepoDigests[0]' | sed -e "s/^.*@/$(BUILD_IMAGE_TAG)@/" + SED:=sed update-build-image-tag: @echo 'Need to use gnu sed (brew install gnu-sed; make update-build-image-tag SED=gsed)' - $(SED) --in-place=.bak -e 's!$(DOCKER_PREFIX).build:v..!$(BUILD_IMAGE)!g' $(FILES_WITH_IMAGE) + $(SED) --in-place=.bak -E -e 's!$(DOCKER_PREFIX).build:v[^ ]+!$(BUILD_IMAGE)!g' $(FILES_WITH_IMAGE) docker-default-platform: @docker buildx --builder default inspect | tail -1 | sed -e "s/Platforms: //" -e "s/,//g" | awk '{print $$1}' @@ -135,7 +140,7 @@ .PHONY: all docker-internal docker-push-internal docker-version test dependencies -.PHONY: go-install lint install-linters coverage webtest release-test update-build-image +.PHONY: go-install lint install-linters coverage webtest release-test update-build-image build-image-sha .PHONY: local-lint update-build-image-tag release pull certs certs-clean @@ -143,7 +148,8 @@ BUILD_DIR := /tmp/fortio_build BUILD_DIR_ABS := $(abspath $(BUILD_DIR)) BUILD_DIR_BIN := $(BUILD_DIR_ABS)/bin -OFFICIAL_BIN ?= $(BUILD_DIR)/result/fortio +OFFICIAL_EXE ?= $(notdir $(OFFICIAL_TARGET)) +OFFICIAL_BIN ?= $(BUILD_DIR)/result/$(OFFICIAL_EXE) OFFICIAL_DIR ?= $(dir $(OFFICIAL_BIN)) GOOS := @@ -177,14 +183,16 @@ official-build: official-build-internal official-build-internal: $(BUILD_DIR) $(OFFICIAL_DIR) + @echo "Building OFFICIAL_EXE=$(OFFICIAL_EXE) BUILD_DIR=$(BUILD_DIR) BUILD_DIR_BIN=$(BUILD_DIR_BIN) MODE=$(MODE)" + @echo "OFFICIAL_BIN=$(OFFICIAL_BIN) OFFICIAL_DIR=$(OFFICIAL_DIR) OFFICIAL_TARGET=$(OFFICIAL_TARGET)" $(GO_BIN) version ifeq ($(MODE),install) GOPATH=$(BUILD_DIR_ABS) CGO_ENABLED=0 GOOS=$(GOOS) $(GO_BIN) install -a -ldflags -s $(OFFICIAL_TARGET)@v$(DIST_VERSION) # rename when building cross architecture (on windows it has .exe suffix thus the *) ls -lR $(BUILD_DIR_BIN) - -mv -f $(BUILD_DIR_BIN)/*_*/fortio* $(BUILD_DIR_BIN) + -mv -f $(BUILD_DIR_BIN)/*_*/$(OFFICIAL_EXE)* $(BUILD_DIR_BIN) -rmdir $(BUILD_DIR_BIN)/*_* - mv -f $(BUILD_DIR_BIN)/fortio* $(OFFICIAL_DIR) + mv -f $(BUILD_DIR_BIN)/$(OFFICIAL_EXE)* $(OFFICIAL_DIR) else CGO_ENABLED=0 GOOS=$(GOOS) $(GO_BIN) build -a -ldflags -s -o $(OFFICIAL_BIN) $(OFFICIAL_TARGET) endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/README.md new/fortio-1.38.4/README.md --- old/fortio-1.38.2/README.md 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/README.md 2022-11-13 00:06:32.000000000 +0100 @@ -11,7 +11,7 @@ Fortio (ΦοÏÏίο) started as, and is, [Istio](https://istio.io/)'s load testing tool and later (2018) graduated to be its own project. -Fortio is also used by, among others, [Meshery](https://docs.meshery.io/extensibility/load-generators) +Fortio is also used by, among others, [Meshery](https://docs.meshery.io/extensibility/load-generators). Fortio runs at a specified query per second (qps) and records an histogram of execution time and calculates percentiles (e.g. p99 ie the response time such as 99% of the requests take less than that number (in seconds, SI unit)). @@ -31,11 +31,11 @@ As well as the newly integrated [Dynamic Flags](dflag/) support (greatly inspired/imported initially from https://github.com/mwitkow/go-flagz but recently reimplemented using Go generics). Even more recent is the new `jrpc` JSON Remote Procedure Calls library package ([docs](https://pkg.go.dev/fortio.org/fortio/jrpc)). -If you want to connect to fortio using https and fortio to provide real TLS certificates, or to multiplex grpc and regular http behind a single port, check out [Fortio Proxy](https://github.com/fortio/proxy#fortio-proxy) +If you want to connect to fortio using https and fortio to provide real TLS certificates, or to multiplex grpc and regular http behind a single port, check out [Fortio Proxy](https://github.com/fortio/proxy#fortio-proxy). ## Installation -We publish a multi architecture docker image (linux/amd64, linux/arm64, linux/ppc64le, linux/s390x) `fortio/fortio` +We publish a multi architecture docker image (linux/amd64, linux/arm64, linux/ppc64le, linux/s390x) `fortio/fortio`. For instance: ```shell @@ -49,16 +49,16 @@ 2. `go install fortio.org/fortio@latest` 3. you can now run `fortio` (from your gopath bin/ directory, usually `~/go/bin`) -The [releases](https://github.com/fortio/fortio/releases) page has binaries for many OS/architecture combinations (see assets). +The [releases](https://github.com/fortio/fortio/releases) page has binaries for many OS/architecture combinations (see assets): ```shell -curl -L https://github.com/fortio/fortio/releases/download/v1.38.2/fortio-linux_amd64-1.38.2.tgz \ +curl -L https://github.com/fortio/fortio/releases/download/v1.38.4/fortio-linux_amd64-1.38.4.tgz \ | sudo tar -C / -xvzpf - # or the debian package -wget https://github.com/fortio/fortio/releases/download/v1.38.2/fortio_1.38.2_amd64.deb -dpkg -i fortio_1.38.2_amd64.deb +wget https://github.com/fortio/fortio/releases/download/v1.38.4/fortio_1.38.4_amd64.deb +dpkg -i fortio_1.38.4_amd64.deb # or the rpm -rpm -i https://github.com/fortio/fortio/releases/download/v1.38.2/fortio-1.38.2-1.x86_64.rpm +rpm -i https://github.com/fortio/fortio/releases/download/v1.38.4/fortio-1.38.4-1.x86_64.rpm # and more, see assets in release page ``` @@ -68,7 +68,7 @@ brew install fortio ``` -On Windows, download https://github.com/fortio/fortio/releases/download/v1.38.2/fortio_win_1.38.2.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: +On Windows, download https://github.com/fortio/fortio/releases/download/v1.38.4/fortio_win_1.38.4.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: ``` fortio.exe server ``` @@ -76,7 +76,7 @@ Once `fortio server` is running, you can visit its web UI at [http://localhost:8080/fortio/](http://localhost:8080/fortio/) -You can get a preview of the reporting/graphing UI at [https://demo.fortio.org/](https://demo.fortio.org/). +You can get a preview of the reporting/graphing UI at [https://demo.fortio.org](https://demo.fortio.org) <!-- and on [istio.io/docs/performance-and-scalability/synthetic-benchmarks/](https://istio.io/docs/performance-and-scalability/synthetic-benchmarks/) --> @@ -116,15 +116,17 @@ <details> <!-- use release/updateFlags.sh to update this section --> <pre> -ΦοÏÏίο 1.38.2 usage: +ΦοÏÏίο 1.38.4 usage: fortio command [flags] target where command is one of: load (load testing), server (starts ui, rest api, - http-echo, redirect, proxies, tcp-echo and grpc ping servers), tcp-echo (only - the tcp-echo server), report (report only UI server), redirect (only the - redirect server), proxies (only the -M and -P configured proxies), grpcping - (grpc client), or curl (single URL debug), or nc (single tcp or udp:// - connection), or version (prints the full version and build details). -where target is a url (http load tests) or host:port (grpc health test). + http-echo, redirect, proxies, tcp-echo, udp-echo and grpc ping servers), + tcp-echo (only the tcp-echo server), udp-echo (only udp-echo server), + report (report only UI server), redirect (only the redirect server), + proxies (only the -M and -P configured proxies), grpcping (grpc client), + or curl (single URL debug), or nc (single tcp or udp:// connection), + or version (prints the full version and build details). +where target is a url (http load tests) or host:port (grpc health test), + or tcp://host:port (tcp load test), or udp://host:port (udp load test). flags are: -H header Additional header(s) @@ -341,7 +343,7 @@ </pre> </details> -See also the FAQ entry about [fortio flags for best results](https://github.com/fortio/fortio/wiki/FAQ#i-want-to-get-the-best-results-what-flags-should-i-pass) +See also the FAQ entry about [fortio flags for best results](https://github.com/fortio/fortio/wiki/FAQ#i-want-to-get-the-best-results-what-flags-should-i-pass). ## Server URLs and features @@ -370,10 +372,10 @@ * `/fortio/` A UI to * Run/Trigger tests and graph the results. * A UI to browse saved results and single graph or multi graph them (comparative graph of min,avg, median, p75, p99, p99.9 and max). - * Proxy/fetch other URLs + * Proxy/fetch other URLs. * `/fortio/data/index.tsv` an tab separated value file conforming to Google cloud storage [URL list data transfer format](https://cloud.google.com/storage/transfer/create-url-list) so you can export/backup local results to the cloud. - * Download/sync peer to peer JSON results files from other Fortio servers (using their `index.tsv` URLs) - * Download/sync from an Amazon S3 or Google Cloud compatible bucket listings [XML URLs](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html) + * Download/sync peer to peer JSON results files from other Fortio servers (using their `index.tsv` URLs). + * Download/sync from an Amazon S3 or Google Cloud compatible bucket listings [XML URLs](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html). * API to trigger and cancel runs from the running server (like the form ui but more directly and with `async=on` option) * `/fortio/rest/run` starts a run; the arguments are either from the command line or from POSTed JSON; `jsonPath` can be provided to look for in a subset of the json object, for instance `jsonPath=metadata` allows to use the flagger webhook meta data for fortio run parameters (see [Remote Triggered load test section below](#remote-triggered-load-test-server-mode-rest-api)). @@ -406,7 +408,7 @@ ### Sample of the graphing UI -With the 2 histograms - total and errors overlayed +With the 2 histograms - total and errors overlayed:  @@ -457,7 +459,7 @@ ``` ### TCP -Start the echo-server alone and run a load (use `tcp://` prefix for the load test to be for tcp echo server) +Start the echo-server alone and run a load (use `tcp://` prefix for the load test to be for tcp echo server): ```Shell $ fortio tcp-echo & Fortio X.Y.Z tcp-echo TCP server listening on [::]:8078 @@ -486,7 +488,7 @@ ``` ### UDP -Start the udp-echo server alone and run a load (use `tcp://` prefix for the load test to be for tcp echo server) +Start the udp-echo server alone and run a load (use `udp://` prefix for the load test to be for udp echo server): ``` $ fortio udp-echo & Fortio X.Y.Z udp-echo UDP server listening on [::]:8078 @@ -568,7 +570,7 @@ * First, start Fortio server with the `-cert` and `-key` flags: `/path/to/fortio/server.crt` and `/path/to/fortio/server.key` are paths to the TLS certificate and key that -you must provide. +you must provide: ```Shell $ fortio server -cert /path/to/fortio/server.crt -key /path/to/fortio/server.key @@ -655,10 +657,10 @@ ### Remote triggered load test (server mode rest API) New since 1.18 the server has a `fortio/rest/run` endpoint similar to what the form UI submit in `fortio/` to start a run. - - plus `async` query arg or json value `"on"` will make the run asynchronous (returns just the runid of the run instead of waiting for the result) - - plus read all the run configuration from either query args or jsonPath POSTed info - - compatible with [flagger](https://github.com/fluxcd/flagger) and other webhooks - - New in 1.22: use `headers` json array to send headers (or multiple `&H=` query args) + - plus `async` query arg or json value `"on"` will make the run asynchronous (returns just the runid of the run instead of waiting for the result); + - plus read all the run configuration from either query args or jsonPath POSTed info; + - compatible with [flagger](https://github.com/fluxcd/flagger) and other webhooks; + - New in 1.22: use `headers` json array to send headers (or multiple `&H=` query args). Examples: @@ -710,7 +712,7 @@ foo ``` -and you get in result.json +and you get in result.json: ```json { "RunType": "HTTP", @@ -828,7 +830,7 @@ } ``` -- There is also the `fortio/rest/stop` endpoint to stop a run by its id or all runs if not specified +- There is also the `fortio/rest/stop` endpoint to stop a run by its id or all runs if not specified. ### GRPC load test @@ -956,6 +958,8 @@ ``` +Note: if you do not want the default fortio User-Agent to be sent pass `-H user-agent:`. If you want to send a present yet empty User-Agent: header, pass `-H "user-agent: "` (ie only whitespace sends empty one, empty value doesn't send any). + ### Report only UI If you have json files saved from running the full UI or downloaded, using the `-sync` option, from an amazon or google cloud storage bucket or from a peer fortio server (to synchronize from a peer fortio, use `http://`_peer_`:8080/data/index.tsv` as the sync URL). You can then serve just the reports: @@ -977,7 +981,7 @@ Fortio X.Y.Z Multi on 5554 server listening on [::]:5554 10:09:56 I http_forwarder.go:152> Multi-server on [::]:5554 running with &{Targets:[{Destination:http://localhost:8080 MirrorOrigin:true} {Destination:http://localhost:8080 MirrorOrigin:true}] Name:Multi on [::]:5554 client:0xc0001ccc00} ``` -Call the debug endpoint on both +Call the debug endpoint on both: ```Shell # in new window $ fortio curl -payload "a test" http://localhost:5554/debug @@ -1022,7 +1026,7 @@ There are 2 flags to further control the behaviour of the multi server proxies: - pass `-mirrorOriginFlag=false` to not mirror all headers and request type to targets. -- pass `-multi-serial-mode` to stream request response serially instead of fetching in parallel and writing combined data after completion +- pass `-multi-serial-mode` to stream request response serially instead of fetching in parallel and writing combined data after completion. Also remember you can pass multiple `-M`. @@ -1045,14 +1049,14 @@ ## Implementation details -Fortio is written in the [Go](https://golang.org) language and includes a scalable semi log histogram in [stats.go](stats/stats.go) and a periodic runner engine in [periodic.go](periodic/periodic.go) with specializations for [http](http/httprunner.go) and [grpc](fortiogrpc/grpcrunner.go). -The [http/](http/) package includes a very high performance specialized http 1.1 client. +Fortio is written in the [Go](https://golang.org) language and includes a scalable semi log histogram in [stats.go](stats/stats.go) and a periodic runner engine in [periodic.go](periodic/periodic.go) with specializations for [http](fhttp/httprunner.go) and [grpc](fgrpc/grpcrunner.go). +The [fhttp/](fhttp/) package includes a very high performance specialized http 1.1 client. You may find fortio's [logger](log/logger.go) useful as well. You can run the histogram code standalone as a command line in [histogram/](histogram/), a basic echo http server in [echosrv/](echosrv/), or both the http echo and GRPC ping server through `fortio server`, the fortio command line interface lives in this top level directory [fortio_main.go](fortio_main.go) There is also [fcurl/](fcurl/) which is the `fortio curl` part of the code (if you need a light http client without grpc or server side). -A matching tiny (2Mb compressed) docker image is [fortio/fortio.fcurl](https://hub.docker.com/r/fortio/fortio.fcurl/tags/) +A matching tiny (2Mb compressed) docker image is [fortio/fortio.fcurl](https://hub.docker.com/r/fortio/fortio.fcurl/tags/). ## More examples @@ -1117,7 +1121,7 @@ Response Body Sizes : count 300000 avg 0 +/- 0 min 0 max 0 sum 0 </pre></details> -Or you can get the data in [JSON format](https://github.com/fortio/fortio/wiki/Sample-JSON-output) (using `-json result.json`) +Or you can get the data in [JSON format](https://github.com/fortio/fortio/wiki/Sample-JSON-output) (using `-json result.json`). ### Web/Graphical UI @@ -1125,7 +1129,7 @@ Simple form/UI: -Sample requests with responses delayed by 250us and 0.5% of 503 and 1.5% of 429 simulated http errors. +Sample requests with responses delayed by 250us and 0.5% of 503 and 1.5% of 429 simulated http errors:  @@ -1139,7 +1143,7 @@ Code 503 : 15 (0.5 %) ``` -There are live examples on [demo.fortio.org](https://demo.fortio.org/) +There are live examples on [https://demo.fortio.org](https://demo.fortio.org/) ## Contributing @@ -1169,7 +1173,7 @@ ## See also -Our wiki and the [Fortio FAQ](https://github.com/fortio/fortio/wiki/FAQ) (including for instance differences between `fortio` and `wrk` or `httpbin`) +Our wiki and the [Fortio FAQ](https://github.com/fortio/fortio/wiki/FAQ) (including for instance differences between `fortio` and `wrk` or `httpbin`). ## Disclaimer diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/Webtest.sh new/fortio-1.38.4/Webtest.sh --- old/fortio-1.38.2/Webtest.sh 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/Webtest.sh 2022-11-13 00:06:32.000000000 +0100 @@ -28,6 +28,22 @@ DOCKERSECNAME=fortio_secure_server DOCKERSECVOLNAME=fortio_certs FORTIO_BIN_PATH=fortio # /usr/bin/fortio is the full path but isn't needed + +# Unresolvable should error out - #653 +docker run fortio/fortio:webtest curl http://doesnt.exist.google.com/ +if [[ $? == 0 ]]; then + echo "Error in curl should show up in status" + exit 1 +fi + +# Expect error with extra args: (timeout (brew install coreutils) returns 124 +# for timeout) - #652 +timeout 3 docker run fortio/fortio:webtest server -loglevel debug extra-arg +if [[ $? == 124 || $? == 0 ]]; then + echo "Unrecognized extra args/typo in flags should error out immediatly" + exit 1 +fi + DOCKERID=$(docker run -d --ulimit nofile=$FILE_LIMIT --net host --name $DOCKERNAME fortio/fortio:webtest server -ui-path $FORTIO_UI_PREFIX -loglevel $LOGLEVEL -maxpayloadsizekb $MAXPAYLOAD -timeout=$TIMEOUT) function cleanup { set +e # errors are ok during cleanup @@ -125,7 +141,7 @@ PPROF_URL="$BASE_URL/debug/pprof/heap?debug=1" $CURL "$PPROF_URL" | grep -i TotalAlloc # should find this in memory profile # creating dummy container to hold a volume for test certs due to remote docker bind mount limitation. -DOCKERCURLID=$(docker run -d -v $TEST_CERT_VOL --net host --name $DOCKERSECVOLNAME docker.io/fortio/fortio.build:v47 sleep 120) +DOCKERCURLID=$(docker run -d -v $TEST_CERT_VOL --net host --name $DOCKERSECVOLNAME docker.io/fortio/fortio.build:v50@sha256:fe69c193d8ad40eb0d791984881f3678aead02660b8e3468c757f717892ada4c sleep 120) # while we have something with actual curl binary do # Test for h2c upgrade (#562) docker exec $DOCKERSECVOLNAME /usr/bin/curl -v --http2 -m 10 -d foo42 http://localhost:8080/debug | tee >(cat 1>&2) | grep foo42 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/bincommon/commonflags.go new/fortio-1.38.4/bincommon/commonflags.go --- old/fortio-1.38.2/bincommon/commonflags.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/bincommon/commonflags.go 2022-11-13 00:06:32.000000000 +0100 @@ -152,7 +152,7 @@ client, _ := fhttp.NewClient(o) // big gotcha that nil client isn't nil interface value (!) if client == nil || reflect.ValueOf(client).IsNil() { - return // error logged already + os.Exit(1) // error logged already } code, data, header := client.Fetch() log.LogVf("Fetch result code %d, data len %d, headerlen %d", code, len(data), header) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/dflag/Dockerfile.tests new/fortio-1.38.4/dflag/Dockerfile.tests --- old/fortio-1.38.2/dflag/Dockerfile.tests 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/dflag/Dockerfile.tests 1970-01-01 01:00:00.000000000 +0100 @@ -1,8 +0,0 @@ -FROM golang as builder - -WORKDIR /build -COPY go.mod . -COPY go.sum . -RUN go mod download -ADD . /build/ -RUN go test -v ./dflag/... diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/dflag/Makefile new/fortio-1.38.4/dflag/Makefile --- old/fortio-1.38.2/dflag/Makefile 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/dflag/Makefile 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -tests: - docker build -f Dockerfile.tests .. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/dflag/dynstring_test.go new/fortio-1.38.4/dflag/dynstring_test.go --- old/fortio-1.38.2/dflag/dynstring_test.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/dflag/dynstring_test.go 2022-11-13 00:06:32.000000000 +0100 @@ -12,7 +12,7 @@ "fortio.org/assert" ) -const notifierTimeout = 50 * time.Millisecond +const notifierTimeout = 100 * time.Millisecond func TestDynString_SetAndGet(t *testing.T) { set := flag.NewFlagSet("foobar", flag.ContinueOnError) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/echosrv/echo.go new/fortio-1.38.4/echosrv/echo.go --- old/fortio-1.38.2/echosrv/echo.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/echosrv/echo.go 2022-11-13 00:06:32.000000000 +0100 @@ -37,7 +37,7 @@ func main() { flag.Parse() if len(os.Args) >= 2 && strings.Contains(os.Args[1], "version") { - fmt.Println(version.Long()) + fmt.Println(version.Full()) os.Exit(0) } if _, addr := fhttp.Serve(*port, *debugPath); addr == nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/fhttp/http_client.go new/fortio-1.38.4/fhttp/http_client.go --- old/fortio-1.38.2/fhttp/http_client.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/fhttp/http_client.go 2022-11-13 00:06:32.000000000 +0100 @@ -273,11 +273,22 @@ return fmt.Errorf("invalid extra header '%s', expecting Key: Value", hdr) } key := strings.TrimSpace(s[0]) - value := strings.TrimSpace(s[1]) - if strings.EqualFold(key, "host") { + // No TrimSpace for the value, so we can set empty "" vs just whitespace " " which + // will get trimmed later but treated differently: not emitted vs emitted empty for User-Agent. + value := s[1] + switch strings.ToLower(key) { + case "host": log.LogVf("Will be setting special Host header to %s", value) - h.hostOverride = value - } else { + h.hostOverride = strings.TrimSpace(value) // This one needs to be trimmed + case "user-agent": + if value == "" { + log.Infof("Deleting default User-Agent: header.") + h.extraHeaders.Del(key) + } else { + log.Infof("User-Agent being Set to %q", value) + h.extraHeaders.Set(key, value) + } + default: log.LogVf("Setting regular extra header %s: %s", key, value) h.extraHeaders.Add(key, value) log.Debugf("headers now %+v", h.extraHeaders) @@ -333,6 +344,10 @@ if o.hostOverride != "" { req.Host = o.hostOverride } + // Another workaround for std client otherwise trying to set a default User-Agent + if _, ok := req.Header["User-Agent"]; !ok { + req.Header.Set("User-Agent", "") + } if !log.LogDebug() { return req, nil } @@ -474,7 +489,6 @@ if req == nil { return nil, err } - client := Client{ url: o.URL, path: req.URL.Path, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/fhttp/http_forwarder.go new/fortio-1.38.4/fhttp/http_forwarder.go --- old/fortio-1.38.2/fhttp/http_forwarder.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/fhttp/http_forwarder.go 2022-11-13 00:06:32.000000000 +0100 @@ -72,7 +72,7 @@ return req } -// CopyHeaders copies all or trace headers. +// CopyHeaders copies all or trace headers from `r` into `req`. func CopyHeaders(req, r *http.Request, all bool) { // Copy only trace headers unless all is true. for k, v := range r.Header { @@ -85,6 +85,11 @@ log.Debugf("Skipping header %q", k) } } + if _, ok := r.Header["User-Agent"]; !ok { + // explicitly disable User-Agent so it's not set + // to default value (go client lib 'feature' workaround) + req.Header.Set("User-Agent", "") + } } // MakeSimpleRequest makes a new request for url but copies trace headers from input request r. @@ -97,9 +102,7 @@ } // Copy only trace headers or all of them: CopyHeaders(req, r, copyAllHeaders) - if copyAllHeaders { - req.Header.Add("X-Proxy-Agent", jrpc.UserAgent) - } else { + if !copyAllHeaders { req.Header.Set(jrpc.UserAgentHeader, jrpc.UserAgent) } return req diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/fhttp/http_forwarder_test.go new/fortio-1.38.4/fhttp/http_forwarder_test.go --- old/fortio-1.38.2/fhttp/http_forwarder_test.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/fhttp/http_forwarder_test.go 2022-11-13 00:06:32.000000000 +0100 @@ -19,6 +19,8 @@ "fmt" "net/http" "testing" + + "fortio.org/fortio/jrpc" ) func TestMultiProxy(t *testing.T) { @@ -27,12 +29,18 @@ for i := 0; i < 2; i++ { serial := (i == 0) mcfg := MultiServerConfig{Serial: serial} - mcfg.Targets = []TargetConf{{Destination: urlBase, MirrorOrigin: true}, {Destination: urlBase + "echo?status=555"}} + mcfg.Targets = []TargetConf{ + {Destination: urlBase, MirrorOrigin: true}, + {Destination: urlBase + "debug", MirrorOrigin: false}, + {Destination: urlBase + "echo?status=555"}, + } _, multiAddr := MultiServer("0", &mcfg) url := fmt.Sprintf("http://%s/debug", multiAddr) payload := "A test payload" opts := HTTPOptions{URL: url, Payload: []byte(payload)} + opts.AddAndValidateExtraHeader("User-agent:") opts.AddAndValidateExtraHeader("b3: traceid...") + opts.AddAndValidateExtraHeader("X-FA: bar") // so it comes just before X-Fortio-Multi-Id code, data := Fetch(&opts) if serial && code != http.StatusOK { t.Errorf("Got %d %s instead of ok in serial mode (first response sets code) for %s", code, DebugSummary(data, 256), url) @@ -41,18 +49,49 @@ t.Errorf("Got %d %s instead of 555 in parallel mode (non ok response sets code) for %s", code, DebugSummary(data, 256), url) } if !bytes.Contains(data, []byte(payload)) { - t.Errorf("Result %s doesn't contain expected payload echo back %q", DebugSummary(data, 1024), payload) + t.Errorf("Missing expected payload %q in %s", payload, DebugSummary(data, 1024)) + } + searchFor := "B3: traceid..." + if !bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Missing expected trace header %q in %s", searchFor, DebugSummary(data, 1024)) + } + searchFor = "\nX-Fa: bar\nX-Fortio-Multi-Id: 1\n" + if !bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Missing expected general header %q in 1st req %s", searchFor, DebugSummary(data, 1024)) + } + searchFor = "\nX-Fa: bar\nX-Fortio-Multi-Id: 2\n" + if bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Unexpected non trace header %q in 2nd req %s", searchFor, DebugSummary(data, 1024)) } // Issue #624 if bytes.Contains(data, []byte("gzip")) { - t.Errorf("Result %s contains unexpected gzip (accept encoding)", DebugSummary(data, 1024)) - } - if !bytes.Contains(data, []byte("X-Fortio-Multi-Id: 1")) { - t.Errorf("Result %s doesn't contain expected X-Fortio-Multi-Id: 1", DebugSummary(data, 1024)) + t.Errorf("Unexpected gzip (accept encoding)in %s", DebugSummary(data, 1024)) } - // Second request errors 100% so shouldn't be found - if bytes.Contains(data, []byte("X-Fortio-Multi-Id: 2")) { - t.Errorf("Result %s contains unexpected X-Fortio-Multi-Id: 2", DebugSummary(data, 1024)) + searchFor = "X-Fortio-Multi-Id: 1" + if !bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Missing expected %q in %s", searchFor, DebugSummary(data, 1024)) + } + // Second request should be found + searchFor = "X-Fortio-Multi-Id: 2" + if !bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Missing expected %q in %s", searchFor, DebugSummary(data, 1024)) + } + // Third request errors 100% so shouldn't be found + searchFor = "X-Fortio-Multi-Id: 3" + if bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Unexpected %q in %s", searchFor, DebugSummary(data, 1024)) + } + searchFor = "\nX-Proxy-Agent: " + jrpc.UserAgent + "\n" + if !bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Missing %q in %s", searchFor, DebugSummary(data, 2048)) + } + searchFor = "\nUser-Agent: " + jrpc.UserAgent + "\nX-Fortio-Multi-Id: 2\n" + if !bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Missing %q in %s", searchFor, DebugSummary(data, 2048)) + } + searchFor = "\nUser-Agent: " + jrpc.UserAgent + "\nX-Fortio-Multi-Id: 1\n" + if bytes.Contains(data, []byte(searchFor)) { + t.Errorf("Unexpected %q in %s", searchFor, DebugSummary(data, 2048)) } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/fhttp/http_test.go new/fortio-1.38.4/fhttp/http_test.go --- old/fortio-1.38.2/fhttp/http_test.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/fhttp/http_test.go 2022-11-13 00:06:32.000000000 +0100 @@ -67,13 +67,22 @@ if err == nil { t.Errorf("Expected error for header without value, did not get one") } - o.ResetHeaders() + o.InitHeaders() h = o.AllHeaders() if h.Get("Host") != "" { t.Errorf("After reset Host header should be nil, got '%v'", h.Get("Host")) } + if len(h) != 1 { + t.Errorf("Header count mismatch after reset, got %d instead of 1. %+v", len(h), h) + } + // test user-agent delete: + o.AddAndValidateExtraHeader("UsER-AgENT:") + h = o.AllHeaders() if len(h) != 0 { - t.Errorf("Header count mismatch after reset, got %d instead of 1", len(h)) + t.Errorf("Header count mismatch after delete, got %d instead of 0. %+v", len(h), h) + } + if h.Get("User-Agent") != "" { + t.Errorf("User-Agent header should be empty after delete, got '%v'", h.Get("User-Agent")) } } @@ -115,7 +124,7 @@ if o.URL != expected { t.Errorf("Got initially '%s', expected '%s'", o.URL, expected) } - o.AddAndValidateExtraHeader("FoO: BaR") + o.AddAndValidateExtraHeader("FoO:BaR") // re init should not erase headers o.Init(o.URL) if o.AllHeaders().Get("Foo") != "BaR" { @@ -1120,6 +1129,11 @@ o.AddAndValidateExtraHeader("CCC: ccc") o.AddAndValidateExtraHeader("ZZZ: zzz") o.AddAndValidateExtraHeader("AAA: aaa") + // test that headers usually Add (list) but stay in order of being set + o.AddAndValidateExtraHeader("BBB: aa2") + // test that User-Agent is special, only last value is kept - and replaces the default jrpc.UserAgent + o.AddAndValidateExtraHeader("User-Agent: ua1") + o.AddAndValidateExtraHeader("User-Agent: ua2") client, _ := NewClient(&o) now := time.Now() code, data, header := client.Fetch() // used to panic/bug #127 @@ -1140,13 +1154,13 @@ "Host: localhost:%d\n"+ "Aaa: aaa\n"+ "Accept-Encoding: gzip\n"+ - "Bbb: bbb\n"+ + "Bbb: bbb,aa2\n"+ "Ccc: ccc\n"+ "Content-Length: 4\n"+ "Content-Type: application/octet-stream\n"+ - "User-Agent: %s\n"+ + "User-Agent: ua2\n"+ "Zzz: zzz\n\n"+ - "body:\n\nabcd\n", a.Port, jrpc.UserAgent) + "body:\n\nabcd\n", a.Port) if body != expected { t.Errorf("Get body: %s not as expected: %s", body, expected) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/fhttp/http_utils.go new/fortio-1.38.4/fhttp/http_utils.go --- old/fortio-1.38.2/fhttp/http_utils.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/fhttp/http_utils.go 2022-11-13 00:06:32.000000000 +0100 @@ -33,6 +33,7 @@ "fortio.org/fortio/dflag" "fortio.org/fortio/fnet" + "fortio.org/fortio/jrpc" "fortio.org/fortio/log" "fortio.org/fortio/stats" ) @@ -572,9 +573,14 @@ _ = o.AddAndValidateExtraHeader("X-On-Behalf-Of: " + r.RemoteAddr) } -// OnBehalfOfRequest same as OnBehalfOf but places the header directly on the dst request object. +// OnBehalfOfRequest same as OnBehalfOf but places the header directly on the dst request object +// but also adds a X-Proxy-Agent header if the user-agent isn't already the same as this running +// server's version. func OnBehalfOfRequest(to *http.Request, from *http.Request) { to.Header.Add("X-On-Behalf-Of", from.RemoteAddr) + if to.Header.Get("User-Agent") != jrpc.UserAgent { + to.Header.Add("X-Proxy-Agent", jrpc.UserAgent) + } } // AddHTTPS replaces "http://" in url with "https://" or prepends "https://" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/fortio_main.go new/fortio-1.38.4/fortio_main.go --- old/fortio-1.38.2/fortio_main.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/fortio_main.go 2022-11-13 00:06:32.000000000 +0100 @@ -71,16 +71,18 @@ // Usage to a writer. func usage(w io.Writer, msgs ...interface{}) { - _, _ = fmt.Fprintf(w, "ΦοÏÏίο %s usage:\n\t%s command [flags] target\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", + _, _ = fmt.Fprintf(w, "ΦοÏÏίο %s usage:\n\t%s command [flags] target\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n", version.Short(), os.Args[0], "where command is one of: load (load testing), server (starts ui, rest api,", - " http-echo, redirect, proxies, tcp-echo and grpc ping servers), tcp-echo (only", - " the tcp-echo server), report (report only UI server), redirect (only the", - " redirect server), proxies (only the -M and -P configured proxies), grpcping", - " (grpc client), or curl (single URL debug), or nc (single tcp or udp://", - " connection), or version (prints the full version and build details).", - "where target is a url (http load tests) or host:port (grpc health test).") + " http-echo, redirect, proxies, tcp-echo, udp-echo and grpc ping servers), ", + " tcp-echo (only the tcp-echo server), udp-echo (only udp-echo server),", + " report (report only UI server), redirect (only the redirect server),", + " proxies (only the -M and -P configured proxies), grpcping (grpc client),", + " or curl (single URL debug), or nc (single tcp or udp:// connection),", + " or version (prints the full version and build details).", + "where target is a url (http load tests) or host:port (grpc health test),", + " or tcp://host:port (tcp load test), or udp://host:port (udp load test).") bincommon.FlagsUsage(w, msgs...) } @@ -183,7 +185,16 @@ calcQPS = flag.Bool("calc-qps", false, "Calculate the qps based on number of requests (-n) and duration (-t)") ) -//nolint:funlen,gocyclo // well yes it's fairly big and lotsa ifs. +// serverArgCheck always returns true after checking arguments length. +// so it can be used with isServer = serverArgCheck() below. +func serverArgCheck() bool { + if len(flag.Args()) != 0 { + usageErr("Error: too many arguments (typo in a flag?)") + } + return true +} + +//nolint:funlen // well yes it's fairly long func main() { flag.Var(&proxiesFlags, "P", "Tcp proxies to run, e.g -P \"localport1 dest_host1:dest_port1\" -P \"[::1]:0 www.google.com:443\" ...") @@ -229,10 +240,10 @@ case "load": fortioLoad(*curlFlag, percList) case "redirect": - isServer = true + isServer = serverArgCheck() fhttp.RedirectToHTTPS(*redirectFlag) case "report": - isServer = true + isServer = serverArgCheck() if *redirectFlag != disabled { fhttp.RedirectToHTTPS(*redirectFlag) } @@ -240,23 +251,20 @@ os.Exit(1) // error already logged } case "tcp-echo": - isServer = true + isServer = serverArgCheck() fnet.TCPEchoServer("tcp-echo", *tcpPortFlag) startProxies() case "udp-echo": - isServer = true + isServer = serverArgCheck() fnet.UDPEchoServer("udp-echo", *udpPortFlag, *udpAsyncFlag) startProxies() case "proxies": - if len(flag.Args()) != 0 { - usageErr("Error: fortio proxies command only takes -P / -M flags") - } - isServer = true + isServer = serverArgCheck() if startProxies() == 0 { usageErr("Error: fortio proxies command needs at least one -P / -M flag") } case "server": - isServer = true + isServer = serverArgCheck() if *tcpPortFlag != disabled { fnet.TCPEchoServer("tcp-echo", *tcpPortFlag) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/go.mod new/fortio-1.38.4/go.mod --- old/fortio-1.38.2/go.mod 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/go.mod 2022-11-13 00:06:32.000000000 +0100 @@ -3,18 +3,18 @@ go 1.18 require ( - fortio.org/assert v1.1.0 + fortio.org/assert v1.1.2 github.com/fsnotify/fsnotify v1.6.0 github.com/golang/protobuf v1.5.2 github.com/google/uuid v1.3.0 - golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 - golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 + golang.org/x/exp v0.0.0-20221111204811-129d8d6c17ab + golang.org/x/net v0.2.0 google.golang.org/grpc v1.50.0 ) require ( - golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/sys v0.2.0 // indirect + golang.org/x/text v0.4.0 // indirect google.golang.org/genproto v0.0.0-20220714211235-042d03aeabc9 // indirect google.golang.org/protobuf v1.28.0 // indirect ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/go.sum new/fortio-1.38.4/go.sum --- old/fortio-1.38.2/go.sum 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/go.sum 2022-11-13 00:06:32.000000000 +0100 @@ -1,5 +1,5 @@ -fortio.org/assert v1.1.0 h1:AEkX3WzLx4Qsvgg+HyZTp9wHKo0lr1ZcAylZ2YJgGYc= -fortio.org/assert v1.1.0/go.mod h1:039mG+/iYDPO8Ibx8TrNuJCm2T2SuhwRI3uL9nHTTls= +fortio.org/assert v1.1.2 h1:t6WGDqPD5VFrUvx30U0+3mgXXcoPonrdKqt0vfJHn8E= +fortio.org/assert v1.1.2/go.mod h1:039mG+/iYDPO8Ibx8TrNuJCm2T2SuhwRI3uL9nHTTls= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -9,15 +9,15 @@ github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg= -golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 h1:MgJ6t2zo8v0tbmLCueaCbF1RM+TtB0rs3Lv8DGtOIpY= -golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/exp v0.0.0-20221111204811-129d8d6c17ab h1:1S7USr8/C0Sgk4egxq4zZ07zYt2Xh1IiFp8hUMXH/us= +golang.org/x/exp v0.0.0-20221111204811-129d8d6c17ab/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 h1:OK7RB6t2WQX54srQQYSXMW8dF5C6/8+oA/s5QBmmto4= -golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20220714211235-042d03aeabc9 h1:zfXhTgBfGlIh3jMXN06W8qbhFGsh6MJNJiYEuhTddOI= google.golang.org/genproto v0.0.0-20220714211235-042d03aeabc9/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/rapi/restHandler.go new/fortio-1.38.4/rapi/restHandler.go --- old/fortio-1.38.2/rapi/restHandler.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/rapi/restHandler.go 2022-11-13 00:06:32.000000000 +0100 @@ -266,7 +266,6 @@ httpopts := &fhttp.HTTPOptions{} httpopts.HTTPReqTimeOut = timeout // to be normalized in init 0 replaced by default value httpopts = httpopts.Init(url) - httpopts.ResetHeaders() httpopts.DisableFastClient = stdClient httpopts.SequentialWarmup = sequentialWarmup httpopts.Insecure = httpsInsecure diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/release/Dockerfile.in new/fortio-1.38.4/release/Dockerfile.in --- old/fortio-1.38.2/release/Dockerfile.in 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/release/Dockerfile.in 2022-11-13 00:06:32.000000000 +0100 @@ -1,5 +1,5 @@ # Concatenated after ../Dockerfile to create the tgz -FROM docker.io/fortio/fortio.build:v47 as stage +FROM docker.io/fortio/fortio.build:v50@sha256:fe69c193d8ad40eb0d791984881f3678aead02660b8e3468c757f717892ada4c as stage ARG archs="amd64 arm64 ppc64le s390x" ENV archs=${archs} # Build image defaults to build user, switch back to root for @@ -11,7 +11,7 @@ RUN mkdir -p /tgz usr/bin -WORKDIR /go/src/fortio.org +WORKDIR /build COPY . fortio # Check macos does not break RUN make -C fortio official-build BUILD_DIR=/build OFFICIAL_DIR=/tmp/fortio_mac GOOS=darwin GO_BIN=/usr/local/go/bin/go diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/release/README.md new/fortio-1.38.4/release/README.md --- old/fortio-1.38.2/release/README.md 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/release/README.md 2022-11-13 00:06:32.000000000 +0100 @@ -29,8 +29,8 @@ Update [../Dockerfile.build](../Dockerfile.build) -Edit the `BUILD_IMAGE_TAG := v5` line in the Makefile, set it to `v6` -for instance (replace `v6` by whichever is the next one at the time) +Edit the `BUILD_IMAGE_TAG := v50@sha...` line in the Makefile, set it to `v51` +for instance (replace `v50` by whichever is the next one at the time and temporarily remove the SHA part) run @@ -40,7 +40,9 @@ Make sure it gets successfully pushed to the fortio registry (requires org access) -run +Then do `make build-image-sha` to get the new image sha to replace/put in `BUILD_IMAGE_TAG` line of the Makefile + +Then run ```Shell make update-build-image-tag SED=gsed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/ui/templates/main.html new/fortio-1.38.4/ui/templates/main.html --- old/fortio-1.38.2/ui/templates/main.html 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/ui/templates/main.html 2022-11-13 00:06:32.000000000 +0100 @@ -53,12 +53,7 @@ No Catch-Up (qps is a ceiling): <input type="checkbox" name="nocatchup" /><br /> Percentiles: <input type="text" name="p" size="20" value="50, 75, 90, 99, 99.9" /> <br /> Histogram Resolution: <input type="text" name="r" size="8" value="0.0001" /> <br /> - Headers: <br /> - {{ range $name, $vals := .Headers }}{{range $val := $vals}} - <input type="text" name="H" size=40 value="{{$name}}: {{ $val }}" /> <br /> - {{end}}{{end}} <!-- 3 extra header lines, TODO(#283): add a JS 'more headers' button --> - <input type="text" name="H" size=40 value="" /> <br /> - <input type="text" name="H" size=40 value="" /> <br /> + Extra Headers:<br /> <input type="text" name="H" size=40 value="" /> <br /> <button type="button" onclick="addCustomHeader()">+</button> <br /> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.38.2/ui/uihandler.go new/fortio-1.38.4/ui/uihandler.go --- old/fortio-1.38.2/ui/uihandler.go 2022-10-24 22:53:20.000000000 +0200 +++ new/fortio-1.38.4/ui/uihandler.go 2022-11-13 00:06:32.000000000 +0100 @@ -185,8 +185,6 @@ httpopts := &fhttp.HTTPOptions{} httpopts.HTTPReqTimeOut = timeout // to be normalized in init 0 replaced by default value httpopts = httpopts.Init(url) - defaultHeaders := httpopts.AllHeaders() - httpopts.ResetHeaders() httpopts.DisableFastClient = stdClient httpopts.SequentialWarmup = sequentialWarmup httpopts.Insecure = httpsInsecure @@ -221,7 +219,6 @@ } err := mainTemplate.Execute(w, &struct { R *http.Request - Headers http.Header Version string LogoPath string DebugPath string @@ -237,7 +234,7 @@ DoStop bool DoLoad bool }{ - r, defaultHeaders, version.Short(), logoPath, debugPath, echoPath, chartJSPath, + r, version.Short(), logoPath, debugPath, echoPath, chartJSPath, startTime.Format(time.ANSIC), url, labels, runid, fhttp.RoundDuration(time.Since(startTime)), durSeconds, urlHostPort, mode == stop, mode == run, }) ++++++ vendor.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/fortio.org/assert/README.md new/vendor/fortio.org/assert/README.md --- old/vendor/fortio.org/assert/README.md 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/fortio.org/assert/README.md 2022-11-15 10:21:46.000000000 +0100 @@ -1,2 +1,3 @@ # assert -Minimalistic replacement for https://pkg.go.dev/github.com/stretchr/testify/assert + +Minimalistic replacement for https://pkg.go.dev/github.com/stretchr/testify/assert diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/fortio.org/assert/assert.go new/vendor/fortio.org/assert/assert.go --- old/vendor/fortio.org/assert/assert.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/fortio.org/assert/assert.go 2022-11-15 10:21:46.000000000 +0100 @@ -81,7 +81,10 @@ // Fail fails the test. func Fail(t *testing.T, msg string) { - t.Fatal(msg) + _, file, line, _ := runtime.Caller(1) + file = file[strings.LastIndex(file, "/")+1:] + fmt.Printf("%s:%d %s\n", file, line, msg) + t.FailNow() } // CheckEquals checks if actual == expect and fails the test and logs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/http2/h2c/h2c.go new/vendor/golang.org/x/net/http2/h2c/h2c.go --- old/vendor/golang.org/x/net/http2/h2c/h2c.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/golang.org/x/net/http2/h2c/h2c.go 2022-11-15 10:21:46.000000000 +0100 @@ -109,6 +109,7 @@ if http2VerboseLogs { log.Printf("h2c: error h2c upgrade: %v", err) } + w.WriteHeader(http.StatusInternalServerError) return } defer conn.Close() @@ -167,7 +168,10 @@ return nil, nil, errors.New("h2c: connection does not support Hijack") } - body, _ := io.ReadAll(r.Body) + body, err := io.ReadAll(r.Body) + if err != nil { + return nil, nil, err + } r.Body = io.NopCloser(bytes.NewBuffer(body)) conn, rw, err := hijacker.Hijack() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/http2/headermap.go new/vendor/golang.org/x/net/http2/headermap.go --- old/vendor/golang.org/x/net/http2/headermap.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/golang.org/x/net/http2/headermap.go 2022-11-15 10:21:46.000000000 +0100 @@ -27,7 +27,14 @@ "accept-language", "accept-ranges", "age", + "access-control-allow-credentials", + "access-control-allow-headers", + "access-control-allow-methods", "access-control-allow-origin", + "access-control-expose-headers", + "access-control-max-age", + "access-control-request-headers", + "access-control-request-method", "allow", "authorization", "cache-control", @@ -53,6 +60,7 @@ "link", "location", "max-forwards", + "origin", "proxy-authenticate", "proxy-authorization", "range", @@ -68,6 +76,8 @@ "vary", "via", "www-authenticate", + "x-forwarded-for", + "x-forwarded-proto", } commonLowerHeader = make(map[string]string, len(common)) commonCanonHeader = make(map[string]string, len(common)) @@ -85,3 +95,11 @@ } return asciiToLower(v) } + +func canonicalHeader(v string) string { + buildCommonHeaderMapsOnce() + if s, ok := commonCanonHeader[v]; ok { + return s + } + return http.CanonicalHeaderKey(v) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/http2/hpack/static_table.go new/vendor/golang.org/x/net/http2/hpack/static_table.go --- old/vendor/golang.org/x/net/http2/hpack/static_table.go 1970-01-01 01:00:00.000000000 +0100 +++ new/vendor/golang.org/x/net/http2/hpack/static_table.go 2022-11-15 10:21:46.000000000 +0100 @@ -0,0 +1,188 @@ +// go generate gen.go +// Code generated by the command above; DO NOT EDIT. + +package hpack + +var staticTable = &headerFieldTable{ + evictCount: 0, + byName: map[string]uint64{ + ":authority": 1, + ":method": 3, + ":path": 5, + ":scheme": 7, + ":status": 14, + "accept-charset": 15, + "accept-encoding": 16, + "accept-language": 17, + "accept-ranges": 18, + "accept": 19, + "access-control-allow-origin": 20, + "age": 21, + "allow": 22, + "authorization": 23, + "cache-control": 24, + "content-disposition": 25, + "content-encoding": 26, + "content-language": 27, + "content-length": 28, + "content-location": 29, + "content-range": 30, + "content-type": 31, + "cookie": 32, + "date": 33, + "etag": 34, + "expect": 35, + "expires": 36, + "from": 37, + "host": 38, + "if-match": 39, + "if-modified-since": 40, + "if-none-match": 41, + "if-range": 42, + "if-unmodified-since": 43, + "last-modified": 44, + "link": 45, + "location": 46, + "max-forwards": 47, + "proxy-authenticate": 48, + "proxy-authorization": 49, + "range": 50, + "referer": 51, + "refresh": 52, + "retry-after": 53, + "server": 54, + "set-cookie": 55, + "strict-transport-security": 56, + "transfer-encoding": 57, + "user-agent": 58, + "vary": 59, + "via": 60, + "www-authenticate": 61, + }, + byNameValue: map[pairNameValue]uint64{ + {name: ":authority", value: ""}: 1, + {name: ":method", value: "GET"}: 2, + {name: ":method", value: "POST"}: 3, + {name: ":path", value: "/"}: 4, + {name: ":path", value: "/index.html"}: 5, + {name: ":scheme", value: "http"}: 6, + {name: ":scheme", value: "https"}: 7, + {name: ":status", value: "200"}: 8, + {name: ":status", value: "204"}: 9, + {name: ":status", value: "206"}: 10, + {name: ":status", value: "304"}: 11, + {name: ":status", value: "400"}: 12, + {name: ":status", value: "404"}: 13, + {name: ":status", value: "500"}: 14, + {name: "accept-charset", value: ""}: 15, + {name: "accept-encoding", value: "gzip, deflate"}: 16, + {name: "accept-language", value: ""}: 17, + {name: "accept-ranges", value: ""}: 18, + {name: "accept", value: ""}: 19, + {name: "access-control-allow-origin", value: ""}: 20, + {name: "age", value: ""}: 21, + {name: "allow", value: ""}: 22, + {name: "authorization", value: ""}: 23, + {name: "cache-control", value: ""}: 24, + {name: "content-disposition", value: ""}: 25, + {name: "content-encoding", value: ""}: 26, + {name: "content-language", value: ""}: 27, + {name: "content-length", value: ""}: 28, + {name: "content-location", value: ""}: 29, + {name: "content-range", value: ""}: 30, + {name: "content-type", value: ""}: 31, + {name: "cookie", value: ""}: 32, + {name: "date", value: ""}: 33, + {name: "etag", value: ""}: 34, + {name: "expect", value: ""}: 35, + {name: "expires", value: ""}: 36, + {name: "from", value: ""}: 37, + {name: "host", value: ""}: 38, + {name: "if-match", value: ""}: 39, + {name: "if-modified-since", value: ""}: 40, + {name: "if-none-match", value: ""}: 41, + {name: "if-range", value: ""}: 42, + {name: "if-unmodified-since", value: ""}: 43, + {name: "last-modified", value: ""}: 44, + {name: "link", value: ""}: 45, + {name: "location", value: ""}: 46, + {name: "max-forwards", value: ""}: 47, + {name: "proxy-authenticate", value: ""}: 48, + {name: "proxy-authorization", value: ""}: 49, + {name: "range", value: ""}: 50, + {name: "referer", value: ""}: 51, + {name: "refresh", value: ""}: 52, + {name: "retry-after", value: ""}: 53, + {name: "server", value: ""}: 54, + {name: "set-cookie", value: ""}: 55, + {name: "strict-transport-security", value: ""}: 56, + {name: "transfer-encoding", value: ""}: 57, + {name: "user-agent", value: ""}: 58, + {name: "vary", value: ""}: 59, + {name: "via", value: ""}: 60, + {name: "www-authenticate", value: ""}: 61, + }, + ents: []HeaderField{ + {Name: ":authority", Value: "", Sensitive: false}, + {Name: ":method", Value: "GET", Sensitive: false}, + {Name: ":method", Value: "POST", Sensitive: false}, + {Name: ":path", Value: "/", Sensitive: false}, + {Name: ":path", Value: "/index.html", Sensitive: false}, + {Name: ":scheme", Value: "http", Sensitive: false}, + {Name: ":scheme", Value: "https", Sensitive: false}, + {Name: ":status", Value: "200", Sensitive: false}, + {Name: ":status", Value: "204", Sensitive: false}, + {Name: ":status", Value: "206", Sensitive: false}, + {Name: ":status", Value: "304", Sensitive: false}, + {Name: ":status", Value: "400", Sensitive: false}, + {Name: ":status", Value: "404", Sensitive: false}, + {Name: ":status", Value: "500", Sensitive: false}, + {Name: "accept-charset", Value: "", Sensitive: false}, + {Name: "accept-encoding", Value: "gzip, deflate", Sensitive: false}, + {Name: "accept-language", Value: "", Sensitive: false}, + {Name: "accept-ranges", Value: "", Sensitive: false}, + {Name: "accept", Value: "", Sensitive: false}, + {Name: "access-control-allow-origin", Value: "", Sensitive: false}, + {Name: "age", Value: "", Sensitive: false}, + {Name: "allow", Value: "", Sensitive: false}, + {Name: "authorization", Value: "", Sensitive: false}, + {Name: "cache-control", Value: "", Sensitive: false}, + {Name: "content-disposition", Value: "", Sensitive: false}, + {Name: "content-encoding", Value: "", Sensitive: false}, + {Name: "content-language", Value: "", Sensitive: false}, + {Name: "content-length", Value: "", Sensitive: false}, + {Name: "content-location", Value: "", Sensitive: false}, + {Name: "content-range", Value: "", Sensitive: false}, + {Name: "content-type", Value: "", Sensitive: false}, + {Name: "cookie", Value: "", Sensitive: false}, + {Name: "date", Value: "", Sensitive: false}, + {Name: "etag", Value: "", Sensitive: false}, + {Name: "expect", Value: "", Sensitive: false}, + {Name: "expires", Value: "", Sensitive: false}, + {Name: "from", Value: "", Sensitive: false}, + {Name: "host", Value: "", Sensitive: false}, + {Name: "if-match", Value: "", Sensitive: false}, + {Name: "if-modified-since", Value: "", Sensitive: false}, + {Name: "if-none-match", Value: "", Sensitive: false}, + {Name: "if-range", Value: "", Sensitive: false}, + {Name: "if-unmodified-since", Value: "", Sensitive: false}, + {Name: "last-modified", Value: "", Sensitive: false}, + {Name: "link", Value: "", Sensitive: false}, + {Name: "location", Value: "", Sensitive: false}, + {Name: "max-forwards", Value: "", Sensitive: false}, + {Name: "proxy-authenticate", Value: "", Sensitive: false}, + {Name: "proxy-authorization", Value: "", Sensitive: false}, + {Name: "range", Value: "", Sensitive: false}, + {Name: "referer", Value: "", Sensitive: false}, + {Name: "refresh", Value: "", Sensitive: false}, + {Name: "retry-after", Value: "", Sensitive: false}, + {Name: "server", Value: "", Sensitive: false}, + {Name: "set-cookie", Value: "", Sensitive: false}, + {Name: "strict-transport-security", Value: "", Sensitive: false}, + {Name: "transfer-encoding", Value: "", Sensitive: false}, + {Name: "user-agent", Value: "", Sensitive: false}, + {Name: "vary", Value: "", Sensitive: false}, + {Name: "via", Value: "", Sensitive: false}, + {Name: "www-authenticate", Value: "", Sensitive: false}, + }, +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/http2/hpack/tables.go new/vendor/golang.org/x/net/http2/hpack/tables.go --- old/vendor/golang.org/x/net/http2/hpack/tables.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/golang.org/x/net/http2/hpack/tables.go 2022-11-15 10:21:46.000000000 +0100 @@ -96,8 +96,7 @@ // meaning t.ents is reversed for dynamic tables. Hence, when t is a dynamic // table, the return value i actually refers to the entry t.ents[t.len()-i]. // -// All tables are assumed to be a dynamic tables except for the global -// staticTable pointer. +// All tables are assumed to be a dynamic tables except for the global staticTable. // // See Section 2.3.3. func (t *headerFieldTable) search(f HeaderField) (i uint64, nameValueMatch bool) { @@ -125,81 +124,6 @@ return k + 1 } -// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B -var staticTable = newStaticTable() -var staticTableEntries = [...]HeaderField{ - {Name: ":authority"}, - {Name: ":method", Value: "GET"}, - {Name: ":method", Value: "POST"}, - {Name: ":path", Value: "/"}, - {Name: ":path", Value: "/index.html"}, - {Name: ":scheme", Value: "http"}, - {Name: ":scheme", Value: "https"}, - {Name: ":status", Value: "200"}, - {Name: ":status", Value: "204"}, - {Name: ":status", Value: "206"}, - {Name: ":status", Value: "304"}, - {Name: ":status", Value: "400"}, - {Name: ":status", Value: "404"}, - {Name: ":status", Value: "500"}, - {Name: "accept-charset"}, - {Name: "accept-encoding", Value: "gzip, deflate"}, - {Name: "accept-language"}, - {Name: "accept-ranges"}, - {Name: "accept"}, - {Name: "access-control-allow-origin"}, - {Name: "age"}, - {Name: "allow"}, - {Name: "authorization"}, - {Name: "cache-control"}, - {Name: "content-disposition"}, - {Name: "content-encoding"}, - {Name: "content-language"}, - {Name: "content-length"}, - {Name: "content-location"}, - {Name: "content-range"}, - {Name: "content-type"}, - {Name: "cookie"}, - {Name: "date"}, - {Name: "etag"}, - {Name: "expect"}, - {Name: "expires"}, - {Name: "from"}, - {Name: "host"}, - {Name: "if-match"}, - {Name: "if-modified-since"}, - {Name: "if-none-match"}, - {Name: "if-range"}, - {Name: "if-unmodified-since"}, - {Name: "last-modified"}, - {Name: "link"}, - {Name: "location"}, - {Name: "max-forwards"}, - {Name: "proxy-authenticate"}, - {Name: "proxy-authorization"}, - {Name: "range"}, - {Name: "referer"}, - {Name: "refresh"}, - {Name: "retry-after"}, - {Name: "server"}, - {Name: "set-cookie"}, - {Name: "strict-transport-security"}, - {Name: "transfer-encoding"}, - {Name: "user-agent"}, - {Name: "vary"}, - {Name: "via"}, - {Name: "www-authenticate"}, -} - -func newStaticTable() *headerFieldTable { - t := &headerFieldTable{} - t.init() - for _, e := range staticTableEntries[:] { - t.addEntry(e) - } - return t -} - var huffmanCodes = [256]uint32{ 0x1ff8, 0x7fffd8, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/http2/server.go new/vendor/golang.org/x/net/http2/server.go --- old/vendor/golang.org/x/net/http2/server.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/golang.org/x/net/http2/server.go 2022-11-15 10:21:46.000000000 +0100 @@ -622,7 +622,9 @@ resetQueued bool // RST_STREAM queued for write; set by sc.resetStream gotTrailerHeader bool // HEADER frame for trailers was seen wroteHeaders bool // whether we wrote headers (not status 100) + readDeadline *time.Timer // nil if unused writeDeadline *time.Timer // nil if unused + closeErr error // set before cw is closed trailer http.Header // accumulated trailers reqTrailer http.Header // handler's Request.Trailer @@ -869,7 +871,9 @@ // Each connection starts with initialWindowSize inflow tokens. // If a higher value is configured, we add more tokens. - sc.sendWindowUpdate(nil) + if diff := sc.srv.initialConnRecvWindowSize() - initialWindowSize; diff > 0 { + sc.sendWindowUpdate(nil, int(diff)) + } if err := sc.readPreface(); err != nil { sc.condlogf(err, "http2: server: error reading preface from client %v: %v", sc.conn.RemoteAddr(), err) @@ -946,6 +950,8 @@ } case *startPushRequest: sc.startPush(v) + case func(*serverConn): + v(sc) default: panic(fmt.Sprintf("unexpected type %T", v)) } @@ -1459,6 +1465,21 @@ sc.sawFirstSettings = true } + // Discard frames for streams initiated after the identified last + // stream sent in a GOAWAY, or all frames after sending an error. + // We still need to return connection-level flow control for DATA frames. + // RFC 9113 Section 6.8. + if sc.inGoAway && (sc.goAwayCode != ErrCodeNo || f.Header().StreamID > sc.maxClientStreamID) { + + if f, ok := f.(*DataFrame); ok { + if sc.inflow.available() < int32(f.Length) { + return sc.countError("data_flow", streamError(f.Header().StreamID, ErrCodeFlowControl)) + } + sc.sendWindowUpdate(nil, int(f.Length)) // conn-level + } + return nil + } + switch f := f.(type) { case *SettingsFrame: return sc.processSettings(f) @@ -1501,9 +1522,6 @@ // PROTOCOL_ERROR." return sc.countError("ping_on_stream", ConnectionError(ErrCodeProtocol)) } - if sc.inGoAway && sc.goAwayCode != ErrCodeNo { - return nil - } sc.writeFrame(FrameWriteRequest{write: writePingAck{f}}) return nil } @@ -1565,6 +1583,9 @@ panic(fmt.Sprintf("invariant; can't close stream in state %v", st.state)) } st.state = stateClosed + if st.readDeadline != nil { + st.readDeadline.Stop() + } if st.writeDeadline != nil { st.writeDeadline.Stop() } @@ -1586,10 +1607,18 @@ if p := st.body; p != nil { // Return any buffered unread bytes worth of conn-level flow control. // See golang.org/issue/16481 - sc.sendWindowUpdate(nil) + sc.sendWindowUpdate(nil, p.Len()) p.CloseWithError(err) } + if e, ok := err.(StreamError); ok { + if e.Cause != nil { + err = e.Cause + } else { + err = errStreamClosed + } + } + st.closeErr = err st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc sc.writeSched.CloseStream(st.id) } @@ -1686,16 +1715,6 @@ func (sc *serverConn) processData(f *DataFrame) error { sc.serveG.check() id := f.Header().StreamID - if sc.inGoAway && (sc.goAwayCode != ErrCodeNo || id > sc.maxClientStreamID) { - // Discard all DATA frames if the GOAWAY is due to an - // error, or: - // - // Section 6.8: After sending a GOAWAY frame, the sender - // can discard frames for streams initiated by the - // receiver with identifiers higher than the identified - // last stream. - return nil - } data := f.Data() state, st := sc.state(id) @@ -1734,7 +1753,7 @@ // sendWindowUpdate, which also schedules sending the // frames. sc.inflow.take(int32(f.Length)) - sc.sendWindowUpdate(nil) // conn-level + sc.sendWindowUpdate(nil, int(f.Length)) // conn-level if st != nil && st.resetQueued { // Already have a stream error in flight. Don't send another. @@ -1752,7 +1771,7 @@ return sc.countError("data_flow", streamError(id, ErrCodeFlowControl)) } sc.inflow.take(int32(f.Length)) - sc.sendWindowUpdate(nil) // conn-level + sc.sendWindowUpdate(nil, int(f.Length)) // conn-level st.body.CloseWithError(fmt.Errorf("sender tried to send more than declared Content-Length of %d bytes", st.declBodyBytes)) // RFC 7540, sec 8.1.2.6: A request or response is also malformed if the @@ -1770,7 +1789,7 @@ if len(data) > 0 { wrote, err := st.body.Write(data) if err != nil { - sc.sendWindowUpdate32(nil, int32(f.Length)-int32(wrote)) + sc.sendWindowUpdate(nil, int(f.Length)-wrote) return sc.countError("body_write_err", streamError(id, ErrCodeStreamClosed)) } if wrote != len(data) { @@ -1838,19 +1857,27 @@ } } +// onReadTimeout is run on its own goroutine (from time.AfterFunc) +// when the stream's ReadTimeout has fired. +func (st *stream) onReadTimeout() { + // Wrap the ErrDeadlineExceeded to avoid callers depending on us + // returning the bare error. + st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded)) +} + // onWriteTimeout is run on its own goroutine (from time.AfterFunc) // when the stream's WriteTimeout has fired. func (st *stream) onWriteTimeout() { - st.sc.writeFrameFromHandler(FrameWriteRequest{write: streamError(st.id, ErrCodeInternal)}) + st.sc.writeFrameFromHandler(FrameWriteRequest{write: StreamError{ + StreamID: st.id, + Code: ErrCodeInternal, + Cause: os.ErrDeadlineExceeded, + }}) } func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error { sc.serveG.check() id := f.StreamID - if sc.inGoAway { - // Ignore. - return nil - } // http://tools.ietf.org/html/rfc7540#section-5.1.1 // Streams initiated by a client MUST use odd-numbered stream // identifiers. [...] An endpoint that receives an unexpected @@ -1953,6 +1980,9 @@ // (in Go 1.8), though. That's a more sane option anyway. if sc.hs.ReadTimeout != 0 { sc.conn.SetReadDeadline(time.Time{}) + if st.body != nil { + st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout) + } } go sc.runHandler(rw, req, handler) @@ -2021,9 +2051,6 @@ } func (sc *serverConn) processPriority(f *PriorityFrame) error { - if sc.inGoAway { - return nil - } if err := sc.checkPriority(f.StreamID, f.PriorityParam); err != nil { return err } @@ -2322,39 +2349,24 @@ func (sc *serverConn) noteBodyRead(st *stream, n int) { sc.serveG.check() - sc.sendWindowUpdate(nil) // conn-level + sc.sendWindowUpdate(nil, n) // conn-level if st.state != stateHalfClosedRemote && st.state != stateClosed { // Don't send this WINDOW_UPDATE if the stream is closed // remotely. - sc.sendWindowUpdate(st) + sc.sendWindowUpdate(st, n) } } // st may be nil for conn-level -func (sc *serverConn) sendWindowUpdate(st *stream) { +func (sc *serverConn) sendWindowUpdate(st *stream, n int) { sc.serveG.check() - - var n int32 - if st == nil { - if avail, windowSize := sc.inflow.available(), sc.srv.initialConnRecvWindowSize(); avail > windowSize/2 { - return - } else { - n = windowSize - avail - } - } else { - if avail, windowSize := st.inflow.available(), sc.srv.initialStreamRecvWindowSize(); avail > windowSize/2 { - return - } else { - n = windowSize - avail - } - } // "The legal range for the increment to the flow control // window is 1 to 2^31-1 (2,147,483,647) octets." // A Go Read call on 64-bit machines could in theory read // a larger Read than this. Very unlikely, but we handle it here // rather than elsewhere for now. const maxUint31 = 1<<31 - 1 - for n >= maxUint31 { + for n > maxUint31 { sc.sendWindowUpdate32(st, maxUint31) n -= maxUint31 } @@ -2474,7 +2486,15 @@ type chunkWriter struct{ rws *responseWriterState } -func (cw chunkWriter) Write(p []byte) (n int, err error) { return cw.rws.writeChunk(p) } +func (cw chunkWriter) Write(p []byte) (n int, err error) { + n, err = cw.rws.writeChunk(p) + if err == errStreamClosed { + // If writing failed because the stream has been closed, + // return the reason it was closed. + err = cw.rws.stream.closeErr + } + return n, err +} func (rws *responseWriterState) hasTrailers() bool { return len(rws.trailers) > 0 } @@ -2668,23 +2688,85 @@ } } +func (w *responseWriter) SetReadDeadline(deadline time.Time) error { + st := w.rws.stream + if !deadline.IsZero() && deadline.Before(time.Now()) { + // If we're setting a deadline in the past, reset the stream immediately + // so writes after SetWriteDeadline returns will fail. + st.onReadTimeout() + return nil + } + w.rws.conn.sendServeMsg(func(sc *serverConn) { + if st.readDeadline != nil { + if !st.readDeadline.Stop() { + // Deadline already exceeded, or stream has been closed. + return + } + } + if deadline.IsZero() { + st.readDeadline = nil + } else if st.readDeadline == nil { + st.readDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onReadTimeout) + } else { + st.readDeadline.Reset(deadline.Sub(time.Now())) + } + }) + return nil +} + +func (w *responseWriter) SetWriteDeadline(deadline time.Time) error { + st := w.rws.stream + if !deadline.IsZero() && deadline.Before(time.Now()) { + // If we're setting a deadline in the past, reset the stream immediately + // so writes after SetWriteDeadline returns will fail. + st.onWriteTimeout() + return nil + } + w.rws.conn.sendServeMsg(func(sc *serverConn) { + if st.writeDeadline != nil { + if !st.writeDeadline.Stop() { + // Deadline already exceeded, or stream has been closed. + return + } + } + if deadline.IsZero() { + st.writeDeadline = nil + } else if st.writeDeadline == nil { + st.writeDeadline = time.AfterFunc(deadline.Sub(time.Now()), st.onWriteTimeout) + } else { + st.writeDeadline.Reset(deadline.Sub(time.Now())) + } + }) + return nil +} + func (w *responseWriter) Flush() { + w.FlushError() +} + +func (w *responseWriter) FlushError() error { rws := w.rws if rws == nil { panic("Header called after Handler finished") } + var err error if rws.bw.Buffered() > 0 { - if err := rws.bw.Flush(); err != nil { - // Ignore the error. The frame writer already knows. - return - } + err = rws.bw.Flush() } else { // The bufio.Writer won't call chunkWriter.Write // (writeChunk with zero bytes, so we have to do it // ourselves to force the HTTP response header and/or // final DATA frame (with END_STREAM) to be sent. - rws.writeChunk(nil) + _, err = chunkWriter{rws}.Write(nil) + if err == nil { + select { + case <-rws.stream.cw: + err = rws.stream.closeErr + default: + } + } } + return err } func (w *responseWriter) CloseNotify() <-chan bool { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/net/http2/transport.go new/vendor/golang.org/x/net/http2/transport.go --- old/vendor/golang.org/x/net/http2/transport.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/golang.org/x/net/http2/transport.go 2022-11-15 10:21:46.000000000 +0100 @@ -16,6 +16,7 @@ "errors" "fmt" "io" + "io/fs" "log" "math" mathrand "math/rand" @@ -501,6 +502,15 @@ return net.JoinHostPort(host, port) } +var retryBackoffHook func(time.Duration) *time.Timer + +func backoffNewTimer(d time.Duration) *time.Timer { + if retryBackoffHook != nil { + return retryBackoffHook(d) + } + return time.NewTimer(d) +} + // RoundTripOpt is like RoundTrip, but takes options. func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) { if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) { @@ -526,11 +536,14 @@ } backoff := float64(uint(1) << (uint(retry) - 1)) backoff += backoff * (0.1 * mathrand.Float64()) + d := time.Second * time.Duration(backoff) + timer := backoffNewTimer(d) select { - case <-time.After(time.Second * time.Duration(backoff)): + case <-timer.C: t.vlogf("RoundTrip retrying after failure: %v", err) continue case <-req.Context().Done(): + timer.Stop() err = req.Context().Err() } } @@ -1075,7 +1088,7 @@ func commaSeparatedTrailers(req *http.Request) (string, error) { keys := make([]string, 0, len(req.Trailer)) for k := range req.Trailer { - k = http.CanonicalHeaderKey(k) + k = canonicalHeader(k) switch k { case "Transfer-Encoding", "Trailer", "Content-Length": return "", fmt.Errorf("invalid Trailer key %q", k) @@ -1612,7 +1625,7 @@ var sawEOF bool for !sawEOF { - n, err := body.Read(buf[:len(buf)]) + n, err := body.Read(buf) if hasContentLen { remainLen -= int64(n) if remainLen == 0 && err == nil { @@ -1915,7 +1928,7 @@ // Header list size is ok. Write the headers. enumerateHeaders(func(name, value string) { - name, ascii := asciiToLower(name) + name, ascii := lowerHeader(name) if !ascii { // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header // field names have to be ASCII characters (just as in HTTP/1.x). @@ -1968,7 +1981,7 @@ } for k, vv := range trailer { - lowKey, ascii := asciiToLower(k) + lowKey, ascii := lowerHeader(k) if !ascii { // Skip writing invalid headers. Per RFC 7540, Section 8.1.2, header // field names have to be ASCII characters (just as in HTTP/1.x). @@ -2301,7 +2314,7 @@ Status: status + " " + http.StatusText(statusCode), } for _, hf := range regularFields { - key := http.CanonicalHeaderKey(hf.Name) + key := canonicalHeader(hf.Name) if key == "Trailer" { t := res.Trailer if t == nil { @@ -2309,7 +2322,7 @@ res.Trailer = t } foreachHeaderElement(hf.Value, func(v string) { - t[http.CanonicalHeaderKey(v)] = nil + t[canonicalHeader(v)] = nil }) } else { vv := header[key] @@ -2414,7 +2427,7 @@ trailer := make(http.Header) for _, hf := range f.RegularFields() { - key := http.CanonicalHeaderKey(hf.Name) + key := canonicalHeader(hf.Name) trailer[key] = append(trailer[key], hf.Value) } cs.trailer = trailer @@ -2985,7 +2998,11 @@ } func (gz *gzipReader) Close() error { - return gz.body.Close() + if err := gz.body.Close(); err != nil { + return err + } + gz.zerr = fs.ErrClosed + return nil } type errorReader struct{ err error } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/sys/unix/sockcmsg_unix.go new/vendor/golang.org/x/sys/unix/sockcmsg_unix.go --- old/vendor/golang.org/x/sys/unix/sockcmsg_unix.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/golang.org/x/sys/unix/sockcmsg_unix.go 2022-11-15 10:21:46.000000000 +0100 @@ -52,6 +52,20 @@ return msgs, nil } +// ParseOneSocketControlMessage parses a single socket control message from b, returning the message header, +// message data (a slice of b), and the remainder of b after that single message. +// When there are no remaining messages, len(remainder) == 0. +func ParseOneSocketControlMessage(b []byte) (hdr Cmsghdr, data []byte, remainder []byte, err error) { + h, dbuf, err := socketControlMessageHeaderAndData(b) + if err != nil { + return Cmsghdr{}, nil, nil, err + } + if i := cmsgAlignOf(int(h.Len)); i < len(b) { + remainder = b[i:] + } + return *h, dbuf, remainder, nil +} + func socketControlMessageHeaderAndData(b []byte) (*Cmsghdr, []byte, error) { h := (*Cmsghdr)(unsafe.Pointer(&b[0])) if h.Len < SizeofCmsghdr || uint64(h.Len) > uint64(len(b)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/sys/unix/syscall_linux.go new/vendor/golang.org/x/sys/unix/syscall_linux.go --- old/vendor/golang.org/x/sys/unix/syscall_linux.go 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/golang.org/x/sys/unix/syscall_linux.go 2022-11-15 10:21:46.000000000 +0100 @@ -1554,6 +1554,7 @@ var iova [1]Iovec iova[0].Base = &dummy iova[0].SetLen(1) + iov = iova[:] } } msg.Control = &oob[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/modules.txt new/vendor/modules.txt --- old/vendor/modules.txt 2022-10-25 20:45:32.000000000 +0200 +++ new/vendor/modules.txt 2022-11-15 10:21:46.000000000 +0100 @@ -1,4 +1,4 @@ -# fortio.org/assert v1.1.0 +# fortio.org/assert v1.1.2 ## explicit; go 1.18 fortio.org/assert # github.com/fsnotify/fsnotify v1.6.0 @@ -15,10 +15,10 @@ # github.com/google/uuid v1.3.0 ## explicit github.com/google/uuid -# golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 +# golang.org/x/exp v0.0.0-20221111204811-129d8d6c17ab ## explicit; go 1.18 golang.org/x/exp/constraints -# golang.org/x/net v0.0.0-20221012135044-0b7e1fb9d458 +# golang.org/x/net v0.2.0 ## explicit; go 1.17 golang.org/x/net/context golang.org/x/net/http/httpguts @@ -28,12 +28,12 @@ golang.org/x/net/idna golang.org/x/net/internal/timeseries golang.org/x/net/trace -# golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43 +# golang.org/x/sys v0.2.0 ## explicit; go 1.17 golang.org/x/sys/internal/unsafeheader golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/text v0.3.8 +# golang.org/x/text v0.4.0 ## explicit; go 1.17 golang.org/x/text/secure/bidirule golang.org/x/text/transform