Repository: fortio/fortio
Pull request #449 (2021-01-23 05:37:25)

splitting h2 vs h1.1 old style fetch for debug fetch ui to work with h2c

codeclimate[bot]: Code Climate has analyzed commit c8d76118 and detected **1 issue** on this pull request.

Here's the issue category breakdown:

| Category | Count |
|:---:|:---:|
| Style | 1 |


View more on [Code Climate](https://codeclimate.com/github/fortio/fortio/pull/449).

codecov[bot]: # [Codecov](https://codecov.io/gh/fortio/fortio/pull/449?src=pr&el=h1) Report
> Merging [#449](https://codecov.io/gh/fortio/fortio/pull/449?src=pr&el=desc) (bbb0e3e) into [master](https://codecov.io/gh/fortio/fortio/commit/a511dd1e3b316fc8d7cfcaedc50f5a561224e393?el=desc) (a511dd1) will **decrease** coverage by `0.4%`.
> The diff coverage is `48.6%`.

[![Impacted file tree graph](https://codecov.io/gh/fortio/fortio/pull/449/graphs/tree.svg?width=650&height=150&src=pr&token=6HLno2jpvU)](https://codecov.io/gh/fortio/fortio/pull/449?src=pr&el=tree)

```diff
@@           Coverage Diff            @@
##           master    #449     +/-   ##
========================================
- Coverage    86.6%   86.2%   -0.4%     
========================================
  Files          25      25             
  Lines        2745    2743      -2     
========================================
- Hits         2378    2364     -14     
- Misses        245     252      +7     
- Partials      122     127      +5     
```


| [Impacted Files](https://codecov.io/gh/fortio/fortio/pull/449?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [fhttp/http\_server.go](https://codecov.io/gh/fortio/fortio/pull/449/diff?src=pr&el=tree#diff-Zmh0dHAvaHR0cF9zZXJ2ZXIuZ28=) | `71.4% <38.7%> (-4.6%)` | :arrow_down: |
| [fhttp/http\_forwarder.go](https://codecov.io/gh/fortio/fortio/pull/449/diff?src=pr&el=tree#diff-Zmh0dHAvaHR0cF9mb3J3YXJkZXIuZ28=) | `90.2% <100.0%> (ø)` | |
| [fhttp/http\_utils.go](https://codecov.io/gh/fortio/fortio/pull/449/diff?src=pr&el=tree#diff-Zmh0dHAvaHR0cF91dGlscy5nbw==) | `100.0% <100.0%> (ø)` | |
| [fgrpc/grpcrunner.go](https://codecov.io/gh/fortio/fortio/pull/449/diff?src=pr&el=tree#diff-ZmdycGMvZ3JwY3J1bm5lci5nbw==) | `88.7% <0.0%> (-1.1%)` | :arrow_down: |
| [fhttp/http\_client.go](https://codecov.io/gh/fortio/fortio/pull/449/diff?src=pr&el=tree#diff-Zmh0dHAvaHR0cF9jbGllbnQuZ28=) | `80.5% <0.0%> (+0.9%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/fortio/fortio/pull/449?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/fortio/fortio/pull/449?src=pr&el=footer). Last update [a511dd1...bbb0e3e](https://codecov.io/gh/fortio/fortio/pull/449?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2b9e4f2..3539348 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,7 +7,7 @@ version: 2
 defaultEnv: &defaultEnv
     docker:
       # specify the version
-      - image: docker.io/fortio/fortio.build:v30
+      - image: docker.io/fortio/fortio.build:v31
     working_directory: /go/src/fortio.org/fortio
 
 jobs:
diff --git a/Dockerfile b/Dockerfile
index 9cfad4d..2c79dbd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
 # Build the binaries in larger image
-FROM docker.io/fortio/fortio.build:v30 as build
+FROM docker.io/fortio/fortio.build:v31 as build
 WORKDIR /go/src/fortio.org
 COPY . fortio
 # We moved a lot of the logic into the Makefile so it can be reused in brew
diff --git a/Dockerfile.build b/Dockerfile.build
index 01f4adb..cf58c91 100644
--- a/Dockerfile.build
+++ b/Dockerfile.build
@@ -8,7 +8,7 @@ RUN apt-get -y update && \
 # Install FPM
 RUN gem install --no-document fpm
 # From fortio 1.4 onward we dropped go 1.8 compatibility
-RUN curl -f https://dl.google.com/go/go1.15.6.linux-amd64.tar.gz | tar xfz - -C /usr/local
+RUN curl -f https://dl.google.com/go/go1.15.7.linux-amd64.tar.gz | tar xfz - -C /usr/local
 ENV GOPATH /go
 RUN mkdir -p $GOPATH/bin
 ENV PATH /usr/local/go/bin:$PATH:$GOPATH/bin
diff --git a/Dockerfile.echosrv b/Dockerfile.echosrv
index 197351a..b39cb08 100644
--- a/Dockerfile.echosrv
+++ b/Dockerfile.echosrv
@@ -1,5 +1,5 @@
 # Build the binaries in larger image
-FROM docker.io/fortio/fortio.build:v30 as build
+FROM docker.io/fortio/fortio.build:v31 as build
 WORKDIR /go/src/fortio.org
 COPY . fortio
 RUN make -C fortio official-build-version BUILD_DIR=/build OFFICIAL_TARGET=fortio.org/fortio/echosrv OFFICIAL_BIN=../echosrv.bin
diff --git a/Dockerfile.fcurl b/Dockerfile.fcurl
index 1109249..0d9c0de 100644
--- a/Dockerfile.fcurl
+++ b/Dockerfile.fcurl
@@ -1,5 +1,5 @@
 # Build the binaries in larger image
-FROM docker.io/fortio/fortio.build:v30 as build
+FROM docker.io/fortio/fortio.build:v31 as build
 WORKDIR /go/src/fortio.org
 COPY . fortio
 # fcurl should not need vendor/no dependencies
diff --git a/Makefile b/Makefile
index 34c58d9..42c69af 100755
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@
 IMAGES=echosrv fcurl # plus the combo image / Dockerfile without ext.
 
 DOCKER_PREFIX := docker.io/fortio/fortio
-BUILD_IMAGE_TAG := v30
+BUILD_IMAGE_TAG := v31
 BUILD_IMAGE := $(DOCKER_PREFIX).build:$(BUILD_IMAGE_TAG)
 
 TAG:=$(USER)$(shell date +%y%m%d_%H%M%S)
diff --git a/README.md b/README.md
index d3fa119..094c7ef 100644
--- a/README.md
+++ b/README.md
@@ -46,13 +46,13 @@ docker run fortio/fortio load http://www.google.com/ # For a test run
 Or download one of the binary distributions, from the [releases](https://github.com/fortio/fortio/releases) assets page or for instance:
 
 ```shell
-curl -L https://github.com/fortio/fortio/releases/download/v1.11.5/fortio-linux_x64-1.11.5.tgz \
+curl -L https://github.com/fortio/fortio/releases/download/v1.12.2/fortio-linux_x64-1.12.2.tgz \
  | sudo tar -C / -xvzpf -
 # or the debian package
-wget https://github.com/fortio/fortio/releases/download/v1.11.5/fortio_1.11.5-1_amd64.deb
-dpkg -i fortio_1.11.5-1_amd64.deb
+wget https://github.com/fortio/fortio/releases/download/v1.12.2/fortio_1.12.2-1_amd64.deb
+dpkg -i fortio_1.12.2-1_amd64.deb
 # or the rpm
-rpm -i https://github.com/fortio/fortio/releases/download/v1.11.5/fortio-1.11.5-1.x86_64.rpm
+rpm -i https://github.com/fortio/fortio/releases/download/v1.12.2/fortio-1.12.2-1.x86_64.rpm
 ```
 
 On a MacOS you can also install Fortio using [Homebrew](https://brew.sh/):
@@ -61,7 +61,7 @@ On a MacOS you can also install Fortio using [Homebrew](https://brew.sh/):
 brew install fortio
 ```
 
-On Windows, download https://github.com/fortio/fortio/releases/download/v1.11.5/fortio_win_1.11.5.zip and extract all to some location then using the Windows Command Prompt:
+On Windows, download https://github.com/fortio/fortio/releases/download/v1.12.2/fortio_win_1.12.2.zip and extract all to some location then using the Windows Command Prompt:
 ```
 cd fortio
 fortio.exe server
@@ -105,7 +105,7 @@ Full list of command line flags (`fortio help`):
 <details>
 <!-- use release/updateFlags.sh to update this section -->
 <pre>
-Φορτίο 1.11.5 usage:
+Φορτίο 1.12.2 usage:
         fortio command [flags] target
 where command is one of: load (load testing), server (starts ui, http-echo,
 redirect, proxies, tcp-echo and grpc ping servers), tcp-echo (only the tcp-echo
@@ -284,15 +284,15 @@ See also the FAQ entry about [fortio flags for best results](https://github.com/
 ```Shell
 $ fortio server &
 14:11:05 I fortio_main.go:171> Not using dynamic flag watching (use -config to set watch directory)
-Fortio 1.11.5 tcp-echo server listening on [::]:8078
-Fortio 1.11.5 grpc 'ping' server listening on [::]:8079
-Fortio 1.11.5 https redirector server listening on [::]:8081
-Fortio 1.11.5 echo server listening on [::]:8080
+Fortio 1.12.2 tcp-echo server listening on [::]:8078
+Fortio 1.12.2 grpc 'ping' server listening on [::]:8079
+Fortio 1.12.2 https redirector server listening on [::]:8081
+Fortio 1.12.2 echo server listening on [::]:8080
 Data directory is /Users/ldemailly/go/src/fortio.org/fortio
 UI started - visit:
 http://localhost:8080/fortio/
 (or any host/ip reachable on this server)
-14:11:05 I fortio_main.go:233> All fortio 1.11.5 release go1.15.2 servers started!
+14:11:05 I fortio_main.go:233> All fortio 1.12.2 release go1.15.2 servers started!
 ```
 
 ### Change the port / binding address
@@ -305,8 +305,8 @@ $ fortio server -http-port 10.10.10.10:8088
 UI starting - visit:
 http://10.10.10.10:8088/fortio/
 Https redirector running on :8081
-Fortio 1.11.5 grpc ping server listening on port :8079
-Fortio 1.11.5 echo server listening on port 10.10.10.10:8088
+Fortio 1.12.2 grpc ping server listening on port :8079
+Fortio 1.12.2 echo server listening on port 10.10.10.10:8088
 ```
 
 ### Unix domain sockets
@@ -315,12 +315,12 @@ You can use unix domain socket for any server/client:
 
 ```Shell
 $ fortio server --http-port /tmp/fortio-uds-http &
-Fortio 1.11.5 grpc 'ping' server listening on [::]:8079
-Fortio 1.11.5 https redirector server listening on [::]:8081
-Fortio 1.11.5 echo server listening on /tmp/fortio-uds-http
+Fortio 1.12.2 grpc 'ping' server listening on [::]:8079
+Fortio 1.12.2 https redirector server listening on [::]:8081
+Fortio 1.12.2 echo server listening on /tmp/fortio-uds-http
 UI started - visit:
 fortio curl -unix-socket=/tmp/fortio-uds-http http://localhost/fortio/
-14:58:45 I fortio_main.go:217> All fortio 1.11.5 unknown go1.10.3 servers started!
+14:58:45 I fortio_main.go:217> All fortio 1.12.2 unknown go1.10.3 servers started!
 $ fortio curl -unix-socket=/tmp/fortio-uds-http http://foo.bar/debug
 15:00:48 I http_client.go:428> Using unix domain socket /tmp/fortio-uds-http instead of foo.bar http
 HTTP/1.1 200 OK
@@ -328,14 +328,14 @@ Content-Type: text/plain; charset=UTF-8
 Date: Wed, 08 Aug 2018 22:00:48 GMT
 Content-Length: 231
 
-Φορτίο version 1.11.5 unknown go1.10.3 echo debug server up for 2m3.4s on ldemailly-macbookpro - request from
+Φορτίο version 1.12.2 unknown go1.10.3 echo debug server up for 2m3.4s on ldemailly-macbookpro - request from
 
 GET /debug HTTP/1.1
 
 headers:
 
 Host: foo.bar
-User-Agent: fortio.org/fortio-1.11.5
+User-Agent: fortio.org/fortio-1.12.2
 
 body:
 ```
@@ -344,10 +344,10 @@ body:
 Start the echo-server alone and run a load (use `tcp://` prefix for the load test to be for tcp echo server)
 ```Shell
 $ fortio echo-server &
-Fortio 1.11.5 tcp-echo server listening on [::]:8078
-19:45:30 I fortio_main.go:238> All fortio 1.11.5 release go1.15.2 servers started!
+Fortio 1.12.2 tcp-echo server listening on [::]:8078
+19:45:30 I fortio_main.go:238> All fortio 1.12.2 release go1.15.2 servers started!
 $ fortio load -qps -1 -n 100000 tcp://localhost:8078
-Fortio 1.11.5 running at -1 queries per second, 16->16 procs, for 100000 calls: tcp://localhost:8078
+Fortio 1.12.2 running at -1 queries per second, 16->16 procs, for 100000 calls: tcp://localhost:8078
 20:01:31 I tcprunner.go:218> Starting tcp test for tcp://localhost:8078 with 4 threads at -1.0 qps
 Starting at max qps with 4 thread(s) [gomax 16] for exactly 100000 calls (25000 per thread + 0)
 20:01:32 I periodic.go:558> T003 ended after 1.240585427s : 25000 calls. qps=20151.77629520873
@@ -374,17 +374,28 @@ All done 100000 calls (plus 0 warmup) 0.049 ms avg, 80495.0 qps
 #### Simple grpc ping
 
 ```Shell
-$ fortio grpcping localhost
-02:29:27 I pingsrv.go:116> Ping RTT 305334 (avg of 342970, 293515, 279517 ns) clock skew -2137
-Clock skew histogram usec : count 1 avg -2.137 +/- 0 min -2.137 max -2.137 sum -2.137
+$ fortio grpcping -n 5 localhost
+22:36:55 I pingsrv.go:150> Ping RTT 212000 (avg of 259000, 217000, 160000 ns) clock skew -10500
+22:36:55 I pingsrv.go:150> Ping RTT 134333 (avg of 170000, 124000, 109000 ns) clock skew 5000
+22:36:55 I pingsrv.go:150> Ping RTT 112000 (avg of 111000, 122000, 103000 ns) clock skew 5000
+22:36:55 I pingsrv.go:150> Ping RTT 157000 (avg of 136000, 158000, 177000 ns) clock skew 6000
+22:36:55 I pingsrv.go:150> Ping RTT 108333 (avg of 118000, 106000, 101000 ns) clock skew 1000
+Clock skew histogram usec : count 5 avg 1.3 +/- 6.145 min -10.5 max 6 sum 6.5
 # range, mid point, percentile, count
->= -4 < -2 , -3 , 100.00, 1
-# target 50% -2.137
-RTT histogram usec : count 3 avg 305.334 +/- 27.22 min 279.517 max 342.97 sum 916.002
+>= -10.5 <= -10 , -10.25 , 20.00, 1
+> 0 <= 2 , 1 , 40.00, 1
+> 4 <= 6 , 5 , 100.00, 3
+# target 50% 4.33333
+RTT histogram usec : count 15 avg 144.73333 +/- 44.48 min 101 max 259 sum 2171
 # range, mid point, percentile, count
->= 250 < 300 , 275 , 66.67, 2
->= 300 < 350 , 325 , 100.00, 1
-# target 50% 294.879
+>= 101 <= 110 , 105.5 , 26.67, 4
+> 110 <= 120 , 115 , 40.00, 2
+> 120 <= 140 , 130 , 60.00, 3
+> 140 <= 160 , 150 , 73.33, 2
+> 160 <= 180 , 170 , 86.67, 2
+> 200 <= 250 , 225 , 93.33, 1
+> 250 <= 259 , 254.5 , 100.00, 1
+# target 50% 130
 ```
 
 #### Change the target port for grpc
@@ -418,8 +429,8 @@ $ fortio server -cert /path/to/fortio/server.crt -key /path/to/fortio/server.key
 UI starting - visit:
 http://localhost:8080/fortio/
 Https redirector running on :8081
-Fortio 1.11.5 grpc ping server listening on port :8079
-Fortio 1.11.5 echo server listening on port localhost:8080
+Fortio 1.12.2 grpc ping server listening on port :8079
+Fortio 1.12.2 echo server listening on port localhost:8080
 Using server certificate /path/to/fortio/server.crt to construct TLS credentials
 Using server key /path/to/fortio/server.key to construct TLS credentials
 ```
@@ -464,7 +475,7 @@ Load (low default qps/threading) test:
 
 ```Shell
 $ fortio load http://www.google.com
-Fortio 1.11.5 running at 8 queries per second, 8->8 procs, for 5s: http://www.google.com
+Fortio 1.12.2 running at 8 queries per second, 8->8 procs, for 5s: http://www.google.com
 19:10:33 I httprunner.go:84> Starting http test for http://www.google.com with 4 threads at 8.0 qps
 Starting at 8 qps with 4 thread(s) [gomax 8] for 5s : 10 calls each (total 40)
 19:10:39 I periodic.go:314> T002 ended after 5.056753279s : 10 calls. qps=1.9775534712220633
@@ -495,7 +506,7 @@ Uses `-s` to use multiple (h2/grpc) streams per connection (`-c`), request to hi
 
 ```bash
 $ fortio load -a -grpc -ping -grpc-ping-delay 0.25s -payload "01234567890" -c 2 -s 4 https://fortio-stage.istio.io
-Fortio 1.11.5 running at 8 queries per second, 8->8 procs, for 5s: https://fortio-stage.istio.io
+Fortio 1.12.2 running at 8 queries per second, 8->8 procs, for 5s: https://fortio-stage.istio.io
 16:32:56 I grpcrunner.go:139> Starting GRPC Ping Delay=250ms PayloadLength=11 test for https://fortio-stage.istio.io with 4*2 threads at 8.0 qps
 16:32:56 I grpcrunner.go:261> stripping https scheme. grpc destination: fortio-stage.istio.io. grpc port: 443
 16:32:57 I grpcrunner.go:261> stripping https scheme. grpc destination: fortio-stage.istio.io. grpc port: 443
@@ -600,14 +611,14 @@ Content-Type: text/plain; charset=UTF-8
 Date: Mon, 08 Jan 2018 22:26:26 GMT
 Content-Length: 230
 
-Φορτίο version 1.11.5 echo debug server up for 39s on ldemailly-macbookpro - request from [::1]:65055
+Φορτίο version 1.12.2 echo debug server up for 39s on ldemailly-macbookpro - request from [::1]:65055
 
 GET /debug HTTP/1.1
 
 headers:
 
 Host: localhost:8080
-User-Agent: fortio.org/fortio-1.11.5
+User-Agent: fortio.org/fortio-1.12.2
 Foo: Bar
 
 body:
@@ -632,7 +643,7 @@ Example listen on 1 extra port and every request sent to that 1 port is forward
 # in one window or &
 $ fortio server -M "5554 http://localhost:8080 http://localhost:8080"
 [...]
-Fortio 1.11.5 Multi on 5554 server listening on [::]:5554
+Fortio 1.12.2 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
@@ -644,7 +655,7 @@ Date: Wed, 07 Oct 2020 17:11:06 GMT
 Content-Length: 684
 Content-Type: text/plain; charset=utf-8
 
-Φορτίο version 1.11.5 unknown go1.15.2 echo debug server up for 1m9.3s on C02C77BHMD6R - request from [::1]:51020
+Φορτίο version 1.12.2 unknown go1.15.2 echo debug server up for 1m9.3s on C02C77BHMD6R - request from [::1]:51020
 
 POST /debug HTTP/1.1
 
@@ -653,14 +664,14 @@ headers:
 Host: localhost:8080
 Accept-Encoding: gzip
 Content-Type: application/octet-stream
-User-Agent: fortio.org/fortio-1.11.5
+User-Agent: fortio.org/fortio-1.12.2
 X-Fortio-Multi-Id: 1
 X-On-Behalf-Of: [::1]:51019
 
 body:
 
 a test
-Φορτίο version 1.11.5 unknown go1.15.2 echo debug server up for 1m9.3s on C02C77BHMD6R - request from [::1]:51020
+Φορτίο version 1.12.2 unknown go1.15.2 echo debug server up for 1m9.3s on C02C77BHMD6R - request from [::1]:51020
 
 POST /debug HTTP/1.1
 
@@ -669,7 +680,7 @@ headers:
 Host: localhost:8080
 Accept-Encoding: gzip
 Content-Type: application/octet-stream
-User-Agent: fortio.org/fortio-1.11.5
+User-Agent: fortio.org/fortio-1.12.2
 X-Fortio-Multi-Id: 2
 X-On-Behalf-Of: [::1]:51019
 
@@ -690,15 +701,15 @@ Example: open 2 additional listening ports and forward all requests received on
 
 ```Shell
 $ fortio server -P "8888 [::1]:8080" -P "[::1]:8889 [::1]:8080" 
-Fortio 1.11.5 grpc 'ping' server listening on [::]:8079
-Fortio 1.11.5 https redirector server listening on [::]:8081
-Fortio 1.11.5 echo server listening on [::]:8080
+Fortio 1.12.2 grpc 'ping' server listening on [::]:8079
+Fortio 1.12.2 https redirector server listening on [::]:8081
+Fortio 1.12.2 echo server listening on [::]:8080
 Data directory is /home/dl
 UI started - visit:
 http://localhost:8080/fortio/
 (or any host/ip reachable on this server)
-Fortio 1.11.5 proxy for [::1]:8080 server listening on [::]:8888
-Fortio 1.11.5 proxy for [::1]:8080 server listening on [::1]:8889
+Fortio 1.12.2 proxy for [::1]:8080 server listening on [::]:8888
+Fortio 1.12.2 proxy for [::1]:8080 server listening on [::1]:8889
 ```
 
 ## Server URLs and features
diff --git a/bincommon/commonflags.go b/bincommon/commonflags.go
index 12b4593..1933d3f 100644
--- a/bincommon/commonflags.go
+++ b/bincommon/commonflags.go
@@ -33,8 +33,7 @@ import (
 )
 
 // -- Support for multiple instances of -H flag on cmd line.
-type headersFlagList struct {
-}
+type headersFlagList struct{}
 
 func (f *headersFlagList) String() string {
 	return ""
diff --git a/fgrpc/pingsrv.go b/fgrpc/pingsrv.go
index 49f8737..2ac410f 100644
--- a/fgrpc/pingsrv.go
+++ b/fgrpc/pingsrv.go
@@ -39,8 +39,7 @@ const (
 	Error = "ERROR"
 )
 
-type pingSrv struct {
-}
+type pingSrv struct{}
 
 func (s *pingSrv) Ping(c context.Context, in *PingMessage) (*PingMessage, error) {
 	log.LogVf("Ping called %+v (ctx %+v)", *in, c)
diff --git a/fhttp/http_forwarder.go b/fhttp/http_forwarder.go
index 78e8b8f..eabb07a 100644
--- a/fhttp/http_forwarder.go
+++ b/fhttp/http_forwarder.go
@@ -68,11 +68,12 @@ func makeMirrorRequest(baseURL string, r *http.Request, data []byte) *http.Reque
 	}
 	// Copy all headers
 	// Host header is not in Header so safe to copy
-	copyHeaders(req, r, true)
+	CopyHeaders(req, r, true)
 	return req
 }
 
-func copyHeaders(req, r *http.Request, all bool) {
+// CopyHeaders copies all or trace headers.
+func CopyHeaders(req, r *http.Request, all bool) {
 	// Copy only trace headers unless all is true.
 	for k, v := range r.Header {
 		if all || k == EnvoyRequestID || k == TraceHeader || strings.HasPrefix(k, TraceHeadersPrefix) {
@@ -86,14 +87,15 @@ func copyHeaders(req, r *http.Request, all bool) {
 	}
 }
 
-func makeSimpleRequest(url string, r *http.Request) *http.Request {
+// MakeSimpleRequest makes a new request for url but copies trace headers from input request r.
+func MakeSimpleRequest(url string, r *http.Request) *http.Request {
 	req, err := http.NewRequestWithContext(r.Context(), "GET", url, nil)
 	if err != nil {
 		log.Warnf("new request error for %q: %v", url, err)
 		return nil
 	}
 	// Copy only trace headers
-	copyHeaders(req, r, false)
+	CopyHeaders(req, r, false)
 	req.Header.Add("User-Agent", userAgent)
 	return req
 }
@@ -122,13 +124,13 @@ func setupRequest(r *http.Request, i int, t TargetConf, data []byte) *http.Reque
 	if t.MirrorOrigin {
 		req = makeMirrorRequest(t.Destination, r, data)
 	} else {
-		req = makeSimpleRequest(t.Destination, r)
+		req = MakeSimpleRequest(t.Destination, r)
 	}
 	if req == nil {
 		// error already logged
 		return nil
 	}
-	req.Header.Add("X-On-Behalf-Of", r.RemoteAddr)
+	OnBehalfOfRequest(req, r)
 	req.Header.Add("X-Fortio-Multi-ID", strconv.Itoa(i+1))
 	log.LogVf("Going to %s", req.URL.String())
 	return req
@@ -231,8 +233,8 @@ func (mcfg *MultiServerConfig) TeeParallelHandler(w http.ResponseWriter, r *http
 	}
 }
 
-// createClient http client for connection reuse.
-func createClient() *http.Client {
+// CreateProxyClient http client for connection reuse.
+func CreateProxyClient() *http.Client {
 	client := &http.Client{
 		Transport: &http.Transport{
 			// TODO make configurable, should be fine for now for most but extreme -c values
@@ -261,7 +263,7 @@ func MultiServer(port string, cfg *MultiServerConfig) (*http.ServeMux, net.Addr)
 		// get actual bound port in case of :0
 		cfg.Name = "Multi on " + aStr
 	}
-	cfg.client = createClient()
+	cfg.client = CreateProxyClient()
 	for i := range cfg.Targets {
 		t := &cfg.Targets[i]
 		if t.MirrorOrigin {
diff --git a/fhttp/http_server.go b/fhttp/http_server.go
index 1dd9ee4..f763f33 100644
--- a/fhttp/http_server.go
+++ b/fhttp/http_server.go
@@ -375,12 +375,59 @@ func SetupPPROF(mux *http.ServeMux) {
 
 // -- Fetch er (simple http proxy) --
 
+var proxyClient = CreateProxyClient()
+
+// FetcherHandler2 is the handler for the fetcher/proxy that supports h2 input and makes a
+// new request with only tracing headers copied.
+func FetcherHandler2(w http.ResponseWriter, r *http.Request) {
+	LogRequest(r, "Fetch proxy2")
+	vals, ok := r.URL.Query()["url"]
+	if !ok {
+		http.Error(w, "missing url query argument", http.StatusBadRequest)
+		return
+	}
+	url := strings.TrimSpace(vals[0])
+	if url == "" {
+		http.Error(w, "missing url value", http.StatusBadRequest)
+		return
+	}
+	if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
+		url = "http://" + url
+	}
+	req := MakeSimpleRequest(url, r)
+	if req == nil {
+		http.Error(w, "parsing url failed, invalid url", http.StatusBadRequest)
+		return
+	}
+	OnBehalfOfRequest(req, r)
+	resp, err := proxyClient.Do(req)
+	if err != nil {
+		log.Errf("Error for %q: %v", url, err)
+		http.Error(w, "query failed, invalid url", http.StatusBadRequest)
+		return
+	}
+	log.LogVf("Success for %+v", req)
+	for k, v := range resp.Header {
+		for _, vv := range v {
+			w.Header().Add(k, vv)
+		}
+	}
+	w.WriteHeader(resp.StatusCode)
+	bw, err := fnet.Copy(w, resp.Body)
+	if err != nil {
+		log.Warnf("Error copying response for %s: %v", url, err)
+	}
+	log.LogVf("fh2 copied %d from %s - code %d", bw, url, resp.StatusCode)
+	_ = resp.Body.Close()
+}
+
 // FetcherHandler is the handler for the fetcher/proxy.
 func FetcherHandler(w http.ResponseWriter, r *http.Request) {
 	LogRequest(r, "Fetch (prefix stripped)")
 	hj, ok := w.(http.Hijacker)
 	if !ok {
-		log.Critf("hijacking not supported")
+		log.Errf("hijacking not supported: %v", r.Proto)
+		http.Error(w, "User fetch2 when using http/2.0", http.StatusHTTPVersionNotSupported)
 		return
 	}
 	conn, _, err := hj.Hijack()
diff --git a/fhttp/http_utils.go b/fhttp/http_utils.go
index 23e0d75..46f65d1 100644
--- a/fhttp/http_utils.go
+++ b/fhttp/http_utils.go
@@ -435,6 +435,11 @@ func OnBehalfOf(o *HTTPOptions, r *http.Request) {
 	_ = o.AddAndValidateExtraHeader("X-On-Behalf-Of: " + r.RemoteAddr)
 }
 
+// OnBehalfOfRequest same as OnBehalfOf but places the header directly on the dst request object.
+func OnBehalfOfRequest(to *http.Request, from *http.Request) {
+	to.Header.Add("X-On-Behalf-Of", from.RemoteAddr)
+}
+
 // AddHTTPS replaces "http://" in url with "https://" or prepends "https://"
 // if url does not contain prefix "http://".
 func AddHTTPS(url string) string {
diff --git a/fortio_main.go b/fortio_main.go
index 967e5fa..27a3fd2 100644
--- a/fortio_main.go
+++ b/fortio_main.go
@@ -41,8 +41,7 @@ import (
 )
 
 // -- Start of support for multiple proxies (-P) flags on cmd line.
-type proxiesFlagList struct {
-}
+type proxiesFlagList struct{}
 
 func (f *proxiesFlagList) String() string {
 	return ""
@@ -56,8 +55,7 @@ func (f *proxiesFlagList) Set(value string) error {
 // -- End of functions for -P support.
 
 // -- Same for -M.
-type httpMultiFlagList struct {
-}
+type httpMultiFlagList struct{}
 
 func (f *httpMultiFlagList) String() string {
 	return ""
diff --git a/go.mod b/go.mod
index 4cbc355..daf06bd 100644
--- a/go.mod
+++ b/go.mod
@@ -1,19 +1,12 @@
 module fortio.org/fortio
 
-go 1.14
+go 1.15
 
 require (
-	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/fsnotify/fsnotify v1.4.9
-	github.com/golang/protobuf v1.4.2
-	github.com/google/go-cmp v0.5.0 // indirect
-	github.com/google/uuid v1.1.2
-	github.com/kr/pretty v0.1.0 // indirect
-	github.com/stretchr/testify v1.4.0
-	golang.org/x/net v0.0.0-20200602114024-627f9648deb9
-	golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 // indirect
-	golang.org/x/text v0.3.3 // indirect
-	google.golang.org/genproto v0.0.0-20200619004808-3e7fca5c55db // indirect
-	google.golang.org/grpc v1.29.1
-	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
+	github.com/golang/protobuf v1.4.3
+	github.com/google/uuid v1.2.0
+	github.com/stretchr/testify v1.5.1
+	golang.org/x/net v0.0.0-20210119194325-5f4716e94777
+	google.golang.org/grpc v1.35.0
 )
diff --git a/go.sum b/go.sum
index f2b9920..fe281f8 100644
--- a/go.sum
+++ b/go.sum
@@ -2,13 +2,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -16,37 +15,29 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -56,8 +47,8 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
-golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -65,9 +56,9 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -76,21 +67,19 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200619004808-3e7fca5c55db h1:Q5+mRMPseAnmi+ah5YkFXuVnZqUTgxmQF6e4PnjSkcE=
-google.golang.org/genproto v0.0.0-20200619004808-3e7fca5c55db/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -99,11 +88,9 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
 google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
-gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/log/logger.go b/log/logger.go
index cfef4c9..d258b15 100644
--- a/log/logger.go
+++ b/log/logger.go
@@ -83,11 +83,11 @@ func init() {
 	}
 	// virtual dynLevel flag that maps back to actual level
 	_ = dflag.DynString(flag.CommandLine, "loglevel", GetLogLevel().String(),
-		fmt.Sprintf("loglevel, one of %v", levelToStrA)).WithValidator(func(new string) error {
-		_, err := ValidateLevel(new)
+		fmt.Sprintf("loglevel, one of %v", levelToStrA)).WithValidator(func(newStr string) error {
+		_, err := ValidateLevel(newStr)
 		return err
-	}).WithSyncNotifier(func(old, new string) {
-		_ = setLogLevelStr(new) // will succeed as we just validated it first
+	}).WithSyncNotifier(func(old, newStr string) {
+		_ = setLogLevelStr(newStr) // will succeed as we just validated it first
 	})
 	log.SetFlags(log.Ltime)
 }
@@ -253,8 +253,7 @@ type LoggerI interface {
 	Printf(format string, rest ...interface{})
 }
 
-type loggerShm struct {
-}
+type loggerShm struct{}
 
 func (l *loggerShm) Printf(format string, rest ...interface{}) {
 	logPrintf(Info, format, rest...)
diff --git a/release/Dockerfile.in b/release/Dockerfile.in
index 1738863..c2c3ebf 100644
--- a/release/Dockerfile.in
+++ b/release/Dockerfile.in
@@ -1,5 +1,5 @@
 # Concatenated after ../Dockerfile to create the tgz
-FROM docker.io/fortio/fortio.build:v30 as stage
+FROM docker.io/fortio/fortio.build:v31 as stage
 WORKDIR /stage
 COPY --from=release /usr/bin/fortio usr/bin/fortio
 COPY --from=release /usr/share/fortio usr/share/fortio
diff --git a/stats/stats.go b/stats/stats.go
index c63a742..bb89914 100644
--- a/stats/stats.go
+++ b/stats/stats.go
@@ -437,9 +437,9 @@ func (h *Histogram) Reset() {
 
 // Clone returns a copy of the histogram.
 func (h *Histogram) Clone() *Histogram {
-	copy := NewHistogram(h.Offset, h.Divider)
-	copy.CopyFrom(h)
-	return copy
+	hCopy := NewHistogram(h.Offset, h.Divider)
+	hCopy.CopyFrom(h)
+	return hCopy
 }
 
 // CopyFrom sets the content of this object to a copy of the src.
diff --git a/ui/templates/main.html b/ui/templates/main.html
index 16ae5e7..69e0b1b 100644
--- a/ui/templates/main.html
+++ b/ui/templates/main.html
@@ -76,8 +76,15 @@
   Browse <a href='browse'>saved results</a> (or <a href="data/">raw JSON</a>)
 </div>
 <p><i>Or</i></p>
+<form action="fetch2/">
+<div>
+    Debug fetch any single URL: <input type="text" id="url" name="url" value="" size=50/>
+  </div>
+</form>
+<p><i>Or</i></p>
 <form action="javascript:document.location += 'fetch/' + document.getElementById('uri').value">
   <div>
+    HTTP/1.1 only semi proxy
     Debug fetch http://<input type="text" id="uri" name="uri" value="" size=50/>
   </div>
 </form>
diff --git a/ui/uihandler.go b/ui/uihandler.go
index 7744420..788429d 100644
--- a/ui/uihandler.go
+++ b/ui/uihandler.go
@@ -87,6 +87,7 @@ var (
 
 const (
 	fetchURI    = "fetch/"
+	fetch2URI   = "fetch2/"
 	faviconPath = "/favicon.ico"
 	modegrpc    = "grpc"
 )
@@ -896,7 +897,10 @@ func Serve(baseurl, port, debugpath, uipath, staticRsrcDir string, datadir strin
 	debugPath = ".." + debugpath // TODO: calculate actual path if not same number of directories
 	mux.HandleFunc(uiPath, Handler)
 	fetchPath = uiPath + fetchURI
+	// For backward compatibility with http:// only fetcher
 	mux.Handle(fetchPath, http.StripPrefix(fetchPath, http.HandlerFunc(fhttp.FetcherHandler)))
+	// h2 incoming and https outgoing ok fetcher
+	mux.HandleFunc(uiPath+fetch2URI, fhttp.FetcherHandler2)
 	fhttp.CheckConnectionClosedHeader = true // needed for proxy to avoid errors
 
 	logoPath = version.Short() + "/static/img/logo.svg"


Test patch:
diff --git a/Dockerfile.test b/Dockerfile.test
index ef8d3e6..94620ac 100644
--- a/Dockerfile.test
+++ b/Dockerfile.test
@@ -1,5 +1,5 @@
 # Getting the source tree ready for running tests (sut)
-FROM docker.io/fortio/fortio.build:v30
+FROM docker.io/fortio/fortio.build:v31
 WORKDIR /go/src/fortio.org
 COPY . fortio
 RUN make -C fortio dependencies
diff --git a/Webtest.sh b/Webtest.sh
index 455e0b0..3e15e8b 100755
--- a/Webtest.sh
+++ b/Webtest.sh
@@ -112,7 +112,7 @@ docker exec $DOCKERNAME $FORTIO_BIN_PATH grpcping localhost
 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.
-docker create -v $TEST_CERT_VOL --name $DOCKERSECVOLNAME docker.io/fortio/fortio.build:v30 /bin/true # cleaned up by name
+docker create -v $TEST_CERT_VOL --name $DOCKERSECVOLNAME docker.io/fortio/fortio.build:v31 /bin/true # cleaned up by name
 # copying cert files into the certs volume of the dummy container
 for f in ca.crt server.crt server.key; do docker cp $PWD/cert-tmp/$f $DOCKERSECVOLNAME:$TEST_CERT_VOL/$f; done
 # start server in secure grpc mode. uses non-default ports to avoid conflicts with fortio_server container.
diff --git a/dflag/nildptr_test.go b/dflag/nildptr_test.go
index fd08d20..adaf169 100644
--- a/dflag/nildptr_test.go
+++ b/dflag/nildptr_test.go
@@ -13,8 +13,7 @@ import (
 	"time"
 )
 
-type foo struct {
-}
+type foo struct{}
 
 func TestDynFlagPrintDefaultsNotCrashing(t *testing.T) {
 	fs := flag.NewFlagSet("test", flag.PanicOnError)
diff --git a/fhttp/http_test.go b/fhttp/http_test.go
index 6f1aa9d..0047876 100644
--- a/fhttp/http_test.go
+++ b/fhttp/http_test.go
@@ -1131,6 +1131,57 @@ func TestFetchAndOnBehalfOf(t *testing.T) {
 	}
 }
 
+func TestFetch2(t *testing.T) {
+	mux, addr := ServeTCP("0", "/debug")
+	mux.HandleFunc("/fetch2/", FetcherHandler2)
+	url := fmt.Sprintf("localhost:%d/fetch2/?url=localhost:%d/debug", addr.Port, addr.Port)
+	code, data := Fetch(&HTTPOptions{URL: url})
+	if code != http.StatusOK {
+		t.Errorf("Got %d %s instead of ok for %s", code, DebugSummary(data, 256), url)
+	}
+	if !bytes.Contains(data, []byte("X-On-Behalf-Of: ")) {
+		t.Errorf("Result %s doesn't contain expected On-Behalf-Of:", DebugSummary(data, 1024))
+	}
+}
+
+func TestFetch2Header(t *testing.T) {
+	mux, addr := ServeTCP("0", "")
+	mux.HandleFunc("/fetch2/", FetcherHandler2)
+	url := fmt.Sprintf("localhost:%d/fetch2/?url=http://localhost:%d/echo%%3fheader%%3dFoo:Bar", addr.Port, addr.Port)
+	code, data := Fetch(&HTTPOptions{URL: url})
+	if code != http.StatusOK {
+		t.Errorf("Got %d %s instead of ok for %s", code, DebugSummary(data, 256), url)
+	}
+	if !bytes.Contains(data, []byte("Foo: Bar")) {
+		t.Errorf("Result %s doesn't contain expected Foo: Bar header", DebugSummary(data, 1024))
+	}
+}
+
+func TestFetch2Errors(t *testing.T) {
+	mux, addr := ServeTCP("0", "")
+	mux.HandleFunc("/fetch2/", FetcherHandler2)
+	url := fmt.Sprintf("localhost:%d/fetch2/", addr.Port)
+	code, data := Fetch(&HTTPOptions{URL: url})
+	if code != http.StatusBadRequest {
+		t.Errorf("Got %d %s instead of bad request for missing url for %s", code, DebugSummary(data, 256), url)
+	}
+	url = fmt.Sprintf("localhost:%d/fetch2/?url=%%20", addr.Port)
+	code, data = Fetch(&HTTPOptions{URL: url})
+	if code != http.StatusBadRequest {
+		t.Errorf("Got %d %s instead of bad request for empty url for %s", code, DebugSummary(data, 256), url)
+	}
+	url = fmt.Sprintf("localhost:%d/fetch2/?url=%%00", addr.Port)
+	code, data = Fetch(&HTTPOptions{URL: url})
+	if code != http.StatusBadRequest {
+		t.Errorf("Got %d %s instead of bad request for illegal char in url for %s", code, DebugSummary(data, 256), url)
+	}
+	url = fmt.Sprintf("localhost:%d/fetch2/?url=doesnotexist.fortio.org", addr.Port)
+	code, data = Fetch(&HTTPOptions{URL: url})
+	if code != http.StatusBadRequest {
+		t.Errorf("Got %d %s instead of bad request for no such host url for %s", code, DebugSummary(data, 256), url)
+	}
+}
+
 func TestServeError(t *testing.T) {
 	_, addr := Serve("0", "")
 	port := fnet.GetPort(addr)
diff --git a/fnet/network_test.go b/fnet/network_test.go
index d3b7442..a382c7e 100644
--- a/fnet/network_test.go
+++ b/fnet/network_test.go
@@ -170,8 +170,7 @@ func TestTcpEcho(t *testing.T) {
 	}
 }
 
-type ErroringWriter struct {
-}
+type ErroringWriter struct{}
 
 func (cbb *ErroringWriter) Close() error {
 	return nil