vendor: use github.com/VictoriaMetrics/fasthttp instead of github.com/fasthttp/fasthttp

The upstream fasthttp may contain issues like 996610f021 ,
plus a code that isn't used by VictoriaMetrics. So let's use a private copy under our control instead.
This commit is contained in:
Aliaksandr Valialkin 2020-04-29 16:20:23 +03:00
parent cc1878607a
commit 43c39dc36c
59 changed files with 738 additions and 2483 deletions

View file

@ -14,8 +14,8 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/logger"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/persistentqueue"
"github.com/VictoriaMetrics/fasthttp"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fasthttp"
)
var (

View file

@ -4,8 +4,8 @@ import (
"net"
"sync/atomic"
"github.com/VictoriaMetrics/fasthttp"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fasthttp"
)
func statDial(addr string) (net.Conn, error) {

8
go.mod
View file

@ -4,6 +4,10 @@ require (
cloud.google.com/go v0.56.0 // indirect
cloud.google.com/go/storage v1.6.0
github.com/VictoriaMetrics/fastcache v1.5.7
// Do not use the original github.com/valyala/fasthttp because of issues
// like https://github.com/valyala/fasthttp/commit/996610f021ff45fdc98c2ce7884d5fa4e7f9199b
github.com/VictoriaMetrics/fasthttp v1.0.1
github.com/VictoriaMetrics/metrics v1.11.2
github.com/VictoriaMetrics/metricsql v0.1.0
github.com/aws/aws-sdk-go v1.30.13
@ -11,10 +15,6 @@ require (
github.com/golang/protobuf v1.4.0 // indirect
github.com/golang/snappy v0.0.1
github.com/klauspost/compress v1.10.5
// do not update fasthttp releases because of issues like https://github.com/valyala/fasthttp/commit/996610f021ff45fdc98c2ce7884d5fa4e7f9199b
// in the new code.
github.com/valyala/fasthttp v1.2.0
github.com/valyala/fastjson v1.5.1
github.com/valyala/fastrand v1.0.0
github.com/valyala/gozstd v1.7.0

37
go.sum
View file

@ -7,26 +7,21 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0 h1:MZQCQQaRwOrAcuKjiHWHrgKykt4fZyuwF2dtiG3fGW8=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.56.0 h1:WRz29PgAsVEyPSDHyk+0fpEkwEFyfhHn+JbksT6gIL4=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0 h1:sAbMqjY1PEQKZBWfbu6Y6bsupJ9c4QdHnzg/VvYTLcE=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0 h1:xE3CPsOgttP4ACBePh79zTKALtXwn/Edhcr16R5hMWU=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0 h1:9/vpR43S4aJaROxqQHQ3nH9lfyKKV0dC3vOmnw8ebQQ=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0 h1:Lpy6hKgdcl7a3WGSfJIFmxmcdjSpP6OmBEfcOv1Y680=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0 h1:RPUcBvDeYgQFMfQu1eBMq6piD1SXmLH+vK3qjewZPus=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0 h1:UDpwYIwla4jHGzZJaEJYx1tOejbgSoNqsAfHAUYe2r8=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
@ -36,6 +31,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8=
github.com/VictoriaMetrics/fasthttp v1.0.1 h1:I7YdbswTIW63WxoFoUOSNxeOEGB46rdKULQhn+jt+AY=
github.com/VictoriaMetrics/fasthttp v1.0.1/go.mod h1:BqgsieH90PR7x97c89j+eqZDloKkDhAEQTwhLw6jw/4=
github.com/VictoriaMetrics/metrics v1.11.2 h1:t/ceLP6SvagUqypCKU7cI7+tQn54+TIV/tGoxihHvx8=
github.com/VictoriaMetrics/metrics v1.11.2/go.mod h1:LU2j9qq7xqZYXz8tF3/RQnB2z2MbZms5TDiIg9/NHiQ=
github.com/VictoriaMetrics/metricsql v0.1.0 h1:IoyG84PCwFY15rNuxpr2nQ+YZBYIhnd7zTiaGo5BNpc=
@ -63,10 +60,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -77,11 +72,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
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=
@ -95,7 +87,6 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
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 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
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=
@ -137,13 +128,11 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.2.0 h1:dzZJf2IuMiclVjdw0kkT+f9u4YdrapbNyGAN47E/qnk=
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
github.com/valyala/fastjson v1.5.1 h1:SXaQZVSwLjZOVhDEhjiCcDtnX0Feu7Z7A1+C5atpoHM=
github.com/valyala/fastjson v1.5.1/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
@ -160,7 +149,6 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -176,9 +164,7 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd h1:zkO/Lhoka23X63N9OSzpSeROEUQ5ODw47tM3YWjygbs=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -189,9 +175,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@ -199,7 +183,6 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
@ -218,12 +201,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI=
golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@ -238,7 +218,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -257,9 +236,7 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -315,12 +292,9 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0 h1:0q95w+VuFtv4PAx4PZVQdBMmYbaCHbnfKaEiDIcVyag=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0 h1:jz2KixHX7EcCPiQrySzPdnYT7DbINAypCqKZ1Z7GM40=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0 h1:J1Pl9P2lnmYFSJvgs70DKELqHNh8CNWXPbud4njEE2s=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
@ -328,7 +302,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
@ -347,7 +320,6 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce h1:1mbrb1tUU+Zmt5C94IGKADBTJZjZXAd+BubWi7r9EiI=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
@ -358,12 +330,9 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
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.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
@ -377,7 +346,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -385,7 +353,6 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=

View file

@ -8,8 +8,8 @@ import (
"time"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
"github.com/VictoriaMetrics/fasthttp"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fasthttp"
)
var (
@ -46,17 +46,17 @@ func newClient(sw *ScrapeWork) *client {
}
}
hc := &fasthttp.HostClient{
Addr: host,
Name: "vm_promscrape",
Dial: statDial,
DialDualStack: netutil.TCP6Enabled(),
IsTLS: isTLS,
TLSConfig: tlsCfg,
MaxIdleConnDuration: 2 * sw.ScrapeInterval,
ReadTimeout: sw.ScrapeTimeout,
WriteTimeout: 10 * time.Second,
MaxResponseBodySize: *maxScrapeSize,
MaxIdemponentCallAttempts: 1,
Addr: host,
Name: "vm_promscrape",
Dial: statDial,
DialDualStack: netutil.TCP6Enabled(),
IsTLS: isTLS,
TLSConfig: tlsCfg,
MaxIdleConnDuration: 2 * sw.ScrapeInterval,
ReadTimeout: sw.ScrapeTimeout,
WriteTimeout: 10 * time.Second,
MaxResponseBodySize: *maxScrapeSize,
MaxIdempotentRequestAttempts: 1,
}
return &client{
hc: hc,

View file

@ -11,7 +11,7 @@ import (
"github.com/VictoriaMetrics/VictoriaMetrics/lib/netutil"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
"github.com/valyala/fasthttp"
"github.com/VictoriaMetrics/fasthttp"
)
// apiConfig contains config for API server

View file

@ -4,8 +4,8 @@ import (
"net"
"sync/atomic"
"github.com/VictoriaMetrics/fasthttp"
"github.com/VictoriaMetrics/metrics"
"github.com/valyala/fasthttp"
)
func statDial(addr string) (net.Conn, error) {

16
vendor/github.com/VictoriaMetrics/fasthttp/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,16 @@
language: go
go:
- 1.9.x
- 1.8.x
script:
# build test for supported platforms
- GOOS=linux go build
- GOOS=darwin go build
- GOOS=freebsd go build
- GOOS=windows go build
- GOARCH=386 go build
# run tests on a standard platform
- go test -v ./...

View file

@ -1,9 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015-present Aliaksandr Valialkin, VertaMedia
Copyright (c) 2018-present Kirill Danshin
Copyright (c) 2018-present Erik Dubbelboer
Copyright (c) 2018-present FastHTTP Authors
Copyright (c) 2015-2016 Aliaksandr Valialkin, VertaMedia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

5
vendor/github.com/VictoriaMetrics/fasthttp/README.md generated vendored Normal file
View file

@ -0,0 +1,5 @@
Private copy of [fasthttp](https://github.com/valyala/fasthttp) for VictoriaMetrics usage.
It contains only the functionality required for [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics).
Do not use it in your own projects!

View file

@ -4,15 +4,7 @@ import (
"bytes"
"errors"
"io"
"sort"
"sync"
"github.com/valyala/bytebufferpool"
)
const (
argsNoValue = true
argsHasValue = false
)
// AcquireArgs returns an empty Args object from the pool.
@ -23,7 +15,7 @@ func AcquireArgs() *Args {
return argsPool.Get().(*Args)
}
// ReleaseArgs returns the object acquired via AcquireArgs to the pool.
// ReleaseArgs returns the object acquired via AquireArgs to the pool.
//
// Do not access the released Args object, otherwise data races may occur.
func ReleaseArgs(a *Args) {
@ -51,9 +43,8 @@ type Args struct {
}
type argsKV struct {
key []byte
value []byte
noValue bool
key []byte
value []byte
}
// Reset clears query args.
@ -116,29 +107,14 @@ func (a *Args) QueryString() []byte {
return a.buf
}
// Sort sorts Args by key and then value using 'f' as comparison function.
//
// For example args.Sort(bytes.Compare)
func (a *Args) Sort(f func(x, y []byte) int) {
sort.SliceStable(a.args, func(i, j int) bool {
n := f(a.args[i].key, a.args[j].key)
if n == 0 {
return f(a.args[i].value, a.args[j].value) == -1
}
return n == -1
})
}
// AppendBytes appends query string to dst and returns the extended dst.
func (a *Args) AppendBytes(dst []byte) []byte {
for i, n := 0, len(a.args); i < n; i++ {
kv := &a.args[i]
dst = AppendQuotedArg(dst, kv.key)
if !kv.noValue {
if len(kv.value) > 0 {
dst = append(dst, '=')
if len(kv.value) > 0 {
dst = AppendQuotedArg(dst, kv.value)
}
dst = AppendQuotedArg(dst, kv.value)
}
if i+1 < n {
dst = append(dst, '&')
@ -169,74 +145,48 @@ func (a *Args) DelBytes(key []byte) {
//
// Multiple values for the same key may be added.
func (a *Args) Add(key, value string) {
a.args = appendArg(a.args, key, value, argsHasValue)
a.args = appendArg(a.args, key, value)
}
// AddBytesK adds 'key=value' argument.
//
// Multiple values for the same key may be added.
func (a *Args) AddBytesK(key []byte, value string) {
a.args = appendArg(a.args, b2s(key), value, argsHasValue)
a.args = appendArg(a.args, b2s(key), value)
}
// AddBytesV adds 'key=value' argument.
//
// Multiple values for the same key may be added.
func (a *Args) AddBytesV(key string, value []byte) {
a.args = appendArg(a.args, key, b2s(value), argsHasValue)
a.args = appendArg(a.args, key, b2s(value))
}
// AddBytesKV adds 'key=value' argument.
//
// Multiple values for the same key may be added.
func (a *Args) AddBytesKV(key, value []byte) {
a.args = appendArg(a.args, b2s(key), b2s(value), argsHasValue)
}
// AddNoValue adds only 'key' as argument without the '='.
//
// Multiple values for the same key may be added.
func (a *Args) AddNoValue(key string) {
a.args = appendArg(a.args, key, "", argsNoValue)
}
// AddBytesKNoValue adds only 'key' as argument without the '='.
//
// Multiple values for the same key may be added.
func (a *Args) AddBytesKNoValue(key []byte) {
a.args = appendArg(a.args, b2s(key), "", argsNoValue)
a.args = appendArg(a.args, b2s(key), b2s(value))
}
// Set sets 'key=value' argument.
func (a *Args) Set(key, value string) {
a.args = setArg(a.args, key, value, argsHasValue)
a.args = setArg(a.args, key, value)
}
// SetBytesK sets 'key=value' argument.
func (a *Args) SetBytesK(key []byte, value string) {
a.args = setArg(a.args, b2s(key), value, argsHasValue)
a.args = setArg(a.args, b2s(key), value)
}
// SetBytesV sets 'key=value' argument.
func (a *Args) SetBytesV(key string, value []byte) {
a.args = setArg(a.args, key, b2s(value), argsHasValue)
a.args = setArg(a.args, key, b2s(value))
}
// SetBytesKV sets 'key=value' argument.
func (a *Args) SetBytesKV(key, value []byte) {
a.args = setArgBytes(a.args, key, value, argsHasValue)
}
// SetNoValue sets only 'key' as argument without the '='.
//
// Only key in argumemt, like key1&key2
func (a *Args) SetNoValue(key string) {
a.args = setArg(a.args, key, "", argsNoValue)
}
// SetBytesKNoValue sets 'key' argument.
func (a *Args) SetBytesKNoValue(key []byte) {
a.args = setArg(a.args, b2s(key), "", argsNoValue)
a.args = setArgBytes(a.args, key, value)
}
// Peek returns query arg value for the given key.
@ -293,10 +243,10 @@ func (a *Args) GetUint(key string) (int, error) {
// SetUint sets uint value for the given key.
func (a *Args) SetUint(key string, value int) {
bb := bytebufferpool.Get()
bb := AcquireByteBuffer()
bb.B = AppendUint(bb.B[:0], value)
a.SetBytesV(key, bb.B)
bytebufferpool.Put(bb)
ReleaseByteBuffer(bb)
}
// SetUintBytes sets uint value for the given key.
@ -337,14 +287,11 @@ func (a *Args) GetUfloatOrZero(key string) float64 {
// GetBool returns boolean value for the given key.
//
// true is returned for "1", "t", "T", "true", "TRUE", "True", "y", "yes", "Y", "YES", "Yes",
// true is returned for '1', 'y' and 'yes' values,
// otherwise false is returned.
func (a *Args) GetBool(key string) bool {
switch b2s(a.Peek(key)) {
// Support the same true cases as strconv.ParseBool
// See: https://github.com/golang/go/blob/4e1b11e2c9bdb0ddea1141eed487be1a626ff5be/src/strconv/atob.go#L12
// and Y and Yes versions.
case "1", "t", "T", "true", "TRUE", "True", "y", "yes", "Y", "YES", "Yes":
switch string(a.Peek(key)) {
case "1", "y", "yes":
return true
default:
return false
@ -370,12 +317,7 @@ func copyArgs(dst, src []argsKV) []argsKV {
dstKV := &dst[i]
srcKV := &src[i]
dstKV.key = append(dstKV.key[:0], srcKV.key...)
if srcKV.noValue {
dstKV.value = dstKV.value[:0]
} else {
dstKV.value = append(dstKV.value[:0], srcKV.value...)
}
dstKV.noValue = srcKV.noValue
dstKV.value = append(dstKV.value[:0], srcKV.value...)
}
return dst
}
@ -398,41 +340,31 @@ func delAllArgs(args []argsKV, key string) []argsKV {
return args
}
func setArgBytes(h []argsKV, key, value []byte, noValue bool) []argsKV {
return setArg(h, b2s(key), b2s(value), noValue)
func setArgBytes(h []argsKV, key, value []byte) []argsKV {
return setArg(h, b2s(key), b2s(value))
}
func setArg(h []argsKV, key, value string, noValue bool) []argsKV {
func setArg(h []argsKV, key, value string) []argsKV {
n := len(h)
for i := 0; i < n; i++ {
kv := &h[i]
if key == string(kv.key) {
if noValue {
kv.value = kv.value[:0]
} else {
kv.value = append(kv.value[:0], value...)
}
kv.noValue = noValue
kv.value = append(kv.value[:0], value...)
return h
}
}
return appendArg(h, key, value, noValue)
return appendArg(h, key, value)
}
func appendArgBytes(h []argsKV, key, value []byte, noValue bool) []argsKV {
return appendArg(h, b2s(key), b2s(value), noValue)
func appendArgBytes(h []argsKV, key, value []byte) []argsKV {
return appendArg(h, b2s(key), b2s(value))
}
func appendArg(args []argsKV, key, value string, noValue bool) []argsKV {
func appendArg(args []argsKV, key, value string) []argsKV {
var kv *argsKV
args, kv = allocArg(args)
kv.key = append(kv.key[:0], key...)
if noValue {
kv.value = kv.value[:0]
} else {
kv.value = append(kv.value[:0], value...)
}
kv.noValue = noValue
kv.value = append(kv.value[:0], value...)
return args
}
@ -488,7 +420,6 @@ func (s *argsScanner) next(kv *argsKV) bool {
if len(s.b) == 0 {
return false
}
kv.noValue = argsHasValue
isKey := true
k := 0
@ -504,7 +435,6 @@ func (s *argsScanner) next(kv *argsKV) bool {
if isKey {
kv.key = decodeArgAppend(kv.key[:0], s.b[:i])
kv.value = kv.value[:0]
kv.noValue = argsNoValue
} else {
kv.value = decodeArgAppend(kv.value[:0], s.b[k:i])
}
@ -516,7 +446,6 @@ func (s *argsScanner) next(kv *argsKV) bool {
if isKey {
kv.key = decodeArgAppend(kv.key[:0], s.b)
kv.value = kv.value[:0]
kv.noValue = argsNoValue
} else {
kv.value = decodeArgAppend(kv.value[:0], s.b[k:])
}
@ -557,7 +486,7 @@ func decodeArgAppend(dst, src []byte) []byte {
// decodeArgAppendNoPlus is almost identical to decodeArgAppend, but it doesn't
// substitute '+' with ' '.
//
// The function is copy-pasted from decodeArgAppend due to the performance
// The function is copy-pasted from decodeArgAppend due to the preformance
// reasons only.
func decodeArgAppendNoPlus(dst, src []byte) []byte {
if bytes.IndexByte(src, '%') < 0 {

View file

@ -0,0 +1,64 @@
package fasthttp
import (
"github.com/valyala/bytebufferpool"
)
// ByteBuffer provides byte buffer, which can be used with fasthttp API
// in order to minimize memory allocations.
//
// ByteBuffer may be used with functions appending data to the given []byte
// slice. See example code for details.
//
// Use AcquireByteBuffer for obtaining an empty byte buffer.
//
// ByteBuffer is deprecated. Use github.com/valyala/bytebufferpool instead.
type ByteBuffer bytebufferpool.ByteBuffer
// Write implements io.Writer - it appends p to ByteBuffer.B
func (b *ByteBuffer) Write(p []byte) (int, error) {
return bb(b).Write(p)
}
// WriteString appends s to ByteBuffer.B
func (b *ByteBuffer) WriteString(s string) (int, error) {
return bb(b).WriteString(s)
}
// Set sets ByteBuffer.B to p
func (b *ByteBuffer) Set(p []byte) {
bb(b).Set(p)
}
// SetString sets ByteBuffer.B to s
func (b *ByteBuffer) SetString(s string) {
bb(b).SetString(s)
}
// Reset makes ByteBuffer.B empty.
func (b *ByteBuffer) Reset() {
bb(b).Reset()
}
// AcquireByteBuffer returns an empty byte buffer from the pool.
//
// Acquired byte buffer may be returned to the pool via ReleaseByteBuffer call.
// This reduces the number of memory allocations required for byte buffer
// management.
func AcquireByteBuffer() *ByteBuffer {
return (*ByteBuffer)(defaultByteBufferPool.Get())
}
// ReleaseByteBuffer returns byte buffer to the pool.
//
// ByteBuffer.B mustn't be touched after returning it to the pool.
// Otherwise data races occur.
func ReleaseByteBuffer(b *ByteBuffer) {
defaultByteBufferPool.Put(bb(b))
}
func bb(b *ByteBuffer) *bytebufferpool.ByteBuffer {
return (*bytebufferpool.ByteBuffer)(b)
}
var defaultByteBufferPool bytebufferpool.Pool

View file

@ -164,7 +164,7 @@ func ParseUint(buf []byte) (int, error) {
var (
errEmptyInt = errors.New("empty integer")
errUnexpectedFirstChar = errors.New("unexpected first char found. Expecting 0-9")
errUnexpectedTrailingChar = errors.New("unexpected trailing char found. Expecting 0-9")
errUnexpectedTrailingChar = errors.New("unexpected traling char found. Expecting 0-9")
errTooLongInt = errors.New("too long int")
)
@ -183,8 +183,7 @@ func parseUintBuf(b []byte) (int, int, error) {
}
return v, i, nil
}
// Test for overflow.
if v*10 < v {
if i >= maxIntChars {
return -1, i, errTooLongInt
}
v = 10*v + int(k)
@ -417,17 +416,8 @@ func AppendQuotedArg(dst, src []byte) []byte {
func appendQuotedPath(dst, src []byte) []byte {
for _, c := range src {
// From the spec: http://tools.ietf.org/html/rfc3986#section-3.3
// an path can contain zero or more of pchar that is defined as follows:
// pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
// pct-encoded = "%" HEXDIG HEXDIG
// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
// sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
// / "*" / "+" / "," / ";" / "="
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
c == '-' || c == '.' || c == '_' || c == '~' || c == '!' || c == '$' ||
c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' ||
c == ',' || c == ';' || c == '=' || c == ':' || c == '@' || c == '/' {
c == '/' || c == '.' || c == ',' || c == '=' || c == ':' || c == '&' || c == '~' || c == '-' || c == '_' {
dst = append(dst, c)
} else {
dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
@ -435,3 +425,23 @@ func appendQuotedPath(dst, src []byte) []byte {
}
return dst
}
// EqualBytesStr returns true if string(b) == s.
//
// This function has no performance benefits comparing to string(b) == s.
// It is left here for backwards compatibility only.
//
// This function is deperecated and may be deleted soon.
func EqualBytesStr(b []byte, s string) bool {
return string(b) == s
}
// AppendBytesStr appends src to dst and returns the extended dst.
//
// This function has no performance benefits comparing to append(dst, src...).
// It is left here for backwards compatibility only.
//
// This function is deprecated and may be deleted soon.
func AppendBytesStr(dst []byte, src string) []byte {
return append(dst, src...)
}

View file

@ -3,5 +3,6 @@
package fasthttp
const (
maxIntChars = 9
maxHexIntChars = 7
)

View file

@ -3,5 +3,6 @@
package fasthttp
const (
maxIntChars = 18
maxHexIntChars = 15
)

View file

@ -60,11 +60,6 @@ func Do(req *Request, resp *Response) error {
//
// It is recommended obtaining req and resp via AcquireRequest
// and AcquireResponse in performance-critical code.
//
// Warning: DoTimeout does not terminate the request itself. The request will
// continue in the background and the response will be discarded.
// If requests take too long and the connection pool gets filled up please
// try using a Client and setting a ReadTimeout.
func DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
return defaultClient.DoTimeout(req, resp, timeout)
}
@ -96,36 +91,33 @@ func DoDeadline(req *Request, resp *Response, deadline time.Time) error {
return defaultClient.DoDeadline(req, resp, deadline)
}
// Get returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// Get appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
func Get(dst []byte, url string) (statusCode int, body []byte, err error) {
return defaultClient.Get(dst, url)
}
// GetTimeout returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// GetTimeout appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// ErrTimeout error is returned if url contents couldn't be fetched
// during the given timeout.
func GetTimeout(dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error) {
return defaultClient.GetTimeout(dst, url, timeout)
}
// GetDeadline returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// GetDeadline appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// ErrTimeout error is returned if url contents couldn't be fetched
// until the given deadline.
func GetDeadline(dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error) {
@ -134,11 +126,12 @@ func GetDeadline(dst []byte, url string, deadline time.Time) (statusCode int, bo
// Post sends POST request to the given url with the given POST arguments.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// Response body is appended to dst, which is returned as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// Empty POST body is sent if postArgs is nil.
func Post(dst []byte, url string, postArgs *Args) (statusCode int, body []byte, err error) {
return defaultClient.Post(dst, url, postArgs)
@ -159,10 +152,6 @@ type Client struct {
// Default client name is used if not set.
Name string
// NoDefaultUserAgentHeader when set to true, causes the default
// User-Agent header to be excluded from the Request.
NoDefaultUserAgentHeader bool
// Callback for establishing new connections to hosts.
//
// Default Dial is used if not set.
@ -193,11 +182,6 @@ type Client struct {
// after DefaultMaxIdleConnDuration.
MaxIdleConnDuration time.Duration
// Maximum number of attempts for idempotent calls
//
// DefaultMaxIdemponentCallAttempts is used if not set.
MaxIdemponentCallAttempts int
// Per-connection buffer size for responses' reading.
// This also limits the maximum header size.
//
@ -227,59 +211,41 @@ type Client struct {
// By default response body size is unlimited.
MaxResponseBodySize int
// Header names are passed as-is without normalization
// if this option is set.
//
// Disabled header names' normalization may be useful only for proxying
// responses to other clients expecting case-sensitive
// header names. See https://github.com/valyala/fasthttp/issues/57
// for details.
//
// By default request and response header names are normalized, i.e.
// The first letter and the first letters following dashes
// are uppercased, while all the other letters are lowercased.
// Examples:
//
// * HOST -> Host
// * content-type -> Content-Type
// * cONTENT-lenGTH -> Content-Length
DisableHeaderNamesNormalizing bool
// The maximum number of idempotent requests the client can make.
MaxIdempotentRequestAttempts int
mLock sync.Mutex
m map[string]*HostClient
ms map[string]*HostClient
}
// Get returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// Get appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
func (c *Client) Get(dst []byte, url string) (statusCode int, body []byte, err error) {
return clientGetURL(dst, url, c)
}
// GetTimeout returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// GetTimeout appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// ErrTimeout error is returned if url contents couldn't be fetched
// during the given timeout.
func (c *Client) GetTimeout(dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error) {
return clientGetURLTimeout(dst, url, timeout, c)
}
// GetDeadline returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// GetDeadline appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// ErrTimeout error is returned if url contents couldn't be fetched
// until the given deadline.
func (c *Client) GetDeadline(dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error) {
@ -288,11 +254,12 @@ func (c *Client) GetDeadline(dst []byte, url string, deadline time.Time) (status
// Post sends POST request to the given url with the given POST arguments.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// Response body is appended to dst, which is returned as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// Empty POST body is sent if postArgs is nil.
func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, body []byte, err error) {
return clientPostURL(dst, url, postArgs, c)
@ -321,11 +288,6 @@ func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, b
//
// It is recommended obtaining req and resp via AcquireRequest
// and AcquireResponse in performance-critical code.
//
// Warning: DoTimeout does not terminate the request itself. The request will
// continue in the background and the response will be discarded.
// If requests take too long and the connection pool gets filled up please
// try setting a ReadTimeout.
func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
return clientDoTimeout(req, resp, timeout, c)
}
@ -406,22 +368,20 @@ func (c *Client) Do(req *Request, resp *Response) error {
hc := m[string(host)]
if hc == nil {
hc = &HostClient{
Addr: addMissingPort(string(host), isTLS),
Name: c.Name,
NoDefaultUserAgentHeader: c.NoDefaultUserAgentHeader,
Dial: c.Dial,
DialDualStack: c.DialDualStack,
IsTLS: isTLS,
TLSConfig: c.TLSConfig,
MaxConns: c.MaxConnsPerHost,
MaxIdleConnDuration: c.MaxIdleConnDuration,
MaxIdemponentCallAttempts: c.MaxIdemponentCallAttempts,
ReadBufferSize: c.ReadBufferSize,
WriteBufferSize: c.WriteBufferSize,
ReadTimeout: c.ReadTimeout,
WriteTimeout: c.WriteTimeout,
MaxResponseBodySize: c.MaxResponseBodySize,
DisableHeaderNamesNormalizing: c.DisableHeaderNamesNormalizing,
Addr: addMissingPort(string(host), isTLS),
Name: c.Name,
Dial: c.Dial,
DialDualStack: c.DialDualStack,
IsTLS: isTLS,
TLSConfig: c.TLSConfig,
MaxConns: c.MaxConnsPerHost,
MaxIdleConnDuration: c.MaxIdleConnDuration,
ReadBufferSize: c.ReadBufferSize,
WriteBufferSize: c.WriteBufferSize,
ReadTimeout: c.ReadTimeout,
WriteTimeout: c.WriteTimeout,
MaxResponseBodySize: c.MaxResponseBodySize,
MaxIdempotentRequestAttempts: c.MaxIdempotentRequestAttempts,
}
m[string(host)] = hc
if len(m) == 1 {
@ -439,15 +399,11 @@ func (c *Client) Do(req *Request, resp *Response) error {
func (c *Client) mCleaner(m map[string]*HostClient) {
mustStop := false
for {
t := time.Now()
c.mLock.Lock()
for k, v := range m {
v.connsLock.Lock()
shouldRemove := v.connsCount == 0
v.connsLock.Unlock()
if shouldRemove {
if t.Sub(v.LastUseTime()) > time.Minute {
delete(m, k)
}
}
@ -472,9 +428,6 @@ const DefaultMaxConnsPerHost = 512
// connection is closed.
const DefaultMaxIdleConnDuration = 10 * time.Second
// DefaultMaxIdemponentCallAttempts is the default idempotent calls attempts count.
const DefaultMaxIdemponentCallAttempts = 5
// DialFunc must establish connection to addr.
//
// There is no need in establishing TLS (SSL) connection for https.
@ -516,10 +469,6 @@ type HostClient struct {
// Client name. Used in User-Agent request header.
Name string
// NoDefaultUserAgentHeader when set to true, causes the default
// User-Agent header to be excluded from the Request.
NoDefaultUserAgentHeader bool
// Callback for establishing new connection to the host.
//
// Default Dial is used if not set.
@ -544,9 +493,6 @@ type HostClient struct {
// Maximum number of connections which may be established to all hosts
// listed in Addr.
//
// You can change this value while the HostClient is being used
// using HostClient.SetMaxConns(value)
//
// DefaultMaxConnsPerHost is used if not set.
MaxConns int
@ -561,11 +507,6 @@ type HostClient struct {
// after DefaultMaxIdleConnDuration.
MaxIdleConnDuration time.Duration
// Maximum number of attempts for idempotent calls
//
// DefaultMaxIdemponentCallAttempts is used if not set.
MaxIdemponentCallAttempts int
// Per-connection buffer size for responses' reading.
// This also limits the maximum header size.
//
@ -595,23 +536,8 @@ type HostClient struct {
// By default response body size is unlimited.
MaxResponseBodySize int
// Header names are passed as-is without normalization
// if this option is set.
//
// Disabled header names' normalization may be useful only for proxying
// responses to other clients expecting case-sensitive
// header names. See https://github.com/valyala/fasthttp/issues/57
// for details.
//
// By default request and response header names are normalized, i.e.
// The first letter and the first letters following dashes
// are uppercased, while all the other letters are lowercased.
// Examples:
//
// * HOST -> Host
// * content-type -> Content-Type
// * cONTENT-lenGTH -> Content-Length
DisableHeaderNamesNormalizing bool
// The maximum number of idempotent requests the client can make.
MaxIdempotentRequestAttempts int
clientName atomic.Value
lastUseTime uint32
@ -630,7 +556,7 @@ type HostClient struct {
readerPool sync.Pool
writerPool sync.Pool
pendingRequests int32
pendingRequests uint64
connsCleanerRun bool
}
@ -640,6 +566,9 @@ type clientConn struct {
createdTime time.Time
lastUseTime time.Time
lastReadDeadlineTime time.Time
lastWriteDeadlineTime time.Time
}
var startTimeUnix = time.Now().Unix()
@ -650,36 +579,33 @@ func (c *HostClient) LastUseTime() time.Time {
return time.Unix(startTimeUnix+int64(n), 0)
}
// Get returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// Get appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
func (c *HostClient) Get(dst []byte, url string) (statusCode int, body []byte, err error) {
return clientGetURL(dst, url, c)
}
// GetTimeout returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// GetTimeout appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// ErrTimeout error is returned if url contents couldn't be fetched
// during the given timeout.
func (c *HostClient) GetTimeout(dst []byte, url string, timeout time.Duration) (statusCode int, body []byte, err error) {
return clientGetURLTimeout(dst, url, timeout, c)
}
// GetDeadline returns the status code and body of url.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// GetDeadline appends url contents to dst and returns it as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// ErrTimeout error is returned if url contents couldn't be fetched
// until the given deadline.
func (c *HostClient) GetDeadline(dst []byte, url string, deadline time.Time) (statusCode int, body []byte, err error) {
@ -688,11 +614,12 @@ func (c *HostClient) GetDeadline(dst []byte, url string, deadline time.Time) (st
// Post sends POST request to the given url with the given POST arguments.
//
// The contents of dst will be replaced by the body and returned, if the dst
// is too small a new slice will be allocated.
// Response body is appended to dst, which is returned as body.
//
// The function follows redirects. Use Do* for manually handling redirects.
//
// New body buffer is allocated if dst is nil.
//
// Empty POST body is sent if postArgs is nil.
func (c *HostClient) Post(dst []byte, url string, postArgs *Args) (statusCode int, body []byte, err error) {
return clientPostURL(dst, url, postArgs, c)
@ -753,7 +680,7 @@ func clientGetURLDeadline(dst []byte, url string, deadline time.Time, c clientDo
}
}()
tc := AcquireTimer(timeout)
tc := acquireTimer(timeout)
select {
case resp := <-ch:
ReleaseRequest(req)
@ -765,7 +692,7 @@ func clientGetURLDeadline(dst []byte, url string, deadline time.Time, c clientDo
body = dst
err = ErrTimeout
}
ReleaseTimer(tc)
releaseTimer(tc)
return statusCode, body, err
}
@ -799,24 +726,9 @@ func doRequestFollowRedirects(req *Request, dst []byte, url string, c clientDoer
resp.keepBodyBuffer = true
oldBody := bodyBuf.B
bodyBuf.B = dst
scheme := req.uri.Scheme()
req.schemaUpdate = false
redirectsCount := 0
for {
// In case redirect to different scheme
if redirectsCount > 0 && !bytes.Equal(scheme, req.uri.Scheme()) {
if strings.HasPrefix(url, string(strHTTPS)) {
req.isTLS = true
req.uri.SetSchemeBytes(strHTTPS)
} else {
req.isTLS = false
req.uri.SetSchemeBytes(strHTTP)
}
scheme = req.uri.Scheme()
req.schemaUpdate = true
}
req.parsedURI = false
req.Header.host = req.Header.host[:0]
req.SetRequestURI(url)
@ -825,11 +737,7 @@ func doRequestFollowRedirects(req *Request, dst []byte, url string, c clientDoer
break
}
statusCode = resp.Header.StatusCode()
if statusCode != StatusMovedPermanently &&
statusCode != StatusFound &&
statusCode != StatusSeeOther &&
statusCode != StatusTemporaryRedirect &&
statusCode != StatusPermanentRedirect {
if statusCode != StatusMovedPermanently && statusCode != StatusFound && statusCode != StatusSeeOther {
break
}
@ -930,11 +838,6 @@ func ReleaseResponse(resp *Response) {
//
// It is recommended obtaining req and resp via AcquireRequest
// and AcquireResponse in performance-critical code.
//
// Warning: DoTimeout does not terminate the request itself. The request will
// continue in the background and the response will be discarded.
// If requests take too long and the connection pool gets filled up please
// try setting a ReadTimeout.
func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
return clientDoTimeout(req, resp, timeout, c)
}
@ -985,9 +888,6 @@ func clientDoDeadline(req *Request, resp *Response, deadline time.Time, c client
req.copyToSkipBody(reqCopy)
swapRequestBody(req, reqCopy)
respCopy := AcquireResponse()
// Not calling resp.copyToSkipBody(respCopy) here to avoid
// unexpected messing with headers
respCopy.SkipBody = resp.SkipBody
// Note that the request continues execution on ErrTimeout until
// client-specific ReadTimeout exceeds. This helps limiting load
@ -996,20 +896,11 @@ func clientDoDeadline(req *Request, resp *Response, deadline time.Time, c client
// Without this 'hack' the load on slow host could exceed MaxConns*
// concurrent requests, since timed out requests on client side
// usually continue execution on the host.
var cleanup int32
go func() {
errDo := c.Do(reqCopy, respCopy)
if atomic.LoadInt32(&cleanup) == 1 {
ReleaseResponse(respCopy)
ReleaseRequest(reqCopy)
errorChPool.Put(chv)
} else {
ch <- errDo
}
ch <- c.Do(reqCopy, respCopy)
}()
tc := AcquireTimer(timeout)
tc := acquireTimer(timeout)
var err error
select {
case err = <-ch:
@ -1022,10 +913,9 @@ func clientDoDeadline(req *Request, resp *Response, deadline time.Time, c client
ReleaseRequest(reqCopy)
errorChPool.Put(chv)
case <-tc.C:
atomic.StoreInt32(&cleanup, 1)
err = ErrTimeout
}
ReleaseTimer(tc)
releaseTimer(tc)
return err
}
@ -1049,13 +939,13 @@ var errorChPool sync.Pool
func (c *HostClient) Do(req *Request, resp *Response) error {
var err error
var retry bool
maxAttempts := c.MaxIdemponentCallAttempts
maxAttempts := c.MaxIdempotentRequestAttempts
if maxAttempts <= 0 {
maxAttempts = DefaultMaxIdemponentCallAttempts
maxAttempts = 5
}
attempts := 0
atomic.AddInt32(&c.pendingRequests, 1)
atomic.AddUint64(&c.pendingRequests, 1)
for {
retry, err = c.do(req, resp)
if err == nil || !retry {
@ -1079,7 +969,7 @@ func (c *HostClient) Do(req *Request, resp *Response) error {
break
}
}
atomic.AddInt32(&c.pendingRequests, -1)
atomic.AddUint64(&c.pendingRequests, ^uint64(0))
if err == io.EOF {
err = ErrConnectionClosed
@ -1093,7 +983,7 @@ func (c *HostClient) Do(req *Request, resp *Response) error {
// This function may be used for balancing load among multiple HostClient
// instances.
func (c *HostClient) PendingRequests() int {
return int(atomic.LoadInt32(&c.pendingRequests))
return int(atomic.LoadUint64(&c.pendingRequests))
}
func isIdempotent(req *Request) bool {
@ -1124,37 +1014,29 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error)
panic("BUG: resp cannot be nil")
}
atomic.StoreUint32(&c.lastUseTime, uint32(time.Now().Unix()-startTimeUnix))
atomic.StoreUint32(&c.lastUseTime, uint32(CoarseTimeNow().Unix()-startTimeUnix))
// Free up resources occupied by response before sending the request,
// so the GC may reclaim these resources (e.g. response body).
resp.Reset()
// If we detected a redirect to another schema
if req.schemaUpdate {
c.IsTLS = bytes.Equal(req.URI().Scheme(), strHTTPS)
c.Addr = addMissingPort(string(req.Host()), c.IsTLS)
c.addrIdx = 0
c.addrs = nil
req.schemaUpdate = false
req.SetConnectionClose()
}
cc, err := c.acquireConn()
if err != nil {
return false, err
}
conn := cc.c
resp.parseNetConn(conn)
if c.WriteTimeout > 0 {
// Set Deadline every time, since golang has fixed the performance issue
// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
currentTime := time.Now()
if err = conn.SetWriteDeadline(currentTime.Add(c.WriteTimeout)); err != nil {
c.closeConn(cc)
return true, err
// Optimization: update write deadline only if more than 25%
// of the last write deadline exceeded.
// See https://github.com/golang/go/issues/15133 for details.
currentTime := CoarseTimeNow()
if currentTime.Sub(cc.lastWriteDeadlineTime) > (c.WriteTimeout >> 2) {
if err = conn.SetWriteDeadline(currentTime.Add(c.WriteTimeout)); err != nil {
c.closeConn(cc)
return true, err
}
cc.lastWriteDeadlineTime = currentTime
}
}
@ -1170,6 +1052,9 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error)
}
bw := c.acquireWriter(conn)
err = req.Write(bw)
if len(userAgentOld) == 0 {
req.Header.userAgent = userAgentOld
}
if resetConnection {
req.Header.ResetConnectionClose()
@ -1186,29 +1071,28 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error)
c.releaseWriter(bw)
if c.ReadTimeout > 0 {
// Set Deadline every time, since golang has fixed the performance issue
// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
currentTime := time.Now()
if err = conn.SetReadDeadline(currentTime.Add(c.ReadTimeout)); err != nil {
c.closeConn(cc)
return true, err
// Optimization: update read deadline only if more than 25%
// of the last read deadline exceeded.
// See https://github.com/golang/go/issues/15133 for details.
currentTime := CoarseTimeNow()
if currentTime.Sub(cc.lastReadDeadlineTime) > (c.ReadTimeout >> 2) {
if err = conn.SetReadDeadline(currentTime.Add(c.ReadTimeout)); err != nil {
c.closeConn(cc)
return true, err
}
cc.lastReadDeadlineTime = currentTime
}
}
if !req.Header.IsGet() && req.Header.IsHead() {
resp.SkipBody = true
}
if c.DisableHeaderNamesNormalizing {
resp.Header.DisableNormalizing()
}
br := c.acquireReader(conn)
if err = resp.ReadLimitBody(br, c.MaxResponseBodySize); err != nil {
c.releaseReader(br)
c.closeConn(cc)
// Don't retry in case of ErrBodyTooLarge since we will just get the same again.
retry := err != ErrBodyTooLarge
return retry, err
return true, err
}
c.releaseReader(br)
@ -1243,12 +1127,6 @@ var (
"Make sure the server returns 'Connection: close' response header before closing the connection")
)
func (c *HostClient) SetMaxConns(newMaxConns int) {
c.connsLock.Lock()
c.MaxConns = newMaxConns
c.connsLock.Unlock()
}
func (c *HostClient) acquireConn() (*clientConn, error) {
var cc *clientConn
createConn := false
@ -1318,12 +1196,6 @@ func (c *HostClient) connsCleaner() {
for i < n && currentTime.Sub(conns[i].lastUseTime) > maxIdleConnDuration {
i++
}
sleepFor := maxIdleConnDuration
if i < n {
// + 1 so we actually sleep past the expiration time and not up to it.
// Otherwise the > check above would still fail.
sleepFor = maxIdleConnDuration - currentTime.Sub(conns[i].lastUseTime) + 1
}
scratch = append(scratch[:0], conns[:i]...)
if i > 0 {
m := copy(conns, conns[i:])
@ -1351,7 +1223,7 @@ func (c *HostClient) connsCleaner() {
break
}
time.Sleep(sleepFor)
time.Sleep(maxIdleConnDuration)
}
}
@ -1374,20 +1246,19 @@ func acquireClientConn(conn net.Conn) *clientConn {
}
cc := v.(*clientConn)
cc.c = conn
cc.createdTime = time.Now()
cc.createdTime = CoarseTimeNow()
return cc
}
func releaseClientConn(cc *clientConn) {
// Reset all fields.
*cc = clientConn{}
cc.c = nil
clientConnPool.Put(cc)
}
var clientConnPool sync.Pool
func (c *HostClient) releaseConn(cc *clientConn) {
cc.lastUseTime = time.Now()
cc.lastUseTime = CoarseTimeNow()
c.connsLock.Lock()
c.conns = append(c.conns, cc)
c.connsLock.Unlock()
@ -1581,7 +1452,7 @@ func (c *HostClient) getClientName() []byte {
var clientName []byte
if v == nil {
clientName = []byte(c.Name)
if len(clientName) == 0 && !c.NoDefaultUserAgentHeader {
if len(clientName) == 0 {
clientName = defaultUserAgent
}
c.clientName.Store(clientName)
@ -1623,7 +1494,7 @@ type PipelineClient struct {
// The maximum number of concurrent connections to the Addr.
//
// A single connection is used by default.
// A sinle connection is used by default.
MaxConns int
// The maximum number of pending pipelined requests over
@ -1748,11 +1619,6 @@ type pipelineWork struct {
//
// It is recommended obtaining req and resp via AcquireRequest
// and AcquireResponse in performance-critical code.
//
// Warning: DoTimeout does not terminate the request itself. The request will
// continue in the background and the response will be discarded.
// If requests take too long and the connection pool gets filled up please
// try setting a ReadTimeout.
func (c *PipelineClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
return c.DoDeadline(req, resp, time.Now().Add(timeout))
}
@ -2058,6 +1924,8 @@ func (c *pipelineConnClient) writer(conn net.Conn, stopCh <-chan struct{}) error
w *pipelineWork
err error
lastWriteDeadlineTime time.Time
)
close(instantTimerCh)
for {
@ -2089,16 +1957,18 @@ func (c *pipelineConnClient) writer(conn net.Conn, stopCh <-chan struct{}) error
continue
}
w.resp.parseNetConn(conn)
if writeTimeout > 0 {
// Set Deadline every time, since golang has fixed the performance issue
// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
currentTime := time.Now()
if err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil {
w.err = err
w.done <- struct{}{}
return err
// Optimization: update write deadline only if more than 25%
// of the last write deadline exceeded.
// See https://github.com/golang/go/issues/15133 for details.
currentTime := CoarseTimeNow()
if currentTime.Sub(lastWriteDeadlineTime) > (writeTimeout >> 2) {
if err = conn.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil {
w.err = err
w.done <- struct{}{}
return err
}
lastWriteDeadlineTime = currentTime
}
}
if err = w.req.Write(bw); err != nil {
@ -2152,6 +2022,8 @@ func (c *pipelineConnClient) reader(conn net.Conn, stopCh <-chan struct{}) error
var (
w *pipelineWork
err error
lastReadDeadlineTime time.Time
)
for {
select {
@ -2167,13 +2039,17 @@ func (c *pipelineConnClient) reader(conn net.Conn, stopCh <-chan struct{}) error
}
if readTimeout > 0 {
// Set Deadline every time, since golang has fixed the performance issue
// See https://github.com/golang/go/issues/15133#issuecomment-271571395 for details
currentTime := time.Now()
if err = conn.SetReadDeadline(currentTime.Add(readTimeout)); err != nil {
w.err = err
w.done <- struct{}{}
return err
// Optimization: update read deadline only if more than 25%
// of the last read deadline exceeded.
// See https://github.com/golang/go/issues/15133 for details.
currentTime := CoarseTimeNow()
if currentTime.Sub(lastReadDeadlineTime) > (readTimeout >> 2) {
if err = conn.SetReadDeadline(currentTime.Add(readTimeout)); err != nil {
w.err = err
w.done <- struct{}{}
return err
}
lastReadDeadlineTime = currentTime
}
}
if err = w.resp.Read(br); err != nil {

View file

@ -0,0 +1,28 @@
package fasthttp
import (
"sync/atomic"
"time"
)
// CoarseTimeNow returns the current time truncated to the nearest second.
//
// This is a faster alternative to time.Now().
func CoarseTimeNow() time.Time {
tp := coarseTime.Load().(*time.Time)
return *tp
}
func init() {
t := time.Now().Truncate(time.Second)
coarseTime.Store(&t)
go func() {
for {
time.Sleep(time.Second)
t := time.Now().Truncate(time.Second)
coarseTime.Store(&t)
}
}()
}
var coarseTime atomic.Value

View file

@ -7,11 +7,11 @@ import (
"os"
"sync"
"github.com/VictoriaMetrics/fasthttp/stackless"
"github.com/klauspost/compress/flate"
"github.com/klauspost/compress/gzip"
"github.com/klauspost/compress/zlib"
"github.com/valyala/bytebufferpool"
"github.com/valyala/fasthttp/stackless"
)
// Supported compression levels.
@ -152,6 +152,7 @@ func WriteGzipLevel(w io.Writer, p []byte, level int) (int, error) {
switch w.(type) {
case *byteSliceWriter,
*bytes.Buffer,
*ByteBuffer,
*bytebufferpool.ByteBuffer:
// These writers don't block, so we can just use stacklessWriteGzip
ctx := &compressCtx{
@ -248,6 +249,7 @@ func WriteDeflateLevel(w io.Writer, p []byte, level int) (int, error) {
switch w.(type) {
case *byteSliceWriter,
*bytes.Buffer,
*ByteBuffer,
*bytebufferpool.ByteBuffer:
// These writers don't block, so we can just use stacklessWriteDeflate
ctx := &compressCtx{
@ -407,7 +409,7 @@ func isFileCompressible(f *os.File, minCompressRatio float64) bool {
// Try compressing the first 4kb of of the file
// and see if it can be compressed by more than
// the given minCompressRatio.
b := bytebufferpool.Get()
b := AcquireByteBuffer()
zw := acquireStacklessGzipWriter(b, CompressDefaultCompression)
lr := &io.LimitedReader{
R: f,
@ -422,7 +424,7 @@ func isFileCompressible(f *os.File, minCompressRatio float64) bool {
n := 4096 - lr.N
zn := len(b.B)
bytebufferpool.Put(b)
ReleaseByteBuffer(b)
return float64(zn) < float64(n)*minCompressRatio
}

View file

@ -18,21 +18,6 @@ var (
CookieExpireUnlimited = zeroTime
)
// CookieSameSite is an enum for the mode in which the SameSite flag should be set for the given cookie.
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type CookieSameSite int
const (
// CookieSameSiteDisabled removes the SameSite flag
CookieSameSiteDisabled CookieSameSite = iota
// CookieSameSiteDefaultMode sets the SameSite flag
CookieSameSiteDefaultMode
// CookieSameSiteLaxMode sets the SameSite flag with the "Lax" parameter
CookieSameSiteLaxMode
// CookieSameSiteStrictMode sets the SameSite flag with the "Strict" parameter
CookieSameSiteStrictMode
)
// AcquireCookie returns an empty Cookie object from the pool.
//
// The returned object may be returned back to the pool with ReleaseCookie.
@ -67,13 +52,11 @@ type Cookie struct {
key []byte
value []byte
expire time.Time
maxAge int
domain []byte
path []byte
httpOnly bool
secure bool
sameSite CookieSameSite
bufKV argsKV
buf []byte
@ -85,12 +68,10 @@ func (c *Cookie) CopyTo(src *Cookie) {
c.key = append(c.key[:0], src.key...)
c.value = append(c.value[:0], src.value...)
c.expire = src.expire
c.maxAge = src.maxAge
c.domain = append(c.domain[:0], src.domain...)
c.path = append(c.path[:0], src.path...)
c.httpOnly = src.httpOnly
c.secure = src.secure
c.sameSite = src.sameSite
}
// HTTPOnly returns true if the cookie is http only.
@ -113,16 +94,6 @@ func (c *Cookie) SetSecure(secure bool) {
c.secure = secure
}
// SameSite returns the SameSite mode.
func (c *Cookie) SameSite() CookieSameSite {
return c.sameSite
}
// SetSameSite sets the cookie's SameSite flag to the given value.
func (c *Cookie) SetSameSite(mode CookieSameSite) {
c.sameSite = mode
}
// Path returns cookie path.
func (c *Cookie) Path() []byte {
return c.path
@ -157,20 +128,6 @@ func (c *Cookie) SetDomainBytes(domain []byte) {
c.domain = append(c.domain[:0], domain...)
}
// MaxAge returns the seconds until the cookie is meant to expire or 0
// if no max age.
func (c *Cookie) MaxAge() int {
return c.maxAge
}
// SetMaxAge sets cookie expiration time based on seconds. This takes precedence
// over any absolute expiry set on the cookie
//
// Set max age to 0 to unset
func (c *Cookie) SetMaxAge(seconds int) {
c.maxAge = seconds
}
// Expire returns cookie expiration time.
//
// CookieExpireUnlimited is returned if cookie doesn't expire
@ -231,12 +188,10 @@ func (c *Cookie) Reset() {
c.key = c.key[:0]
c.value = c.value[:0]
c.expire = zeroTime
c.maxAge = 0
c.domain = c.domain[:0]
c.path = c.path[:0]
c.httpOnly = false
c.secure = false
c.sameSite = CookieSameSiteDisabled
}
// AppendBytes appends cookie representation to dst and returns
@ -248,12 +203,7 @@ func (c *Cookie) AppendBytes(dst []byte) []byte {
}
dst = append(dst, c.value...)
if c.maxAge > 0 {
dst = append(dst, ';', ' ')
dst = append(dst, strCookieMaxAge...)
dst = append(dst, '=')
dst = AppendUint(dst, c.maxAge)
} else if !c.expire.IsZero() {
if !c.expire.IsZero() {
c.bufKV.value = AppendHTTPDate(c.bufKV.value[:0], c.expire)
dst = append(dst, ';', ' ')
dst = append(dst, strCookieExpires...)
@ -274,21 +224,6 @@ func (c *Cookie) AppendBytes(dst []byte) []byte {
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSecure...)
}
switch c.sameSite {
case CookieSameSiteDefaultMode:
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSameSite...)
case CookieSameSiteLaxMode:
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSameSite...)
dst = append(dst, '=')
dst = append(dst, strCookieSameSiteLax...)
case CookieSameSiteStrictMode:
dst = append(dst, ';', ' ')
dst = append(dst, strCookieSameSite...)
dst = append(dst, '=')
dst = append(dst, strCookieSameSiteStrict...)
}
return dst
}
@ -337,75 +272,29 @@ func (c *Cookie) ParseBytes(src []byte) error {
c.value = append(c.value[:0], kv.value...)
for s.next(kv) {
if len(kv.key) != 0 {
// Case insensitive switch on first char
switch kv.key[0] | 0x20 {
case 'm':
if caseInsensitiveCompare(strCookieMaxAge, kv.key) {
maxAge, err := ParseUint(kv.value)
if err != nil {
return err
}
c.maxAge = maxAge
}
case 'e': // "expires"
if caseInsensitiveCompare(strCookieExpires, kv.key) {
v := b2s(kv.value)
// Try the same two formats as net/http
// See: https://github.com/golang/go/blob/00379be17e63a5b75b3237819392d2dc3b313a27/src/net/http/cookie.go#L133-L135
exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
if err != nil {
exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", v)
if err != nil {
return err
}
}
c.expire = exptime
}
case 'd': // "domain"
if caseInsensitiveCompare(strCookieDomain, kv.key) {
c.domain = append(c.domain[:0], kv.value...)
}
case 'p': // "path"
if caseInsensitiveCompare(strCookiePath, kv.key) {
c.path = append(c.path[:0], kv.value...)
}
case 's': // "samesite"
if caseInsensitiveCompare(strCookieSameSite, kv.key) {
// Case insensitive switch on first char
switch kv.value[0] | 0x20 {
case 'l': // "lax"
if caseInsensitiveCompare(strCookieSameSiteLax, kv.value) {
c.sameSite = CookieSameSiteLaxMode
}
case 's': // "strict"
if caseInsensitiveCompare(strCookieSameSiteStrict, kv.value) {
c.sameSite = CookieSameSiteStrictMode
}
}
}
if len(kv.key) == 0 && len(kv.value) == 0 {
continue
}
switch string(kv.key) {
case "expires":
v := b2s(kv.value)
exptime, err := time.ParseInLocation(time.RFC1123, v, time.UTC)
if err != nil {
return err
}
} else if len(kv.value) != 0 {
// Case insensitive switch on first char
switch kv.value[0] | 0x20 {
case 'h': // "httponly"
if caseInsensitiveCompare(strCookieHTTPOnly, kv.value) {
c.httpOnly = true
}
case 's': // "secure"
if caseInsensitiveCompare(strCookieSecure, kv.value) {
c.secure = true
} else if caseInsensitiveCompare(strCookieSameSite, kv.value) {
c.sameSite = CookieSameSiteDefaultMode
}
c.expire = exptime
case "domain":
c.domain = append(c.domain[:0], kv.value...)
case "path":
c.path = append(c.path[:0], kv.value...)
case "":
switch string(kv.value) {
case "HttpOnly":
c.httpOnly = true
case "secure":
c.secure = true
}
} // else empty or no match
}
}
return nil
}
@ -440,19 +329,6 @@ func appendRequestCookieBytes(dst []byte, cookies []argsKV) []byte {
return dst
}
// For Response we can not use the above function as response cookies
// already contain the key= in the value.
func appendResponseCookieBytes(dst []byte, cookies []argsKV) []byte {
for i, n := 0, len(cookies); i < n; i++ {
kv := &cookies[i]
dst = append(dst, kv.value...)
if i+1 < n {
dst = append(dst, ';', ' ')
}
}
return dst
}
func parseRequestCookies(cookies []argsKV, src []byte) []argsKV {
var s cookieScanner
s.b = src
@ -518,17 +394,3 @@ func decodeCookieArg(dst, src []byte, skipQuotes bool) []byte {
}
return append(dst[:0], src...)
}
// caseInsensitiveCompare does a case insensitive equality comparison of
// two []byte. Assumes only letters need to be matched.
func caseInsensitiveCompare(a, b []byte) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i]|0x20 != b[i]|0x20 {
return false
}
}
return true
}

View file

@ -7,6 +7,9 @@ Fasthttp provides the following features:
concurrent keep-alive connections on modern hardware.
* Optimized for low memory usage.
* Easy 'Connection: Upgrade' support via RequestCtx.Hijack.
* Server supports requests' pipelining. Multiple requests may be read from
a single network packet and multiple responses may be sent in a single
network packet. This may be useful for highly loaded REST services.
* Server provides the following anti-DoS limits:
* The number of concurrent connections.

View file

@ -8,23 +8,18 @@ import (
// InmemoryListener provides in-memory dialer<->net.Listener implementation.
//
// It may be used either for fast in-process client<->server communications
// It may be used either for fast in-process client<->server communcations
// without network stack overhead or for client<->server tests.
type InmemoryListener struct {
lock sync.Mutex
closed bool
conns chan acceptConn
}
type acceptConn struct {
conn net.Conn
accepted chan struct{}
conns chan net.Conn
}
// NewInmemoryListener returns new in-memory dialer<->net.Listener.
func NewInmemoryListener() *InmemoryListener {
return &InmemoryListener{
conns: make(chan acceptConn, 1024),
conns: make(chan net.Conn, 1024),
}
}
@ -38,8 +33,7 @@ func (ln *InmemoryListener) Accept() (net.Conn, error) {
if !ok {
return nil, fmt.Errorf("InmemoryListener is already closed: use of closed network connection")
}
close(c.accepted)
return c.conn, nil
return c, nil
}
// Close implements net.Listener's Close.
@ -65,9 +59,8 @@ func (ln *InmemoryListener) Addr() net.Addr {
}
}
// Dial creates new client<->server connection.
// Just like a real Dial it only returns once the server
// has accepted the connection.
// Dial creates new client<->server connection, enqueues server side
// of the connection to Accept and returns client side of the connection.
//
// It is safe calling Dial from concurrently running goroutines.
func (ln *InmemoryListener) Dial() (net.Conn, error) {
@ -75,11 +68,8 @@ func (ln *InmemoryListener) Dial() (net.Conn, error) {
cConn := pc.Conn1()
sConn := pc.Conn2()
ln.lock.Lock()
accepted := make(chan struct{})
if !ln.closed {
ln.conns <- acceptConn{sConn, accepted}
// Wait until the connection has been accepted.
<-accepted
ln.conns <- sConn
} else {
sConn.Close()
cConn.Close()

View file

@ -8,7 +8,7 @@ import (
"time"
)
// NewPipeConns returns new bi-directional connection pipe.
// NewPipeConns returns new bi-directonal connection pipe.
func NewPipeConns() *PipeConns {
ch1 := make(chan *byteBuffer, 4)
ch2 := make(chan *byteBuffer, 4)

View file

@ -17,7 +17,6 @@ import (
"time"
"github.com/klauspost/compress/gzip"
"github.com/valyala/bytebufferpool"
)
// ServeFileBytesUncompressed returns HTTP response containing file contents
@ -140,12 +139,12 @@ func NewVHostPathRewriter(slashesCount int) PathRewriteFunc {
if len(host) == 0 {
host = strInvalidHost
}
b := bytebufferpool.Get()
b := AcquireByteBuffer()
b.B = append(b.B, '/')
b.B = append(b.B, host...)
b.B = append(b.B, path...)
ctx.URI().SetPathBytes(b.B)
bytebufferpool.Put(b)
ReleaseByteBuffer(b)
return ctx.Path()
}
@ -226,7 +225,7 @@ type FS struct {
// It adds CompressedFileSuffix suffix to the original file name and
// tries saving the resulting compressed file under the new file name.
// So it is advisable to give the server write access to Root
// and to all inner folders in order to minimize CPU usage when serving
// and to all inner folders in order to minimze CPU usage when serving
// compressed responses.
//
// Transparent compression is disabled by default.
@ -242,14 +241,6 @@ type FS struct {
// By default request path is not modified.
PathRewrite PathRewriteFunc
// PathNotFound fires when file is not found in filesystem
// this functions tries to replace "Cannot open requested path"
// server response giving to the programmer the control of server flow.
//
// By default PathNotFound returns
// "Cannot open requested path"
PathNotFound RequestHandler
// Expiration duration for inactive file handlers.
//
// FSHandlerCacheDuration is used by default.
@ -352,7 +343,6 @@ func (fs *FS) initRequestHandler() {
pathRewrite: fs.PathRewrite,
generateIndexPages: fs.GenerateIndexPages,
compress: fs.Compress,
pathNotFound: fs.PathNotFound,
acceptByteRange: fs.AcceptByteRange,
cacheDuration: cacheDuration,
compressedFileSuffix: compressedFileSuffix,
@ -375,7 +365,6 @@ type fsHandler struct {
root string
indexNames []string
pathRewrite PathRewriteFunc
pathNotFound RequestHandler
generateIndexPages bool
compress bool
acceptByteRange bool
@ -737,12 +726,7 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
}
} else if err != nil {
ctx.Logger().Printf("cannot open file %q: %s", filePath, err)
if h.pathNotFound == nil {
ctx.Error("Cannot open requested path", StatusNotFound)
} else {
ctx.SetStatusCode(StatusNotFound)
h.pathNotFound(ctx)
}
ctx.Error("Cannot open requested path", StatusNotFound)
return
}
@ -824,10 +808,7 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
}
}
}
hdr.noDefaultContentType = true
if len(hdr.ContentType()) == 0 {
ctx.SetContentType(ff.contentType)
}
ctx.SetContentType(ff.contentType)
ctx.SetStatusCode(statusCode)
}
@ -916,7 +897,7 @@ var (
)
func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool) (*fsFile, error) {
w := &bytebufferpool.ByteBuffer{}
w := &ByteBuffer{}
basePathEscaped := html.EscapeString(string(base.Path()))
fmt.Fprintf(w, "<html><head><title>%s</title><style>.dir { font-weight: bold }</style></head><body>", basePathEscaped)
@ -943,7 +924,7 @@ func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool)
}
fm := make(map[string]os.FileInfo, len(fileinfos))
filenames := make([]string, 0, len(fileinfos))
var filenames []string
for _, fi := range fileinfos {
name := fi.Name()
if strings.HasSuffix(name, h.compressedFileSuffix) {
@ -958,7 +939,7 @@ func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool)
base.CopyTo(&u)
u.Update(string(u.Path()) + "/")
sort.Strings(filenames)
sort.Sort(sort.StringSlice(filenames))
for _, name := range filenames {
u.Update(name)
pathEscaped := html.EscapeString(string(u.Path()))
@ -976,7 +957,7 @@ func (h *fsHandler) createDirIndex(base *URI, dirPath string, mustCompress bool)
fmt.Fprintf(w, "</ul></body></html>")
if mustCompress {
var zbuf bytebufferpool.ByteBuffer
var zbuf ByteBuffer
zbuf.B = AppendGzipBytesLevel(zbuf.B, w.B, CompressDefaultCompression)
w = &zbuf
}

9
vendor/github.com/VictoriaMetrics/fasthttp/go.mod generated vendored Normal file
View file

@ -0,0 +1,9 @@
module github.com/VictoriaMetrics/fasthttp
go 1.13
require (
github.com/klauspost/compress v1.10.5
github.com/valyala/bytebufferpool v1.0.0
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
)

6
vendor/github.com/VictoriaMetrics/fasthttp/go.sum generated vendored Normal file
View file

@ -0,0 +1,6 @@
github.com/klauspost/compress v1.10.5 h1:7q6vHIqubShURwQz8cQK6yIe/xC3IF0Vm7TGfqjewrc=
github.com/klauspost/compress v1.10.5/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=

View file

@ -20,10 +20,8 @@ import (
type ResponseHeader struct {
noCopy noCopy
disableNormalizing bool
noHTTP11 bool
connectionClose bool
noDefaultContentType bool
noHTTP11 bool
connectionClose bool
statusCode int
contentLength int
@ -48,9 +46,9 @@ type ResponseHeader struct {
type RequestHeader struct {
noCopy noCopy
disableNormalizing bool
noHTTP11 bool
connectionClose bool
noHTTP11 bool
connectionClose bool
isGet bool
// These two fields have been moved close to other bool fields
// for reducing RequestHeader object size.
@ -72,10 +70,6 @@ type RequestHeader struct {
cookies []argsKV
rawHeaders []byte
// stores an immutable copy of headers as they were received from the
// wire.
rawHeadersCopy []byte
}
// SetContentRange sets 'Content-Range: bytes startPos-endPos/contentLength'
@ -161,6 +155,12 @@ func (h *RequestHeader) ConnectionClose() bool {
return h.connectionClose
}
func (h *RequestHeader) connectionCloseFast() bool {
// h.parseRawHeaders() isn't called for performance reasons.
// Use ConnectionClose for triggering raw headers parsing.
return h.connectionClose
}
// SetConnectionClose sets 'Connection: close' header.
func (h *RequestHeader) SetConnectionClose() {
// h.parseRawHeaders() isn't called for performance reasons.
@ -187,11 +187,6 @@ func (h *RequestHeader) ConnectionUpgrade() bool {
return hasHeaderValue(h.Peek("Connection"), strUpgrade)
}
// PeekCookie is able to returns cookie by a given key from response.
func (h *ResponseHeader) PeekCookie(key string) []byte {
return peekArgStr(h.cookies, key)
}
// ContentLength returns Content-Length header value.
//
// It may be negative:
@ -221,7 +216,7 @@ func (h *ResponseHeader) SetContentLength(contentLength int) {
h.SetConnectionClose()
value = strIdentity
}
h.h = setArgBytes(h.h, strTransferEncoding, value, argsHasValue)
h.h = setArgBytes(h.h, strTransferEncoding, value)
}
}
@ -244,15 +239,9 @@ func (h *ResponseHeader) mustSkipContentLength() bool {
// It may be negative:
// -1 means Transfer-Encoding: chunked.
func (h *RequestHeader) ContentLength() int {
if h.ignoreBody() {
if h.noBody() {
return 0
}
return h.realContentLength()
}
// realContentLength returns the actual Content-Length set in the request,
// including positive lengths for GET/HEAD requests.
func (h *RequestHeader) realContentLength() int {
h.parseRawHeaders()
return h.contentLength
}
@ -268,7 +257,7 @@ func (h *RequestHeader) SetContentLength(contentLength int) {
h.h = delAllArgsBytes(h.h, strTransferEncoding)
} else {
h.contentLengthBytes = h.contentLengthBytes[:0]
h.h = setArgBytes(h.h, strTransferEncoding, strChunked, argsHasValue)
h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
}
}
@ -281,7 +270,7 @@ func (h *ResponseHeader) isCompressibleContentType() bool {
// ContentType returns Content-Type header value.
func (h *ResponseHeader) ContentType() []byte {
contentType := h.contentType
if !h.noDefaultContentType && len(h.contentType) == 0 {
if len(h.contentType) == 0 {
contentType = defaultContentType
}
return contentType
@ -513,10 +502,14 @@ func (h *RequestHeader) SetRequestURIBytes(requestURI []byte) {
// IsGet returns true if request method is GET.
func (h *RequestHeader) IsGet() bool {
return bytes.Equal(h.Method(), strGet)
// Optimize fast path for GET requests.
if !h.isGet {
h.isGet = bytes.Equal(h.Method(), strGet)
}
return h.isGet
}
// IsPost returns true if request method is POST.
// IsPost returns true if request methos is POST.
func (h *RequestHeader) IsPost() bool {
return bytes.Equal(h.Method(), strPost)
}
@ -528,6 +521,10 @@ func (h *RequestHeader) IsPut() bool {
// IsHead returns true if request method is HEAD.
func (h *RequestHeader) IsHead() bool {
// Fast path
if h.isGet {
return false
}
return bytes.Equal(h.Method(), strHead)
}
@ -536,26 +533,6 @@ func (h *RequestHeader) IsDelete() bool {
return bytes.Equal(h.Method(), strDelete)
}
// IsConnect returns true if request method is CONNECT.
func (h *RequestHeader) IsConnect() bool {
return bytes.Equal(h.Method(), strConnect)
}
// IsOptions returns true if request method is OPTIONS.
func (h *RequestHeader) IsOptions() bool {
return bytes.Equal(h.Method(), strOptions)
}
// IsTrace returns true if request method is TRACE.
func (h *RequestHeader) IsTrace() bool {
return bytes.Equal(h.Method(), strTrace)
}
// IsPatch returns true if request method is PATCH.
func (h *RequestHeader) IsPatch() bool {
return bytes.Equal(h.Method(), strPatch)
}
// IsHTTP11 returns true if the request is HTTP/1.1.
func (h *RequestHeader) IsHTTP11() bool {
return !h.noHTTP11
@ -607,46 +584,8 @@ func (h *RequestHeader) Len() int {
return n
}
// DisableNormalizing disables header names' normalization.
//
// By default all the header names are normalized by uppercasing
// the first letter and all the first letters following dashes,
// while lowercasing all the other letters.
// Examples:
//
// * CONNECTION -> Connection
// * conteNT-tYPE -> Content-Type
// * foo-bar-baz -> Foo-Bar-Baz
//
// Disable header names' normalization only if know what are you doing.
func (h *RequestHeader) DisableNormalizing() {
h.disableNormalizing = true
}
// DisableNormalizing disables header names' normalization.
//
// By default all the header names are normalized by uppercasing
// the first letter and all the first letters following dashes,
// while lowercasing all the other letters.
// Examples:
//
// * CONNECTION -> Connection
// * conteNT-tYPE -> Content-Type
// * foo-bar-baz -> Foo-Bar-Baz
//
// Disable header names' normalization only if know what are you doing.
func (h *ResponseHeader) DisableNormalizing() {
h.disableNormalizing = true
}
// Reset clears response header.
func (h *ResponseHeader) Reset() {
h.disableNormalizing = false
h.noDefaultContentType = false
h.resetSkipNormalize()
}
func (h *ResponseHeader) resetSkipNormalize() {
h.noHTTP11 = false
h.connectionClose = false
@ -663,13 +602,9 @@ func (h *ResponseHeader) resetSkipNormalize() {
// Reset clears request header.
func (h *RequestHeader) Reset() {
h.disableNormalizing = false
h.resetSkipNormalize()
}
func (h *RequestHeader) resetSkipNormalize() {
h.noHTTP11 = false
h.connectionClose = false
h.isGet = false
h.contentLength = 0
h.contentLengthBytes = h.contentLengthBytes[:0]
@ -692,10 +627,8 @@ func (h *RequestHeader) resetSkipNormalize() {
func (h *ResponseHeader) CopyTo(dst *ResponseHeader) {
dst.Reset()
dst.disableNormalizing = h.disableNormalizing
dst.noHTTP11 = h.noHTTP11
dst.connectionClose = h.connectionClose
dst.noDefaultContentType = h.noDefaultContentType
dst.statusCode = h.statusCode
dst.contentLength = h.contentLength
@ -710,9 +643,9 @@ func (h *ResponseHeader) CopyTo(dst *ResponseHeader) {
func (h *RequestHeader) CopyTo(dst *RequestHeader) {
dst.Reset()
dst.disableNormalizing = h.disableNormalizing
dst.noHTTP11 = h.noHTTP11
dst.connectionClose = h.connectionClose
dst.isGet = h.isGet
dst.contentLength = h.contentLength
dst.contentLengthBytes = append(dst.contentLengthBytes[:0], h.contentLengthBytes...)
@ -726,7 +659,6 @@ func (h *RequestHeader) CopyTo(dst *RequestHeader) {
dst.cookiesCollected = h.cookiesCollected
dst.rawHeaders = append(dst.rawHeaders[:0], h.rawHeaders...)
dst.rawHeadersParsed = h.rawHeadersParsed
dst.rawHeadersCopy = append(dst.rawHeadersCopy[:0], h.rawHeadersCopy...)
}
// VisitAll calls f for each header.
@ -780,8 +712,6 @@ func (h *RequestHeader) VisitAllCookie(f func(key, value []byte)) {
//
// f must not retain references to key and/or value after returning.
// Copy key and/or value contents before returning if you need retaining them.
//
// To get the headers in order they were received use VisitAllInOrder.
func (h *RequestHeader) VisitAll(f func(key, value []byte)) {
h.parseRawHeaders()
host := h.Host()
@ -811,35 +741,16 @@ func (h *RequestHeader) VisitAll(f func(key, value []byte)) {
}
}
// VisitAllInOrder calls f for each header in the order they were received.
//
// f must not retain references to key and/or value after returning.
// Copy key and/or value contents before returning if you need retaining them.
//
// This function is slightly slower than VisitAll because it has to reparse the
// raw headers to get the order.
func (h *RequestHeader) VisitAllInOrder(f func(key, value []byte)) {
h.parseRawHeaders()
var s headerScanner
s.b = h.rawHeaders
s.disableNormalizing = h.disableNormalizing
for s.next() {
if len(s.key) > 0 {
f(s.key, s.value)
}
}
}
// Del deletes header with the given key.
func (h *ResponseHeader) Del(key string) {
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
k := getHeaderKeyBytes(&h.bufKV, key)
h.del(k)
}
// DelBytes deletes header with the given key.
func (h *ResponseHeader) DelBytes(key []byte) {
h.bufKV.key = append(h.bufKV.key[:0], key...)
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
normalizeHeaderKey(h.bufKV.key)
h.del(h.bufKV.key)
}
@ -863,7 +774,7 @@ func (h *ResponseHeader) del(key []byte) {
// Del deletes header with the given key.
func (h *RequestHeader) Del(key string) {
h.parseRawHeaders()
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
k := getHeaderKeyBytes(&h.bufKV, key)
h.del(k)
}
@ -871,7 +782,7 @@ func (h *RequestHeader) Del(key string) {
func (h *RequestHeader) DelBytes(key []byte) {
h.parseRawHeaders()
h.bufKV.key = append(h.bufKV.key[:0], key...)
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
normalizeHeaderKey(h.bufKV.key)
h.del(h.bufKV.key)
}
@ -899,8 +810,8 @@ func (h *RequestHeader) del(key []byte) {
// Multiple headers with the same key may be added with this function.
// Use Set for setting a single header for the given key.
func (h *ResponseHeader) Add(key, value string) {
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
h.h = appendArg(h.h, b2s(k), value, argsHasValue)
k := getHeaderKeyBytes(&h.bufKV, key)
h.h = appendArg(h.h, b2s(k), value)
}
// AddBytesK adds the given 'key: value' header.
@ -931,7 +842,7 @@ func (h *ResponseHeader) AddBytesKV(key, value []byte) {
//
// Use Add for setting multiple header values under the same key.
func (h *ResponseHeader) Set(key, value string) {
initHeaderKV(&h.bufKV, key, value, h.disableNormalizing)
initHeaderKV(&h.bufKV, key, value)
h.SetCanonical(h.bufKV.key, h.bufKV.value)
}
@ -947,7 +858,7 @@ func (h *ResponseHeader) SetBytesK(key []byte, value string) {
//
// Use AddBytesV for setting multiple header values under the same key.
func (h *ResponseHeader) SetBytesV(key string, value []byte) {
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
k := getHeaderKeyBytes(&h.bufKV, key)
h.SetCanonical(k, value)
}
@ -956,7 +867,7 @@ func (h *ResponseHeader) SetBytesV(key string, value []byte) {
// Use AddBytesKV for setting multiple header values under the same key.
func (h *ResponseHeader) SetBytesKV(key, value []byte) {
h.bufKV.key = append(h.bufKV.key[:0], key...)
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
normalizeHeaderKey(h.bufKV.key)
h.SetCanonical(h.bufKV.key, value)
}
@ -983,14 +894,14 @@ func (h *ResponseHeader) SetCanonical(key, value []byte) {
h.SetConnectionClose()
} else {
h.ResetConnectionClose()
h.h = setArgBytes(h.h, key, value, argsHasValue)
h.h = setArgBytes(h.h, key, value)
}
case "Transfer-Encoding":
// Transfer-Encoding is managed automatically.
case "Date":
// Date is managed automatically.
default:
h.h = setArgBytes(h.h, key, value, argsHasValue)
h.h = setArgBytes(h.h, key, value)
}
}
@ -998,14 +909,14 @@ func (h *ResponseHeader) SetCanonical(key, value []byte) {
//
// It is save re-using the cookie after the function returns.
func (h *ResponseHeader) SetCookie(cookie *Cookie) {
h.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), argsHasValue)
h.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie())
}
// SetCookie sets 'key: value' cookies.
func (h *RequestHeader) SetCookie(key, value string) {
h.parseRawHeaders()
h.collectCookies()
h.cookies = setArg(h.cookies, key, value, argsHasValue)
h.cookies = setArg(h.cookies, key, value)
}
// SetCookieBytesK sets 'key: value' cookies.
@ -1083,8 +994,8 @@ func (h *RequestHeader) DelAllCookies() {
// Multiple headers with the same key may be added with this function.
// Use Set for setting a single header for the given key.
func (h *RequestHeader) Add(key, value string) {
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
h.h = appendArg(h.h, b2s(k), value, argsHasValue)
k := getHeaderKeyBytes(&h.bufKV, key)
h.h = appendArg(h.h, b2s(k), value)
}
// AddBytesK adds the given 'key: value' header.
@ -1115,7 +1026,7 @@ func (h *RequestHeader) AddBytesKV(key, value []byte) {
//
// Use Add for setting multiple header values under the same key.
func (h *RequestHeader) Set(key, value string) {
initHeaderKV(&h.bufKV, key, value, h.disableNormalizing)
initHeaderKV(&h.bufKV, key, value)
h.SetCanonical(h.bufKV.key, h.bufKV.value)
}
@ -1131,7 +1042,7 @@ func (h *RequestHeader) SetBytesK(key []byte, value string) {
//
// Use AddBytesV for setting multiple header values under the same key.
func (h *RequestHeader) SetBytesV(key string, value []byte) {
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
k := getHeaderKeyBytes(&h.bufKV, key)
h.SetCanonical(k, value)
}
@ -1140,7 +1051,7 @@ func (h *RequestHeader) SetBytesV(key string, value []byte) {
// Use AddBytesKV for setting multiple header values under the same key.
func (h *RequestHeader) SetBytesKV(key, value []byte) {
h.bufKV.key = append(h.bufKV.key[:0], key...)
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
normalizeHeaderKey(h.bufKV.key)
h.SetCanonical(h.bufKV.key, value)
}
@ -1168,12 +1079,12 @@ func (h *RequestHeader) SetCanonical(key, value []byte) {
h.SetConnectionClose()
} else {
h.ResetConnectionClose()
h.h = setArgBytes(h.h, key, value, argsHasValue)
h.h = setArgBytes(h.h, key, value)
}
case "Transfer-Encoding":
// Transfer-Encoding is managed automatically.
default:
h.h = setArgBytes(h.h, key, value, argsHasValue)
h.h = setArgBytes(h.h, key, value)
}
}
@ -1182,7 +1093,7 @@ func (h *RequestHeader) SetCanonical(key, value []byte) {
// Returned value is valid until the next call to ResponseHeader.
// Do not store references to returned value. Make copies instead.
func (h *ResponseHeader) Peek(key string) []byte {
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
k := getHeaderKeyBytes(&h.bufKV, key)
return h.peek(k)
}
@ -1192,7 +1103,7 @@ func (h *ResponseHeader) Peek(key string) []byte {
// Do not store references to returned value. Make copies instead.
func (h *ResponseHeader) PeekBytes(key []byte) []byte {
h.bufKV.key = append(h.bufKV.key[:0], key...)
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
normalizeHeaderKey(h.bufKV.key)
return h.peek(h.bufKV.key)
}
@ -1201,7 +1112,7 @@ func (h *ResponseHeader) PeekBytes(key []byte) []byte {
// Returned value is valid until the next call to RequestHeader.
// Do not store references to returned value. Make copies instead.
func (h *RequestHeader) Peek(key string) []byte {
k := getHeaderKeyBytes(&h.bufKV, key, h.disableNormalizing)
k := getHeaderKeyBytes(&h.bufKV, key)
return h.peek(k)
}
@ -1211,7 +1122,7 @@ func (h *RequestHeader) Peek(key string) []byte {
// Do not store references to returned value. Make copies instead.
func (h *RequestHeader) PeekBytes(key []byte) []byte {
h.bufKV.key = append(h.bufKV.key[:0], key...)
normalizeHeaderKey(h.bufKV.key, h.disableNormalizing)
normalizeHeaderKey(h.bufKV.key)
return h.peek(h.bufKV.key)
}
@ -1228,8 +1139,6 @@ func (h *ResponseHeader) peek(key []byte) []byte {
return peekArgBytes(h.h, key)
case "Content-Length":
return h.contentLengthBytes
case "Set-Cookie":
return appendResponseCookieBytes(nil, h.cookies)
default:
return peekArgBytes(h.h, key)
}
@ -1251,12 +1160,6 @@ func (h *RequestHeader) peek(key []byte) []byte {
return peekArgBytes(h.h, key)
case "Content-Length":
return h.contentLengthBytes
case "Cookie":
if h.cookiesCollected {
return appendRequestCookieBytes(nil, h.cookies)
} else {
return peekArgBytes(h.h, key)
}
default:
return peekArgBytes(h.h, key)
}
@ -1299,7 +1202,7 @@ func (h *ResponseHeader) Read(r *bufio.Reader) error {
return nil
}
if err != errNeedMore {
h.resetSkipNormalize()
h.Reset()
return err
}
n = r.Buffered() + 1
@ -1307,7 +1210,7 @@ func (h *ResponseHeader) Read(r *bufio.Reader) error {
}
func (h *ResponseHeader) tryRead(r *bufio.Reader, n int) error {
h.resetSkipNormalize()
h.Reset()
b, err := r.Peek(n)
if len(b) == 0 {
// treat all errors on the first byte read as EOF
@ -1370,7 +1273,7 @@ func (h *RequestHeader) Read(r *bufio.Reader) error {
return nil
}
if err != errNeedMore {
h.resetSkipNormalize()
h.Reset()
return err
}
n = r.Buffered() + 1
@ -1378,15 +1281,12 @@ func (h *RequestHeader) Read(r *bufio.Reader) error {
}
func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error {
h.resetSkipNormalize()
h.Reset()
b, err := r.Peek(n)
if len(b) == 0 {
if err == io.EOF {
return err
}
if err == nil {
panic("bufio.Reader.Peek() returned nil, nil")
// treat all errors on the first byte read as EOF
if n == 1 || err == io.EOF {
return io.EOF
}
// This is for go 1.6 bug. See https://github.com/golang/go/issues/14121 .
@ -1396,11 +1296,6 @@ func (h *RequestHeader) tryRead(r *bufio.Reader, n int) error {
}
}
if n == 1 {
// We didn't read a single byte.
return errNothingRead
}
return fmt.Errorf("error when reading request headers: %s", err)
}
b = mustPeekBuffered(r)
@ -1490,9 +1385,10 @@ func (h *ResponseHeader) AppendBytes(dst []byte) []byte {
dst = append(dst, statusLine(statusCode)...)
server := h.Server()
if len(server) != 0 {
dst = appendHeaderLine(dst, strServer, server)
if len(server) == 0 {
server = defaultServerName
}
dst = appendHeaderLine(dst, strServer, server)
dst = appendHeaderLine(dst, strDate, serverDate.Load().([]byte))
// Append Content-Type only for non-zero responses
@ -1550,20 +1446,6 @@ func (h *RequestHeader) Header() []byte {
return h.bufKV.value
}
// RawHeaders returns raw header key/value bytes.
//
// Depending on server configuration, header keys may be normalized to
// capital-case in place.
//
// This copy is set aside during parsing, so empty slice is returned for all
// cases where parsing did not happen. Similarly, request line is not stored
// during parsing and can not be returned.
//
// The slice is not safe to use after the handler returns.
func (h *RequestHeader) RawHeaders() []byte {
return h.rawHeadersCopy
}
// String returns request header representation.
func (h *RequestHeader) String() string {
return string(h.Header())
@ -1585,9 +1467,10 @@ func (h *RequestHeader) AppendBytes(dst []byte) []byte {
}
userAgent := h.UserAgent()
if len(userAgent) > 0 {
dst = appendHeaderLine(dst, strUserAgent, userAgent)
if len(userAgent) == 0 {
userAgent = defaultUserAgent
}
dst = appendHeaderLine(dst, strUserAgent, userAgent)
host := h.Host()
if len(host) > 0 {
@ -1595,7 +1478,7 @@ func (h *RequestHeader) AppendBytes(dst []byte) []byte {
}
contentType := h.ContentType()
if !h.ignoreBody() {
if !h.noBody() {
if len(contentType) == 0 {
contentType = strPostArgsContentType
}
@ -1649,7 +1532,7 @@ func (h *ResponseHeader) parse(buf []byte) (int, error) {
return m + n, nil
}
func (h *RequestHeader) ignoreBody() bool {
func (h *RequestHeader) noBody() bool {
return h.IsGet() || h.IsHead()
}
@ -1660,20 +1543,18 @@ func (h *RequestHeader) parse(buf []byte) (int, error) {
}
var n int
var rawHeaders []byte
rawHeaders, n, err = readRawHeaders(h.rawHeaders[:0], buf[m:])
if err != nil {
return 0, err
}
h.rawHeadersCopy = append(h.rawHeadersCopy[:0], rawHeaders...)
if !h.ignoreBody() || h.noHTTP11 {
if !h.noBody() || h.noHTTP11 {
n, err = h.parseHeaders(buf[m:])
if err != nil {
return 0, err
}
h.rawHeaders = append(h.rawHeaders[:0], buf[m:m+n]...)
h.rawHeadersParsed = true
} else {
var rawHeaders []byte
rawHeaders, n, err = readRawHeaders(h.rawHeaders[:0], buf[m:])
if err != nil {
return 0, err
}
h.rawHeaders = rawHeaders
}
return m + n, nil
@ -1807,57 +1688,40 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
var s headerScanner
s.b = buf
s.disableNormalizing = h.disableNormalizing
var err error
var kv *argsKV
for s.next() {
if len(s.key) > 0 {
switch s.key[0] | 0x20 {
case 'c':
if caseInsensitiveCompare(s.key, strContentType) {
h.contentType = append(h.contentType[:0], s.value...)
continue
}
if caseInsensitiveCompare(s.key, strContentLength) {
if h.contentLength != -1 {
if h.contentLength, err = parseContentLength(s.value); err != nil {
h.contentLength = -2
} else {
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
}
}
continue
}
if caseInsensitiveCompare(s.key, strConnection) {
if bytes.Equal(s.value, strClose) {
h.connectionClose = true
} else {
h.connectionClose = false
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
}
continue
}
case 's':
if caseInsensitiveCompare(s.key, strServer) {
h.server = append(h.server[:0], s.value...)
continue
}
if caseInsensitiveCompare(s.key, strSetCookie) {
h.cookies, kv = allocArg(h.cookies)
kv.key = getCookieKey(kv.key, s.value)
kv.value = append(kv.value[:0], s.value...)
continue
}
case 't':
if caseInsensitiveCompare(s.key, strTransferEncoding) {
if !bytes.Equal(s.value, strIdentity) {
h.contentLength = -1
h.h = setArgBytes(h.h, strTransferEncoding, strChunked, argsHasValue)
}
continue
switch string(s.key) {
case "Content-Type":
h.contentType = append(h.contentType[:0], s.value...)
case "Server":
h.server = append(h.server[:0], s.value...)
case "Content-Length":
if h.contentLength != -1 {
if h.contentLength, err = parseContentLength(s.value); err != nil {
h.contentLength = -2
} else {
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
}
}
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
case "Transfer-Encoding":
if !bytes.Equal(s.value, strIdentity) {
h.contentLength = -1
h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
}
case "Set-Cookie":
h.cookies, kv = allocArg(h.cookies)
kv.key = getCookieKey(kv.key, s.value)
kv.value = append(kv.value[:0], s.value...)
case "Connection":
if bytes.Equal(s.value, strClose) {
h.connectionClose = true
} else {
h.connectionClose = false
h.h = appendArgBytes(h.h, s.key, s.value)
}
default:
h.h = appendArgBytes(h.h, s.key, s.value)
}
}
if s.err != nil {
@ -1869,13 +1733,13 @@ func (h *ResponseHeader) parseHeaders(buf []byte) (int, error) {
h.contentLengthBytes = h.contentLengthBytes[:0]
}
if h.contentLength == -2 && !h.ConnectionUpgrade() && !h.mustSkipContentLength() {
h.h = setArgBytes(h.h, strTransferEncoding, strIdentity, argsHasValue)
h.h = setArgBytes(h.h, strTransferEncoding, strIdentity)
h.connectionClose = true
}
if h.noHTTP11 && !h.connectionClose {
// close connection for non-http/1.1 response unless 'Connection: keep-alive' is set.
v := peekArgBytes(h.h, strConnection)
h.connectionClose = !hasHeaderValue(v, strKeepAlive)
h.connectionClose = !hasHeaderValue(v, strKeepAlive) && !hasHeaderValue(v, strKeepAliveCamelCase)
}
return len(buf) - len(s.b), nil
@ -1886,56 +1750,38 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
var s headerScanner
s.b = buf
s.disableNormalizing = h.disableNormalizing
var err error
for s.next() {
if len(s.key) > 0 {
switch s.key[0] | 0x20 {
case 'h':
if caseInsensitiveCompare(s.key, strHost) {
h.host = append(h.host[:0], s.value...)
continue
}
case 'u':
if caseInsensitiveCompare(s.key, strUserAgent) {
h.userAgent = append(h.userAgent[:0], s.value...)
continue
}
case 'c':
if caseInsensitiveCompare(s.key, strContentType) {
h.contentType = append(h.contentType[:0], s.value...)
continue
}
if caseInsensitiveCompare(s.key, strContentLength) {
if h.contentLength != -1 {
if h.contentLength, err = parseContentLength(s.value); err != nil {
h.contentLength = -2
} else {
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
}
}
continue
}
if caseInsensitiveCompare(s.key, strConnection) {
if bytes.Equal(s.value, strClose) {
h.connectionClose = true
} else {
h.connectionClose = false
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
}
continue
}
case 't':
if caseInsensitiveCompare(s.key, strTransferEncoding) {
if !bytes.Equal(s.value, strIdentity) {
h.contentLength = -1
h.h = setArgBytes(h.h, strTransferEncoding, strChunked, argsHasValue)
}
continue
switch string(s.key) {
case "Host":
h.host = append(h.host[:0], s.value...)
case "User-Agent":
h.userAgent = append(h.userAgent[:0], s.value...)
case "Content-Type":
h.contentType = append(h.contentType[:0], s.value...)
case "Content-Length":
if h.contentLength != -1 {
if h.contentLength, err = parseContentLength(s.value); err != nil {
h.contentLength = -2
} else {
h.contentLengthBytes = append(h.contentLengthBytes[:0], s.value...)
}
}
case "Transfer-Encoding":
if !bytes.Equal(s.value, strIdentity) {
h.contentLength = -1
h.h = setArgBytes(h.h, strTransferEncoding, strChunked)
}
case "Connection":
if bytes.Equal(s.value, strClose) {
h.connectionClose = true
} else {
h.connectionClose = false
h.h = appendArgBytes(h.h, s.key, s.value)
}
default:
h.h = appendArgBytes(h.h, s.key, s.value)
}
h.h = appendArgBytes(h.h, s.key, s.value, argsHasValue)
}
if s.err != nil {
h.connectionClose = true
@ -1945,12 +1791,17 @@ func (h *RequestHeader) parseHeaders(buf []byte) (int, error) {
if h.contentLength < 0 {
h.contentLengthBytes = h.contentLengthBytes[:0]
}
if h.noBody() {
h.contentLength = 0
h.contentLengthBytes = h.contentLengthBytes[:0]
}
if h.noHTTP11 && !h.connectionClose {
// close connection for non-http/1.1 request unless 'Connection: keep-alive' is set.
v := peekArgBytes(h.h, strConnection)
h.connectionClose = !hasHeaderValue(v, strKeepAlive)
h.connectionClose = !hasHeaderValue(v, strKeepAlive) && !hasHeaderValue(v, strKeepAliveCamelCase)
}
return s.hLen, nil
return len(buf) - len(s.b), nil
}
func (h *RequestHeader) parseRawHeaders() {
@ -2000,23 +1851,16 @@ type headerScanner struct {
key []byte
value []byte
err error
// hLen stores header subslice len
hLen int
disableNormalizing bool
}
func (s *headerScanner) next() bool {
bLen := len(s.b)
if bLen >= 2 && s.b[0] == '\r' && s.b[1] == '\n' {
s.b = s.b[2:]
s.hLen += 2
return false
}
if bLen >= 1 && s.b[0] == '\n' {
s.b = s.b[1:]
s.hLen++
return false
}
n := bytes.IndexByte(s.b, ':')
@ -2025,12 +1869,11 @@ func (s *headerScanner) next() bool {
return false
}
s.key = s.b[:n]
normalizeHeaderKey(s.key, s.disableNormalizing)
normalizeHeaderKey(s.key)
n++
for len(s.b) > n && s.b[n] == ' ' {
n++
}
s.hLen += n
s.b = s.b[n:]
n = bytes.IndexByte(s.b, '\n')
if n < 0 {
@ -2038,7 +1881,6 @@ func (s *headerScanner) next() bool {
return false
}
s.value = s.b[:n]
s.hLen += n + 1
s.b = s.b[n+1:]
if n > 0 && s.value[n-1] == '\r' {
@ -2086,7 +1928,7 @@ func hasHeaderValue(s, value []byte) bool {
var vs headerValueScanner
vs.b = s
for vs.next() {
if caseInsensitiveCompare(vs.value, value) {
if bytes.Equal(vs.value, value) {
return true
}
}
@ -2105,22 +1947,18 @@ func nextLine(b []byte) ([]byte, []byte, error) {
return b[:n], b[nNext+1:], nil
}
func initHeaderKV(kv *argsKV, key, value string, disableNormalizing bool) {
kv.key = getHeaderKeyBytes(kv, key, disableNormalizing)
func initHeaderKV(kv *argsKV, key, value string) {
kv.key = getHeaderKeyBytes(kv, key)
kv.value = append(kv.value[:0], value...)
}
func getHeaderKeyBytes(kv *argsKV, key string, disableNormalizing bool) []byte {
func getHeaderKeyBytes(kv *argsKV, key string) []byte {
kv.key = append(kv.key[:0], key...)
normalizeHeaderKey(kv.key, disableNormalizing)
normalizeHeaderKey(kv.key)
return kv.key
}
func normalizeHeaderKey(b []byte, disableNormalizing bool) {
if disableNormalizing {
return
}
func normalizeHeaderKey(b []byte) {
n := len(b)
if n == 0 {
return
@ -2152,7 +1990,7 @@ func normalizeHeaderKey(b []byte, disableNormalizing bool) {
// * foo-bar-baz -> Foo-Bar-Baz
func AppendNormalizedHeaderKey(dst []byte, key string) []byte {
dst = append(dst, key...)
normalizeHeaderKey(dst[len(dst)-len(key):], false)
normalizeHeaderKey(dst[len(dst)-len(key):])
return dst
}
@ -2173,7 +2011,6 @@ func AppendNormalizedHeaderKeyBytes(dst, key []byte) []byte {
var (
errNeedMore = errors.New("need more data: cannot find trailing lf")
errSmallBuffer = errors.New("small read buffer. Increase ReadBufferSize")
errNothingRead = errors.New("read timeout with nothing read")
)
// ErrSmallBuffer is returned when the provided buffer size is too small

View file

@ -7,7 +7,6 @@ import (
"fmt"
"io"
"mime/multipart"
"net"
"os"
"sync"
@ -45,9 +44,6 @@ type Request struct {
keepBodyBuffer bool
isTLS bool
// To detect scheme changes in redirects
schemaUpdate bool
}
// Response represents HTTP response.
@ -76,11 +72,6 @@ type Response struct {
SkipBody bool
keepBodyBuffer bool
// Remote TCPAddr from concurrently net.Conn
raddr net.Addr
// Local TCPAddr from concurrently net.Conn
laddr net.Addr
}
// SetHost sets host for the request.
@ -287,23 +278,6 @@ func (w *requestBodyWriter) Write(p []byte) (int, error) {
return len(p), nil
}
func (resp *Response) parseNetConn(conn net.Conn) {
resp.raddr = conn.RemoteAddr()
resp.laddr = conn.LocalAddr()
}
// RemoteAddr returns the remote network address. The Addr returned is shared
// by all invocations of RemoteAddr, so do not modify it.
func (resp *Response) RemoteAddr() net.Addr {
return resp.raddr
}
// LocalAddr returns the local network address. The Addr returned is shared
// by all invocations of LocalAddr, so do not modify it.
func (resp *Response) LocalAddr() net.Addr {
return resp.laddr
}
// Body returns response body.
//
// The returned body is valid until the response modification.
@ -372,7 +346,7 @@ func (resp *Response) BodyGunzip() ([]byte, error) {
}
func gunzipData(p []byte) ([]byte, error) {
var bb bytebufferpool.ByteBuffer
var bb ByteBuffer
_, err := WriteGunzip(&bb, p)
if err != nil {
return nil, err
@ -399,7 +373,7 @@ func (resp *Response) BodyInflate() ([]byte, error) {
}
func inflateData(p []byte) ([]byte, error) {
var bb bytebufferpool.ByteBuffer
var bb ByteBuffer
_, err := WriteInflate(&bb, p)
if err != nil {
return nil, err
@ -650,8 +624,6 @@ func (resp *Response) copyToSkipBody(dst *Response) {
dst.Reset()
resp.Header.CopyTo(&dst.Header)
dst.SkipBody = resp.SkipBody
dst.raddr = resp.raddr
dst.laddr = resp.laddr
}
func swapRequestBody(a, b *Request) {
@ -739,7 +711,7 @@ func (req *Request) MultipartForm() (*multipart.Form, error) {
}
func marshalMultipartForm(f *multipart.Form, boundary string) ([]byte, error) {
var buf bytebufferpool.ByteBuffer
var buf ByteBuffer
if err := WriteMultipartForm(&buf, f, boundary); err != nil {
return nil, err
}
@ -750,7 +722,7 @@ func marshalMultipartForm(f *multipart.Form, boundary string) ([]byte, error) {
// boundary to w.
func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
// Do not care about memory allocations here, since multipart
// form processing is slow.
// form processing is slooow.
if len(boundary) == 0 {
panic("BUG: form boundary cannot be empty")
}
@ -772,7 +744,7 @@ func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
// marshal files
for k, fvv := range f.File {
for _, fv := range fvv {
vw, err := mw.CreatePart(fv.Header)
vw, err := mw.CreateFormFile(k, fv.Filename)
if err != nil {
return fmt.Errorf("cannot create form file %q (%q): %s", k, fv.Filename, err)
}
@ -845,8 +817,6 @@ func (resp *Response) Reset() {
resp.Header.Reset()
resp.resetSkipHeader()
resp.SkipBody = false
resp.raddr = nil
resp.laddr = nil
}
func (resp *Response) resetSkipHeader() {
@ -874,9 +844,7 @@ func (req *Request) Read(r *bufio.Reader) error {
const defaultMaxInMemoryFileSize = 16 * 1024 * 1024
// ErrGetOnly is returned when server expects only GET requests,
// but some other type of request came (Server.GetOnly option is true).
var ErrGetOnly = errors.New("non-GET request received")
var errGetOnly = errors.New("non-GET request received")
// ReadLimitBody reads request from the given r, limiting the body size.
//
@ -910,7 +878,11 @@ func (req *Request) readLimitBody(r *bufio.Reader, maxBodySize int, getOnly bool
return err
}
if getOnly && !req.Header.IsGet() {
return ErrGetOnly
return errGetOnly
}
if req.Header.noBody() {
return nil
}
if req.MayContinue() {
@ -946,7 +918,7 @@ func (req *Request) MayContinue() bool {
// then ErrBodyTooLarge is returned.
func (req *Request) ContinueReadBody(r *bufio.Reader, maxBodySize int) error {
var err error
contentLength := req.Header.realContentLength()
contentLength := req.Header.ContentLength()
if contentLength > 0 {
if maxBodySize > 0 && contentLength > maxBodySize {
return ErrBodyTooLarge
@ -1016,6 +988,7 @@ func (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error {
bodyBuf.Reset()
bodyBuf.B, err = readBody(r, resp.Header.ContentLength(), maxBodySize, bodyBuf.B)
if err != nil {
resp.Reset()
return err
}
resp.Header.SetContentLength(len(bodyBuf.B))
@ -1136,11 +1109,8 @@ func (req *Request) Write(w *bufio.Writer) error {
req.Header.SetMultipartFormBoundary(req.multipartFormBoundary)
}
hasBody := !req.Header.ignoreBody()
hasBody := !req.Header.noBody()
if hasBody {
if len(body) == 0 {
body = req.postArgs.QueryString()
}
req.Header.SetContentLength(len(body))
}
if err = req.Header.Write(w); err != nil {
@ -1488,7 +1458,7 @@ func (resp *Response) String() string {
}
func getHTTPString(hw httpWriter) string {
w := bytebufferpool.Get()
w := AcquireByteBuffer()
bw := bufio.NewWriter(w)
if err := hw.Write(bw); err != nil {
return err.Error()
@ -1497,7 +1467,7 @@ func getHTTPString(hw httpWriter) string {
return err.Error()
}
s := string(w.B)
bytebufferpool.Put(w)
ReleaseByteBuffer(w)
return s
}
@ -1680,11 +1650,6 @@ func appendBodyFixedSize(r *bufio.Reader, dst []byte, n int) ([]byte, error) {
}
}
// ErrBrokenChunk is returned when server receives a broken chunked body (Transfer-Encoding: chunked).
type ErrBrokenChunk struct {
error
}
func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) {
if len(dst) > 0 {
panic("BUG: expected zero-length buffer")
@ -1704,9 +1669,7 @@ func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, erro
return dst, err
}
if !bytes.Equal(dst[len(dst)-strCRLFLen:], strCRLF) {
return dst, ErrBrokenChunk{
error: fmt.Errorf("cannot find crlf at the end of chunk"),
}
return dst, fmt.Errorf("cannot find crlf at the end of chunk")
}
dst = dst[:len(dst)-strCRLFLen]
if chunkSize == 0 {
@ -1720,34 +1683,19 @@ func parseChunkSize(r *bufio.Reader) (int, error) {
if err != nil {
return -1, err
}
for {
c, err := r.ReadByte()
if err != nil {
return -1, ErrBrokenChunk{
error: fmt.Errorf("cannot read '\r' char at the end of chunk size: %s", err),
}
}
// Skip any trailing whitespace after chunk size.
if c == ' ' {
continue
}
if c != '\r' {
return -1, ErrBrokenChunk{
error: fmt.Errorf("unexpected char %q at the end of chunk size. Expected %q", c, '\r'),
}
}
break
}
c, err := r.ReadByte()
if err != nil {
return -1, ErrBrokenChunk{
error: fmt.Errorf("cannot read '\n' char at the end of chunk size: %s", err),
}
return -1, fmt.Errorf("cannot read '\r' char at the end of chunk size: %s", err)
}
if c != '\r' {
return -1, fmt.Errorf("unexpected char %q at the end of chunk size. Expected %q", c, '\r')
}
c, err = r.ReadByte()
if err != nil {
return -1, fmt.Errorf("cannot read '\n' char at the end of chunk size: %s", err)
}
if c != '\n' {
return -1, ErrBrokenChunk{
error: fmt.Errorf("unexpected char %q at the end of chunk size. Expected %q", c, '\n'),
}
return -1, fmt.Errorf("unexpected char %q at the end of chunk size. Expected %q", c, '\n')
}
return n, nil
}

View file

@ -59,7 +59,7 @@ type LBClient struct {
// DefaultLBClientTimeout is the default request timeout used by LBClient
// when calling LBClient.Do.
//
// The timeout may be overridden via LBClient.Timeout.
// The timeout may be overriden via LBClient.Timeout.
const DefaultLBClientTimeout = time.Second
// DoDeadline calls DoDeadline on the least loaded client

View file

@ -4,8 +4,6 @@ package fasthttp
// so `go vet` gives a warning if this struct is copied.
//
// See https://github.com/golang/go/issues/8005#issuecomment-190753527 for details.
// and also: https://stackoverflow.com/questions/52494458/nocopy-minimal-example
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
func (*noCopy) Lock() {}

View file

@ -3,9 +3,8 @@ package stackless
import (
"errors"
"fmt"
"io"
"github.com/valyala/bytebufferpool"
"io"
)
// Writer is an interface stackless writer must conform to.

View file

@ -5,7 +5,7 @@ import (
"io"
"sync"
"github.com/valyala/fasthttp/fasthttputil"
"github.com/VictoriaMetrics/fasthttp/fasthttputil"
)
// StreamWriter must write data to w.

View file

@ -22,15 +22,11 @@ var (
strResponseContinue = []byte("HTTP/1.1 100 Continue\r\n\r\n")
strGet = []byte("GET")
strHead = []byte("HEAD")
strPost = []byte("POST")
strPut = []byte("PUT")
strDelete = []byte("DELETE")
strConnect = []byte("CONNECT")
strOptions = []byte("OPTIONS")
strTrace = []byte("TRACE")
strPatch = []byte("PATCH")
strGet = []byte("GET")
strHead = []byte("HEAD")
strPost = []byte("POST")
strPut = []byte("PUT")
strDelete = []byte("DELETE")
strExpect = []byte("Expect")
strConnection = []byte("Connection")
@ -53,20 +49,17 @@ var (
strRange = []byte("Range")
strContentRange = []byte("Content-Range")
strCookieExpires = []byte("expires")
strCookieDomain = []byte("domain")
strCookiePath = []byte("path")
strCookieHTTPOnly = []byte("HttpOnly")
strCookieSecure = []byte("secure")
strCookieMaxAge = []byte("max-age")
strCookieSameSite = []byte("SameSite")
strCookieSameSiteLax = []byte("Lax")
strCookieSameSiteStrict = []byte("Strict")
strCookieExpires = []byte("expires")
strCookieDomain = []byte("domain")
strCookiePath = []byte("path")
strCookieHTTPOnly = []byte("HttpOnly")
strCookieSecure = []byte("secure")
strClose = []byte("close")
strGzip = []byte("gzip")
strDeflate = []byte("deflate")
strKeepAlive = []byte("keep-alive")
strKeepAliveCamelCase = []byte("Keep-Alive")
strUpgrade = []byte("Upgrade")
strChunked = []byte("chunked")
strIdentity = []byte("identity")

View file

@ -33,7 +33,7 @@ import (
// * foo.bar:80
// * aaa.com:8080
func Dial(addr string) (net.Conn, error) {
return defaultDialer.Dial(addr)
return getDialer(DefaultDialTimeout, false)(addr)
}
// DialTimeout dials the given TCP addr using tcp4 using the given timeout.
@ -58,7 +58,7 @@ func Dial(addr string) (net.Conn, error) {
// * foo.bar:80
// * aaa.com:8080
func DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
return defaultDialer.DialTimeout(addr, timeout)
return getDialer(timeout, false)(addr)
}
// DialDualStack dials the given TCP addr using both tcp4 and tcp6.
@ -86,7 +86,7 @@ func DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
// * foo.bar:80
// * aaa.com:8080
func DialDualStack(addr string) (net.Conn, error) {
return defaultDialer.DialDualStack(addr)
return getDialer(DefaultDialTimeout, true)(addr)
}
// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6
@ -112,22 +112,45 @@ func DialDualStack(addr string) (net.Conn, error) {
// * foo.bar:80
// * aaa.com:8080
func DialDualStackTimeout(addr string, timeout time.Duration) (net.Conn, error) {
return defaultDialer.DialDualStackTimeout(addr, timeout)
return getDialer(timeout, true)(addr)
}
func getDialer(timeout time.Duration, dualStack bool) DialFunc {
if timeout <= 0 {
timeout = DefaultDialTimeout
}
timeoutRounded := int(timeout.Seconds()*10 + 9)
m := dialMap
if dualStack {
m = dialDualStackMap
}
dialMapLock.Lock()
d := m[timeoutRounded]
if d == nil {
dialer := dialerStd
if dualStack {
dialer = dialerDualStack
}
d = dialer.NewDial(timeout)
m[timeoutRounded] = d
}
dialMapLock.Unlock()
return d
}
var (
defaultDialer = &TCPDialer{Concurrency: 1000}
dialerStd = &tcpDialer{}
dialerDualStack = &tcpDialer{DualStack: true}
dialMap = make(map[int]DialFunc)
dialDualStackMap = make(map[int]DialFunc)
dialMapLock sync.Mutex
)
// TCPDialer contains options to control a group of Dial calls.
type TCPDialer struct {
// Concurrency controls the maximum number of concurrent Dails
// that can be performed using this object.
// Setting this to 0 means unlimited.
//
// WARNING: This can only be changed before the first Dial.
// Changes made after the first Dial will not affect anything.
Concurrency int
type tcpDialer struct {
DualStack bool
tcpAddrsLock sync.Mutex
tcpAddrsMap map[string]*tcpAddrEntry
@ -137,145 +160,41 @@ type TCPDialer struct {
once sync.Once
}
// Dial dials the given TCP addr using tcp4.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
// * It returns ErrDialTimeout if connection cannot be established during
// DefaultDialTimeout seconds. Use DialTimeout for customizing dial timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func (d *TCPDialer) Dial(addr string) (net.Conn, error) {
return d.dial(addr, false, DefaultDialTimeout)
}
const maxDialConcurrency = 1000
// DialTimeout dials the given TCP addr using tcp4 using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func (d *TCPDialer) DialTimeout(addr string, timeout time.Duration) (net.Conn, error) {
return d.dial(addr, false, timeout)
}
// DialDualStack dials the given TCP addr using both tcp4 and tcp6.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
// * It returns ErrDialTimeout if connection cannot be established during
// DefaultDialTimeout seconds. Use DialDualStackTimeout for custom dial
// timeout.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func (d *TCPDialer) DialDualStack(addr string) (net.Conn, error) {
return d.dial(addr, true, DefaultDialTimeout)
}
// DialDualStackTimeout dials the given TCP addr using both tcp4 and tcp6
// using the given timeout.
//
// This function has the following additional features comparing to net.Dial:
//
// * It reduces load on DNS resolver by caching resolved TCP addressed
// for DefaultDNSCacheDuration.
// * It dials all the resolved TCP addresses in round-robin manner until
// connection is established. This may be useful if certain addresses
// are temporarily unreachable.
//
// This dialer is intended for custom code wrapping before passing
// to Client.Dial or HostClient.Dial.
//
// For instance, per-host counters and/or limits may be implemented
// by such wrappers.
//
// The addr passed to the function must contain port. Example addr values:
//
// * foobar.baz:443
// * foo.bar:80
// * aaa.com:8080
func (d *TCPDialer) DialDualStackTimeout(addr string, timeout time.Duration) (net.Conn, error) {
return d.dial(addr, true, timeout)
}
func (d *TCPDialer) dial(addr string, dualStack bool, timeout time.Duration) (net.Conn, error) {
func (d *tcpDialer) NewDial(timeout time.Duration) DialFunc {
d.once.Do(func() {
if d.Concurrency > 0 {
d.concurrencyCh = make(chan struct{}, d.Concurrency)
}
d.concurrencyCh = make(chan struct{}, maxDialConcurrency)
d.tcpAddrsMap = make(map[string]*tcpAddrEntry)
go d.tcpAddrsClean()
})
addrs, idx, err := d.getTCPAddrs(addr, dualStack)
if err != nil {
return nil, err
}
network := "tcp4"
if dualStack {
network = "tcp"
}
var conn net.Conn
n := uint32(len(addrs))
deadline := time.Now().Add(timeout)
for n > 0 {
conn, err = tryDial(network, &addrs[idx%n], deadline, d.concurrencyCh)
if err == nil {
return conn, nil
}
if err == ErrDialTimeout {
return func(addr string) (net.Conn, error) {
addrs, idx, err := d.getTCPAddrs(addr)
if err != nil {
return nil, err
}
idx++
n--
network := "tcp4"
if d.DualStack {
network = "tcp"
}
var conn net.Conn
n := uint32(len(addrs))
deadline := time.Now().Add(timeout)
for n > 0 {
conn, err = tryDial(network, &addrs[idx%n], deadline, d.concurrencyCh)
if err == nil {
return conn, nil
}
if err == ErrDialTimeout {
return nil, err
}
idx++
n--
}
return nil, err
}
return nil, err
}
func tryDial(network string, addr *net.TCPAddr, deadline time.Time, concurrencyCh chan struct{}) (net.Conn, error) {
@ -284,22 +203,26 @@ func tryDial(network string, addr *net.TCPAddr, deadline time.Time, concurrencyC
return nil, ErrDialTimeout
}
if concurrencyCh != nil {
select {
case concurrencyCh <- struct{}{}:
default:
tc := acquireTimer(timeout)
isTimeout := false
select {
case concurrencyCh <- struct{}{}:
default:
tc := AcquireTimer(timeout)
isTimeout := false
select {
case concurrencyCh <- struct{}{}:
case <-tc.C:
isTimeout = true
}
ReleaseTimer(tc)
if isTimeout {
return nil, ErrDialTimeout
}
case <-tc.C:
isTimeout = true
}
releaseTimer(tc)
if isTimeout {
return nil, ErrDialTimeout
}
}
timeout = -time.Since(deadline)
if timeout <= 0 {
<-concurrencyCh
return nil, ErrDialTimeout
}
chv := dialResultChanPool.Get()
@ -311,9 +234,7 @@ func tryDial(network string, addr *net.TCPAddr, deadline time.Time, concurrencyC
var dr dialResult
dr.conn, dr.err = net.DialTCP(network, nil, addr)
ch <- dr
if concurrencyCh != nil {
<-concurrencyCh
}
<-concurrencyCh
}()
var (
@ -321,7 +242,7 @@ func tryDial(network string, addr *net.TCPAddr, deadline time.Time, concurrencyC
err error
)
tc := AcquireTimer(timeout)
tc := acquireTimer(timeout)
select {
case dr := <-ch:
conn = dr.conn
@ -330,7 +251,7 @@ func tryDial(network string, addr *net.TCPAddr, deadline time.Time, concurrencyC
case <-tc.C:
err = ErrDialTimeout
}
ReleaseTimer(tc)
releaseTimer(tc)
return conn, err
}
@ -361,7 +282,7 @@ type tcpAddrEntry struct {
// by Dial* functions.
const DefaultDNSCacheDuration = time.Minute
func (d *TCPDialer) tcpAddrsClean() {
func (d *tcpDialer) tcpAddrsClean() {
expireDuration := 2 * DefaultDNSCacheDuration
for {
time.Sleep(time.Second)
@ -377,7 +298,7 @@ func (d *TCPDialer) tcpAddrsClean() {
}
}
func (d *TCPDialer) getTCPAddrs(addr string, dualStack bool) ([]net.TCPAddr, uint32, error) {
func (d *tcpDialer) getTCPAddrs(addr string) ([]net.TCPAddr, uint32, error) {
d.tcpAddrsLock.Lock()
e := d.tcpAddrsMap[addr]
if e != nil && !e.pending && time.Since(e.resolveTime) > DefaultDNSCacheDuration {
@ -387,7 +308,7 @@ func (d *TCPDialer) getTCPAddrs(addr string, dualStack bool) ([]net.TCPAddr, uin
d.tcpAddrsLock.Unlock()
if e == nil {
addrs, err := resolveTCPAddrs(addr, dualStack)
addrs, err := resolveTCPAddrs(addr, d.DualStack)
if err != nil {
d.tcpAddrsLock.Lock()
e = d.tcpAddrsMap[addr]

View file

@ -26,12 +26,7 @@ func stopTimer(t *time.Timer) {
}
}
// AcquireTimer returns a time.Timer from the pool and updates it to
// send the current time on its channel after at least timeout.
//
// The returned Timer may be returned to the pool with ReleaseTimer
// when no longer needed. This allows reducing GC load.
func AcquireTimer(timeout time.Duration) *time.Timer {
func acquireTimer(timeout time.Duration) *time.Timer {
v := timerPool.Get()
if v == nil {
return time.NewTimer(timeout)
@ -41,12 +36,7 @@ func AcquireTimer(timeout time.Duration) *time.Timer {
return t
}
// ReleaseTimer returns the time.Timer acquired via AcquireTimer to the pool
// and prevents the Timer from firing.
//
// Do not access the released time.Timer or read from it's channel otherwise
// data races may occur.
func ReleaseTimer(t *time.Timer) {
func releaseTimer(t *time.Timer) {
stopTimer(t)
timerPool.Put(t)
}

View file

@ -180,7 +180,7 @@ func (u *URI) Reset() {
u.parsedQueryArgs = false
// There is no need in u.fullURI = u.fullURI[:0], since full uri
// is calculated on each call to FullURI().
// is calucalted on each call to FullURI().
// There is no need in u.requestURI = u.requestURI[:0], since requestURI
// is calculated on each call to RequestURI().

View file

@ -16,7 +16,7 @@ import (
type workerPool struct {
// Function for serving server connections.
// It must leave c unclosed.
WorkerFunc ServeHandler
WorkerFunc func(c net.Conn) error
MaxWorkersCount int
@ -35,8 +35,6 @@ type workerPool struct {
stopCh chan struct{}
workerChanPool sync.Pool
connState func(net.Conn, ConnState)
}
type workerChan struct {
@ -189,7 +187,7 @@ func (wp *workerPool) getCh() *workerChan {
}
func (wp *workerPool) release(ch *workerChan) bool {
ch.lastUseTime = time.Now()
ch.lastUseTime = CoarseTimeNow()
wp.lock.Lock()
if wp.mustStop {
wp.lock.Unlock()
@ -213,16 +211,12 @@ func (wp *workerPool) workerFunc(ch *workerChan) {
errStr := err.Error()
if wp.LogAllErrors || !(strings.Contains(errStr, "broken pipe") ||
strings.Contains(errStr, "reset by peer") ||
strings.Contains(errStr, "request headers: small read buffer") ||
strings.Contains(errStr, "i/o timeout")) {
wp.Logger.Printf("error when serving connection %q<->%q: %s", c.LocalAddr(), c.RemoteAddr(), err)
}
}
if err == errHijacked {
wp.connState(c, StateHijacked)
} else {
if err != errHijacked {
c.Close()
wp.connState(c, StateClosed)
}
c = nil

View file

@ -1,36 +0,0 @@
language: go
go:
- tip
- 1.11.x
- 1.10.x
- 1.9.x
os:
- linux
- osx
matrix:
allow_failures:
- tip
fast_finish: true
before_install:
- go get -t -v ./...
# - go get -v golang.org/x/tools/cmd/goimports
script:
# TODO(@kirilldanshin)
# - test -z "$(goimports -d $(find . -type f -name '*.go' -not -path "./vendor/*"))"
# build test for supported platforms
- GOOS=linux go build
- GOOS=darwin go build
- GOOS=freebsd go build
- GOOS=windows go build
- GOARCH=386 go build
# run tests on a standard platform
- go test -v ./...
# run tests with the race detector as well
- go test -race -v ./...

View file

@ -1,585 +0,0 @@
[![Build Status](https://travis-ci.org/valyala/fasthttp.svg)](https://travis-ci.org/valyala/fasthttp)
[![GoDoc](https://godoc.org/github.com/valyala/fasthttp?status.svg)](http://godoc.org/github.com/valyala/fasthttp)
[![Go Report](https://goreportcard.com/badge/github.com/valyala/fasthttp)](https://goreportcard.com/report/github.com/valyala/fasthttp)
# fasthttp
Fast HTTP implementation for Go.
Currently fasthttp is successfully used by [VertaMedia](https://vertamedia.com/)
in a production serving up to 200K rps from more than 1.5M concurrent keep-alive
connections per physical server.
[TechEmpower Benchmark round 12 results](https://www.techempower.com/benchmarks/#section=data-r12&hw=peak&test=plaintext)
[Server Benchmarks](#http-server-performance-comparison-with-nethttp)
[Client Benchmarks](#http-client-comparison-with-nethttp)
[Install](#install)
[Documentation](https://godoc.org/github.com/valyala/fasthttp)
[Examples from docs](https://godoc.org/github.com/valyala/fasthttp#pkg-examples)
[Code examples](examples)
[Awesome fasthttp tools](https://github.com/fasthttp)
[Switching from net/http to fasthttp](#switching-from-nethttp-to-fasthttp)
[Fasthttp best practices](#fasthttp-best-practices)
[Tricks with byte buffers](#tricks-with-byte-buffers)
[Related projects](#related-projects)
[FAQ](#faq)
# HTTP server performance comparison with [net/http](https://golang.org/pkg/net/http/)
In short, fasthttp server is up to 10 times faster than net/http.
Below are benchmark results.
*GOMAXPROCS=1*
net/http server:
```
$ GOMAXPROCS=1 go test -bench=NetHTTPServerGet -benchmem -benchtime=10s
BenchmarkNetHTTPServerGet1ReqPerConn 1000000 12052 ns/op 2297 B/op 29 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn 1000000 12278 ns/op 2327 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn 2000000 8903 ns/op 2112 B/op 19 allocs/op
BenchmarkNetHTTPServerGet10KReqPerConn 2000000 8451 ns/op 2058 B/op 18 allocs/op
BenchmarkNetHTTPServerGet1ReqPerConn10KClients 500000 26733 ns/op 3229 B/op 29 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn10KClients 1000000 23351 ns/op 3211 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn10KClients 1000000 13390 ns/op 2483 B/op 19 allocs/op
BenchmarkNetHTTPServerGet100ReqPerConn10KClients 1000000 13484 ns/op 2171 B/op 18 allocs/op
```
fasthttp server:
```
$ GOMAXPROCS=1 go test -bench=kServerGet -benchmem -benchtime=10s
BenchmarkServerGet1ReqPerConn 10000000 1559 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn 10000000 1248 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn 20000000 797 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10KReqPerConn 20000000 716 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet1ReqPerConn10KClients 10000000 1974 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn10KClients 10000000 1352 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn10KClients 20000000 789 ns/op 2 B/op 0 allocs/op
BenchmarkServerGet100ReqPerConn10KClients 20000000 604 ns/op 0 B/op 0 allocs/op
```
*GOMAXPROCS=4*
net/http server:
```
$ GOMAXPROCS=4 go test -bench=NetHTTPServerGet -benchmem -benchtime=10s
BenchmarkNetHTTPServerGet1ReqPerConn-4 3000000 4529 ns/op 2389 B/op 29 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn-4 5000000 3896 ns/op 2418 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn-4 5000000 3145 ns/op 2160 B/op 19 allocs/op
BenchmarkNetHTTPServerGet10KReqPerConn-4 5000000 3054 ns/op 2065 B/op 18 allocs/op
BenchmarkNetHTTPServerGet1ReqPerConn10KClients-4 1000000 10321 ns/op 3710 B/op 30 allocs/op
BenchmarkNetHTTPServerGet2ReqPerConn10KClients-4 2000000 7556 ns/op 3296 B/op 24 allocs/op
BenchmarkNetHTTPServerGet10ReqPerConn10KClients-4 5000000 3905 ns/op 2349 B/op 19 allocs/op
BenchmarkNetHTTPServerGet100ReqPerConn10KClients-4 5000000 3435 ns/op 2130 B/op 18 allocs/op
```
fasthttp server:
```
$ GOMAXPROCS=4 go test -bench=kServerGet -benchmem -benchtime=10s
BenchmarkServerGet1ReqPerConn-4 10000000 1141 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn-4 20000000 707 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn-4 30000000 341 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10KReqPerConn-4 50000000 310 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet1ReqPerConn10KClients-4 10000000 1119 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet2ReqPerConn10KClients-4 20000000 644 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet10ReqPerConn10KClients-4 30000000 346 ns/op 0 B/op 0 allocs/op
BenchmarkServerGet100ReqPerConn10KClients-4 50000000 282 ns/op 0 B/op 0 allocs/op
```
# HTTP client comparison with net/http
In short, fasthttp client is up to 10 times faster than net/http.
Below are benchmark results.
*GOMAXPROCS=1*
net/http client:
```
$ GOMAXPROCS=1 go test -bench='HTTPClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkNetHTTPClientDoFastServer 1000000 12567 ns/op 2616 B/op 35 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1TCP 200000 67030 ns/op 5028 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10TCP 300000 51098 ns/op 5031 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100TCP 300000 45096 ns/op 5026 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1Inmemory 500000 24779 ns/op 5035 B/op 57 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10Inmemory 1000000 26425 ns/op 5035 B/op 57 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100Inmemory 500000 28515 ns/op 5045 B/op 57 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1000Inmemory 500000 39511 ns/op 5096 B/op 56 allocs/op
```
fasthttp client:
```
$ GOMAXPROCS=1 go test -bench='kClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkClientDoFastServer 20000000 865 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1TCP 1000000 18711 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10TCP 1000000 14664 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100TCP 1000000 14043 ns/op 1 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1Inmemory 5000000 3965 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10Inmemory 3000000 4060 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100Inmemory 5000000 3396 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1000Inmemory 5000000 3306 ns/op 2 B/op 0 allocs/op
```
*GOMAXPROCS=4*
net/http client:
```
$ GOMAXPROCS=4 go test -bench='HTTPClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkNetHTTPClientDoFastServer-4 2000000 8774 ns/op 2619 B/op 35 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1TCP-4 500000 22951 ns/op 5047 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10TCP-4 1000000 19182 ns/op 5037 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100TCP-4 1000000 16535 ns/op 5031 B/op 55 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1Inmemory-4 1000000 14495 ns/op 5038 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd10Inmemory-4 1000000 10237 ns/op 5034 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd100Inmemory-4 1000000 10125 ns/op 5045 B/op 56 allocs/op
BenchmarkNetHTTPClientGetEndToEnd1000Inmemory-4 1000000 11132 ns/op 5136 B/op 56 allocs/op
```
fasthttp client:
```
$ GOMAXPROCS=4 go test -bench='kClient(Do|GetEndToEnd)' -benchmem -benchtime=10s
BenchmarkClientDoFastServer-4 50000000 397 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1TCP-4 2000000 7388 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10TCP-4 2000000 6689 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100TCP-4 3000000 4927 ns/op 1 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1Inmemory-4 10000000 1604 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd10Inmemory-4 10000000 1458 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd100Inmemory-4 10000000 1329 ns/op 0 B/op 0 allocs/op
BenchmarkClientGetEndToEnd1000Inmemory-4 10000000 1316 ns/op 5 B/op 0 allocs/op
```
# Install
```
go get -u github.com/valyala/fasthttp
```
# Switching from net/http to fasthttp
Unfortunately, fasthttp doesn't provide API identical to net/http.
See the [FAQ](#faq) for details.
There is [net/http -> fasthttp handler converter](https://godoc.org/github.com/valyala/fasthttp/fasthttpadaptor),
but it is better to write fasthttp request handlers by hand in order to use
all of the fasthttp advantages (especially high performance :) ).
Important points:
* Fasthttp works with [RequestHandler functions](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
instead of objects implementing [Handler interface](https://golang.org/pkg/net/http/#Handler).
Fortunately, it is easy to pass bound struct methods to fasthttp:
```go
type MyHandler struct {
foobar string
}
// request handler in net/http style, i.e. method bound to MyHandler struct.
func (h *MyHandler) HandleFastHTTP(ctx *fasthttp.RequestCtx) {
// notice that we may access MyHandler properties here - see h.foobar.
fmt.Fprintf(ctx, "Hello, world! Requested path is %q. Foobar is %q",
ctx.Path(), h.foobar)
}
// request handler in fasthttp style, i.e. just plain function.
func fastHTTPHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hi there! RequestURI is %q", ctx.RequestURI())
}
// pass bound struct method to fasthttp
myHandler := &MyHandler{
foobar: "foobar",
}
fasthttp.ListenAndServe(":8080", myHandler.HandleFastHTTP)
// pass plain function to fasthttp
fasthttp.ListenAndServe(":8081", fastHTTPHandler)
```
* The [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
accepts only one argument - [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx).
It contains all the functionality required for http request processing
and response writing. Below is an example of a simple request handler conversion
from net/http to fasthttp.
```go
// net/http request handler
requestHandler := func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/foo":
fooHandler(w, r)
case "/bar":
barHandler(w, r)
default:
http.Error(w, "Unsupported path", http.StatusNotFound)
}
}
```
```go
// the corresponding fasthttp request handler
requestHandler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/foo":
fooHandler(ctx)
case "/bar":
barHandler(ctx)
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
```
* Fasthttp allows setting response headers and writing response body
in an arbitrary order. There is no 'headers first, then body' restriction
like in net/http. The following code is valid for fasthttp:
```go
requestHandler := func(ctx *fasthttp.RequestCtx) {
// set some headers and status code first
ctx.SetContentType("foo/bar")
ctx.SetStatusCode(fasthttp.StatusOK)
// then write the first part of body
fmt.Fprintf(ctx, "this is the first part of body\n")
// then set more headers
ctx.Response.Header.Set("Foo-Bar", "baz")
// then write more body
fmt.Fprintf(ctx, "this is the second part of body\n")
// then override already written body
ctx.SetBody([]byte("this is completely new body contents"))
// then update status code
ctx.SetStatusCode(fasthttp.StatusNotFound)
// basically, anything may be updated many times before
// returning from RequestHandler.
//
// Unlike net/http fasthttp doesn't put response to the wire until
// returning from RequestHandler.
}
```
* Fasthttp doesn't provide [ServeMux](https://golang.org/pkg/net/http/#ServeMux),
but there are more powerful third-party routers and web frameworks
with fasthttp support:
* [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing)
* [fasthttprouter](https://github.com/buaazp/fasthttprouter)
* [lu](https://github.com/vincentLiuxiang/lu)
* [atreugo](https://github.com/savsgio/atreugo)
Net/http code with simple ServeMux is trivially converted to fasthttp code:
```go
// net/http code
m := &http.ServeMux{}
m.HandleFunc("/foo", fooHandlerFunc)
m.HandleFunc("/bar", barHandlerFunc)
m.Handle("/baz", bazHandler)
http.ListenAndServe(":80", m)
```
```go
// the corresponding fasthttp code
m := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/foo":
fooHandlerFunc(ctx)
case "/bar":
barHandlerFunc(ctx)
case "/baz":
bazHandler.HandlerFunc(ctx)
default:
ctx.Error("not found", fasthttp.StatusNotFound)
}
}
fasthttp.ListenAndServe(":80", m)
```
* net/http -> fasthttp conversion table:
* All the pseudocode below assumes w, r and ctx have these types:
```go
var (
w http.ResponseWriter
r *http.Request
ctx *fasthttp.RequestCtx
)
```
* r.Body -> [ctx.PostBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody)
* r.URL.Path -> [ctx.Path()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Path)
* r.URL -> [ctx.URI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.URI)
* r.Method -> [ctx.Method()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Method)
* r.Header -> [ctx.Request.Header](https://godoc.org/github.com/valyala/fasthttp#RequestHeader)
* r.Header.Get() -> [ctx.Request.Header.Peek()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Peek)
* r.Host -> [ctx.Host()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Host)
* r.Form -> [ctx.QueryArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.QueryArgs) +
[ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.PostForm -> [ctx.PostArgs()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostArgs)
* r.FormValue() -> [ctx.FormValue()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormValue)
* r.FormFile() -> [ctx.FormFile()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.FormFile)
* r.MultipartForm -> [ctx.MultipartForm()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.MultipartForm)
* r.RemoteAddr -> [ctx.RemoteAddr()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)
* r.RequestURI -> [ctx.RequestURI()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.RequestURI)
* r.TLS -> [ctx.IsTLS()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.IsTLS)
* r.Cookie() -> [ctx.Request.Header.Cookie()](https://godoc.org/github.com/valyala/fasthttp#RequestHeader.Cookie)
* r.Referer() -> [ctx.Referer()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Referer)
* r.UserAgent() -> [ctx.UserAgent()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.UserAgent)
* w.Header() -> [ctx.Response.Header](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader)
* w.Header().Set() -> [ctx.Response.Header.Set()](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader.Set)
* w.Header().Set("Content-Type") -> [ctx.SetContentType()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetContentType)
* w.Header().Set("Set-Cookie") -> [ctx.Response.Header.SetCookie()](https://godoc.org/github.com/valyala/fasthttp#ResponseHeader.SetCookie)
* w.Write() -> [ctx.Write()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Write),
[ctx.SetBody()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBody),
[ctx.SetBodyStream()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStream),
[ctx.SetBodyStreamWriter()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter)
* w.WriteHeader() -> [ctx.SetStatusCode()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.SetStatusCode)
* w.(http.Hijacker).Hijack() -> [ctx.Hijack()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack)
* http.Error() -> [ctx.Error()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Error)
* http.FileServer() -> [fasthttp.FSHandler()](https://godoc.org/github.com/valyala/fasthttp#FSHandler),
[fasthttp.FS](https://godoc.org/github.com/valyala/fasthttp#FS)
* http.ServeFile() -> [fasthttp.ServeFile()](https://godoc.org/github.com/valyala/fasthttp#ServeFile)
* http.Redirect() -> [ctx.Redirect()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Redirect)
* http.NotFound() -> [ctx.NotFound()](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.NotFound)
* http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://godoc.org/github.com/valyala/fasthttp#PathRewriteFunc)
* *VERY IMPORTANT!* Fasthttp disallows holding references
to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx) or to its'
members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
Otherwise [data races](http://blog.golang.org/race-detector) are inevitable.
Carefully inspect all the net/http request handlers converted to fasthttp whether
they retain references to RequestCtx or to its' members after returning.
RequestCtx provides the following _band aids_ for this case:
* Wrap RequestHandler into [TimeoutHandler](https://godoc.org/github.com/valyala/fasthttp#TimeoutHandler).
* Call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from RequestHandler if there are references to RequestCtx or to its' members.
See [the example](https://godoc.org/github.com/valyala/fasthttp#example-RequestCtx-TimeoutError)
for more details.
Use this brilliant tool - [race detector](http://blog.golang.org/race-detector) -
for detecting and eliminating data races in your program. If you detected
data race related to fasthttp in your program, then there is high probability
you forgot calling [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
* Blind switching from net/http to fasthttp won't give you performance boost.
While fasthttp is optimized for speed, its' performance may be easily saturated
by slow [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
So [profile](http://blog.golang.org/profiling-go-programs) and optimize your
code after switching to fasthttp. For instance, use [quicktemplate](https://github.com/valyala/quicktemplate)
instead of [html/template](https://golang.org/pkg/html/template/).
* See also [fasthttputil](https://godoc.org/github.com/valyala/fasthttp/fasthttputil),
[fasthttpadaptor](https://godoc.org/github.com/valyala/fasthttp/fasthttpadaptor) and
[expvarhandler](https://godoc.org/github.com/valyala/fasthttp/expvarhandler).
# Performance optimization tips for multi-core systems
* Use [reuseport](https://godoc.org/github.com/valyala/fasthttp/reuseport) listener.
* Run a separate server instance per CPU core with GOMAXPROCS=1.
* Pin each server instance to a separate CPU core using [taskset](http://linux.die.net/man/1/taskset).
* Ensure the interrupts of multiqueue network card are evenly distributed between CPU cores.
See [this article](https://blog.cloudflare.com/how-to-achieve-low-latency/) for details.
* Use Go 1.6 as it provides some considerable performance improvements.
# Fasthttp best practices
* Do not allocate objects and `[]byte` buffers - just reuse them as much
as possible. Fasthttp API design encourages this.
* [sync.Pool](https://golang.org/pkg/sync/#Pool) is your best friend.
* [Profile your program](http://blog.golang.org/profiling-go-programs)
in production.
`go tool pprof --alloc_objects your-program mem.pprof` usually gives better
insights for optimization opportunities than `go tool pprof your-program cpu.pprof`.
* Write [tests and benchmarks](https://golang.org/pkg/testing/) for hot paths.
* Avoid conversion between `[]byte` and `string`, since this may result in memory
allocation+copy. Fasthttp API provides functions for both `[]byte` and `string` -
use these functions instead of converting manually between `[]byte` and `string`.
There are some exceptions - see [this wiki page](https://github.com/golang/go/wiki/CompilerOptimizations#string-and-byte)
for more details.
* Verify your tests and production code under
[race detector](https://golang.org/doc/articles/race_detector.html) on a regular basis.
* Prefer [quicktemplate](https://github.com/valyala/quicktemplate) instead of
[html/template](https://golang.org/pkg/html/template/) in your webserver.
# Tricks with `[]byte` buffers
The following tricks are used by fasthttp. Use them in your code too.
* Standard Go functions accept nil buffers
```go
var (
// both buffers are uninitialized
dst []byte
src []byte
)
dst = append(dst, src...) // is legal if dst is nil and/or src is nil
copy(dst, src) // is legal if dst is nil and/or src is nil
(string(src) == "") // is true if src is nil
(len(src) == 0) // is true if src is nil
src = src[:0] // works like a charm with nil src
// this for loop doesn't panic if src is nil
for i, ch := range src {
doSomething(i, ch)
}
```
So throw away nil checks for `[]byte` buffers from you code. For example,
```go
srcLen := 0
if src != nil {
srcLen = len(src)
}
```
becomes
```go
srcLen := len(src)
```
* String may be appended to `[]byte` buffer with `append`
```go
dst = append(dst, "foobar"...)
```
* `[]byte` buffer may be extended to its' capacity.
```go
buf := make([]byte, 100)
a := buf[:10] // len(a) == 10, cap(a) == 100.
b := a[:100] // is valid, since cap(a) == 100.
```
* All fasthttp functions accept nil `[]byte` buffer
```go
statusCode, body, err := fasthttp.Get(nil, "http://google.com/")
uintBuf := fasthttp.AppendUint(nil, 1234)
```
# Related projects
* [fasthttp](https://github.com/fasthttp) - various useful
helpers for projects based on fasthttp.
* [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing) - fast and
powerful routing package for fasthttp servers.
* [fasthttprouter](https://github.com/buaazp/fasthttprouter) - a high
performance fasthttp request router that scales well.
* [gramework](https://github.com/gramework/gramework) - a web framework made by one of fasthttp maintainers
* [lu](https://github.com/vincentLiuxiang/lu) - a high performance
go middleware web framework which is based on fasthttp.
* [websocket](https://github.com/fasthttp/websocket) - Gorilla-based
websocket implementation for fasthttp.
* [fasthttpsession](https://github.com/phachon/fasthttpsession) - a fast and powerful session package for fasthttp servers.
* [atreugo](https://github.com/savsgio/atreugo) - Micro-framework to make simple the use of routing and middlewares.
* [kratgo](https://github.com/savsgio/kratgo) - Simple, lightweight and ultra-fast HTTP Cache to speed up your websites.
# FAQ
* *Why creating yet another http package instead of optimizing net/http?*
Because net/http API limits many optimization opportunities.
For example:
* net/http Request object lifetime isn't limited by request handler execution
time. So the server must create a new request object per each request instead
of reusing existing objects like fasthttp does.
* net/http headers are stored in a `map[string][]string`. So the server
must parse all the headers, convert them from `[]byte` to `string` and put
them into the map before calling user-provided request handler.
This all requires unnecessary memory allocations avoided by fasthttp.
* net/http client API requires creating a new response object per each request.
* *Why fasthttp API is incompatible with net/http?*
Because net/http API limits many optimization opportunities. See the answer
above for more details. Also certain net/http API parts are suboptimal
for use:
* Compare [net/http connection hijacking](https://golang.org/pkg/net/http/#Hijacker)
to [fasthttp connection hijacking](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack).
* Compare [net/http Request.Body reading](https://golang.org/pkg/net/http/#Request)
to [fasthttp request body reading](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.PostBody).
* *Why fasthttp doesn't support HTTP/2.0 and WebSockets?*
[HTTP/2.0 support](https://github.com/fasthttp/http2) is in progress. [WebSockets](https://github.com/fasthttp/websockets) has been done already.
Third parties also may use [RequestCtx.Hijack](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.Hijack)
for implementing these goodies.
* *Are there known net/http advantages comparing to fasthttp?*
Yes:
* net/http supports [HTTP/2.0 starting from go1.6](https://http2.golang.org/).
* net/http API is stable, while fasthttp API constantly evolves.
* net/http handles more HTTP corner cases.
* net/http should contain less bugs, since it is used and tested by much
wider audience.
* net/http works on Go older than 1.5.
* *Why fasthttp API prefers returning `[]byte` instead of `string`?*
Because `[]byte` to `string` conversion isn't free - it requires memory
allocation and copy. Feel free wrapping returned `[]byte` result into
`string()` if you prefer working with strings instead of byte slices.
But be aware that this has non-zero overhead.
* *Which GO versions are supported by fasthttp?*
Go1.5+. Older versions won't be supported, since their standard package
[miss useful functions](https://github.com/valyala/fasthttp/issues/5).
**NOTE**: Go 1.9.7 is the oldest tested version. We recommend you to update as soon as you can. As of 1.11.3 we will drop 1.9.x support.
* *Please provide real benchmark data and server information*
See [this issue](https://github.com/valyala/fasthttp/issues/4).
* *Are there plans to add request routing to fasthttp?*
There are no plans to add request routing into fasthttp.
Use third-party routers and web frameworks with fasthttp support:
* [fasthttp-routing](https://github.com/qiangxue/fasthttp-routing)
* [fasthttprouter](https://github.com/buaazp/fasthttprouter)
* [gramework](https://github.com/gramework/gramework)
* [lu](https://github.com/vincentLiuxiang/lu)
* [atreugo](https://github.com/savsgio/atreugo)
See also [this issue](https://github.com/valyala/fasthttp/issues/9) for more info.
* *I detected data race in fasthttp!*
Cool! [File a bug](https://github.com/valyala/fasthttp/issues/new). But before
doing this check the following in your code:
* Make sure there are no references to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx)
or to its' members after returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler).
* Make sure you call [TimeoutError](https://godoc.org/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
before returning from [RequestHandler](https://godoc.org/github.com/valyala/fasthttp#RequestHandler)
if there are references to [RequestCtx](https://godoc.org/github.com/valyala/fasthttp#RequestCtx)
or to its' members, which may be accessed by other goroutines.
* *I didn't find an answer for my question here*
Try exploring [these questions](https://github.com/valyala/fasthttp/issues?q=label%3Aquestion).

View file

@ -1,13 +0,0 @@
package fasthttp
import (
"time"
)
// CoarseTimeNow returns the current time truncated to the nearest second.
//
// Deprecated: This is slower than calling time.Now() directly.
// This is now time.Now().Truncate(time.Second) shortcut.
func CoarseTimeNow() time.Time {
return time.Now().Truncate(time.Second)
}

View file

@ -1,9 +0,0 @@
module github.com/valyala/fasthttp
require (
github.com/klauspost/compress v1.4.0
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e // indirect
github.com/valyala/bytebufferpool v1.0.0
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3
)

View file

@ -1,10 +0,0 @@
github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a h1:0R4NLDRDZX6JcmhJgXi5E4b8Wg84ihbmUKp/GvSPEzc=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3 h1:czFLhve3vsQetD6JOJ8NZZvGQIXlnN3/yXxbT6/awxI=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

8
vendor/modules.txt vendored
View file

@ -12,6 +12,10 @@ cloud.google.com/go/storage
github.com/BurntSushi/toml
# github.com/VictoriaMetrics/fastcache v1.5.7
github.com/VictoriaMetrics/fastcache
# github.com/VictoriaMetrics/fasthttp v1.0.1
github.com/VictoriaMetrics/fasthttp
github.com/VictoriaMetrics/fasthttp/fasthttputil
github.com/VictoriaMetrics/fasthttp/stackless
# github.com/VictoriaMetrics/metrics v1.11.2
github.com/VictoriaMetrics/metrics
# github.com/VictoriaMetrics/metricsql v0.1.0
@ -96,10 +100,6 @@ github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd/internal/xxhash
# github.com/valyala/bytebufferpool v1.0.0
github.com/valyala/bytebufferpool
# github.com/valyala/fasthttp v1.2.0
github.com/valyala/fasthttp
github.com/valyala/fasthttp/fasthttputil
github.com/valyala/fasthttp/stackless
# github.com/valyala/fastjson v1.5.1
github.com/valyala/fastjson
github.com/valyala/fastjson/fastfloat