Merge branch 'develop'
This commit is contained in:
commit
fbda134b7d
4
Makefile
4
Makefile
|
@ -7,7 +7,7 @@ srcdir = .
|
|||
|
||||
GO ?= go
|
||||
GOFLAGS ?= -buildvcs=true
|
||||
EXECUTABLE ?= indieauth
|
||||
EXECUTABLE ?= auth
|
||||
|
||||
#### End of system configuration section. ####
|
||||
|
||||
|
@ -17,11 +17,9 @@ all: main.go
|
|||
$(GO) build -v $(GOFLAGS) -o $(EXECUTABLE)
|
||||
|
||||
clean: ## Delete all files in the current directory that are normally created by building the program
|
||||
-rm $(srcdir)/internal/testing/httptest/{cert,key}.pem
|
||||
$(GO) clean
|
||||
|
||||
check: ## Perform self-tests
|
||||
$(GO) generate $(srcdir)/internal/testing/httptest/...
|
||||
$(GO) test -v -cover -failfast -short -shuffle=on $(GOFLAGS) $(srcdir)/...
|
||||
|
||||
.PHONY: help
|
||||
|
|
|
@ -1,83 +1,49 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
kind: "pipeline"
|
||||
type: "docker"
|
||||
name: "default"
|
||||
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
steps:
|
||||
- name: test
|
||||
image: golang:latest
|
||||
- name: "test"
|
||||
image: "golang:1.20"
|
||||
volumes:
|
||||
- name: modules
|
||||
path: /go/pkg/mod
|
||||
- name: "modules"
|
||||
path: "/go/pkg/mod"
|
||||
commands:
|
||||
- make check
|
||||
- "make check"
|
||||
|
||||
- name: build
|
||||
image: golang:latest
|
||||
- name: "build"
|
||||
image: "golang:1.20"
|
||||
volumes:
|
||||
- name: modules
|
||||
path: /go/pkg/mod
|
||||
- name: "modules"
|
||||
path: "/go/pkg/mod"
|
||||
commands:
|
||||
- make
|
||||
- "make"
|
||||
depends_on:
|
||||
- test
|
||||
- "test"
|
||||
|
||||
- name: stop-service
|
||||
image: appleboy/drone-ssh
|
||||
- name: "delivery"
|
||||
image: "drillster/drone-rsync"
|
||||
settings:
|
||||
host:
|
||||
from_secret: SSH_HOST
|
||||
username: root
|
||||
hosts:
|
||||
from_secret: "SSH_HOST_IP"
|
||||
key:
|
||||
from_secret: SSH_PRIVATE_KEY
|
||||
from_secret: "SSH_PRIVATE_KEY"
|
||||
source: "./auth"
|
||||
target: "/etc/auth/auth"
|
||||
prescript:
|
||||
- "systemctl stop auth"
|
||||
script:
|
||||
- "systemctl stop indieauth"
|
||||
- "systemctl start auth"
|
||||
depends_on:
|
||||
- build
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
- name: delivery
|
||||
image: appleboy/drone-scp
|
||||
settings:
|
||||
host:
|
||||
from_secret: SSH_HOST
|
||||
username: root
|
||||
password: ""
|
||||
key:
|
||||
from_secret: SSH_PRIVATE_KEY
|
||||
target: "/root/indieauth"
|
||||
source:
|
||||
- "indieauth"
|
||||
overwrite: true
|
||||
# NOTE(toby3d): Just run a previous version of the instance if it failed to deliver the current one.
|
||||
failure: ignore
|
||||
depends_on:
|
||||
- stop-service
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
- name: start-service
|
||||
image: appleboy/drone-ssh
|
||||
settings:
|
||||
host:
|
||||
from_secret: SSH_HOST
|
||||
username: root
|
||||
key:
|
||||
from_secret: SSH_PRIVATE_KEY
|
||||
script:
|
||||
- "systemctl start indieauth"
|
||||
depends_on:
|
||||
- delivery
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
volumes:
|
||||
- name: modules
|
||||
temp: {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# [auth.toby3d.me](https://auth.toby3d.me/) [![Build Status](https://drone.toby3d.me/api/badges/toby3d/auth/status.svg)](https://drone.toby3d.me/toby3d/auth)
|
||||
# auth [![Build Status](https://drone.toby3d.me/api/badges/toby3d/auth/status.svg)](https://drone.toby3d.me/toby3d/auth)
|
||||
|
||||
> [IndieAuth](https://indieauth.net/source/) personal instance.
|
||||
> Personal [IndieAuth](https://indieauth.net/source/) server.
|
||||
|
||||
An attempt to implement my own server to authenticate and authorize third-party applications through [IndieWeb](https://indieweb.org/) sites on [Go](https://go.dev/). Based on the latest versions of the protocol standards and related batteries like [TicketAuth](https://indieweb.org/IndieAuth_Ticket_Auth) and [RelMeAuth](https://microformats.org/wiki/RelMeAuth).
|
||||
An attempt to implement my own server to authenticate and authorize third-party applications through [IndieWeb](https://indieweb.org/) sites on [Go](https://go.dev/). Based on the latest versions of the protocol standards and related batteries like [TicketAuth](https://indieweb.org/IndieAuth_Ticket_Auth) and [RelMeAuth](https://microformats.org/wiki/RelMeAuth).
|
||||
|
|
44
go.mod
44
go.mod
|
@ -4,33 +4,33 @@ go 1.19
|
|||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0
|
||||
github.com/brianvoe/gofakeit/v6 v6.20.1
|
||||
github.com/caarlos0/env/v6 v6.10.1
|
||||
github.com/go-logfmt/logfmt v0.5.1
|
||||
github.com/goccy/go-json v0.10.0
|
||||
github.com/brianvoe/gofakeit/v6 v6.20.2
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
github.com/go-logfmt/logfmt v0.6.0
|
||||
github.com/goccy/go-json v0.10.1
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.8
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
github.com/valyala/quicktemplate v1.7.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4
|
||||
golang.org/x/text v0.6.0
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
|
||||
golang.org/x/text v0.8.0
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317
|
||||
modernc.org/sqlite v1.20.2
|
||||
modernc.org/sqlite v1.21.0
|
||||
source.toby3d.me/toby3d/form v0.3.0
|
||||
willnorris.com/go/microformats v1.1.1
|
||||
willnorris.com/go/microformats v1.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.14 // indirect
|
||||
github.com/klauspost/compress v1.16.3 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.4 // indirect
|
||||
|
@ -38,20 +38,20 @@ require (
|
|||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/lib/pq v1.10.6 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.44.0 // indirect
|
||||
go4.org/intern v0.0.0-20220617035311-6925f38cc365 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/mod v0.7.0 // indirect
|
||||
golang.org/x/net v0.5.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/tools v0.5.0 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
github.com/valyala/fasthttp v1.45.0 // indirect
|
||||
go4.org/intern v0.0.0-20230205224052-192e9f60865c // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
lukechampine.com/uint128 v1.3.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.2 // indirect
|
||||
modernc.org/libc v1.22.3 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
|
|
107
go.sum
107
go.sum
|
@ -1,15 +1,13 @@
|
|||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
|
||||
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/brianvoe/gofakeit/v6 v6.20.1 h1:8ihJ60OvPnPJ2W6wZR7M+TTeaZ9bml0z6oy4gvyJ/ek=
|
||||
github.com/brianvoe/gofakeit/v6 v6.20.1/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
|
||||
github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
|
||||
github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/brianvoe/gofakeit/v6 v6.20.2 h1:FLloufuC7NcbHqDzVQ42CG9AKryS1gAGCRt8nQRsW+Y=
|
||||
github.com/brianvoe/gofakeit/v6 v6.20.2/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8=
|
||||
github.com/caarlos0/env/v7 v7.1.0 h1:9lzTF5amyQeWHZzuZeKlCb5FWSUxpG1js43mhbY8ozg=
|
||||
github.com/caarlos0/env/v7 v7.1.0/go.mod h1:LPPWniDUq4JaO6Q41vtlyikhMknqymCLBw0eX4dcH1E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -19,15 +17,14 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2U
|
|||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
|
||||
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.1 h1:lEs5Ob+oOG/Ze199njvzHbhn6p9T+h64F5hRj69iTTo=
|
||||
github.com/goccy/go-json v0.10.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
|
@ -39,11 +36,9 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
|
|||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.15.14 h1:i7WCKDToww0wA+9qrUZ1xOjp218vfFo3nTU6UHp+gOc=
|
||||
github.com/klauspost/compress v1.15.14/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.16.3 h1:XuJt9zzcnaz6a16/OU53ZjWp/v7/42WcR5t2a0PcNQY=
|
||||
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
|
@ -63,12 +58,12 @@ github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa h1:tEkEyxYeZ43TR55QU/hsIt9aRGBxbgGuz9CGykjvogY=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
@ -82,79 +77,71 @@ github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFy
|
|||
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.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||
github.com/valyala/fasthttp v1.44.0 h1:R+gLUhldIsfg1HokMuQjdQ5bh9nuXHPIfvkYUu9eR5Q=
|
||||
github.com/valyala/fasthttp v1.44.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY=
|
||||
github.com/valyala/fasthttp v1.45.0 h1:zPkkzpIn8tdHZUrVa6PzYd0i5verqiPSkgTd3bSUcpA=
|
||||
github.com/valyala/fasthttp v1.45.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTcjMJzCM=
|
||||
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
|
||||
go4.org/intern v0.0.0-20220617035311-6925f38cc365 h1:t9hFvR102YlOqU0fQn1wgwhNvSbHGBbbJxX9JKfU3l0=
|
||||
go4.org/intern v0.0.0-20220617035311-6925f38cc365/go.mod h1:WXRv3p7T6gzt0CcJm43AAKdKVZmcQbwwC7EwquU5BZU=
|
||||
go4.org/intern v0.0.0-20230205224052-192e9f60865c h1:b8WZ7Ja8nKegYxfwDLLwT00ZKv4lXAQrw8LYPK+cHSI=
|
||||
go4.org/intern v0.0.0-20230205224052-192e9f60865c/go.mod h1:RJ0SVrOMpxLhgb5noIV+09zI1RsRlMsbUcSxpWHqbrE=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 h1:FyBZqvoA/jbNzuAWLQE2kG820zMAkcilx6BMjGbL/E4=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230204201903-c31fa085b70e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296 h1:QJ/xcIANMLApehfgPCHnfK1hZiaMmbaTVmPv7DAoTbo=
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20230221090011-e4bae7ad2296/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4 h1:CNkDRtCj8otM5CFz5jYvbr8ioXX8flVsLfDWEj0M5kk=
|
||||
golang.org/x/exp v0.0.0-20230113213754-f9f960f08ad4/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 h1:pVgRXcIictcr+lBQIFeiwuwtDIs4eL21OuM9nyAADmo=
|
||||
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
|
||||
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
|
@ -164,31 +151,31 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317 h1:U2fwK6P2EqmopP/hFLTOAjWTki0qgd4GMJn5X8wOleU=
|
||||
inet.af/netaddr v0.0.0-20220811202034-502d2d690317/go.mod h1:OIezDfdzOgFhuw4HuWapWq2e9l0H9tK4F1j+ETRtF3k=
|
||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
|
||||
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
|
||||
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
||||
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.20.2 h1:9AaVzJH1Yf0u9iOZRjjuvqxLoGqybqVFbAUC5rvi9u8=
|
||||
modernc.org/sqlite v1.20.2/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
|
||||
modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
|
||||
modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||
source.toby3d.me/toby3d/form v0.3.0 h1:kI8apdFeVr+koqTTGVoIRiR5NMqjrhCJlajYlu+1bVw=
|
||||
source.toby3d.me/toby3d/form v0.3.0/go.mod h1:drlHMC+j/gb5zsttCSwx8qcYsbaRW+wFfE8bK6y+oeY=
|
||||
willnorris.com/go/microformats v1.1.1 h1:h5tk2luq6KBIRcwMGdksxdeea4GGuWrRFie5460OAbo=
|
||||
willnorris.com/go/microformats v1.1.1/go.mod h1:kvVnWrkkEscVAIITCEoiTX66Hcyg59C7q0E49mb9TJ0=
|
||||
willnorris.com/go/microformats v1.2.0 h1:73pzJCLJM69kYE5qsLI9OOC/7sImNVOzya9EQ0+1wmM=
|
||||
willnorris.com/go/microformats v1.2.0/go.mod h1:RrlwCSvib4qz+JICKiN7rON4phzQ3HAT7j6s4O2cZj4=
|
||||
|
|
|
@ -48,7 +48,7 @@ func NewHandler(opts NewHandlerOptions) *Handler {
|
|||
func (h *Handler) Handler() http.Handler {
|
||||
chain := middleware.Chain{
|
||||
middleware.CSRFWithConfig(middleware.CSRFConfig{
|
||||
Skipper: func(w http.ResponseWriter, r *http.Request) bool {
|
||||
Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
|
||||
head, _ := urlutil.ShiftPath(r.URL.Path)
|
||||
|
||||
return head == ""
|
||||
|
@ -65,7 +65,7 @@ func (h *Handler) Handler() http.Handler {
|
|||
CookieHTTPOnly: true,
|
||||
}),
|
||||
middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
|
||||
Skipper: func(w http.ResponseWriter, r *http.Request) bool {
|
||||
Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
|
||||
head, _ := urlutil.ShiftPath(r.URL.Path)
|
||||
|
||||
return r.Method != http.MethodPost ||
|
||||
|
@ -154,11 +154,8 @@ func (h *Handler) handleAuthorize(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusBadRequest)
|
||||
web.WriteTemplate(w, &web.ErrorPage{
|
||||
BaseOf: baseOf,
|
||||
Error: domain.NewError(
|
||||
domain.ErrorCodeInvalidClient,
|
||||
"requested redirect_uri is not registered on client_id side",
|
||||
"",
|
||||
),
|
||||
Error: domain.NewError(domain.ErrorCodeInvalidClient, "requested redirect_uri is not"+
|
||||
" registered on client_id side", ""),
|
||||
})
|
||||
|
||||
return
|
||||
|
|
|
@ -11,10 +11,6 @@ import (
|
|||
|
||||
type (
|
||||
AuthAuthorizationRequest struct {
|
||||
// Indicates to the authorization server that an authorization
|
||||
// code should be returned as the response.
|
||||
ResponseType domain.ResponseType `form:"response_type"` // code
|
||||
|
||||
// The client URL.
|
||||
ClientID domain.ClientID `form:"client_id"`
|
||||
|
||||
|
@ -28,12 +24,9 @@ type (
|
|||
// The hashing method used to calculate the code challenge.
|
||||
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
|
||||
|
||||
// A space-separated list of scopes the client is requesting,
|
||||
// e.g. "profile", or "profile create". If the client omits this
|
||||
// value, the authorization server MUST NOT issue an access
|
||||
// token for this authorization code. Only the user's profile
|
||||
// URL may be returned without any scope requested.
|
||||
Scope domain.Scopes `form:"scope,omitempty"`
|
||||
// Indicates to the authorization server that an authorization
|
||||
// code should be returned as the response.
|
||||
ResponseType domain.ResponseType `form:"response_type"` // code
|
||||
|
||||
// A parameter set by the client which will be included when the
|
||||
// user is redirected back to the client. This is used to
|
||||
|
@ -43,19 +36,26 @@ type (
|
|||
|
||||
// The code challenge as previously described.
|
||||
CodeChallenge string `form:"code_challenge,omitempty"`
|
||||
|
||||
// A space-separated list of scopes the client is requesting,
|
||||
// e.g. "profile", or "profile create". If the client omits this
|
||||
// value, the authorization server MUST NOT issue an access
|
||||
// token for this authorization code. Only the user's profile
|
||||
// URL may be returned without any scope requested.
|
||||
Scope domain.Scopes `form:"scope,omitempty"`
|
||||
}
|
||||
|
||||
AuthVerifyRequest struct {
|
||||
ClientID domain.ClientID `form:"client_id"`
|
||||
Me domain.Me `form:"me"`
|
||||
RedirectURI domain.URL `form:"redirect_uri"`
|
||||
ResponseType domain.ResponseType `form:"response_type"`
|
||||
CodeChallengeMethod *domain.CodeChallengeMethod `form:"code_challenge_method,omitempty"`
|
||||
Scope domain.Scopes `form:"scope[],omitempty"`
|
||||
ResponseType domain.ResponseType `form:"response_type"`
|
||||
Authorize string `form:"authorize"`
|
||||
CodeChallenge string `form:"code_challenge,omitempty"`
|
||||
State string `form:"state"`
|
||||
Provider string `form:"provider"`
|
||||
Scope domain.Scopes `form:"scope[],omitempty"`
|
||||
}
|
||||
|
||||
AuthExchangeRequest struct {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package common
|
||||
|
||||
const charsetUTF8 = "charset=UTF-8"
|
||||
|
||||
const (
|
||||
MIMEApplicationForm string = "application/x-www-form-urlencoded"
|
||||
MIMEApplicationJSON string = "application/json"
|
||||
|
@ -10,6 +8,8 @@ const (
|
|||
MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8
|
||||
MIMETextPlain string = "text/plain"
|
||||
MIMETextPlainCharsetUTF8 string = MIMETextPlain + "; " + charsetUTF8
|
||||
|
||||
charsetUTF8 = "charset=UTF-8"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -12,26 +12,26 @@ import (
|
|||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||
type Action struct {
|
||||
uid string
|
||||
action string
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals // structs cannot be constants
|
||||
var (
|
||||
ActionUnd = Action{uid: ""} // "und"
|
||||
ActionUnd = Action{action: ""} // "und"
|
||||
|
||||
// ActionRevoke represent action for revoke token.
|
||||
ActionRevoke = Action{uid: "revoke"} // "revoke"
|
||||
ActionRevoke = Action{action: "revoke"} // "revoke"
|
||||
|
||||
// ActionTicket represent action for TicketAuth extension.
|
||||
ActionTicket = Action{uid: "ticket"} // "ticket"
|
||||
ActionTicket = Action{action: "ticket"} // "ticket"
|
||||
)
|
||||
|
||||
var ErrActionSyntax error = NewError(ErrorCodeInvalidRequest, "unknown action method", "")
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
var uidsActions = map[string]Action{
|
||||
ActionRevoke.uid: ActionRevoke,
|
||||
ActionTicket.uid: ActionTicket,
|
||||
ActionRevoke.action: ActionRevoke,
|
||||
ActionTicket.action: ActionTicket,
|
||||
}
|
||||
|
||||
// ParseAction parse string identifier of action into struct enum.
|
||||
|
@ -74,8 +74,8 @@ func (a *Action) UnmarshalJSON(v []byte) error {
|
|||
|
||||
// String returns string representation of action.
|
||||
func (a Action) String() string {
|
||||
if a.uid != "" {
|
||||
return a.uid
|
||||
if a.action != "" {
|
||||
return a.action
|
||||
}
|
||||
|
||||
return common.Und
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package domain
|
||||
|
||||
//nolint:gosec // support old clients
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
|
@ -20,17 +21,17 @@ import (
|
|||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||
type CodeChallengeMethod struct {
|
||||
uid string
|
||||
codeChallengeMethod string
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals // structs cannot be constants
|
||||
var (
|
||||
CodeChallengeMethodUnd = CodeChallengeMethod{uid: ""} // "und"
|
||||
CodeChallengeMethodPLAIN = CodeChallengeMethod{uid: "plain"} // "PLAIN"
|
||||
CodeChallengeMethodMD5 = CodeChallengeMethod{uid: "md5"} // "MD5"
|
||||
CodeChallengeMethodS1 = CodeChallengeMethod{uid: "s1"} // "S1"
|
||||
CodeChallengeMethodS256 = CodeChallengeMethod{uid: "s256"} // "S256"
|
||||
CodeChallengeMethodS512 = CodeChallengeMethod{uid: "s512"} // "S512"
|
||||
CodeChallengeMethodUnd = CodeChallengeMethod{codeChallengeMethod: ""} // "und"
|
||||
CodeChallengeMethodPLAIN = CodeChallengeMethod{codeChallengeMethod: "plain"} // "PLAIN"
|
||||
CodeChallengeMethodMD5 = CodeChallengeMethod{codeChallengeMethod: "md5"} // "MD5"
|
||||
CodeChallengeMethodS1 = CodeChallengeMethod{codeChallengeMethod: "s1"} // "S1"
|
||||
CodeChallengeMethodS256 = CodeChallengeMethod{codeChallengeMethod: "s256"} // "S256"
|
||||
CodeChallengeMethodS512 = CodeChallengeMethod{codeChallengeMethod: "s512"} // "S512"
|
||||
)
|
||||
|
||||
var ErrCodeChallengeMethodUnknown error = NewError(
|
||||
|
@ -41,11 +42,11 @@ var ErrCodeChallengeMethodUnknown error = NewError(
|
|||
|
||||
//nolint:gochecknoglobals // maps cannot be constants
|
||||
var uidsMethods = map[string]CodeChallengeMethod{
|
||||
CodeChallengeMethodMD5.uid: CodeChallengeMethodMD5,
|
||||
CodeChallengeMethodPLAIN.uid: CodeChallengeMethodPLAIN,
|
||||
CodeChallengeMethodS1.uid: CodeChallengeMethodS1,
|
||||
CodeChallengeMethodS256.uid: CodeChallengeMethodS256,
|
||||
CodeChallengeMethodS512.uid: CodeChallengeMethodS512,
|
||||
CodeChallengeMethodMD5.codeChallengeMethod: CodeChallengeMethodMD5,
|
||||
CodeChallengeMethodPLAIN.codeChallengeMethod: CodeChallengeMethodPLAIN,
|
||||
CodeChallengeMethodS1.codeChallengeMethod: CodeChallengeMethodS1,
|
||||
CodeChallengeMethodS256.codeChallengeMethod: CodeChallengeMethodS256,
|
||||
CodeChallengeMethodS512.codeChallengeMethod: CodeChallengeMethodS512,
|
||||
}
|
||||
|
||||
// ParseCodeChallengeMethod parse string identifier of code challenge method
|
||||
|
@ -85,13 +86,13 @@ func (ccm *CodeChallengeMethod) UnmarshalJSON(v []byte) error {
|
|||
}
|
||||
|
||||
func (ccm CodeChallengeMethod) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.Quote(ccm.uid)), nil
|
||||
return []byte(strconv.Quote(ccm.codeChallengeMethod)), nil
|
||||
}
|
||||
|
||||
// String returns string representation of code challenge method.
|
||||
func (ccm CodeChallengeMethod) String() string {
|
||||
if ccm.uid != "" {
|
||||
return strings.ToUpper(ccm.uid)
|
||||
if ccm.codeChallengeMethod != "" {
|
||||
return strings.ToUpper(ccm.codeChallengeMethod)
|
||||
}
|
||||
|
||||
return common.Und
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package domain_test
|
||||
|
||||
//nolint:gosec // support old clients
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
|
|
|
@ -11,14 +11,14 @@ import (
|
|||
|
||||
type (
|
||||
Config struct {
|
||||
Code ConfigCode `envPrefix:"CODE_"`
|
||||
Database ConfigDatabase `envPrefix:"DATABASE_"`
|
||||
IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"`
|
||||
JWT ConfigJWT `envPrefix:"JWT_"`
|
||||
Server ConfigServer `envPrefix:"SERVER_"`
|
||||
TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"`
|
||||
Database ConfigDatabase `envPrefix:"DATABASE_"`
|
||||
Name string `env:"NAME" envDefault:"IndieAuth"`
|
||||
RunMode string `env:"RUN_MODE" envDefault:"dev"`
|
||||
IndieAuth ConfigIndieAuth `envPrefix:"INDIEAUTH_"`
|
||||
JWT ConfigJWT `envPrefix:"JWT_"`
|
||||
Code ConfigCode `envPrefix:"CODE_"`
|
||||
TicketAuth ConfigTicketAuth `envPrefix:"TICKETAUTH_"`
|
||||
}
|
||||
|
||||
ConfigServer struct {
|
||||
|
@ -45,10 +45,10 @@ type (
|
|||
}
|
||||
|
||||
ConfigJWT struct {
|
||||
Expiry time.Duration `env:"EXPIRY" envDefault:"1h"` // 1h
|
||||
Algorithm string `env:"ALGORITHM" envDefault:"HS256"` // HS256
|
||||
Algorithm string `env:"ALGORITHM" envDefault:"HS256"`
|
||||
Secret string `env:"SECRET"`
|
||||
NonceLength uint8 `env:"NONCE_LENGTH" envDefault:"22"` // 22
|
||||
Expiry time.Duration `env:"EXPIRY" envDefault:"1h"`
|
||||
NonceLength uint8 `env:"NONCE_LENGTH" envDefault:"22"`
|
||||
}
|
||||
|
||||
ConfigIndieAuth struct {
|
||||
|
|
|
@ -15,6 +15,8 @@ type (
|
|||
// Error describes the format of a typical IndieAuth error.
|
||||
//nolint:tagliatelle // RFC 6749 section 5.2
|
||||
Error struct {
|
||||
frame xerrors.Frame `json:"-"`
|
||||
|
||||
// A single error code.
|
||||
Code ErrorCode `json:"error"`
|
||||
|
||||
|
@ -31,25 +33,23 @@ type (
|
|||
// authorization request. The exact value received from the
|
||||
// client.
|
||||
State string `json:"-"`
|
||||
|
||||
frame xerrors.Frame `json:"-"`
|
||||
}
|
||||
|
||||
// ErrorCode represent error code described in RFC 6749.
|
||||
ErrorCode struct {
|
||||
uid string
|
||||
errorCode string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrorCodeUnd describes an unrecognized error code.
|
||||
ErrorCodeUnd = ErrorCode{uid: ""} // "und"
|
||||
ErrorCodeUnd = ErrorCode{errorCode: ""} // "und"
|
||||
|
||||
// ErrorCodeAccessDenied describes the access_denied error code.
|
||||
//
|
||||
// RFC 6749 section 4.1.2.1: The resource owner or authorization server
|
||||
// denied the request.
|
||||
ErrorCodeAccessDenied = ErrorCode{uid: "access_denied"} // "access_denied"
|
||||
ErrorCodeAccessDenied = ErrorCode{errorCode: "access_denied"} // "access_denied"
|
||||
|
||||
// ErrorCodeInvalidClient describes the invalid_client error code.
|
||||
//
|
||||
|
@ -65,7 +65,7 @@ var (
|
|||
// HTTP 401 (Unauthorized) status code and include the
|
||||
// "WWW-Authenticate" response header field matching the authentication
|
||||
// scheme used by the client.
|
||||
ErrorCodeInvalidClient = ErrorCode{uid: "invalid_client"} // "invalid_client"
|
||||
ErrorCodeInvalidClient = ErrorCode{errorCode: "invalid_client"} // "invalid_client"
|
||||
|
||||
// ErrorCodeInvalidGrant describes the invalid_grant error code.
|
||||
//
|
||||
|
@ -73,7 +73,7 @@ var (
|
|||
// authorization code, resource owner credentials) or refresh token is
|
||||
// invalid, expired, revoked, does not match the redirection URI used in
|
||||
// the authorization request, or was issued to another client.
|
||||
ErrorCodeInvalidGrant = ErrorCode{uid: "invalid_grant"} // "invalid_grant"
|
||||
ErrorCodeInvalidGrant = ErrorCode{errorCode: "invalid_grant"} // "invalid_grant"
|
||||
|
||||
// ErrorCodeInvalidRequest describes the invalid_request error code.
|
||||
//
|
||||
|
@ -88,7 +88,7 @@ var (
|
|||
// repeats a parameter, includes multiple credentials, utilizes more
|
||||
// than one mechanism for authenticating the client, or is otherwise
|
||||
// malformed.
|
||||
ErrorCodeInvalidRequest = ErrorCode{uid: "invalid_request"} // "invalid_request"
|
||||
ErrorCodeInvalidRequest = ErrorCode{errorCode: "invalid_request"} // "invalid_request"
|
||||
|
||||
// ErrorCodeInvalidScope describes the invalid_scope error code.
|
||||
//
|
||||
|
@ -97,7 +97,7 @@ var (
|
|||
//
|
||||
// RFC 6749 section 5.2: The requested scope is invalid, unknown,
|
||||
// malformed, or exceeds the scope granted by the resource owner.
|
||||
ErrorCodeInvalidScope = ErrorCode{uid: "invalid_scope"} // "invalid_scope"
|
||||
ErrorCodeInvalidScope = ErrorCode{errorCode: "invalid_scope"} // "invalid_scope"
|
||||
|
||||
// ErrorCodeServerError describes the server_error error code.
|
||||
//
|
||||
|
@ -105,7 +105,7 @@ var (
|
|||
// unexpected condition that prevented it from fulfilling the request.
|
||||
// (This error code is needed because a 500 Internal Server Error HTTP
|
||||
// status code cannot be returned to the client via an HTTP redirect.)
|
||||
ErrorCodeServerError = ErrorCode{uid: "server_error"} // "server_error"
|
||||
ErrorCodeServerError = ErrorCode{errorCode: "server_error"} // "server_error"
|
||||
|
||||
// ErrorCodeTemporarilyUnavailable describes the temporarily_unavailable error code.
|
||||
//
|
||||
|
@ -114,7 +114,7 @@ var (
|
|||
// maintenance of the server. (This error code is needed because a 503
|
||||
// Service Unavailable HTTP status code cannot be returned to the client
|
||||
// via an HTTP redirect.)
|
||||
ErrorCodeTemporarilyUnavailable = ErrorCode{uid: "temporarily_unavailable"} // "temporarily_unavailable"
|
||||
ErrorCodeTemporarilyUnavailable = ErrorCode{errorCode: "temporarily_unavailable"} // "temporarily_unavailable"
|
||||
|
||||
// ErrorCodeUnauthorizedClient describes the unauthorized_client error code.
|
||||
//
|
||||
|
@ -123,53 +123,53 @@ var (
|
|||
//
|
||||
// RFC 6749 section 5.2: The authenticated client is not authorized to
|
||||
// use this authorization grant type.
|
||||
ErrorCodeUnauthorizedClient = ErrorCode{uid: "unauthorized_client"} // "unauthorized_client"
|
||||
ErrorCodeUnauthorizedClient = ErrorCode{errorCode: "unauthorized_client"} // "unauthorized_client"
|
||||
|
||||
// ErrorCodeUnsupportedGrantType describes the unsupported_grant_type error code.
|
||||
//
|
||||
// RFC 6749 section 5.2: The authorization grant type is not supported
|
||||
// by the authorization server.
|
||||
ErrorCodeUnsupportedGrantType = ErrorCode{uid: "unsupported_grant_type"} // "unsupported_grant_type"
|
||||
ErrorCodeUnsupportedGrantType = ErrorCode{errorCode: "unsupported_grant_type"} // "unsupported_grant_type"
|
||||
|
||||
// ErrorCodeUnsupportedResponseType describes the unsupported_response_type error code.
|
||||
//
|
||||
// RFC 6749 section 4.1.2.1: The authorization server does not support
|
||||
// obtaining an authorization code using this method.
|
||||
ErrorCodeUnsupportedResponseType = ErrorCode{uid: "unsupported_response_type"} // "unsupported_response_type"
|
||||
ErrorCodeUnsupportedResponseType = ErrorCode{errorCode: "unsupported_response_type"} // "unsupported_response_type"
|
||||
|
||||
// ErrorCodeInvalidToken describes the invalid_token error code.
|
||||
//
|
||||
// IndieAuth: The access token provided is expired, revoked, or invalid.
|
||||
ErrorCodeInvalidToken = ErrorCode{uid: "invalid_token"} // "invalid_token"
|
||||
ErrorCodeInvalidToken = ErrorCode{errorCode: "invalid_token"} // "invalid_token"
|
||||
|
||||
// ErrorCodeInsufficientScope describes the insufficient_scope error code.
|
||||
//
|
||||
// IndieAuth: The request requires higher privileges than provided.
|
||||
ErrorCodeInsufficientScope = ErrorCode{uid: "insufficient_scope"} // "insufficient_scope"
|
||||
ErrorCodeInsufficientScope = ErrorCode{errorCode: "insufficient_scope"} // "insufficient_scope"
|
||||
)
|
||||
|
||||
var ErrErrorCodeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown error code", "")
|
||||
|
||||
//nolint:gochecknoglobals // maps cannot be constants
|
||||
var uidsErrorCodes = map[string]ErrorCode{
|
||||
ErrorCodeAccessDenied.uid: ErrorCodeAccessDenied,
|
||||
ErrorCodeInsufficientScope.uid: ErrorCodeInsufficientScope,
|
||||
ErrorCodeInvalidClient.uid: ErrorCodeInvalidClient,
|
||||
ErrorCodeInvalidGrant.uid: ErrorCodeInvalidGrant,
|
||||
ErrorCodeInvalidRequest.uid: ErrorCodeInvalidRequest,
|
||||
ErrorCodeInvalidScope.uid: ErrorCodeInvalidScope,
|
||||
ErrorCodeInvalidToken.uid: ErrorCodeInvalidToken,
|
||||
ErrorCodeServerError.uid: ErrorCodeServerError,
|
||||
ErrorCodeTemporarilyUnavailable.uid: ErrorCodeTemporarilyUnavailable,
|
||||
ErrorCodeUnauthorizedClient.uid: ErrorCodeUnauthorizedClient,
|
||||
ErrorCodeUnsupportedGrantType.uid: ErrorCodeUnsupportedGrantType,
|
||||
ErrorCodeUnsupportedResponseType.uid: ErrorCodeUnsupportedResponseType,
|
||||
ErrorCodeAccessDenied.errorCode: ErrorCodeAccessDenied,
|
||||
ErrorCodeInsufficientScope.errorCode: ErrorCodeInsufficientScope,
|
||||
ErrorCodeInvalidClient.errorCode: ErrorCodeInvalidClient,
|
||||
ErrorCodeInvalidGrant.errorCode: ErrorCodeInvalidGrant,
|
||||
ErrorCodeInvalidRequest.errorCode: ErrorCodeInvalidRequest,
|
||||
ErrorCodeInvalidScope.errorCode: ErrorCodeInvalidScope,
|
||||
ErrorCodeInvalidToken.errorCode: ErrorCodeInvalidToken,
|
||||
ErrorCodeServerError.errorCode: ErrorCodeServerError,
|
||||
ErrorCodeTemporarilyUnavailable.errorCode: ErrorCodeTemporarilyUnavailable,
|
||||
ErrorCodeUnauthorizedClient.errorCode: ErrorCodeUnauthorizedClient,
|
||||
ErrorCodeUnsupportedGrantType.errorCode: ErrorCodeUnsupportedGrantType,
|
||||
ErrorCodeUnsupportedResponseType.errorCode: ErrorCodeUnsupportedResponseType,
|
||||
}
|
||||
|
||||
// String returns a string representation of the error code.
|
||||
func (ec ErrorCode) String() string {
|
||||
if ec.uid != "" {
|
||||
return ec.uid
|
||||
if ec.errorCode != "" {
|
||||
return ec.errorCode
|
||||
}
|
||||
|
||||
return common.Und
|
||||
|
@ -193,7 +193,7 @@ func (ec *ErrorCode) UnmarshalForm(v []byte) error {
|
|||
|
||||
// MarshalJSON encodes the error code into its string representation in JSON.
|
||||
func (ec ErrorCode) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.QuoteToASCII(ec.uid)), nil
|
||||
return []byte(strconv.QuoteToASCII(ec.errorCode)), nil
|
||||
}
|
||||
|
||||
// Error returns a string representation of the error, satisfying the error
|
||||
|
@ -231,6 +231,8 @@ func (e Error) SetReirectURI(u *url.URL) {
|
|||
return
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
|
||||
for key, val := range map[string]string{
|
||||
"error": e.Code.String(),
|
||||
"error_description": e.Description,
|
||||
|
@ -241,8 +243,10 @@ func (e Error) SetReirectURI(u *url.URL) {
|
|||
continue
|
||||
}
|
||||
|
||||
u.Query().Set(key, val)
|
||||
q.Set(key, val)
|
||||
}
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
// NewError creates a new Error with the stack pointing to the function call
|
||||
|
|
|
@ -13,17 +13,17 @@ import (
|
|||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||
type GrantType struct {
|
||||
uid string
|
||||
grantType string
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals // structs cannot be constants
|
||||
var (
|
||||
GrantTypeUnd = GrantType{uid: ""} // "und"
|
||||
GrantTypeAuthorizationCode = GrantType{uid: "authorization_code"} // "authorization_code"
|
||||
GrantTypeRefreshToken = GrantType{uid: "refresh_token"} // "refresh_token"
|
||||
GrantTypeUnd = GrantType{grantType: ""} // "und"
|
||||
GrantTypeAuthorizationCode = GrantType{grantType: "authorization_code"} // "authorization_code"
|
||||
GrantTypeRefreshToken = GrantType{grantType: "refresh_token"} // "refresh_token"
|
||||
|
||||
// TicketAuth extension.
|
||||
GrantTypeTicket = GrantType{uid: "ticket"}
|
||||
GrantTypeTicket = GrantType{grantType: "ticket"}
|
||||
)
|
||||
|
||||
var ErrGrantTypeUnknown error = NewError(
|
||||
|
@ -34,9 +34,9 @@ var ErrGrantTypeUnknown error = NewError(
|
|||
|
||||
//nolint:gochecknoglobals // maps cannot be constants
|
||||
var uidsGrantTypes = map[string]GrantType{
|
||||
GrantTypeAuthorizationCode.uid: GrantTypeAuthorizationCode,
|
||||
GrantTypeRefreshToken.uid: GrantTypeRefreshToken,
|
||||
GrantTypeTicket.uid: GrantTypeTicket,
|
||||
GrantTypeAuthorizationCode.grantType: GrantTypeAuthorizationCode,
|
||||
GrantTypeRefreshToken.grantType: GrantTypeRefreshToken,
|
||||
GrantTypeTicket.grantType: GrantTypeTicket,
|
||||
}
|
||||
|
||||
// ParseGrantType parse grant_type value as GrantType struct enum.
|
||||
|
@ -78,13 +78,13 @@ func (gt *GrantType) UnmarshalJSON(v []byte) error {
|
|||
}
|
||||
|
||||
func (gt GrantType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.Quote(gt.uid)), nil
|
||||
return []byte(strconv.Quote(gt.grantType)), nil
|
||||
}
|
||||
|
||||
// String returns string representation of grant type.
|
||||
func (gt GrantType) String() string {
|
||||
if gt.uid != "" {
|
||||
return gt.uid
|
||||
if gt.grantType != "" {
|
||||
return gt.grantType
|
||||
}
|
||||
|
||||
return common.Und
|
||||
|
|
|
@ -11,14 +11,14 @@ import (
|
|||
|
||||
// Me is a URL user identifier.
|
||||
type Me struct {
|
||||
id *url.URL
|
||||
me *url.URL
|
||||
}
|
||||
|
||||
// ParseMe parse string as me URL identifier.
|
||||
//
|
||||
//nolint:funlen,cyclop
|
||||
func ParseMe(raw string) (*Me, error) {
|
||||
id, err := url.Parse(raw)
|
||||
me, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
|
@ -28,57 +28,57 @@ func ParseMe(raw string) (*Me, error) {
|
|||
)
|
||||
}
|
||||
|
||||
if id.Scheme != "http" && id.Scheme != "https" {
|
||||
if me.Scheme != "http" && me.Scheme != "https" {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
"profile URL MUST have either an https or http scheme, got '"+id.Scheme+"'",
|
||||
"profile URL MUST have either an https or http scheme, got '"+me.Scheme+"'",
|
||||
"https://indieauth.net/source/#user-profile-url",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if id.Path == "" {
|
||||
id.Path = "/"
|
||||
if me.Path == "" {
|
||||
me.Path = "/"
|
||||
}
|
||||
|
||||
if strings.Contains(id.Path, "/.") || strings.Contains(id.Path, "/..") {
|
||||
if strings.Contains(me.Path, "/.") || strings.Contains(me.Path, "/..") {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
"profile URL MUST contain a path component (/ is a valid path), MUST NOT contain single-dot "+
|
||||
"or double-dot path segments, got '"+id.Path+"'",
|
||||
"or double-dot path segments, got '"+me.Path+"'",
|
||||
"https://indieauth.net/source/#user-profile-url",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if id.Fragment != "" {
|
||||
if me.Fragment != "" {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
"profile URL MUST NOT contain a fragment component, got '"+id.Fragment+"'",
|
||||
"profile URL MUST NOT contain a fragment component, got '"+me.Fragment+"'",
|
||||
"https://indieauth.net/source/#user-profile-url",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if id.User != nil {
|
||||
if me.User != nil {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
"profile URL MUST NOT contain a username or password component, got '"+id.User.String()+"'",
|
||||
"profile URL MUST NOT contain a username or password component, got '"+me.User.String()+"'",
|
||||
"https://indieauth.net/source/#user-profile-url",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if id.Host == "" {
|
||||
if me.Host == "" {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
"profile host name MUST be a domain name, got '"+id.Host+"'",
|
||||
"profile host name MUST be a domain name, got '"+me.Host+"'",
|
||||
"https://indieauth.net/source/#user-profile-url",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if _, port, _ := net.SplitHostPort(id.Host); port != "" {
|
||||
if _, port, _ := net.SplitHostPort(me.Host); port != "" {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
"profile MUST NOT contain a port, got '"+port+"'",
|
||||
|
@ -87,7 +87,7 @@ func ParseMe(raw string) (*Me, error) {
|
|||
)
|
||||
}
|
||||
|
||||
if out := net.ParseIP(id.Host); out != nil {
|
||||
if out := net.ParseIP(me.Host); out != nil {
|
||||
return nil, NewError(
|
||||
ErrorCodeInvalidRequest,
|
||||
"profile MUST NOT be ipv4 or ipv6 addresses, got '"+out.String()+"'",
|
||||
|
@ -96,7 +96,7 @@ func ParseMe(raw string) (*Me, error) {
|
|||
)
|
||||
}
|
||||
|
||||
return &Me{id: id}, nil
|
||||
return &Me{me: me}, nil
|
||||
}
|
||||
|
||||
// TestMe returns valid random generated me for tests.
|
||||
|
@ -108,7 +108,7 @@ func TestMe(tb testing.TB, src string) *Me {
|
|||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
return &Me{id: u}
|
||||
return &Me{me: u}
|
||||
}
|
||||
|
||||
// UnmarshalForm implements custom unmarshler for form values.
|
||||
|
@ -147,19 +147,19 @@ func (m Me) MarshalJSON() ([]byte, error) {
|
|||
|
||||
// URL returns copy of parsed me in *url.URL representation.
|
||||
func (m Me) URL() *url.URL {
|
||||
if m.id == nil {
|
||||
if m.me == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
out, _ := url.Parse(m.id.String())
|
||||
out, _ := url.Parse(m.me.String())
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// String returns string representation of me.
|
||||
func (m Me) String() string {
|
||||
if m.id != nil {
|
||||
return m.id.String()
|
||||
if m.me != nil {
|
||||
return m.me.String()
|
||||
}
|
||||
|
||||
return ""
|
||||
|
|
|
@ -11,23 +11,23 @@ import (
|
|||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||
type ResponseType struct {
|
||||
uid string
|
||||
responseType string
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals // structs cannot be constants
|
||||
var (
|
||||
ResponseTypeUnd = ResponseType{uid: ""} // "und"
|
||||
ResponseTypeUnd = ResponseType{responseType: ""} // "und"
|
||||
|
||||
// Deprecated(toby3d): Only accept response_type=code requests, and for
|
||||
// backwards-compatible support, treat response_type=id requests as
|
||||
// response_type=code requests:
|
||||
// https://aaronparecki.com/2020/12/03/1/indieauth-2020#response-type
|
||||
ResponseTypeID = ResponseType{uid: "id"} // "id"
|
||||
ResponseTypeID = ResponseType{responseType: "id"} // "id"
|
||||
|
||||
// Indicates to the authorization server that an authorization code
|
||||
// should be returned as the response:
|
||||
// https://indieauth.net/source/#authorization-request-li-1
|
||||
ResponseTypeCode = ResponseType{uid: "code"} // "code"
|
||||
ResponseTypeCode = ResponseType{responseType: "code"} // "code"
|
||||
)
|
||||
|
||||
var ErrResponseTypeUnknown error = NewError(
|
||||
|
@ -39,9 +39,9 @@ var ErrResponseTypeUnknown error = NewError(
|
|||
// ParseResponseType parse string as response type struct enum.
|
||||
func ParseResponseType(uid string) (ResponseType, error) {
|
||||
switch strings.ToLower(uid) {
|
||||
case ResponseTypeCode.uid:
|
||||
case ResponseTypeCode.responseType:
|
||||
return ResponseTypeCode, nil
|
||||
case ResponseTypeID.uid:
|
||||
case ResponseTypeID.responseType:
|
||||
return ResponseTypeID, nil
|
||||
}
|
||||
|
||||
|
@ -78,13 +78,13 @@ func (rt *ResponseType) UnmarshalJSON(v []byte) error {
|
|||
}
|
||||
|
||||
func (rt ResponseType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.Quote(rt.uid)), nil
|
||||
return []byte(strconv.Quote(rt.responseType)), nil
|
||||
}
|
||||
|
||||
// String returns string representation of response type.
|
||||
func (rt ResponseType) String() string {
|
||||
if rt.uid != "" {
|
||||
return rt.uid
|
||||
if rt.responseType != "" {
|
||||
return rt.responseType
|
||||
}
|
||||
|
||||
return common.Und
|
||||
|
|
|
@ -13,35 +13,35 @@ import (
|
|||
// NOTE(toby3d): Encapsulate enums in structs for extra compile-time safety:
|
||||
// https://threedots.tech/post/safer-enums-in-go/#struct-based-enums
|
||||
type Scope struct {
|
||||
uid string
|
||||
scope string
|
||||
}
|
||||
|
||||
var ErrScopeUnknown error = NewError(ErrorCodeInvalidRequest, "unknown scope", "https://indieweb.org/scope")
|
||||
|
||||
//nolint:gochecknoglobals // structs cannot be constants
|
||||
var (
|
||||
ScopeUnd = Scope{uid: ""} // "und"
|
||||
ScopeUnd = Scope{scope: ""} // "und"
|
||||
|
||||
// https://indieweb.org/scope#Micropub_Scopes
|
||||
ScopeCreate = Scope{uid: "create"} // "create"
|
||||
ScopeDelete = Scope{uid: "delete"} // "delete"
|
||||
ScopeDraft = Scope{uid: "draft"} // "draft"
|
||||
ScopeMedia = Scope{uid: "media"} // "media"
|
||||
ScopeUndelete = Scope{uid: "undelete"} // "undelete"
|
||||
ScopeUpdate = Scope{uid: "update"} // "update"
|
||||
ScopeCreate = Scope{scope: "create"} // "create"
|
||||
ScopeDelete = Scope{scope: "delete"} // "delete"
|
||||
ScopeDraft = Scope{scope: "draft"} // "draft"
|
||||
ScopeMedia = Scope{scope: "media"} // "media"
|
||||
ScopeUndelete = Scope{scope: "undelete"} // "undelete"
|
||||
ScopeUpdate = Scope{scope: "update"} // "update"
|
||||
|
||||
// https://indieweb.org/scope#Microsub_Scopes
|
||||
ScopeBlock = Scope{uid: "block"} // "block"
|
||||
ScopeChannels = Scope{uid: "channels"} // "channels"
|
||||
ScopeFollow = Scope{uid: "follow"} // "follow"
|
||||
ScopeMute = Scope{uid: "mute"} // "mute"
|
||||
ScopeRead = Scope{uid: "read"} // "read"
|
||||
ScopeBlock = Scope{scope: "block"} // "block"
|
||||
ScopeChannels = Scope{scope: "channels"} // "channels"
|
||||
ScopeFollow = Scope{scope: "follow"} // "follow"
|
||||
ScopeMute = Scope{scope: "mute"} // "mute"
|
||||
ScopeRead = Scope{scope: "read"} // "read"
|
||||
|
||||
// This scope requests access to the user's default profile information
|
||||
// which include the following properties: name, photo, url.
|
||||
//
|
||||
// NOTE(toby3d): https://indieauth.net/source/#profile-information
|
||||
ScopeProfile = Scope{uid: "profile"} // "profile"
|
||||
ScopeProfile = Scope{scope: "profile"} // "profile"
|
||||
|
||||
// This scope requests access to the user's email address in the
|
||||
// following property: email.
|
||||
|
@ -51,24 +51,24 @@ var (
|
|||
// and must be requested along with the profile scope if desired.
|
||||
//
|
||||
// NOTE(toby3d): https://indieauth.net/source/#profile-information
|
||||
ScopeEmail = Scope{uid: "email"} // "email"
|
||||
ScopeEmail = Scope{scope: "email"} // "email"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals // maps cannot be constants
|
||||
var uidsScopes = map[string]Scope{
|
||||
ScopeBlock.uid: ScopeBlock,
|
||||
ScopeChannels.uid: ScopeChannels,
|
||||
ScopeCreate.uid: ScopeCreate,
|
||||
ScopeDelete.uid: ScopeDelete,
|
||||
ScopeDraft.uid: ScopeDraft,
|
||||
ScopeEmail.uid: ScopeEmail,
|
||||
ScopeFollow.uid: ScopeFollow,
|
||||
ScopeMedia.uid: ScopeMedia,
|
||||
ScopeMute.uid: ScopeMute,
|
||||
ScopeProfile.uid: ScopeProfile,
|
||||
ScopeRead.uid: ScopeRead,
|
||||
ScopeUndelete.uid: ScopeUndelete,
|
||||
ScopeUpdate.uid: ScopeUpdate,
|
||||
ScopeBlock.scope: ScopeBlock,
|
||||
ScopeChannels.scope: ScopeChannels,
|
||||
ScopeCreate.scope: ScopeCreate,
|
||||
ScopeDelete.scope: ScopeDelete,
|
||||
ScopeDraft.scope: ScopeDraft,
|
||||
ScopeEmail.scope: ScopeEmail,
|
||||
ScopeFollow.scope: ScopeFollow,
|
||||
ScopeMedia.scope: ScopeMedia,
|
||||
ScopeMute.scope: ScopeMute,
|
||||
ScopeProfile.scope: ScopeProfile,
|
||||
ScopeRead.scope: ScopeRead,
|
||||
ScopeUndelete.scope: ScopeUndelete,
|
||||
ScopeUpdate.scope: ScopeUpdate,
|
||||
}
|
||||
|
||||
// ParseScope parses scope slug into Scope domain.
|
||||
|
@ -97,13 +97,13 @@ func (s *Scope) UnmarshalJSON(v []byte) error {
|
|||
}
|
||||
|
||||
func (s Scope) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.Quote(s.uid)), nil
|
||||
return []byte(strconv.Quote(s.scope)), nil
|
||||
}
|
||||
|
||||
// String returns string representation of scope.
|
||||
func (s Scope) String() string {
|
||||
if s.uid != "" {
|
||||
return s.uid
|
||||
if s.scope != "" {
|
||||
return s.scope
|
||||
}
|
||||
|
||||
return common.Und
|
||||
|
|
|
@ -2,8 +2,9 @@ package http
|
|||
|
||||
//nolint:tagliatelle // https://indieauth.net/source/#indieauth-server-metadata
|
||||
type MetadataResponse struct {
|
||||
// The server's issuer identifier.
|
||||
Issuer string `json:"issuer"`
|
||||
// URL of a page containing human-readable information that
|
||||
// developers might need to know when using the server.
|
||||
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
||||
|
||||
// The Authorization Endpoint.
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
|
@ -14,37 +15,36 @@ type MetadataResponse struct {
|
|||
// The Introspection Endpoint.
|
||||
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||
|
||||
// JSON array containing a list of client authentication methods
|
||||
// supported by this introspection endpoint.
|
||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` //nolint:lll
|
||||
// The User Info Endpoint.
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
|
||||
// The Revocation Endpoint.
|
||||
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
||||
|
||||
// The server's issuer identifier.
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
// JSON array containing the value "none".
|
||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"` //nolint:lll
|
||||
|
||||
// JSON array containing scope values supported by the
|
||||
// IndieAuth server.
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
|
||||
// JSON array containing the response_type values supported.
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
|
||||
// JSON array containing grant type values supported.
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
|
||||
// URL of a page containing human-readable information that
|
||||
// developers might need to know when using the server.
|
||||
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
||||
// JSON array containing scope values supported by the
|
||||
// IndieAuth server.
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
|
||||
// JSON array containing the methods supported for PKCE.
|
||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||
|
||||
// JSON array containing a list of client authentication methods
|
||||
// supported by this introspection endpoint.
|
||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"` //nolint:lll
|
||||
|
||||
// Boolean parameter indicating whether the authorization server
|
||||
// provides the iss parameter.
|
||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"` //nolint:lll
|
||||
|
||||
// The User Info Endpoint.
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
}
|
||||
|
|
|
@ -18,25 +18,23 @@ import (
|
|||
type (
|
||||
//nolint:tagliatelle,lll
|
||||
Response struct {
|
||||
Issuer domain.URL `json:"issuer"`
|
||||
TicketEndpoint domain.URL `json:"ticket_endpoint"`
|
||||
AuthorizationEndpoint domain.URL `json:"authorization_endpoint"`
|
||||
IntrospectionEndpoint domain.URL `json:"introspection_endpoint"`
|
||||
RevocationEndpoint domain.URL `json:"revocation_endpoint,omitempty"`
|
||||
ServiceDocumentation domain.URL `json:"service_documentation,omitempty"`
|
||||
TokenEndpoint domain.URL `json:"token_endpoint"`
|
||||
UserinfoEndpoint domain.URL `json:"userinfo_endpoint,omitempty"`
|
||||
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
||||
Microsub domain.URL `json:"microsub"`
|
||||
Issuer domain.URL `json:"issuer"`
|
||||
Micropub domain.URL `json:"micropub"`
|
||||
GrantTypesSupported []domain.GrantType `json:"grant_types_supported,omitempty"`
|
||||
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
|
||||
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||
ScopesSupported []domain.Scope `json:"scopes_supported,omitempty"`
|
||||
ResponseTypesSupported []domain.ResponseType `json:"response_types_supported,omitempty"`
|
||||
CodeChallengeMethodsSupported []domain.CodeChallengeMethod `json:"code_challenge_methods_supported"`
|
||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||
|
||||
// Extensions
|
||||
TicketEndpoint domain.URL `json:"ticket_endpoint"`
|
||||
Micropub domain.URL `json:"micropub"`
|
||||
Microsub domain.URL `json:"microsub"`
|
||||
}
|
||||
|
||||
httpMetadataRepository struct {
|
||||
|
@ -150,29 +148,16 @@ func (r Response) populate(dst *domain.Metadata) {
|
|||
dst.TicketEndpoint = r.TicketEndpoint.URL
|
||||
dst.TokenEndpoint = r.TokenEndpoint.URL
|
||||
dst.UserinfoEndpoint = r.UserinfoEndpoint.URL
|
||||
dst.RevocationEndpointAuthMethodsSupported = append(dst.RevocationEndpointAuthMethodsSupported,
|
||||
r.RevocationEndpointAuthMethodsSupported...)
|
||||
dst.ResponseTypesSupported = append(dst.ResponseTypesSupported, r.ResponseTypesSupported...)
|
||||
dst.IntrospectionEndpointAuthMethodsSupported = append(dst.IntrospectionEndpointAuthMethodsSupported,
|
||||
r.IntrospectionEndpointAuthMethodsSupported...)
|
||||
dst.GrantTypesSupported = append(dst.GrantTypesSupported, r.GrantTypesSupported...)
|
||||
dst.CodeChallengeMethodsSupported = append(dst.CodeChallengeMethodsSupported,
|
||||
r.CodeChallengeMethodsSupported...)
|
||||
|
||||
for _, scope := range r.ScopesSupported {
|
||||
dst.ScopesSupported = append(dst.ScopesSupported, scope)
|
||||
}
|
||||
|
||||
for _, method := range r.RevocationEndpointAuthMethodsSupported {
|
||||
dst.RevocationEndpointAuthMethodsSupported = append(dst.RevocationEndpointAuthMethodsSupported, method)
|
||||
}
|
||||
|
||||
for _, responseType := range r.ResponseTypesSupported {
|
||||
dst.ResponseTypesSupported = append(dst.ResponseTypesSupported, responseType)
|
||||
}
|
||||
|
||||
for _, method := range r.IntrospectionEndpointAuthMethodsSupported {
|
||||
dst.IntrospectionEndpointAuthMethodsSupported = append(dst.IntrospectionEndpointAuthMethodsSupported,
|
||||
method)
|
||||
}
|
||||
|
||||
for _, grantType := range r.GrantTypesSupported {
|
||||
dst.GrantTypesSupported = append(dst.GrantTypesSupported, grantType)
|
||||
}
|
||||
|
||||
for _, method := range r.CodeChallengeMethodsSupported {
|
||||
dst.CodeChallengeMethodsSupported = append(dst.CodeChallengeMethodsSupported, method)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,22 +19,22 @@ import (
|
|||
|
||||
//nolint:lll,tagliatelle
|
||||
type Response struct {
|
||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||
Issuer string `json:"issuer"`
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
AuthorizationEndpoint string `json:"authorization_endpoint"`
|
||||
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
||||
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
UserinfoEndpoint string `json:"userinfo_endpoint,omitempty"`
|
||||
TicketEndpoint string `json:"ticket_endpoint"`
|
||||
Micropub string `json:"micropub"`
|
||||
Microsub string `json:"microsub"`
|
||||
RevocationEndpoint string `json:"revocation_endpoint,omitempty"`
|
||||
Micropub string `json:"micropub"`
|
||||
Issuer string `json:"issuer"`
|
||||
ServiceDocumentation string `json:"service_documentation,omitempty"`
|
||||
TicketEndpoint string `json:"ticket_endpoint"`
|
||||
TokenEndpoint string `json:"token_endpoint"`
|
||||
RevocationEndpointAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported,omitempty"`
|
||||
IntrospectionEndpointAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported,omitempty"`
|
||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
|
||||
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
|
||||
ScopesSupported []string `json:"scopes_supported,omitempty"`
|
||||
AuthorizationResponseIssParameterSupported bool `json:"authorization_response_iss_parameter_supported,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ const DefaultRealm string = "Restricted"
|
|||
|
||||
const basic string = "basic"
|
||||
|
||||
//nolint: gochecknoglobals
|
||||
// nolint: gochecknoglobals
|
||||
var DefaultBasicAuthConfig = BasicAuthConfig{
|
||||
Skipper: DefaultSkipper,
|
||||
Realm: DefaultRealm,
|
||||
|
|
|
@ -17,9 +17,8 @@ type (
|
|||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// TokenLength is the length of the generated token.
|
||||
TokenLength uint8
|
||||
// Optional. Default value 32.
|
||||
// ErrorHandler defines a function which is executed for returning custom errors.
|
||||
ErrorHandler CSRFErrorHandler
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that
|
||||
// is used to extract token from the request.
|
||||
|
@ -52,6 +51,14 @@ type (
|
|||
// Optional. Default value 86400 (24hr).
|
||||
CookieMaxAge int
|
||||
|
||||
// Indicates SameSite mode of the CSRF cookie.
|
||||
// Optional. Default value SameSiteDefaultMode.
|
||||
CookieSameSite http.SameSite
|
||||
|
||||
// TokenLength is the length of the generated token.
|
||||
// Optional. Default value 32.
|
||||
TokenLength uint8
|
||||
|
||||
// Indicates if CSRF cookie is secure.
|
||||
// Optional. Default value false.
|
||||
CookieSecure bool
|
||||
|
@ -59,13 +66,6 @@ type (
|
|||
// Indicates if CSRF cookie is HTTP only.
|
||||
// Optional. Default value false.
|
||||
CookieHTTPOnly bool
|
||||
|
||||
// Indicates SameSite mode of the CSRF cookie.
|
||||
// Optional. Default value SameSiteDefaultMode.
|
||||
CookieSameSite http.SameSite
|
||||
|
||||
// ErrorHandler defines a function which is executed for returning custom errors.
|
||||
ErrorHandler CSRFErrorHandler
|
||||
}
|
||||
|
||||
// CSRFErrorHandler is a function which is executed for creating custom errors.
|
||||
|
|
|
@ -15,25 +15,6 @@ import (
|
|||
type (
|
||||
// JWTConfig defines the config for JWT middleware.
|
||||
JWTConfig struct {
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// BeforeFunc defines a function which is executed just before
|
||||
// the middleware.
|
||||
BeforeFunc BeforeFunc
|
||||
|
||||
// SuccessHandler defines a function which is executed for a
|
||||
// valid token.
|
||||
SuccessHandler JWTSuccessHandler
|
||||
|
||||
// ErrorHandler defines a function which is executed for an
|
||||
// invalid token. It may be used to define a custom JWT error.
|
||||
ErrorHandler JWTErrorHandler
|
||||
|
||||
// ErrorHandlerWithContext is almost identical to ErrorHandler,
|
||||
// but it's passed the current context.
|
||||
ErrorHandlerWithContext JWTErrorHandlerWithContext
|
||||
|
||||
// Signing key to validate token.
|
||||
// This is one of the three options to provide a token
|
||||
// validation key. The order of precedence is a user-defined
|
||||
|
@ -52,49 +33,24 @@ type (
|
|||
// provided.
|
||||
SigningKeys map[string]any
|
||||
|
||||
// Signing method used to check the token's signing algorithm.
|
||||
//
|
||||
// Optional. Default value HS256.
|
||||
SigningMethod jwa.SignatureAlgorithm
|
||||
// SuccessHandler defines a function which is executed for a
|
||||
// valid token.
|
||||
SuccessHandler JWTSuccessHandler
|
||||
|
||||
// Context key to store user information from the token into
|
||||
// context.
|
||||
//
|
||||
// Optional. Default value "user".
|
||||
ContextKey string
|
||||
// ErrorHandler defines a function which is executed for an
|
||||
// invalid token. It may be used to define a custom JWT error.
|
||||
ErrorHandler JWTErrorHandler
|
||||
|
||||
// Claims are extendable claims data defining token content.
|
||||
// Used by default ParseTokenFunc implementation. Not used if
|
||||
// custom ParseTokenFunc is set.
|
||||
//
|
||||
// Optional. Default value []jwt.ClaimPair
|
||||
Claims []jwt.ClaimPair
|
||||
// ErrorHandlerWithContext is almost identical to ErrorHandler,
|
||||
// but it's passed the current context.
|
||||
ErrorHandlerWithContext JWTErrorHandlerWithContext
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" or
|
||||
// "<source>:<name>,<source>:<name>" that is used to extract
|
||||
// token from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "query:<name>"
|
||||
// - "cookie:<name>"
|
||||
// - "form:<name>"
|
||||
// Multiply sources example:
|
||||
// - "header: Authorization,cookie: myowncookie"
|
||||
TokenLookup string
|
||||
// BeforeFunc defines a function which is executed just before
|
||||
// the middleware.
|
||||
BeforeFunc BeforeFunc
|
||||
|
||||
// TokenLookupFuncs defines a list of user-defined functions
|
||||
// that extract JWT token from the given context.
|
||||
// This is one of the two options to provide a token extractor.
|
||||
// The order of precedence is user-defined TokenLookupFuncs, and
|
||||
// TokenLookup.
|
||||
// You can also provide both if you want.
|
||||
TokenLookupFuncs []ValuesExtractor
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
//
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
// Skipper defines a function to skip middleware.
|
||||
Skipper Skipper
|
||||
|
||||
// KeyFunc defines a user-defined function that supplies the
|
||||
// public key for a token validation. The function shall take
|
||||
|
@ -119,6 +75,50 @@ type (
|
|||
// using `github.com/golang-jwt/jwt` as JWT implementation library
|
||||
ParseTokenFunc func(auth []byte, w http.ResponseWriter, r *http.Request) (any, error)
|
||||
|
||||
// Context key to store user information from the token into
|
||||
// context.
|
||||
//
|
||||
// Optional. Default value "user".
|
||||
ContextKey string
|
||||
|
||||
// TokenLookup is a string in the form of "<source>:<name>" or
|
||||
// "<source>:<name>,<source>:<name>" that is used to extract
|
||||
// token from the request.
|
||||
// Optional. Default value "header:Authorization".
|
||||
// Possible values:
|
||||
// - "header:<name>"
|
||||
// - "query:<name>"
|
||||
// - "cookie:<name>"
|
||||
// - "form:<name>"
|
||||
// Multiply sources example:
|
||||
// - "header: Authorization,cookie: myowncookie"
|
||||
TokenLookup string
|
||||
|
||||
// AuthScheme to be used in the Authorization header.
|
||||
//
|
||||
// Optional. Default value "Bearer".
|
||||
AuthScheme string
|
||||
|
||||
// Signing method used to check the token's signing algorithm.
|
||||
//
|
||||
// Optional. Default value HS256.
|
||||
SigningMethod jwa.SignatureAlgorithm
|
||||
|
||||
// Claims are extendable claims data defining token content.
|
||||
// Used by default ParseTokenFunc implementation. Not used if
|
||||
// custom ParseTokenFunc is set.
|
||||
//
|
||||
// Optional. Default value []jwt.ClaimPair
|
||||
Claims []jwt.ClaimPair
|
||||
|
||||
// TokenLookupFuncs defines a list of user-defined functions
|
||||
// that extract JWT token from the given context.
|
||||
// This is one of the two options to provide a token extractor.
|
||||
// The order of precedence is user-defined TokenLookupFuncs, and
|
||||
// TokenLookup.
|
||||
// You can also provide both if you want.
|
||||
TokenLookupFuncs []ValuesExtractor
|
||||
|
||||
// ContinueOnIgnoredError allows the next middleware/handler to
|
||||
// be called when ErrorHandlerWithContext decides to ignore the
|
||||
// error (by returning `nil`). This is useful when parts of your
|
||||
|
|
|
@ -17,9 +17,9 @@ type (
|
|||
}
|
||||
|
||||
memorySessionRepository struct {
|
||||
config domain.Config
|
||||
mutex *sync.RWMutex
|
||||
sessions map[string]Session
|
||||
config domain.Config
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@ import (
|
|||
|
||||
type (
|
||||
Session struct {
|
||||
CreatedAt sql.NullTime `db:"created_at"`
|
||||
Code string `db:"code"`
|
||||
Data string `db:"data"`
|
||||
CreatedAt sql.NullTime `db:"created_at"`
|
||||
}
|
||||
|
||||
sqlite3SessionRepository struct {
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"source.toby3d.me/toby3d/auth/internal/testing/sqltest"
|
||||
)
|
||||
|
||||
// nolint: gochecknoglobals // slices cannot be contants
|
||||
//nolint: gochecknoglobals // slices cannot be contants
|
||||
var tableColumns = []string{"created_at", "code", "data"}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
|
|
|
@ -19,7 +19,7 @@ func New(tb testing.TB) (*bolt.DB, func()) {
|
|||
|
||||
filePath := tempFile.Name()
|
||||
|
||||
if err := tempFile.Close(); err != nil {
|
||||
if err = tempFile.Close(); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ import (
|
|||
)
|
||||
|
||||
type Handler struct {
|
||||
config domain.Config
|
||||
matcher language.Matcher
|
||||
tickets ticket.UseCase
|
||||
config domain.Config
|
||||
}
|
||||
|
||||
func NewHandler(tickets ticket.UseCase, matcher language.Matcher, config domain.Config) *Handler {
|
||||
|
@ -36,7 +36,7 @@ func (h *Handler) Handler() http.Handler {
|
|||
//nolint:exhaustivestruct
|
||||
chain := middleware.Chain{
|
||||
middleware.CSRFWithConfig(middleware.CSRFConfig{
|
||||
Skipper: func(w http.ResponseWriter, r *http.Request) bool {
|
||||
Skipper: func(_ http.ResponseWriter, r *http.Request) bool {
|
||||
head, _ := urlutil.ShiftPath(r.URL.Path)
|
||||
|
||||
return r.Method == http.MethodPost && head == "ticket"
|
||||
|
|
|
@ -16,9 +16,9 @@ type (
|
|||
}
|
||||
|
||||
memoryTicketRepository struct {
|
||||
config domain.Config
|
||||
mutex *sync.RWMutex
|
||||
tickets map[string]Ticket
|
||||
config domain.Config
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -65,7 +65,7 @@ func (repo *memoryTicketRepository) GC() {
|
|||
defer ticker.Stop()
|
||||
|
||||
for ts := range ticker.C {
|
||||
ts := ts.UTC()
|
||||
ts = ts.UTC()
|
||||
|
||||
repo.mutex.RLock()
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@ import (
|
|||
|
||||
type (
|
||||
Ticket struct {
|
||||
CreatedAt sql.NullTime `db:"created_at"`
|
||||
Resource string `db:"resource"`
|
||||
Subject string `db:"subject"`
|
||||
Ticket string `db:"ticket"`
|
||||
CreatedAt sql.NullTime `db:"created_at"`
|
||||
}
|
||||
|
||||
sqlite3TicketRepository struct {
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
repository "source.toby3d.me/toby3d/auth/internal/ticket/repository/sqlite3"
|
||||
)
|
||||
|
||||
//nolint: gochecknoglobals // slices cannot be contants
|
||||
// nolint: gochecknoglobals // slices cannot be contants
|
||||
var tableColumns = []string{"created_at", "resource", "subject", "ticket"}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
|
|
|
@ -18,18 +18,18 @@ type (
|
|||
}
|
||||
|
||||
TokenRefreshRequest struct {
|
||||
GrantType domain.GrantType `form:"grant_type"` // refresh_token
|
||||
|
||||
// The client ID that was used when the refresh token was issued.
|
||||
ClientID domain.ClientID `form:"client_id"`
|
||||
|
||||
GrantType domain.GrantType `form:"grant_type"` // refresh_token
|
||||
|
||||
// The refresh token previously offered to the client.
|
||||
RefreshToken string `form:"refresh_token"`
|
||||
|
||||
// The client may request a token with the same or fewer scopes
|
||||
// than the original access token. If omitted, is treated as
|
||||
// equal to the original scopes granted.
|
||||
Scope domain.Scopes `form:"scope"`
|
||||
|
||||
// The refresh token previously offered to the client.
|
||||
RefreshToken string `form:"refresh_token"`
|
||||
}
|
||||
|
||||
TokenRevocationRequest struct {
|
||||
|
|
|
@ -16,11 +16,11 @@ import (
|
|||
|
||||
type (
|
||||
Token struct {
|
||||
CreatedAt sql.NullTime `db:"created_at"`
|
||||
AccessToken string `db:"access_token"`
|
||||
ClientID string `db:"client_id"`
|
||||
Me string `db:"me"`
|
||||
Scope string `db:"scope"`
|
||||
CreatedAt sql.NullTime `db:"created_at"`
|
||||
}
|
||||
|
||||
sqlite3TokenRepository struct {
|
||||
|
|
|
@ -58,6 +58,7 @@ func (h *Handler) handleFunc(w http.ResponseWriter, r *http.Request) {
|
|||
// WARN(toby3d): If the token is not valid, the endpoint still
|
||||
// MUST return a 200 Response.
|
||||
_ = encoder.Encode(err) //nolint:errchkjson
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
return
|
||||
|
@ -70,34 +71,15 @@ func (h *Handler) handleFunc(w http.ResponseWriter, r *http.Request) {
|
|||
"token with 'profile' scope is required to view profile data",
|
||||
"https://indieauth.net/source/#user-information",
|
||||
))
|
||||
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
resp := new(UserInformationResponse)
|
||||
if userInfo == nil {
|
||||
_ = encoder.Encode(resp) //nolint:errchkjson
|
||||
//nolint:errchkjson
|
||||
_ = encoder.Encode(NewUserInformationResponse(userInfo,
|
||||
userInfo.HasEmail() && tkn.Scope.Has(domain.ScopeEmail)))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if userInfo.HasName() {
|
||||
resp.Name = userInfo.GetName()
|
||||
}
|
||||
|
||||
if userInfo.HasURL() {
|
||||
resp.URL = userInfo.GetURL().String()
|
||||
}
|
||||
|
||||
if userInfo.HasPhoto() {
|
||||
resp.Photo = userInfo.GetPhoto().String()
|
||||
}
|
||||
|
||||
if tkn.Scope.Has(domain.ScopeEmail) && userInfo.HasEmail() {
|
||||
resp.Email = userInfo.GetEmail().String()
|
||||
}
|
||||
|
||||
_ = encoder.Encode(resp) //nolint:errchkjson
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,36 @@
|
|||
package http
|
||||
|
||||
import "source.toby3d.me/toby3d/auth/internal/domain"
|
||||
|
||||
type UserInformationResponse struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Photo string `json:"photo,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
URL *domain.URL `json:"url,omitempty"`
|
||||
Photo *domain.URL `json:"photo,omitempty"`
|
||||
Email *domain.Email `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func NewUserInformationResponse(in *domain.Profile, hasEmail bool) *UserInformationResponse {
|
||||
out := new(UserInformationResponse)
|
||||
|
||||
if in == nil {
|
||||
return out
|
||||
}
|
||||
|
||||
if in.HasName() {
|
||||
out.Name = in.GetName()
|
||||
}
|
||||
|
||||
if in.HasURL() {
|
||||
out.URL = &domain.URL{URL: in.GetURL()}
|
||||
}
|
||||
|
||||
if in.HasPhoto() {
|
||||
out.Photo = &domain.URL{URL: in.GetPhoto()}
|
||||
}
|
||||
|
||||
if hasEmail {
|
||||
out.Email = in.GetEmail()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"source.toby3d.me/toby3d/auth/internal/common"
|
||||
"source.toby3d.me/toby3d/auth/internal/domain"
|
||||
|
@ -57,14 +58,15 @@ func TestUserInfo(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result.Name != deps.profile.GetName() ||
|
||||
result.Photo != deps.profile.GetPhoto().String() {
|
||||
t.Errorf("GET /userinfo = %+v, want %+v", result, &delivery.UserInformationResponse{
|
||||
Name: deps.profile.GetName(),
|
||||
URL: deps.profile.GetURL().String(),
|
||||
Photo: deps.profile.GetPhoto().String(),
|
||||
Email: deps.profile.GetEmail().String(),
|
||||
})
|
||||
exp := &delivery.UserInformationResponse{
|
||||
Name: deps.profile.GetName(),
|
||||
URL: &domain.URL{URL: deps.profile.GetURL()},
|
||||
Photo: &domain.URL{URL: deps.profile.GetPhoto()},
|
||||
Email: deps.profile.GetEmail(),
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(result, exp, cmp.AllowUnexported(domain.URL{}, domain.Email{})); diff != "" {
|
||||
t.Errorf("%s %s = %+v, want %+v", req.Method, req.RequestURI, result, exp)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
51
main.go
51
main.go
|
@ -16,13 +16,14 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
|
@ -106,8 +107,8 @@ var (
|
|||
//nolint:gochecknoglobals
|
||||
var cpuProfilePath, memProfilePath string
|
||||
|
||||
//go:embed assets/*
|
||||
var staticFS embed.FS
|
||||
//go:embed web/static/*
|
||||
var static embed.FS
|
||||
|
||||
//nolint:gochecknoinits
|
||||
func init() {
|
||||
|
@ -116,11 +117,9 @@ func init() {
|
|||
flag.Parse()
|
||||
|
||||
if err := env.Parse(config, env.Options{
|
||||
Environment: nil,
|
||||
OnSet: nil,
|
||||
Prefix: "INDIEAUTH_",
|
||||
RequiredIfNoDef: false,
|
||||
TagName: "",
|
||||
Prefix: "AUTH_",
|
||||
TagName: "env",
|
||||
UseFieldNameByDefault: true,
|
||||
}); err != nil {
|
||||
logger.Fatalln(err)
|
||||
}
|
||||
|
@ -156,7 +155,7 @@ func main() {
|
|||
var opts NewAppOptions
|
||||
|
||||
var err error
|
||||
if opts.Static, err = fs.Sub(staticFS, "assets"); err != nil {
|
||||
if opts.Static, err = fs.Sub(static, filepath.Join("web", "static")); err != nil {
|
||||
logger.Fatalln(err)
|
||||
}
|
||||
|
||||
|
@ -164,7 +163,7 @@ func main() {
|
|||
case "sqlite3":
|
||||
store, err := sqlx.Open("sqlite", config.Database.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
logger.Fatalln(err)
|
||||
}
|
||||
|
||||
if err = store.Ping(); err != nil {
|
||||
|
@ -226,7 +225,7 @@ func main() {
|
|||
logger.Printf("started at %s, available at %s", config.Server.GetAddress(),
|
||||
config.Server.GetRootURL())
|
||||
|
||||
err := server.ListenAndServe()
|
||||
err = server.ListenAndServe()
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
logger.Fatalln("cannot listen and serve:", err)
|
||||
}
|
||||
|
@ -234,7 +233,7 @@ func main() {
|
|||
|
||||
<-done
|
||||
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
if err = server.Shutdown(ctx); err != nil {
|
||||
logger.Fatalln("failed shutdown of server:", err)
|
||||
}
|
||||
|
||||
|
@ -343,42 +342,42 @@ func (app *App) Handler() http.Handler {
|
|||
}).Handler()
|
||||
user := userhttpdelivery.NewHandler(app.tokens, config).Handler()
|
||||
ticket := tickethttpdelivery.NewHandler(app.tickets, app.matcher, *config).Handler()
|
||||
static := http.FileServer(http.FS(app.static))
|
||||
staticHandler := http.FileServer(http.FS(app.static))
|
||||
|
||||
return http.HandlerFunc(middleware.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var head string
|
||||
head, r.URL.Path = urlutil.ShiftPath(r.URL.Path)
|
||||
|
||||
switch head {
|
||||
case "", "callback", "token", "introspect", "revocation":
|
||||
if r.URL.Path != "/" {
|
||||
r.URL = r.URL.JoinPath(head, r.URL.Path)
|
||||
} else {
|
||||
r.URL = r.URL.JoinPath(head)
|
||||
}
|
||||
}
|
||||
head, tail := urlutil.ShiftPath(r.URL.Path)
|
||||
|
||||
switch head {
|
||||
default:
|
||||
static.ServeHTTP(w, r)
|
||||
staticHandler.ServeHTTP(w, r)
|
||||
case "", "callback":
|
||||
client.ServeHTTP(w, r)
|
||||
case "token", "introspect", "revocation":
|
||||
token.ServeHTTP(w, r)
|
||||
case ".well-known":
|
||||
r.URL.Path = tail
|
||||
|
||||
if head, _ = urlutil.ShiftPath(r.URL.Path); head == "oauth-authorization-server" {
|
||||
metadata.ServeHTTP(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
case "authorize":
|
||||
r.URL.Path = tail
|
||||
|
||||
auth.ServeHTTP(w, r)
|
||||
case "health":
|
||||
r.URL.Path = tail
|
||||
|
||||
health.ServeHTTP(w, r)
|
||||
case "userinfo":
|
||||
r.URL.Path = tail
|
||||
|
||||
user.ServeHTTP(w, r)
|
||||
case "ticket":
|
||||
r.URL.Path = tail
|
||||
|
||||
ticket.ServeHTTP(w, r)
|
||||
}
|
||||
}) /*.Intercept(middleware.LogFmt())*/)
|
||||
}).Intercept(middleware.LogFmt()))
|
||||
}
|
||||
|
|
|
@ -1304,26 +1304,21 @@ func wrapRingBuffer(s *Reader) {
|
|||
Last two bytes of ring-buffer are initialized to 0, so context calculation
|
||||
could be done uniformly for the first two and all other positions. */
|
||||
func ensureRingBuffer(s *Reader) bool {
|
||||
var old_ringbuffer []byte = s.ringbuffer
|
||||
var old_ringbuffer []byte
|
||||
if s.ringbuffer_size == s.new_ringbuffer_size {
|
||||
return true
|
||||
}
|
||||
|
||||
s.ringbuffer = make([]byte, uint(s.new_ringbuffer_size)+uint(kRingBufferWriteAheadSlack))
|
||||
if s.ringbuffer == nil {
|
||||
/* Restore previous value. */
|
||||
s.ringbuffer = old_ringbuffer
|
||||
|
||||
return false
|
||||
spaceNeeded := int(s.new_ringbuffer_size) + int(kRingBufferWriteAheadSlack)
|
||||
if len(s.ringbuffer) < spaceNeeded {
|
||||
old_ringbuffer = s.ringbuffer
|
||||
s.ringbuffer = make([]byte, spaceNeeded)
|
||||
}
|
||||
|
||||
s.ringbuffer[s.new_ringbuffer_size-2] = 0
|
||||
s.ringbuffer[s.new_ringbuffer_size-1] = 0
|
||||
|
||||
if !(old_ringbuffer == nil) {
|
||||
if old_ringbuffer != nil {
|
||||
copy(s.ringbuffer, old_ringbuffer[:uint(s.pos)])
|
||||
|
||||
old_ringbuffer = nil
|
||||
}
|
||||
|
||||
s.ringbuffer_size = s.new_ringbuffer_size
|
||||
|
|
|
@ -27,10 +27,16 @@ func NewReader(src io.Reader) *Reader {
|
|||
}
|
||||
|
||||
// Reset discards the Reader's state and makes it equivalent to the result of
|
||||
// its original state from NewReader, but writing to src instead.
|
||||
// its original state from NewReader, but reading from src instead.
|
||||
// This permits reusing a Reader rather than allocating a new one.
|
||||
// Error is always nil
|
||||
func (r *Reader) Reset(src io.Reader) error {
|
||||
if r.error_code < 0 {
|
||||
// There was an unrecoverable error, leaving the Reader's state
|
||||
// undefined. Clear out everything but the buffer.
|
||||
*r = Reader{buf: r.buf}
|
||||
}
|
||||
|
||||
decoderStateInit(r)
|
||||
r.src = src
|
||||
if r.buf == nil {
|
||||
|
|
|
@ -200,7 +200,6 @@ func decoderStateInit(s *Reader) bool {
|
|||
|
||||
s.block_type_trees = nil
|
||||
s.block_len_trees = nil
|
||||
s.ringbuffer = nil
|
||||
s.ringbuffer_size = 0
|
||||
s.new_ringbuffer_size = 0
|
||||
s.ringbuffer_mask = 0
|
||||
|
|
|
@ -397,6 +397,13 @@ func rTime(ra *rand.Rand, t reflect.StructField, v reflect.Value, tag string) er
|
|||
// Generate time
|
||||
timeOutput := generate(ra, tag)
|
||||
|
||||
// Check to see if timeOutput has monotonic clock reading
|
||||
// if so, remove it. This is because time.Parse() does not
|
||||
// support parsing the monotonic clock reading
|
||||
if strings.Contains(timeOutput, " m=") {
|
||||
timeOutput = strings.Split(timeOutput, " m=")[0]
|
||||
}
|
||||
|
||||
// Check to see if they are passing in a format to parse the time
|
||||
timeFormat, timeFormatOK := t.Tag.Lookup("format")
|
||||
if timeFormatOK {
|
||||
|
|
141
vendor/github.com/caarlos0/env/v6/README.md → vendor/github.com/caarlos0/env/v7/README.md
generated
vendored
141
vendor/github.com/caarlos0/env/v6/README.md → vendor/github.com/caarlos0/env/v7/README.md
generated
vendored
|
@ -1,8 +1,8 @@
|
|||
# env
|
||||
|
||||
[![Build Status](https://img.shields.io/github/workflow/status/caarlos0/env/build?style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build)
|
||||
[![Build Status](https://img.shields.io/github/actions/workflow/status/caarlos0/env/build.yml?branch=main&style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build)
|
||||
[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env)
|
||||
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v6)
|
||||
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v7)
|
||||
|
||||
A simple and zero-dependencies library to parse environment variables into structs.
|
||||
|
||||
|
@ -11,7 +11,7 @@ A simple and zero-dependencies library to parse environment variables into struc
|
|||
Get the module with:
|
||||
|
||||
```sh
|
||||
go get github.com/caarlos0/env/v6
|
||||
go get github.com/caarlos0/env/v7
|
||||
```
|
||||
|
||||
The usage looks like this:
|
||||
|
@ -23,7 +23,7 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
|
@ -53,7 +53,14 @@ $ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go
|
|||
{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s}
|
||||
```
|
||||
|
||||
⚠️⚠️⚠️ **Attention:** _unexported fields_ will be **ignored**.
|
||||
## Caveats
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> **This is important!**
|
||||
|
||||
- _Unexported fields_ are **ignored**
|
||||
|
||||
|
||||
## Supported types and defaults
|
||||
|
||||
|
@ -80,11 +87,15 @@ Complete list:
|
|||
- `encoding.TextUnmarshaler`
|
||||
- `url.URL`
|
||||
|
||||
Pointers, slices and slices of pointers of those types are also supported.
|
||||
Pointers, slices and slices of pointers, and maps of those types are also
|
||||
supported.
|
||||
|
||||
You can also use/define a [custom parser func](#custom-parser-funcs) for any
|
||||
other type you want.
|
||||
|
||||
You can also use custom keys and values in your maps, as long as you provide a
|
||||
parser function for them.
|
||||
|
||||
If you set the `envDefault` tag for something, this value will be used in the
|
||||
case of absence of it in the environment.
|
||||
|
||||
|
@ -107,7 +118,7 @@ also accepts a `map[reflect.Type]env.ParserFunc`.
|
|||
If you add a custom parser for, say `Foo`, it will also be used to parse
|
||||
`*Foo` and `[]Foo` types.
|
||||
|
||||
Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v6)
|
||||
Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v7)
|
||||
for more info.
|
||||
|
||||
### A note about `TextUnmarshaler` and `time.Time`
|
||||
|
@ -148,7 +159,7 @@ type config struct {
|
|||
|
||||
## Not Empty fields
|
||||
|
||||
While `required` demands the environment variable to be check, it doesn't check its value.
|
||||
While `required` demands the environment variable to be set, it doesn't check its value.
|
||||
If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
|
||||
|
||||
Example:
|
||||
|
@ -185,7 +196,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
|
@ -217,6 +228,46 @@ $ SECRET=/tmp/secret \
|
|||
|
||||
## Options
|
||||
|
||||
### Use field names as environment variables by default
|
||||
|
||||
If you don't want to set the `env` tag on every field, you can use the
|
||||
`UseFieldNameByDefault` option.
|
||||
|
||||
It will use the field name as environment variable name.
|
||||
|
||||
Here's an example:
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string // will use $USERNAME
|
||||
Password string // will use $PASSWORD
|
||||
UserFullName string // will use $USER_FULL_NAME
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := &env.Options{UseFieldNameByDefault: true}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.Parse(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
### Environment
|
||||
|
||||
By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
|
||||
|
@ -232,7 +283,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -251,7 +302,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -268,7 +319,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -285,7 +336,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -302,7 +353,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -318,7 +369,7 @@ type ComplexConfig struct {
|
|||
|
||||
func main() {
|
||||
cfg := ComplexConfig{}
|
||||
if err := Parse(&cfg, Options{
|
||||
if err := Parse(&cfg, Options{
|
||||
Prefix: "T_",
|
||||
Environment: map[string]string{
|
||||
"T_FOO_HOME": "/foo",
|
||||
|
@ -336,7 +387,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -352,7 +403,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -374,7 +425,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -391,7 +442,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -409,7 +460,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg.envData)
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -425,7 +476,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v6"
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
@ -447,6 +498,54 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
You can handle the errors the library throws like so:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v7"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cfg Config
|
||||
err := env.Parse(&cfg)
|
||||
if e, ok := err.(*env.AggregateError); ok {
|
||||
for _, er := range e.Errors {
|
||||
switch v := er.(type) {
|
||||
case env.ParseError:
|
||||
// handle it
|
||||
case env.NotStructPtrError:
|
||||
// handle it
|
||||
case env.NoParserError:
|
||||
// handle it
|
||||
case env.NoSupportedTagOptionError:
|
||||
// handle it
|
||||
default:
|
||||
fmt.Printf("Unknown error type %v", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
|
||||
}
|
||||
```
|
||||
|
||||
> **Info**
|
||||
>
|
||||
> If you want to check if an specific error is in the chain, you can also use
|
||||
> `errors.Is()`.
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)
|
152
vendor/github.com/caarlos0/env/v6/env.go → vendor/github.com/caarlos0/env/v7/env.go
generated
vendored
152
vendor/github.com/caarlos0/env/v6/env.go → vendor/github.com/caarlos0/env/v7/env.go
generated
vendored
|
@ -2,7 +2,6 @@ package env
|
|||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -10,14 +9,11 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// nolint: gochecknoglobals
|
||||
var (
|
||||
// ErrNotAStructPtr is returned if you pass something that is not a pointer to a
|
||||
// Struct to Parse.
|
||||
ErrNotAStructPtr = errors.New("env: expected a pointer to a Struct")
|
||||
|
||||
defaultBuiltInParsers = map[reflect.Kind]ParserFunc{
|
||||
reflect.Bool: func(v string) (interface{}, error) {
|
||||
return strconv.ParseBool(v)
|
||||
|
@ -79,14 +75,14 @@ func defaultTypeParsers() map[reflect.Type]ParserFunc {
|
|||
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse URL: %v", err)
|
||||
return nil, newParseValueError("unable to parse URL", err)
|
||||
}
|
||||
return *u, nil
|
||||
},
|
||||
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
|
||||
s, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse duration: %v", err)
|
||||
return nil, newParseValueError("unable to parse duration", err)
|
||||
}
|
||||
return s, err
|
||||
},
|
||||
|
@ -107,15 +103,20 @@ type Options struct {
|
|||
// TagName specifies another tagname to use rather than the default env.
|
||||
TagName string
|
||||
|
||||
// RequiredIfNoDef automatically sets all env as required if they do not declare 'envDefault'
|
||||
// RequiredIfNoDef automatically sets all env as required if they do not
|
||||
// declare 'envDefault'.
|
||||
RequiredIfNoDef bool
|
||||
|
||||
// OnSet allows to run a function when a value is set
|
||||
// OnSet allows to run a function when a value is set.
|
||||
OnSet OnSetFn
|
||||
|
||||
// Prefix define a prefix for each key
|
||||
// Prefix define a prefix for each key.
|
||||
Prefix string
|
||||
|
||||
// UseFieldNameByDefault defines whether or not env should use the field
|
||||
// name by default if the `env` key is missing.
|
||||
UseFieldNameByDefault bool
|
||||
|
||||
// Sets to true if we have already configured once.
|
||||
configured bool
|
||||
}
|
||||
|
@ -150,6 +151,7 @@ func configure(opts []Options) []Options {
|
|||
if item.Prefix != "" {
|
||||
opt.Prefix = item.Prefix
|
||||
}
|
||||
opt.UseFieldNameByDefault = item.UseFieldNameByDefault
|
||||
opt.RequiredIfNoDef = item.RequiredIfNoDef
|
||||
}
|
||||
|
||||
|
@ -183,11 +185,11 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
|
|||
|
||||
ptrRef := reflect.ValueOf(v)
|
||||
if ptrRef.Kind() != reflect.Ptr {
|
||||
return ErrNotAStructPtr
|
||||
return newAggregateError(NotStructPtrError{})
|
||||
}
|
||||
ref := ptrRef.Elem()
|
||||
if ref.Kind() != reflect.Struct {
|
||||
return ErrNotAStructPtr
|
||||
return newAggregateError(NotStructPtrError{})
|
||||
}
|
||||
parsers := defaultTypeParsers()
|
||||
for k, v := range funcMap {
|
||||
|
@ -200,22 +202,22 @@ func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...
|
|||
func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error {
|
||||
refType := ref.Type()
|
||||
|
||||
var agrErr aggregateError
|
||||
var agrErr AggregateError
|
||||
|
||||
for i := 0; i < refType.NumField(); i++ {
|
||||
refField := ref.Field(i)
|
||||
refTypeField := refType.Field(i)
|
||||
|
||||
if err := doParseField(refField, refTypeField, funcMap, opts); err != nil {
|
||||
if val, ok := err.(aggregateError); ok {
|
||||
agrErr.errors = append(agrErr.errors, val.errors...)
|
||||
if val, ok := err.(AggregateError); ok {
|
||||
agrErr.Errors = append(agrErr.Errors, val.Errors...)
|
||||
} else {
|
||||
agrErr.errors = append(agrErr.errors, err)
|
||||
agrErr.Errors = append(agrErr.Errors, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(agrErr.errors) == 0 {
|
||||
if len(agrErr.Errors) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -226,7 +228,7 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
|
|||
if !refField.CanSet() {
|
||||
return nil
|
||||
}
|
||||
if reflect.Ptr == refField.Kind() && refField.Elem().Kind() == reflect.Struct {
|
||||
if reflect.Ptr == refField.Kind() && !refField.IsNil() {
|
||||
return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...)
|
||||
}
|
||||
if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" {
|
||||
|
@ -248,6 +250,19 @@ func doParseField(refField reflect.Value, refTypeField reflect.StructField, func
|
|||
return nil
|
||||
}
|
||||
|
||||
const underscore rune = '_'
|
||||
|
||||
func toEnvName(input string) string {
|
||||
var output []rune
|
||||
for i, c := range input {
|
||||
if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c {
|
||||
output = append(output, underscore)
|
||||
}
|
||||
output = append(output, unicode.ToUpper(c))
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
|
||||
func get(field reflect.StructField, opts []Options) (val string, err error) {
|
||||
var exists bool
|
||||
var isDefault bool
|
||||
|
@ -258,6 +273,9 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
|
|||
required := opts[0].RequiredIfNoDef
|
||||
prefix := opts[0].Prefix
|
||||
ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts)))
|
||||
if ownKey == "" && opts[0].UseFieldNameByDefault {
|
||||
ownKey = toEnvName(field.Name)
|
||||
}
|
||||
key := prefix + ownKey
|
||||
for _, tag := range tags {
|
||||
switch tag {
|
||||
|
@ -272,7 +290,7 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
|
|||
case "notEmpty":
|
||||
notEmpty = true
|
||||
default:
|
||||
return "", fmt.Errorf("tag option %q not supported", tag)
|
||||
return "", newNoSupportedTagOptionError(tag)
|
||||
}
|
||||
}
|
||||
expand := strings.EqualFold(field.Tag.Get("envExpand"), "true")
|
||||
|
@ -288,18 +306,18 @@ func get(field reflect.StructField, opts []Options) (val string, err error) {
|
|||
}
|
||||
|
||||
if required && !exists && len(ownKey) > 0 {
|
||||
return "", fmt.Errorf(`required environment variable %q is not set`, key)
|
||||
return "", newEnvVarIsNotSet(key)
|
||||
}
|
||||
|
||||
if notEmpty && val == "" {
|
||||
return "", fmt.Errorf("environment variable %q should not be empty", key)
|
||||
return "", newEmptyEnvVarError(key)
|
||||
}
|
||||
|
||||
if loadFile && val != "" {
|
||||
filename := val
|
||||
val, err = getFromFile(filename)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf(`could not load content of file "%s" from variable %s: %v`, filename, key, err)
|
||||
return "", newLoadFileContentError(filename, key, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,6 +343,8 @@ func getOr(key, defaultValue string, defExists bool, envs map[string]string) (st
|
|||
switch {
|
||||
case (!exists || key == "") && defExists:
|
||||
return defaultValue, true, true
|
||||
case exists && value == "" && defExists:
|
||||
return defaultValue, true, true
|
||||
case !exists:
|
||||
return "", false, false
|
||||
}
|
||||
|
@ -369,8 +389,11 @@ func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[
|
|||
return nil
|
||||
}
|
||||
|
||||
if field.Kind() == reflect.Slice {
|
||||
switch field.Kind() {
|
||||
case reflect.Slice:
|
||||
return handleSlice(field, value, sf, funcMap)
|
||||
case reflect.Map:
|
||||
return handleMap(field, value, sf, funcMap)
|
||||
}
|
||||
|
||||
return newNoParserError(sf)
|
||||
|
@ -417,6 +440,54 @@ func handleSlice(field reflect.Value, value string, sf reflect.StructField, func
|
|||
return nil
|
||||
}
|
||||
|
||||
func handleMap(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
|
||||
keyType := sf.Type.Key()
|
||||
keyParserFunc, ok := funcMap[keyType]
|
||||
if !ok {
|
||||
keyParserFunc, ok = defaultBuiltInParsers[keyType.Kind()]
|
||||
if !ok {
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
}
|
||||
|
||||
elemType := sf.Type.Elem()
|
||||
elemParserFunc, ok := funcMap[elemType]
|
||||
if !ok {
|
||||
elemParserFunc, ok = defaultBuiltInParsers[elemType.Kind()]
|
||||
if !ok {
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
}
|
||||
|
||||
separator := sf.Tag.Get("envSeparator")
|
||||
if separator == "" {
|
||||
separator = ","
|
||||
}
|
||||
|
||||
result := reflect.MakeMap(sf.Type)
|
||||
for _, part := range strings.Split(value, separator) {
|
||||
pairs := strings.Split(part, ":")
|
||||
if len(pairs) != 2 {
|
||||
return newParseError(sf, fmt.Errorf(`%q should be in "key:value" format`, part))
|
||||
}
|
||||
|
||||
key, err := keyParserFunc(pairs[0])
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
elem, err := elemParserFunc(pairs[1])
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
result.SetMapIndex(reflect.ValueOf(key).Convert(keyType), reflect.ValueOf(elem).Convert(elemType))
|
||||
}
|
||||
|
||||
field.Set(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler {
|
||||
if reflect.Ptr == field.Kind() {
|
||||
if field.IsNil() {
|
||||
|
@ -459,26 +530,6 @@ func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.Struct
|
|||
return nil
|
||||
}
|
||||
|
||||
func newParseError(sf reflect.StructField, err error) error {
|
||||
return parseError{
|
||||
sf: sf,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
type parseError struct {
|
||||
sf reflect.StructField
|
||||
err error
|
||||
}
|
||||
|
||||
func (e parseError) Error() string {
|
||||
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.sf.Name, e.sf.Type, e.err)
|
||||
}
|
||||
|
||||
func newNoParserError(sf reflect.StructField) error {
|
||||
return fmt.Errorf(`no parser found for field "%s" of type "%s"`, sf.Name, sf.Type)
|
||||
}
|
||||
|
||||
func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
|
||||
subOpts := make([]Options, len(opts))
|
||||
copy(subOpts, opts)
|
||||
|
@ -487,18 +538,3 @@ func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
|
|||
}
|
||||
return subOpts
|
||||
}
|
||||
|
||||
type aggregateError struct {
|
||||
errors []error
|
||||
}
|
||||
|
||||
func (e aggregateError) Error() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("env:")
|
||||
|
||||
for _, err := range e.errors {
|
||||
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
|
||||
}
|
||||
|
||||
return strings.TrimRight(sb.String(), ";")
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// An aggregated error wrapper to combine gathered errors. This allows either to display all errors or convert them individually
|
||||
// List of the available errors
|
||||
// ParseError
|
||||
// NotStructPtrError
|
||||
// NoParserError
|
||||
// NoSupportedTagOptionError
|
||||
// EnvVarIsNotSetError
|
||||
// EmptyEnvVarError
|
||||
// LoadFileContentError
|
||||
// ParseValueError
|
||||
type AggregateError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func newAggregateError(initErr error) error {
|
||||
return AggregateError{
|
||||
[]error{
|
||||
initErr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e AggregateError) Error() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("env:")
|
||||
|
||||
for _, err := range e.Errors {
|
||||
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
|
||||
}
|
||||
|
||||
return strings.TrimRight(sb.String(), ";")
|
||||
}
|
||||
|
||||
// Is conforms with errors.Is.
|
||||
func (e AggregateError) Is(err error) bool {
|
||||
for _, ie := range e.Errors {
|
||||
if reflect.TypeOf(ie) == reflect.TypeOf(err) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// The error occurs when it's impossible to convert the value for given type.
|
||||
type ParseError struct {
|
||||
Name string
|
||||
Type reflect.Type
|
||||
Err error
|
||||
}
|
||||
|
||||
func newParseError(sf reflect.StructField, err error) error {
|
||||
return ParseError{sf.Name, sf.Type, err}
|
||||
}
|
||||
|
||||
func (e ParseError) Error() string {
|
||||
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.Name, e.Type, e.Err)
|
||||
}
|
||||
|
||||
// The error occurs when pass something that is not a pointer to a Struct to Parse
|
||||
type NotStructPtrError struct{}
|
||||
|
||||
func (e NotStructPtrError) Error() string {
|
||||
return "expected a pointer to a Struct"
|
||||
}
|
||||
|
||||
// This error occurs when there is no parser provided for given type
|
||||
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
|
||||
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
|
||||
type NoParserError struct {
|
||||
Name string
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func newNoParserError(sf reflect.StructField) error {
|
||||
return NoParserError{sf.Name, sf.Type}
|
||||
}
|
||||
|
||||
func (e NoParserError) Error() string {
|
||||
return fmt.Sprintf(`no parser found for field "%s" of type "%s"`, e.Name, e.Type)
|
||||
}
|
||||
|
||||
// This error occurs when the given tag is not supported
|
||||
// In-built supported tags: "", "file", "required", "unset", "notEmpty", "envDefault", "envExpand", "envSeparator"
|
||||
// How to create a custom tag: https://github.com/caarlos0/env#changing-default-tag-name
|
||||
type NoSupportedTagOptionError struct {
|
||||
Tag string
|
||||
}
|
||||
|
||||
func newNoSupportedTagOptionError(tag string) error {
|
||||
return NoSupportedTagOptionError{tag}
|
||||
}
|
||||
|
||||
func (e NoSupportedTagOptionError) Error() string {
|
||||
return fmt.Sprintf("tag option %q not supported", e.Tag)
|
||||
}
|
||||
|
||||
// This error occurs when the required variable is not set
|
||||
// Read about required fields: https://github.com/caarlos0/env#required-fields
|
||||
type EnvVarIsNotSetError struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func newEnvVarIsNotSet(key string) error {
|
||||
return EnvVarIsNotSetError{key}
|
||||
}
|
||||
|
||||
func (e EnvVarIsNotSetError) Error() string {
|
||||
return fmt.Sprintf(`required environment variable %q is not set`, e.Key)
|
||||
}
|
||||
|
||||
// This error occurs when the variable which must be not empty is existing but has an empty value
|
||||
// Read about not empty fields: https://github.com/caarlos0/env#not-empty-fields
|
||||
type EmptyEnvVarError struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func newEmptyEnvVarError(key string) error {
|
||||
return EmptyEnvVarError{key}
|
||||
}
|
||||
|
||||
func (e EmptyEnvVarError) Error() string {
|
||||
return fmt.Sprintf("environment variable %q should not be empty", e.Key)
|
||||
}
|
||||
|
||||
// This error occurs when it's impossible to load the value from the file
|
||||
// Read about From file feature: https://github.com/caarlos0/env#from-file
|
||||
type LoadFileContentError struct {
|
||||
Filename string
|
||||
Key string
|
||||
Err error
|
||||
}
|
||||
|
||||
func newLoadFileContentError(filename, key string, err error) error {
|
||||
return LoadFileContentError{filename, key, err}
|
||||
}
|
||||
|
||||
func (e LoadFileContentError) Error() string {
|
||||
return fmt.Sprintf(`could not load content of file "%s" from variable %s: %v`, e.Filename, e.Key, e.Err)
|
||||
}
|
||||
|
||||
// This error occurs when it's impossible to convert value using given parser
|
||||
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
|
||||
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
|
||||
type ParseValueError struct {
|
||||
Msg string
|
||||
Err error
|
||||
}
|
||||
|
||||
func newParseValueError(message string, err error) error {
|
||||
return ParseValueError{message, err}
|
||||
}
|
||||
|
||||
func (e ParseValueError) Error() string {
|
||||
return fmt.Sprintf("%s: %v", e.Msg, e.Err)
|
||||
}
|
|
@ -1,48 +1,82 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.5.0] - 2020-01-03
|
||||
## [0.6.0] - 2023-01-30
|
||||
|
||||
[0.6.0]: https://github.com/go-logfmt/logfmt/compare/v0.5.1...v0.6.0
|
||||
|
||||
### Added
|
||||
|
||||
- NewDecoderSize by [@alexanderjophus]
|
||||
|
||||
## [0.5.1] - 2021-08-18
|
||||
|
||||
[0.5.1]: https://github.com/go-logfmt/logfmt/compare/v0.5.0...v0.5.1
|
||||
|
||||
### Changed
|
||||
|
||||
- Update the `go.mod` file for Go 1.17 as described in the [Go 1.17 release
|
||||
notes](https://golang.org/doc/go1.17#go-command)
|
||||
|
||||
## [0.5.0] - 2020-01-03
|
||||
|
||||
[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove the dependency on github.com/kr/logfmt by [@ChrisHines]
|
||||
- Move fuzz code to github.com/go-logfmt/fuzzlogfmt by [@ChrisHines]
|
||||
|
||||
## [0.4.0] - 2018-11-21
|
||||
|
||||
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
|
||||
|
||||
### Added
|
||||
|
||||
- Go module support by [@ChrisHines]
|
||||
- CHANGELOG by [@ChrisHines]
|
||||
|
||||
### Changed
|
||||
|
||||
- Drop invalid runes from keys instead of returning ErrInvalidKey by [@ChrisHines]
|
||||
- On panic while printing, attempt to print panic value by [@bboreham]
|
||||
|
||||
## [0.3.0] - 2016-11-15
|
||||
|
||||
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
|
||||
|
||||
### Added
|
||||
|
||||
- Pool buffers for quoted strings and byte slices by [@nussjustin]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fuzz fix, quote invalid UTF-8 values by [@judwhite]
|
||||
|
||||
## [0.2.0] - 2016-05-08
|
||||
|
||||
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- Encoder.EncodeKeyvals by [@ChrisHines]
|
||||
|
||||
## [0.1.0] - 2016-03-28
|
||||
|
||||
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
|
||||
|
||||
### Added
|
||||
|
||||
- Encoder by [@ChrisHines]
|
||||
- Decoder by [@ChrisHines]
|
||||
- MarshalKeyvals by [@ChrisHines]
|
||||
|
||||
[0.5.0]: https://github.com/go-logfmt/logfmt/compare/v0.4.0...v0.5.0
|
||||
[0.4.0]: https://github.com/go-logfmt/logfmt/compare/v0.3.0...v0.4.0
|
||||
[0.3.0]: https://github.com/go-logfmt/logfmt/compare/v0.2.0...v0.3.0
|
||||
[0.2.0]: https://github.com/go-logfmt/logfmt/compare/v0.1.0...v0.2.0
|
||||
[0.1.0]: https://github.com/go-logfmt/logfmt/commits/v0.1.0
|
||||
|
||||
[@ChrisHines]: https://github.com/ChrisHines
|
||||
[@bboreham]: https://github.com/bboreham
|
||||
[@judwhite]: https://github.com/judwhite
|
||||
[@nussjustin]: https://github.com/nussjustin
|
||||
[@alexanderjophus]: https://github.com/alexanderjophus
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
# logfmt
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/go-logfmt/logfmt.svg)](https://pkg.go.dev/github.com/go-logfmt/logfmt)
|
||||
[![Go Report Card](https://goreportcard.com/badge/go-logfmt/logfmt)](https://goreportcard.com/report/go-logfmt/logfmt)
|
||||
[![Github Actions](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml/badge.svg)](https://github.com/go-logfmt/logfmt/actions/workflows/test.yml)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=master)
|
||||
|
||||
# logfmt
|
||||
[![Coverage Status](https://coveralls.io/repos/github/go-logfmt/logfmt/badge.svg?branch=master)](https://coveralls.io/github/go-logfmt/logfmt?branch=main)
|
||||
|
||||
Package logfmt implements utilities to marshal and unmarshal data in the [logfmt
|
||||
format](https://brandur.org/logfmt). It provides an API similar to
|
||||
[encoding/json](http://golang.org/pkg/encoding/json/) and
|
||||
[encoding/xml](http://golang.org/pkg/encoding/xml/).
|
||||
format][fmt]. It provides an API similar to [encoding/json][json] and
|
||||
[encoding/xml][xml].
|
||||
|
||||
[fmt]: https://brandur.org/logfmt
|
||||
[json]: https://pkg.go.dev/encoding/json
|
||||
[xml]: https://pkg.go.dev/encoding/xml
|
||||
|
||||
The logfmt format was first documented by Brandur Leach in [this
|
||||
article](https://brandur.org/logfmt). The format has not been formally
|
||||
standardized. The most authoritative public specification to date has been the
|
||||
documentation of a Go Language [package](http://godoc.org/github.com/kr/logfmt)
|
||||
written by Blake Mizerany and Keith Rarick.
|
||||
article][origin]. The format has not been formally standardized. The most
|
||||
authoritative public specification to date has been the documentation of a Go
|
||||
Language [package][parser] written by Blake Mizerany and Keith Rarick.
|
||||
|
||||
[origin]: https://brandur.org/logfmt
|
||||
[parser]: https://pkg.go.dev/github.com/kr/logfmt
|
||||
|
||||
## Goals
|
||||
|
||||
|
@ -30,4 +35,7 @@ standard as a goal.
|
|||
|
||||
## Versioning
|
||||
|
||||
Package logfmt publishes releases via [semver](http://semver.org/) compatible Git tags prefixed with a single 'v'.
|
||||
This project publishes releases according to the Go language guidelines for
|
||||
[developing and publishing modules][pub].
|
||||
|
||||
[pub]: https://go.dev/doc/modules/developing
|
||||
|
|
|
@ -29,6 +29,23 @@ func NewDecoder(r io.Reader) *Decoder {
|
|||
return dec
|
||||
}
|
||||
|
||||
// NewDecoderSize returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read data from r beyond
|
||||
// the logfmt records requested.
|
||||
// The size argument specifies the size of the initial buffer that the
|
||||
// Decoder will use to read records from r.
|
||||
// If a log line is longer than the size argument, the Decoder will return
|
||||
// a bufio.ErrTooLong error.
|
||||
func NewDecoderSize(r io.Reader, size int) *Decoder {
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Buffer(make([]byte, 0, size), size)
|
||||
dec := &Decoder{
|
||||
s: scanner,
|
||||
}
|
||||
return dec
|
||||
}
|
||||
|
||||
// ScanRecord advances the Decoder to the next record, which can then be
|
||||
// parsed with the ScanKeyval method. It returns false when decoding stops,
|
||||
// either by reaching the end of the input or an error. After ScanRecord
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
# v0.10.1 - 2023/03/13
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Fix checkptr error for array decoder ( #415 )
|
||||
* Fix added buffer size check when decoding key ( #430 )
|
||||
* Fix handling of anonymous fields other than struct ( #431 )
|
||||
* Fix to not optimize when lower conversion can't handle byte-by-byte ( #432 )
|
||||
* Fix a problem that MarshalIndent does not work when UnorderedMap is specified ( #435 )
|
||||
* Fix mapDecoder.DecodeStream() for empty objects containing whitespace ( #425 )
|
||||
* Fix an issue that could not set the correct NextField for fields in the embedded structure ( #438 )
|
||||
|
||||
# v0.10.0 - 2022/11/29
|
||||
|
||||
### New features
|
||||
|
||||
* Support JSON Path ( #250 )
|
||||
|
||||
### Fix bugs
|
||||
|
||||
* Fix marshaler for map's key ( #409 )
|
||||
|
||||
# v0.9.11 - 2022/08/18
|
||||
|
||||
### Fix bugs
|
||||
|
|
|
@ -19,7 +19,9 @@ type arrayDecoder struct {
|
|||
}
|
||||
|
||||
func newArrayDecoder(dec Decoder, elemType *runtime.Type, alen int, structName, fieldName string) *arrayDecoder {
|
||||
zeroValue := *(*unsafe.Pointer)(unsafe_New(elemType))
|
||||
// workaround to avoid checkptr errors. cannot use `*(*unsafe.Pointer)(unsafe_New(elemType))` directly.
|
||||
zeroValuePtr := unsafe_New(elemType)
|
||||
zeroValue := **(**unsafe.Pointer)(unsafe.Pointer(&zeroValuePtr))
|
||||
return &arrayDecoder{
|
||||
valueDecoder: dec,
|
||||
elemType: elemType,
|
||||
|
|
|
@ -88,7 +88,7 @@ func (d *mapDecoder) DecodeStream(s *Stream, depth int64, p unsafe.Pointer) erro
|
|||
mapValue = makemap(d.mapType, 0)
|
||||
}
|
||||
s.cursor++
|
||||
if s.equalChar('}') {
|
||||
if s.skipWhiteSpace() == '}' {
|
||||
*(*unsafe.Pointer)(p) = mapValue
|
||||
s.cursor++
|
||||
return nil
|
||||
|
|
|
@ -51,6 +51,14 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
func toASCIILower(s string) string {
|
||||
b := []byte(s)
|
||||
for i := range b {
|
||||
b[i] = largeToSmallTable[b[i]]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func newStructDecoder(structName, fieldName string, fieldMap map[string]*structFieldSet) *structDecoder {
|
||||
return &structDecoder{
|
||||
fieldMap: fieldMap,
|
||||
|
@ -91,6 +99,10 @@ func (d *structDecoder) tryOptimize() {
|
|||
for k, v := range d.fieldMap {
|
||||
key := strings.ToLower(k)
|
||||
if key != k {
|
||||
if key != toASCIILower(k) {
|
||||
d.isTriedOptimize = true
|
||||
return
|
||||
}
|
||||
// already exists same key (e.g. Hello and HELLO has same lower case key
|
||||
if _, exists := conflicted[key]; exists {
|
||||
d.isTriedOptimize = true
|
||||
|
@ -158,49 +170,53 @@ func (d *structDecoder) tryOptimize() {
|
|||
}
|
||||
|
||||
// decode from '\uXXXX'
|
||||
func decodeKeyCharByUnicodeRune(buf []byte, cursor int64) ([]byte, int64) {
|
||||
func decodeKeyCharByUnicodeRune(buf []byte, cursor int64) ([]byte, int64, error) {
|
||||
const defaultOffset = 4
|
||||
const surrogateOffset = 6
|
||||
|
||||
if cursor+defaultOffset >= int64(len(buf)) {
|
||||
return nil, 0, errors.ErrUnexpectedEndOfJSON("escaped string", cursor)
|
||||
}
|
||||
|
||||
r := unicodeToRune(buf[cursor : cursor+defaultOffset])
|
||||
if utf16.IsSurrogate(r) {
|
||||
cursor += defaultOffset
|
||||
if cursor+surrogateOffset >= int64(len(buf)) || buf[cursor] != '\\' || buf[cursor+1] != 'u' {
|
||||
return []byte(string(unicode.ReplacementChar)), cursor + defaultOffset - 1
|
||||
return []byte(string(unicode.ReplacementChar)), cursor + defaultOffset - 1, nil
|
||||
}
|
||||
cursor += 2
|
||||
r2 := unicodeToRune(buf[cursor : cursor+defaultOffset])
|
||||
if r := utf16.DecodeRune(r, r2); r != unicode.ReplacementChar {
|
||||
return []byte(string(r)), cursor + defaultOffset - 1
|
||||
return []byte(string(r)), cursor + defaultOffset - 1, nil
|
||||
}
|
||||
}
|
||||
return []byte(string(r)), cursor + defaultOffset - 1
|
||||
return []byte(string(r)), cursor + defaultOffset - 1, nil
|
||||
}
|
||||
|
||||
func decodeKeyCharByEscapedChar(buf []byte, cursor int64) ([]byte, int64) {
|
||||
func decodeKeyCharByEscapedChar(buf []byte, cursor int64) ([]byte, int64, error) {
|
||||
c := buf[cursor]
|
||||
cursor++
|
||||
switch c {
|
||||
case '"':
|
||||
return []byte{'"'}, cursor
|
||||
return []byte{'"'}, cursor, nil
|
||||
case '\\':
|
||||
return []byte{'\\'}, cursor
|
||||
return []byte{'\\'}, cursor, nil
|
||||
case '/':
|
||||
return []byte{'/'}, cursor
|
||||
return []byte{'/'}, cursor, nil
|
||||
case 'b':
|
||||
return []byte{'\b'}, cursor
|
||||
return []byte{'\b'}, cursor, nil
|
||||
case 'f':
|
||||
return []byte{'\f'}, cursor
|
||||
return []byte{'\f'}, cursor, nil
|
||||
case 'n':
|
||||
return []byte{'\n'}, cursor
|
||||
return []byte{'\n'}, cursor, nil
|
||||
case 'r':
|
||||
return []byte{'\r'}, cursor
|
||||
return []byte{'\r'}, cursor, nil
|
||||
case 't':
|
||||
return []byte{'\t'}, cursor
|
||||
return []byte{'\t'}, cursor, nil
|
||||
case 'u':
|
||||
return decodeKeyCharByUnicodeRune(buf, cursor)
|
||||
}
|
||||
return nil, cursor
|
||||
return nil, cursor, nil
|
||||
}
|
||||
|
||||
func decodeKeyByBitmapUint8(d *structDecoder, buf []byte, cursor int64) (int64, *structFieldSet, error) {
|
||||
|
@ -242,7 +258,10 @@ func decodeKeyByBitmapUint8(d *structDecoder, buf []byte, cursor int64) (int64,
|
|||
return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor)
|
||||
case '\\':
|
||||
cursor++
|
||||
chars, nextCursor := decodeKeyCharByEscapedChar(buf, cursor)
|
||||
chars, nextCursor, err := decodeKeyCharByEscapedChar(buf, cursor)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, c := range chars {
|
||||
curBit &= bitmap[keyIdx][largeToSmallTable[c]]
|
||||
if curBit == 0 {
|
||||
|
@ -305,7 +324,10 @@ func decodeKeyByBitmapUint16(d *structDecoder, buf []byte, cursor int64) (int64,
|
|||
return 0, nil, errors.ErrUnexpectedEndOfJSON("string", cursor)
|
||||
case '\\':
|
||||
cursor++
|
||||
chars, nextCursor := decodeKeyCharByEscapedChar(buf, cursor)
|
||||
chars, nextCursor, err := decodeKeyCharByEscapedChar(buf, cursor)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
for _, c := range chars {
|
||||
curBit &= bitmap[keyIdx][largeToSmallTable[c]]
|
||||
if curBit == 0 {
|
||||
|
|
|
@ -397,7 +397,10 @@ func (c *StructCode) lastFieldCode(field *StructFieldCode, firstField *Opcode) *
|
|||
func (c *StructCode) lastAnonymousFieldCode(firstField *Opcode) *Opcode {
|
||||
// firstField is special StructHead operation for anonymous structure.
|
||||
// So, StructHead's next operation is truly struct head operation.
|
||||
lastField := firstField.Next
|
||||
for firstField.Op == OpStructHead {
|
||||
firstField = firstField.Next
|
||||
}
|
||||
lastField := firstField
|
||||
for lastField.NextField != nil {
|
||||
lastField = lastField.NextField
|
||||
}
|
||||
|
@ -437,11 +440,6 @@ func (c *StructCode) ToOpcode(ctx *compileContext) Opcodes {
|
|||
}
|
||||
if isEndField {
|
||||
endField := fieldCodes.Last()
|
||||
if isEmbeddedStruct(field) {
|
||||
firstField.End = endField
|
||||
lastField := c.lastAnonymousFieldCode(firstField)
|
||||
lastField.NextField = endField
|
||||
}
|
||||
if len(codes) > 0 {
|
||||
codes.First().End = endField
|
||||
} else {
|
||||
|
@ -698,7 +696,15 @@ func (c *StructFieldCode) addStructEndCode(ctx *compileContext, codes Opcodes) O
|
|||
Indent: ctx.indent,
|
||||
}
|
||||
codes.Last().Next = end
|
||||
codes.First().NextField = end
|
||||
code := codes.First()
|
||||
for code.Op == OpStructField || code.Op == OpStructHead {
|
||||
code = code.Next
|
||||
}
|
||||
for code.NextField != nil {
|
||||
code = code.NextField
|
||||
}
|
||||
code.NextField = end
|
||||
|
||||
codes = codes.Add(end)
|
||||
ctx.incOpcodeIndex()
|
||||
return codes
|
||||
|
|
|
@ -617,6 +617,13 @@ func (c *Compiler) structCode(typ *runtime.Type, isPtr bool) (*StructCode, error
|
|||
return code, nil
|
||||
}
|
||||
|
||||
func toElemType(t *runtime.Type) *runtime.Type {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTag, isPtr, isOnlyOneFirstField bool) (*StructFieldCode, error) {
|
||||
field := tag.Field
|
||||
fieldType := runtime.Type2RType(field.Type)
|
||||
|
@ -626,7 +633,7 @@ func (c *Compiler) structFieldCode(structCode *StructCode, tag *runtime.StructTa
|
|||
key: tag.Key,
|
||||
tag: tag,
|
||||
offset: field.Offset,
|
||||
isAnonymous: field.Anonymous && !tag.IsTaggedKey,
|
||||
isAnonymous: field.Anonymous && !tag.IsTaggedKey && toElemType(fieldType).Kind() == reflect.Struct,
|
||||
isTaggedKey: tag.IsTaggedKey,
|
||||
isNilableType: c.isNilableType(fieldType),
|
||||
isNilCheck: true,
|
||||
|
|
|
@ -189,7 +189,7 @@ func appendNullComma(ctx *encoder.RuntimeContext, b []byte) []byte {
|
|||
}
|
||||
|
||||
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
|
||||
return append(b, ':', ' ')
|
||||
return append(b[:len(b)-2], ':', ' ')
|
||||
}
|
||||
|
||||
func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte {
|
||||
|
@ -229,8 +229,9 @@ func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte {
|
|||
|
||||
func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte {
|
||||
last := len(b) - 1
|
||||
b[last] = '\n'
|
||||
b = appendIndent(ctx, b, code.Indent-1)
|
||||
// replace comma to newline
|
||||
b[last-1] = '\n'
|
||||
b = appendIndent(ctx, b[:last], code.Indent)
|
||||
return append(b, '}', ',', '\n')
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ func appendNullComma(_ *encoder.RuntimeContext, b []byte) []byte {
|
|||
}
|
||||
|
||||
func appendColon(_ *encoder.RuntimeContext, b []byte) []byte {
|
||||
return append(b, ':', ' ')
|
||||
return append(b[:len(b)-2], ':', ' ')
|
||||
}
|
||||
|
||||
func appendMapKeyValue(ctx *encoder.RuntimeContext, code *encoder.Opcode, b, key, value []byte) []byte {
|
||||
|
@ -173,8 +173,9 @@ func appendEmptyObject(_ *encoder.RuntimeContext, b []byte) []byte {
|
|||
|
||||
func appendObjectEnd(ctx *encoder.RuntimeContext, code *encoder.Opcode, b []byte) []byte {
|
||||
last := len(b) - 1
|
||||
b[last] = '\n'
|
||||
b = appendIndent(ctx, b, code.Indent-1)
|
||||
// replace comma to newline
|
||||
b[last-1] = '\n'
|
||||
b = appendIndent(ctx, b[:last], code.Indent)
|
||||
return append(b, '}', ',', '\n')
|
||||
}
|
||||
|
||||
|
|
|
@ -294,7 +294,6 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
|
|||
}
|
||||
offset = 0
|
||||
|
||||
cGain := 0
|
||||
if d.chain < 100 {
|
||||
for i := prevHead; tries > 0; tries-- {
|
||||
if wEnd == win[i+length] {
|
||||
|
@ -322,10 +321,14 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
|
|||
return
|
||||
}
|
||||
|
||||
// Minimum gain to accept a match.
|
||||
cGain := 4
|
||||
|
||||
// Some like it higher (CSV), some like it lower (JSON)
|
||||
const baseCost = 6
|
||||
const baseCost = 3
|
||||
// Base is 4 bytes at with an additional cost.
|
||||
// Matches must be better than this.
|
||||
|
||||
for i := prevHead; tries > 0; tries-- {
|
||||
if wEnd == win[i+length] {
|
||||
n := matchLen(win[i:i+minMatchLook], wPos)
|
||||
|
@ -333,7 +336,7 @@ func (d *compressor) findMatch(pos int, prevHead int, lookahead int) (length, of
|
|||
// Calculate gain. Estimate
|
||||
newGain := d.h.bitLengthRaw(wPos[:n]) - int(offsetExtraBits[offsetCode(uint32(pos-i))]) - baseCost - int(lengthExtraBits[lengthCodes[(n-3)&255]])
|
||||
|
||||
//fmt.Println(n, "gain:", newGain, "prev:", cGain, "raw:", d.h.bitLengthRaw(wPos[:n]))
|
||||
//fmt.Println("gain:", newGain, "prev:", cGain, "raw:", d.h.bitLengthRaw(wPos[:n]), "this-len:", n, "prev-len:", length)
|
||||
if newGain > cGain {
|
||||
length = n
|
||||
offset = pos - i
|
||||
|
@ -490,27 +493,103 @@ func (d *compressor) deflateLazy() {
|
|||
}
|
||||
|
||||
if prevLength >= minMatchLength && s.length <= prevLength {
|
||||
// Check for better match at end...
|
||||
// No better match, but check for better match at end...
|
||||
//
|
||||
// checkOff must be >=2 since we otherwise risk checking s.index
|
||||
// Offset of 2 seems to yield best results.
|
||||
// Skip forward a number of bytes.
|
||||
// Offset of 2 seems to yield best results. 3 is sometimes better.
|
||||
const checkOff = 2
|
||||
prevIndex := s.index - 1
|
||||
if prevIndex+prevLength+checkOff < s.maxInsertIndex {
|
||||
end := lookahead
|
||||
if lookahead > maxMatchLength {
|
||||
end = maxMatchLength
|
||||
}
|
||||
end += prevIndex
|
||||
idx := prevIndex + prevLength - (4 - checkOff)
|
||||
h := hash4(d.window[idx:])
|
||||
ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength + (4 - checkOff)
|
||||
if ch2 > minIndex {
|
||||
length := matchLen(d.window[prevIndex:end], d.window[ch2:])
|
||||
// It seems like a pure length metric is best.
|
||||
if length > prevLength {
|
||||
prevLength = length
|
||||
prevOffset = prevIndex - ch2
|
||||
|
||||
// Check all, except full length
|
||||
if prevLength < maxMatchLength-checkOff {
|
||||
prevIndex := s.index - 1
|
||||
if prevIndex+prevLength < s.maxInsertIndex {
|
||||
end := lookahead
|
||||
if lookahead > maxMatchLength+checkOff {
|
||||
end = maxMatchLength + checkOff
|
||||
}
|
||||
end += prevIndex
|
||||
|
||||
// Hash at match end.
|
||||
h := hash4(d.window[prevIndex+prevLength:])
|
||||
ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength
|
||||
if prevIndex-ch2 != prevOffset && ch2 > minIndex+checkOff {
|
||||
length := matchLen(d.window[prevIndex+checkOff:end], d.window[ch2+checkOff:])
|
||||
// It seems like a pure length metric is best.
|
||||
if length > prevLength {
|
||||
prevLength = length
|
||||
prevOffset = prevIndex - ch2
|
||||
|
||||
// Extend back...
|
||||
for i := checkOff - 1; i >= 0; i-- {
|
||||
if prevLength >= maxMatchLength || d.window[prevIndex+i] != d.window[ch2+i] {
|
||||
// Emit tokens we "owe"
|
||||
for j := 0; j <= i; j++ {
|
||||
d.tokens.AddLiteral(d.window[prevIndex+j])
|
||||
if d.tokens.n == maxFlateBlockTokens {
|
||||
// The block includes the current character
|
||||
if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil {
|
||||
return
|
||||
}
|
||||
d.tokens.Reset()
|
||||
}
|
||||
s.index++
|
||||
if s.index < s.maxInsertIndex {
|
||||
h := hash4(d.window[s.index:])
|
||||
ch := s.hashHead[h]
|
||||
s.chainHead = int(ch)
|
||||
s.hashPrev[s.index&windowMask] = ch
|
||||
s.hashHead[h] = uint32(s.index + s.hashOffset)
|
||||
}
|
||||
}
|
||||
break
|
||||
} else {
|
||||
prevLength++
|
||||
}
|
||||
}
|
||||
} else if false {
|
||||
// Check one further ahead.
|
||||
// Only rarely better, disabled for now.
|
||||
prevIndex++
|
||||
h := hash4(d.window[prevIndex+prevLength:])
|
||||
ch2 := int(s.hashHead[h]) - s.hashOffset - prevLength
|
||||
if prevIndex-ch2 != prevOffset && ch2 > minIndex+checkOff {
|
||||
length := matchLen(d.window[prevIndex+checkOff:end], d.window[ch2+checkOff:])
|
||||
// It seems like a pure length metric is best.
|
||||
if length > prevLength+checkOff {
|
||||
prevLength = length
|
||||
prevOffset = prevIndex - ch2
|
||||
prevIndex--
|
||||
|
||||
// Extend back...
|
||||
for i := checkOff; i >= 0; i-- {
|
||||
if prevLength >= maxMatchLength || d.window[prevIndex+i] != d.window[ch2+i-1] {
|
||||
// Emit tokens we "owe"
|
||||
for j := 0; j <= i; j++ {
|
||||
d.tokens.AddLiteral(d.window[prevIndex+j])
|
||||
if d.tokens.n == maxFlateBlockTokens {
|
||||
// The block includes the current character
|
||||
if d.err = d.writeBlock(&d.tokens, s.index, false); d.err != nil {
|
||||
return
|
||||
}
|
||||
d.tokens.Reset()
|
||||
}
|
||||
s.index++
|
||||
if s.index < s.maxInsertIndex {
|
||||
h := hash4(d.window[s.index:])
|
||||
ch := s.hashHead[h]
|
||||
s.chainHead = int(ch)
|
||||
s.hashPrev[s.index&windowMask] = ch
|
||||
s.hashHead[h] = uint32(s.index + s.hashOffset)
|
||||
}
|
||||
}
|
||||
break
|
||||
} else {
|
||||
prevLength++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
This library is a toy proof-of-concept implementation of the
|
||||
well-known Schonhage-Strassen method for multiplying integers.
|
||||
It is not expected to have a real life usecase outside number
|
||||
theory computations, nor is it expected to be used in any production
|
||||
system.
|
||||
|
||||
If you are using it in your project, you may want to carefully
|
||||
examine the actual requirement or problem you are trying to solve.
|
||||
|
||||
# Comparison with the standard library and GMP
|
||||
|
||||
Benchmarking math/big vs. bigfft
|
||||
|
||||
Number size old ns/op new ns/op delta
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
JMP math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addMulVVW(SB)
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
// (same as addVV except for SBBQ instead of ADCQ and label names)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
JMP math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addMulVVW(SB)
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
B math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
B math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
B math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
B math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
B math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
B math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
B math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
B math∕big·addMulVVW(SB)
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
B math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
B math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
B math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
B math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
B math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
B math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
B math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
B math∕big·addMulVVW(SB)
|
||||
|
|
@ -4,13 +4,30 @@
|
|||
|
||||
package bigfft
|
||||
|
||||
import . "math/big"
|
||||
import (
|
||||
"math/big"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
// implemented in arith_$GOARCH.s
|
||||
type Word = big.Word
|
||||
|
||||
//go:linkname addVV math/big.addVV
|
||||
func addVV(z, x, y []Word) (c Word)
|
||||
|
||||
//go:linkname subVV math/big.subVV
|
||||
func subVV(z, x, y []Word) (c Word)
|
||||
|
||||
//go:linkname addVW math/big.addVW
|
||||
func addVW(z, x []Word, y Word) (c Word)
|
||||
|
||||
//go:linkname subVW math/big.subVW
|
||||
func subVW(z, x []Word, y Word) (c Word)
|
||||
|
||||
//go:linkname shlVU math/big.shlVU
|
||||
func shlVU(z, x []Word, s uint) (c Word)
|
||||
|
||||
//go:linkname mulAddVWW math/big.mulAddVWW
|
||||
func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
|
||||
//go:linkname addMulVVW math/big.addMulVVW
|
||||
func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
// +build mips64 mips64le
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
// (same as addVV except for SBBQ instead of ADCQ and label names)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
JMP math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addMulVVW(SB)
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
// +build mips mipsle
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
// (same as addVV except for SBBQ instead of ADCQ and label names)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
JMP math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addMulVVW(SB)
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
// +build ppc64 ppc64le
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
BR math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
BR math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
BR math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
BR math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
BR math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
BR math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
BR math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
BR math∕big·addMulVVW(SB)
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
// +build riscv riscv64
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
// (same as addVV except for SBBQ instead of ADCQ and label names)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
// (same as addVW except for SUBQ/SBBQ instead of ADDQ/ADCQ and label names)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
JMP math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
JMP math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
JMP math∕big·addMulVVW(SB)
|
|
@ -1,37 +0,0 @@
|
|||
|
||||
// Trampolines to math/big assembly implementations.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func addVV(z, x, y []Word) (c Word)
|
||||
TEXT ·addVV(SB),NOSPLIT,$0
|
||||
BR math∕big·addVV(SB)
|
||||
|
||||
// func subVV(z, x, y []Word) (c Word)
|
||||
TEXT ·subVV(SB),NOSPLIT,$0
|
||||
BR math∕big·subVV(SB)
|
||||
|
||||
// func addVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addVW(SB),NOSPLIT,$0
|
||||
BR math∕big·addVW(SB)
|
||||
|
||||
// func subVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·subVW(SB),NOSPLIT,$0
|
||||
BR math∕big·subVW(SB)
|
||||
|
||||
// func shlVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shlVU(SB),NOSPLIT,$0
|
||||
BR math∕big·shlVU(SB)
|
||||
|
||||
// func shrVU(z, x []Word, s uint) (c Word)
|
||||
TEXT ·shrVU(SB),NOSPLIT,$0
|
||||
BR math∕big·shrVU(SB)
|
||||
|
||||
// func mulAddVWW(z, x []Word, y, r Word) (c Word)
|
||||
TEXT ·mulAddVWW(SB),NOSPLIT,$0
|
||||
BR math∕big·mulAddVWW(SB)
|
||||
|
||||
// func addMulVVW(z, x []Word, y Word) (c Word)
|
||||
TEXT ·addMulVVW(SB),NOSPLIT,$0
|
||||
BR math∕big·addMulVVW(SB)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# 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 [![GoDoc](https://pkg.go.dev/badge/github.com/valyala/fasthttp)](https://pkg.go.dev/github.com/valyala/fasthttp) [![Go Report](https://goreportcard.com/badge/github.com/valyala/fasthttp)](https://goreportcard.com/report/github.com/valyala/fasthttp)
|
||||
|
||||
![FastHTTP – Fastest and reliable HTTP implementation in Go](https://github.com/fasthttp/docs-assets/raw/master/banner@0.5.png)
|
||||
|
||||
|
@ -22,9 +22,9 @@ connections per physical server.
|
|||
|
||||
[Install](#install)
|
||||
|
||||
[Documentation](https://godoc.org/github.com/valyala/fasthttp)
|
||||
[Documentation](https://pkg.go.dev/github.com/valyala/fasthttp)
|
||||
|
||||
[Examples from docs](https://godoc.org/github.com/valyala/fasthttp#pkg-examples)
|
||||
[Examples from docs](https://pkg.go.dev/github.com/valyala/fasthttp#pkg-examples)
|
||||
|
||||
[Code examples](examples)
|
||||
|
||||
|
@ -40,7 +40,7 @@ connections per physical server.
|
|||
|
||||
[FAQ](#faq)
|
||||
|
||||
## HTTP server performance comparison with [net/http](https://golang.org/pkg/net/http/)
|
||||
## HTTP server performance comparison with [net/http](https://pkg.go.dev/net/http)
|
||||
|
||||
In short, fasthttp server is up to 10 times faster than net/http.
|
||||
Below are benchmark results.
|
||||
|
@ -174,14 +174,14 @@ go get -u github.com/valyala/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),
|
||||
There is [net/http -> fasthttp handler converter](https://pkg.go.dev/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).
|
||||
* Fasthttp works with [RequestHandler functions](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
|
||||
instead of objects implementing [Handler interface](https://pkg.go.dev/net/http#Handler).
|
||||
Fortunately, it is easy to pass bound struct methods to fasthttp:
|
||||
|
||||
```go
|
||||
|
@ -211,8 +211,8 @@ Fortunately, it is easy to pass bound struct methods 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).
|
||||
* The [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
|
||||
accepts only one argument - [RequestCtx](https://pkg.go.dev/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.
|
||||
|
@ -278,7 +278,7 @@ like in net/http. The following code is valid for fasthttp:
|
|||
}
|
||||
```
|
||||
|
||||
* Fasthttp doesn't provide [ServeMux](https://golang.org/pkg/net/http/#ServeMux),
|
||||
* Fasthttp doesn't provide [ServeMux](https://pkg.go.dev/net/http#ServeMux),
|
||||
but there are more powerful third-party routers and web frameworks
|
||||
with fasthttp support:
|
||||
|
||||
|
@ -347,78 +347,78 @@ with fasthttp support:
|
|||
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)
|
||||
* r.Body -> [ctx.PostBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostBody)
|
||||
* r.URL.Path -> [ctx.Path()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Path)
|
||||
* r.URL -> [ctx.URI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.URI)
|
||||
* r.Method -> [ctx.Method()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Method)
|
||||
* r.Header -> [ctx.Request.Header](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader)
|
||||
* r.Header.Get() -> [ctx.Request.Header.Peek()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Peek)
|
||||
* r.Host -> [ctx.Host()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Host)
|
||||
* r.Form -> [ctx.QueryArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.QueryArgs) +
|
||||
[ctx.PostArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostArgs)
|
||||
* r.PostForm -> [ctx.PostArgs()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.PostArgs)
|
||||
* r.FormValue() -> [ctx.FormValue()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormValue)
|
||||
* r.FormFile() -> [ctx.FormFile()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.FormFile)
|
||||
* r.MultipartForm -> [ctx.MultipartForm()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.MultipartForm)
|
||||
* r.RemoteAddr -> [ctx.RemoteAddr()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RemoteAddr)
|
||||
* r.RequestURI -> [ctx.RequestURI()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.RequestURI)
|
||||
* r.TLS -> [ctx.IsTLS()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.IsTLS)
|
||||
* r.Cookie() -> [ctx.Request.Header.Cookie()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHeader.Cookie)
|
||||
* r.Referer() -> [ctx.Referer()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Referer)
|
||||
* r.UserAgent() -> [ctx.UserAgent()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.UserAgent)
|
||||
* w.Header() -> [ctx.Response.Header](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader)
|
||||
* w.Header().Set() -> [ctx.Response.Header.Set()](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader.Set)
|
||||
* w.Header().Set("Content-Type") -> [ctx.SetContentType()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetContentType)
|
||||
* w.Header().Set("Set-Cookie") -> [ctx.Response.Header.SetCookie()](https://pkg.go.dev/github.com/valyala/fasthttp#ResponseHeader.SetCookie)
|
||||
* w.Write() -> [ctx.Write()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Write),
|
||||
[ctx.SetBody()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBody),
|
||||
[ctx.SetBodyStream()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStream),
|
||||
[ctx.SetBodyStreamWriter()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetBodyStreamWriter)
|
||||
* w.WriteHeader() -> [ctx.SetStatusCode()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.SetStatusCode)
|
||||
* w.(http.Hijacker).Hijack() -> [ctx.Hijack()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack)
|
||||
* http.Error() -> [ctx.Error()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Error)
|
||||
* http.FileServer() -> [fasthttp.FSHandler()](https://pkg.go.dev/github.com/valyala/fasthttp#FSHandler),
|
||||
[fasthttp.FS](https://pkg.go.dev/github.com/valyala/fasthttp#FS)
|
||||
* http.ServeFile() -> [fasthttp.ServeFile()](https://pkg.go.dev/github.com/valyala/fasthttp#ServeFile)
|
||||
* http.Redirect() -> [ctx.Redirect()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Redirect)
|
||||
* http.NotFound() -> [ctx.NotFound()](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.NotFound)
|
||||
* http.StripPrefix() -> [fasthttp.PathRewriteFunc](https://pkg.go.dev/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.
|
||||
to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx) or to its'
|
||||
members after returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
|
||||
Otherwise [data races](http://go.dev/blog/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)
|
||||
* Wrap RequestHandler into [TimeoutHandler](https://pkg.go.dev/github.com/valyala/fasthttp#TimeoutHandler).
|
||||
* Call [TimeoutError](https://pkg.go.dev/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)
|
||||
See [the example](https://pkg.go.dev/github.com/valyala/fasthttp#example-RequestCtx-TimeoutError)
|
||||
for more details.
|
||||
|
||||
Use this brilliant tool - [race detector](http://blog.golang.org/race-detector) -
|
||||
Use this brilliant tool - [race detector](http://go.dev/blog/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).
|
||||
you forgot calling [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
|
||||
before returning from [RequestHandler](https://pkg.go.dev/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
|
||||
by slow [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
|
||||
So [profile](http://go.dev/blog/pprof) 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/).
|
||||
instead of [html/template](https://pkg.go.dev/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).
|
||||
* See also [fasthttputil](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttputil),
|
||||
[fasthttpadaptor](https://pkg.go.dev/github.com/valyala/fasthttp/fasthttpadaptor) and
|
||||
[expvarhandler](https://pkg.go.dev/github.com/valyala/fasthttp/expvarhandler).
|
||||
|
||||
|
||||
## Performance optimization tips for multi-core systems
|
||||
|
||||
* Use [reuseport](https://godoc.org/github.com/valyala/fasthttp/reuseport) listener.
|
||||
* Use [reuseport](https://pkg.go.dev/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.
|
||||
|
@ -430,21 +430,21 @@ instead of [html/template](https://golang.org/pkg/html/template/).
|
|||
|
||||
* 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)
|
||||
* [sync.Pool](https://pkg.go.dev/sync#Pool) is your best friend.
|
||||
* [Profile your program](http://go.dev/blog/pprof)
|
||||
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.
|
||||
* Write [tests and benchmarks](https://pkg.go.dev/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.
|
||||
[race detector](https://go.dev/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.
|
||||
[html/template](https://pkg.go.dev/html/template) in your webserver.
|
||||
|
||||
|
||||
## Tricks with `[]byte` buffers
|
||||
|
@ -547,7 +547,7 @@ This is an **unsafe** way, the result string and `[]byte` buffer share the same
|
|||
* [kit-plugins](https://github.com/wencan/kit-plugins/tree/master/transport/fasthttp) - go-kit transport implementation for fasthttp.
|
||||
* [Fiber](https://github.com/gofiber/fiber) - An Expressjs inspired web framework running on Fasthttp
|
||||
* [Gearbox](https://github.com/gogearbox/gearbox) - :gear: gearbox is a web framework written in Go with a focus on high performance and memory optimization
|
||||
|
||||
* [http2curl](https://github.com/li-jin-gou/http2curl) - A tool to convert fasthttp requests to curl command line
|
||||
|
||||
## FAQ
|
||||
|
||||
|
@ -569,21 +569,21 @@ This is an **unsafe** way, the result string and `[]byte` buffer share the same
|
|||
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).
|
||||
* Compare [net/http connection hijacking](https://pkg.go.dev/net/http#Hijacker)
|
||||
to [fasthttp connection hijacking](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.Hijack).
|
||||
* Compare [net/http Request.Body reading](https://pkg.go.dev/net/http#Request)
|
||||
to [fasthttp request body reading](https://pkg.go.dev/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)
|
||||
Third parties also may use [RequestCtx.Hijack](https://pkg.go.dev/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 supports [HTTP/2.0 starting from go1.6](https://pkg.go.dev/golang.org/x/net/http2).
|
||||
* net/http API is stable, while fasthttp API constantly evolves.
|
||||
* net/http handles more HTTP corner cases.
|
||||
* net/http can stream both request and response bodies
|
||||
|
@ -626,11 +626,11 @@ This is an **unsafe** way, the result string and `[]byte` buffer share the same
|
|||
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)
|
||||
* Make sure there are no references to [RequestCtx](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx)
|
||||
or to its' members after returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler).
|
||||
* Make sure you call [TimeoutError](https://pkg.go.dev/github.com/valyala/fasthttp#RequestCtx.TimeoutError)
|
||||
before returning from [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler)
|
||||
if there are references to [RequestCtx](https://pkg.go.dev/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*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
### TL;DR
|
||||
|
||||
We use a simplified version of [Golang Security Policy](https://golang.org/security).
|
||||
We use a simplified version of [Golang Security Policy](https://go.dev/security).
|
||||
For example, for now we skip CVE assignment.
|
||||
|
||||
### Reporting a Security Bug
|
||||
|
|
|
@ -44,7 +44,7 @@ var argsPool = &sync.Pool{
|
|||
//
|
||||
// Args instance MUST NOT be used from concurrently running goroutines.
|
||||
type Args struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
args []argsKV
|
||||
buf []byte
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
//go:build go1.20
|
||||
// +build go1.20
|
||||
|
||||
package fasthttp
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// b2s converts byte slice to a string without memory allocation.
|
||||
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
|
||||
func b2s(b []byte) string {
|
||||
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
//go:build !go1.20
|
||||
// +build !go1.20
|
||||
|
||||
package fasthttp
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// b2s converts byte slice to a string without memory allocation.
|
||||
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
|
||||
//
|
||||
// Note it may break if string and/or slice header will change
|
||||
// in the future go versions.
|
||||
func b2s(b []byte) string {
|
||||
/* #nosec G103 */
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
|
@ -10,10 +10,8 @@ import (
|
|||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
|
||||
|
@ -317,31 +315,6 @@ func lowercaseBytes(b []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
// b2s converts byte slice to a string without memory allocation.
|
||||
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
|
||||
//
|
||||
// Note it may break if string and/or slice header will change
|
||||
// in the future go versions.
|
||||
func b2s(b []byte) string {
|
||||
/* #nosec G103 */
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
|
||||
// s2b converts string to a byte slice without memory allocation.
|
||||
//
|
||||
// Note it may break if string and/or slice header will change
|
||||
// in the future go versions.
|
||||
func s2b(s string) (b []byte) {
|
||||
/* #nosec G103 */
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
/* #nosec G103 */
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
bh.Data = sh.Data
|
||||
bh.Cap = sh.Len
|
||||
bh.Len = sh.Len
|
||||
return b
|
||||
}
|
||||
|
||||
// AppendUnquotedArg appends url-decoded src to dst and returns appended dst.
|
||||
//
|
||||
// dst may point to src. In this case src will be overwritten.
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// go:build !windows || !race
|
||||
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
|
@ -179,7 +177,7 @@ var defaultClient Client
|
|||
//
|
||||
// The fields of a Client should not be changed while it is in use.
|
||||
type Client struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Client name. Used in User-Agent request header.
|
||||
//
|
||||
|
@ -374,6 +372,7 @@ func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, b
|
|||
//
|
||||
// ErrTimeout is returned if the response wasn't returned during
|
||||
// the given timeout.
|
||||
// Immediately returns ErrTimeout if timeout value is negative.
|
||||
//
|
||||
// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections
|
||||
// to the requested host are busy.
|
||||
|
@ -387,6 +386,9 @@ func (c *Client) Post(dst []byte, url string, postArgs *Args) (statusCode int, b
|
|||
// try setting a ReadTimeout.
|
||||
func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
|
||||
req.timeout = timeout
|
||||
if req.timeout < 0 {
|
||||
return ErrTimeout
|
||||
}
|
||||
return c.Do(req, resp)
|
||||
}
|
||||
|
||||
|
@ -407,6 +409,7 @@ func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration)
|
|||
//
|
||||
// ErrTimeout is returned if the response wasn't returned until
|
||||
// the given deadline.
|
||||
// Immediately returns ErrTimeout if the deadline has already been reached.
|
||||
//
|
||||
// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections
|
||||
// to the requested host are busy.
|
||||
|
@ -415,6 +418,9 @@ func (c *Client) DoTimeout(req *Request, resp *Response, timeout time.Duration)
|
|||
// and AcquireResponse in performance-critical code.
|
||||
func (c *Client) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
|
||||
req.timeout = time.Until(deadline)
|
||||
if req.timeout < 0 {
|
||||
return ErrTimeout
|
||||
}
|
||||
return c.Do(req, resp)
|
||||
}
|
||||
|
||||
|
@ -470,9 +476,9 @@ func (c *Client) Do(req *Request, resp *Response) error {
|
|||
host := uri.Host()
|
||||
|
||||
isTLS := false
|
||||
if uri.isHttps() {
|
||||
if uri.isHTTPS() {
|
||||
isTLS = true
|
||||
} else if !uri.isHttp() {
|
||||
} else if !uri.isHTTP() {
|
||||
return fmt.Errorf("unsupported protocol %q. http and https are supported", uri.Scheme())
|
||||
}
|
||||
|
||||
|
@ -521,6 +527,7 @@ func (c *Client) Do(req *Request, resp *Response) error {
|
|||
|
||||
if c.ConfigureClient != nil {
|
||||
if err := c.ConfigureClient(hc); err != nil {
|
||||
c.mLock.Unlock()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -642,7 +649,7 @@ const (
|
|||
//
|
||||
// It is safe calling HostClient methods from concurrently running goroutines.
|
||||
type HostClient struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Comma-separated list of upstream HTTP server host addresses,
|
||||
// which are passed to Dial in a round-robin manner.
|
||||
|
@ -687,7 +694,7 @@ type HostClient struct {
|
|||
// listed in Addr.
|
||||
//
|
||||
// You can change this value while the HostClient is being used
|
||||
// using HostClient.SetMaxConns(value)
|
||||
// with HostClient.SetMaxConns(value)
|
||||
//
|
||||
// DefaultMaxConnsPerHost is used if not set.
|
||||
MaxConns int
|
||||
|
@ -811,7 +818,7 @@ type HostClient struct {
|
|||
pendingRequests int32
|
||||
|
||||
// pendingClientRequests counts the number of requests that a Client is currently running using this HostClient.
|
||||
// It will be incremented ealier than pendingRequests and will be used by Client to see if the HostClient is still in use.
|
||||
// It will be incremented earlier than pendingRequests and will be used by Client to see if the HostClient is still in use.
|
||||
pendingClientRequests int32
|
||||
|
||||
connsCleanerRun bool
|
||||
|
@ -1139,6 +1146,7 @@ func ReleaseResponse(resp *Response) {
|
|||
//
|
||||
// ErrTimeout is returned if the response wasn't returned during
|
||||
// the given timeout.
|
||||
// Immediately returns ErrTimeout if timeout value is negative.
|
||||
//
|
||||
// ErrNoFreeConns is returned if all HostClient.MaxConns connections
|
||||
// to the host are busy.
|
||||
|
@ -1152,6 +1160,9 @@ func ReleaseResponse(resp *Response) {
|
|||
// try setting a ReadTimeout.
|
||||
func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Duration) error {
|
||||
req.timeout = timeout
|
||||
if req.timeout < 0 {
|
||||
return ErrTimeout
|
||||
}
|
||||
return c.Do(req, resp)
|
||||
}
|
||||
|
||||
|
@ -1167,6 +1178,7 @@ func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Durati
|
|||
//
|
||||
// ErrTimeout is returned if the response wasn't returned until
|
||||
// the given deadline.
|
||||
// Immediately returns ErrTimeout if the deadline has already been reached.
|
||||
//
|
||||
// ErrNoFreeConns is returned if all HostClient.MaxConns connections
|
||||
// to the host are busy.
|
||||
|
@ -1175,6 +1187,9 @@ func (c *HostClient) DoTimeout(req *Request, resp *Response, timeout time.Durati
|
|||
// and AcquireResponse in performance-critical code.
|
||||
func (c *HostClient) DoDeadline(req *Request, resp *Response, deadline time.Time) error {
|
||||
req.timeout = time.Until(deadline)
|
||||
if req.timeout < 0 {
|
||||
return ErrTimeout
|
||||
}
|
||||
return c.Do(req, resp)
|
||||
}
|
||||
|
||||
|
@ -1308,7 +1323,7 @@ func (c *HostClient) doNonNilReqResp(req *Request, resp *Response) (bool, error)
|
|||
req.secureErrorLogMessage = c.SecureErrorLogMessage
|
||||
req.Header.secureErrorLogMessage = c.SecureErrorLogMessage
|
||||
|
||||
if c.IsTLS != req.URI().isHttps() {
|
||||
if c.IsTLS != req.URI().isHTTPS() {
|
||||
return false, ErrHostClientRedirectToDifferentScheme
|
||||
}
|
||||
|
||||
|
@ -1519,6 +1534,7 @@ func (c *HostClient) acquireConn(reqTimeout time.Duration, connectionClose bool)
|
|||
c.conns[n-1] = nil
|
||||
c.conns = c.conns[:n-1]
|
||||
default:
|
||||
c.connsLock.Unlock()
|
||||
return nil, ErrConnPoolStrategyNotImpl
|
||||
}
|
||||
}
|
||||
|
@ -2003,11 +2019,11 @@ func AddMissingPort(addr string, isTLS bool) string {
|
|||
return addr
|
||||
}
|
||||
|
||||
isIp6 := addr[0] == '['
|
||||
if isIp6 {
|
||||
isIP6 := addr[0] == '['
|
||||
if isIP6 {
|
||||
// if the IPv6 has opening bracket but closing bracket is the last char then it doesn't have a port
|
||||
isIp6WithoutPort := addr[addrLen-1] == ']'
|
||||
if !isIp6WithoutPort {
|
||||
isIP6WithoutPort := addr[addrLen-1] == ']'
|
||||
if !isIP6WithoutPort {
|
||||
return addr
|
||||
}
|
||||
} else { // IPv4
|
||||
|
@ -2139,7 +2155,7 @@ func (q *wantConnQueue) peekFront() *wantConn {
|
|||
return nil
|
||||
}
|
||||
|
||||
// cleanFront pops any wantConns that are no longer waiting from the head of the
|
||||
// clearFront pops any wantConns that are no longer waiting from the head of the
|
||||
// queue, reporting whether any were popped.
|
||||
func (q *wantConnQueue) clearFront() (cleaned bool) {
|
||||
for {
|
||||
|
@ -2165,7 +2181,7 @@ func (q *wantConnQueue) clearFront() (cleaned bool) {
|
|||
// It is safe calling PipelineClient methods from concurrently running
|
||||
// goroutines.
|
||||
type PipelineClient struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Address of the host to connect to.
|
||||
Addr string
|
||||
|
@ -2279,7 +2295,7 @@ type PipelineClient struct {
|
|||
}
|
||||
|
||||
type pipelineConnClient struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
Addr string
|
||||
Name string
|
||||
|
|
|
@ -402,7 +402,7 @@ var (
|
|||
|
||||
func newCompressWriterPoolMap() []*sync.Pool {
|
||||
// Initialize pools for all the compression levels defined
|
||||
// in https://golang.org/pkg/compress/flate/#pkg-constants .
|
||||
// in https://pkg.go.dev/compress/flate#pkg-constants .
|
||||
// Compression levels are normalized with normalizeCompressLevel,
|
||||
// so the fit [0..11].
|
||||
var m []*sync.Pool
|
||||
|
@ -413,7 +413,7 @@ func newCompressWriterPoolMap() []*sync.Pool {
|
|||
}
|
||||
|
||||
func isFileCompressible(f *os.File, minCompressRatio float64) bool {
|
||||
// Try compressing the first 4kb of of the file
|
||||
// Try compressing the first 4kb of the file
|
||||
// and see if it can be compressed by more than
|
||||
// the given minCompressRatio.
|
||||
b := bytebufferpool.Get()
|
||||
|
|
|
@ -65,7 +65,7 @@ var cookiePool = &sync.Pool{
|
|||
//
|
||||
// Cookie instance MUST NOT be used from concurrently running goroutines.
|
||||
type Cookie struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
key []byte
|
||||
value []byte
|
||||
|
|
|
@ -121,8 +121,8 @@ func (ln *InmemoryListener) DialWithLocalAddr(local net.Addr) (net.Conn, error)
|
|||
// Wait until the connection has been accepted.
|
||||
<-accepted
|
||||
} else {
|
||||
sConn.Close() //nolint:errcheck
|
||||
cConn.Close() //nolint:errcheck
|
||||
_ = sConn.Close()
|
||||
_ = cConn.Close()
|
||||
cConn = nil
|
||||
}
|
||||
ln.lock.Unlock()
|
||||
|
|
|
@ -223,7 +223,7 @@ func NewPathPrefixStripper(prefixSize int) PathRewriteFunc {
|
|||
//
|
||||
// It is prohibited copying FS values. Create new values instead.
|
||||
type FS struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Path to the root directory to serve files from.
|
||||
Root string
|
||||
|
@ -1304,7 +1304,7 @@ func (h *fsHandler) openFSFile(filePath string, mustCompress bool, fileEncoding
|
|||
}
|
||||
|
||||
// Only re-create the compressed file if there was more than a second between the mod times.
|
||||
// On MacOS the gzip seems to truncate the nanoseconds in the mod time causing the original file
|
||||
// On macOS the gzip seems to truncate the nanoseconds in the mod time causing the original file
|
||||
// to look newer than the gzipped file.
|
||||
if fileInfoOriginal.ModTime().Sub(fileInfo.ModTime()) >= time.Second {
|
||||
// The compressed file became stale. Re-create it.
|
||||
|
|
|
@ -24,7 +24,7 @@ const (
|
|||
// ResponseHeader instance MUST NOT be used from concurrently running
|
||||
// goroutines.
|
||||
type ResponseHeader struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
disableNormalizing bool
|
||||
noHTTP11 bool
|
||||
|
@ -59,7 +59,7 @@ type ResponseHeader struct {
|
|||
// RequestHeader instance MUST NOT be used from concurrently running
|
||||
// goroutines.
|
||||
type RequestHeader struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
disableNormalizing bool
|
||||
noHTTP11 bool
|
||||
|
@ -109,7 +109,7 @@ func (h *ResponseHeader) SetContentRange(startPos, endPos, contentLength int) {
|
|||
h.setNonSpecial(strContentRange, h.bufKV.value)
|
||||
}
|
||||
|
||||
// SetByteRanges sets 'Range: bytes=startPos-endPos' header.
|
||||
// SetByteRange sets 'Range: bytes=startPos-endPos' header.
|
||||
//
|
||||
// - If startPos is negative, then 'bytes=-startPos' value is set.
|
||||
// - If endPos is negative, then 'bytes=startPos-' value is set.
|
||||
|
@ -1495,7 +1495,7 @@ func (h *ResponseHeader) SetCanonical(key, value []byte) {
|
|||
|
||||
// SetCookie sets the given response cookie.
|
||||
//
|
||||
// It is save re-using the cookie after the function returns.
|
||||
// It is safe re-using the cookie after the function returns.
|
||||
func (h *ResponseHeader) SetCookie(cookie *Cookie) {
|
||||
h.cookies = setArgBytes(h.cookies, cookie.Key(), cookie.Cookie(), argsHasValue)
|
||||
}
|
||||
|
@ -3083,7 +3083,7 @@ func (s *headerScanner) next() bool {
|
|||
n++
|
||||
for len(s.b) > n && s.b[n] == ' ' {
|
||||
n++
|
||||
// the newline index is a relative index, and lines below trimed `s.b` by `n`,
|
||||
// the newline index is a relative index, and lines below trimmed `s.b` by `n`,
|
||||
// so the relative newline index also shifted forward. it's safe to decrease
|
||||
// to a minus value, it means it's invalid, and will find the newline again.
|
||||
s.nextNewLine--
|
||||
|
|
|
@ -37,7 +37,7 @@ func SetBodySizePoolLimit(reqBodyLimit, respBodyLimit int) {
|
|||
//
|
||||
// Request instance MUST NOT be used from concurrently running goroutines.
|
||||
type Request struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Request header
|
||||
//
|
||||
|
@ -81,7 +81,7 @@ type Request struct {
|
|||
//
|
||||
// Response instance MUST NOT be used from concurrently running goroutines.
|
||||
type Response struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Response header
|
||||
//
|
||||
|
@ -771,7 +771,7 @@ func (req *Request) ResetBody() {
|
|||
func (req *Request) CopyTo(dst *Request) {
|
||||
req.copyToSkipBody(dst)
|
||||
if req.bodyRaw != nil {
|
||||
dst.bodyRaw = req.bodyRaw
|
||||
dst.bodyRaw = append(dst.bodyRaw[:0], req.bodyRaw...)
|
||||
if dst.body != nil {
|
||||
dst.body.Reset()
|
||||
}
|
||||
|
@ -803,7 +803,7 @@ func (req *Request) copyToSkipBody(dst *Request) {
|
|||
func (resp *Response) CopyTo(dst *Response) {
|
||||
resp.copyToSkipBody(dst)
|
||||
if resp.bodyRaw != nil {
|
||||
dst.bodyRaw = resp.bodyRaw
|
||||
dst.bodyRaw = append(dst.bodyRaw, resp.bodyRaw...)
|
||||
if dst.body != nil {
|
||||
dst.body.Reset()
|
||||
}
|
||||
|
@ -852,9 +852,9 @@ func (req *Request) URI() *URI {
|
|||
// Use this method if a single URI may be reused across multiple requests.
|
||||
// Otherwise, you can just use SetRequestURI() and it will be parsed as new URI.
|
||||
// The URI is copied and can be safely modified later.
|
||||
func (req *Request) SetURI(newUri *URI) {
|
||||
if newUri != nil {
|
||||
newUri.CopyTo(&req.uri)
|
||||
func (req *Request) SetURI(newURI *URI) {
|
||||
if newURI != nil {
|
||||
newURI.CopyTo(&req.uri)
|
||||
req.parsedURI = true
|
||||
return
|
||||
}
|
||||
|
@ -893,7 +893,7 @@ func (req *Request) parsePostArgs() {
|
|||
// isn't 'multipart/form-data'.
|
||||
var ErrNoMultipartForm = errors.New("request has no multipart/form-data Content-Type")
|
||||
|
||||
// MultipartForm returns requests's multipart form.
|
||||
// MultipartForm returns request's multipart form.
|
||||
//
|
||||
// Returns ErrNoMultipartForm if request's Content-Type
|
||||
// isn't 'multipart/form-data'.
|
||||
|
@ -992,6 +992,7 @@ func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error {
|
|||
return fmt.Errorf("cannot open form file %q (%q): %w", k, fv.Filename, err)
|
||||
}
|
||||
if _, err = copyZeroAlloc(vw, fh); err != nil {
|
||||
_ = fh.Close()
|
||||
return fmt.Errorf("error when copying form file %q (%q): %w", k, fv.Filename, err)
|
||||
}
|
||||
if err = fh.Close(); err != nil {
|
||||
|
|
|
@ -25,7 +25,7 @@ type BalancingClient interface {
|
|||
//
|
||||
// It is safe calling LBClient methods from concurrently running goroutines.
|
||||
type LBClient struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Clients must contain non-zero clients list.
|
||||
// Incoming requests are balanced among these clients.
|
||||
|
@ -70,7 +70,7 @@ func (cc *LBClient) DoTimeout(req *Request, resp *Response, timeout time.Duratio
|
|||
return cc.get().DoDeadline(req, resp, deadline)
|
||||
}
|
||||
|
||||
// Do calls calculates deadline using LBClient.Timeout and calls DoDeadline
|
||||
// Do calculates timeout using LBClient.Timeout and calls DoTimeout
|
||||
// on the least loaded client.
|
||||
func (cc *LBClient) Do(req *Request, resp *Response) error {
|
||||
timeout := cc.Timeout
|
||||
|
|
|
@ -5,7 +5,7 @@ package fasthttp
|
|||
//
|
||||
// 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{} //nolint:unused
|
||||
type noCopy struct{}
|
||||
|
||||
func (*noCopy) Lock() {} //nolint:unused
|
||||
func (*noCopy) Unlock() {} //nolint:unused
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
//go:build go1.20
|
||||
// +build go1.20
|
||||
|
||||
package fasthttp
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// s2b converts string to a byte slice without memory allocation.
|
||||
func s2b(s string) []byte {
|
||||
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//go:build !go1.20
|
||||
// +build !go1.20
|
||||
|
||||
package fasthttp
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// s2b converts string to a byte slice without memory allocation.
|
||||
//
|
||||
// Note it may break if string and/or slice header will change
|
||||
// in the future go versions.
|
||||
func s2b(s string) (b []byte) {
|
||||
/* #nosec G103 */
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
/* #nosec G103 */
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
bh.Data = sh.Data
|
||||
bh.Cap = sh.Len
|
||||
bh.Len = sh.Len
|
||||
return b
|
||||
}
|
|
@ -20,8 +20,7 @@ import (
|
|||
var errNoCertOrKeyProvided = errors.New("cert or key has not provided")
|
||||
|
||||
var (
|
||||
// ErrAlreadyServing is returned when calling Serve on a Server
|
||||
// that is already serving connections.
|
||||
// Deprecated: ErrAlreadyServing is never returned from Serve. See issue #633.
|
||||
ErrAlreadyServing = errors.New("Server is already serving connections")
|
||||
)
|
||||
|
||||
|
@ -130,7 +129,7 @@ func ListenAndServeTLSEmbed(addr string, certData, keyData []byte, handler Reque
|
|||
// RequestHandler must process incoming requests.
|
||||
//
|
||||
// RequestHandler must call ctx.TimeoutError() before returning
|
||||
// if it keeps references to ctx and/or its' members after the return.
|
||||
// if it keeps references to ctx and/or its members after the return.
|
||||
// Consider wrapping RequestHandler into TimeoutHandler if response time
|
||||
// must be limited.
|
||||
type RequestHandler func(ctx *RequestCtx)
|
||||
|
@ -148,7 +147,7 @@ type ServeHandler func(c net.Conn) error
|
|||
//
|
||||
// It is safe to call Server methods from concurrently running goroutines.
|
||||
type Server struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Handler for processing incoming requests.
|
||||
//
|
||||
|
@ -377,12 +376,12 @@ type Server struct {
|
|||
// which will close it when needed.
|
||||
KeepHijackedConns bool
|
||||
|
||||
// CloseOnShutdown when true adds a `Connection: close` header when when the server is shutting down.
|
||||
// CloseOnShutdown when true adds a `Connection: close` header when the server is shutting down.
|
||||
CloseOnShutdown bool
|
||||
|
||||
// StreamRequestBody enables request body streaming,
|
||||
// and calls the handler sooner when given body is
|
||||
// larger then the current limit.
|
||||
// larger than the current limit.
|
||||
StreamRequestBody bool
|
||||
|
||||
// ConnState specifies an optional callback function that is
|
||||
|
@ -567,7 +566,7 @@ func CompressHandlerBrotliLevel(h RequestHandler, brotliLevel, otherLevel int) R
|
|||
// It is forbidden copying RequestCtx instances.
|
||||
//
|
||||
// RequestHandler should avoid holding references to incoming RequestCtx and/or
|
||||
// its' members after the return.
|
||||
// its members after the return.
|
||||
// If holding RequestCtx references after the return is unavoidable
|
||||
// (for instance, ctx is passed to a separate goroutine and ctx lifetime cannot
|
||||
// be controlled), then the RequestHandler MUST call ctx.TimeoutError()
|
||||
|
@ -577,7 +576,7 @@ func CompressHandlerBrotliLevel(h RequestHandler, brotliLevel, otherLevel int) R
|
|||
// running goroutines. The only exception is TimeoutError*, which may be called
|
||||
// while other goroutines accessing RequestCtx.
|
||||
type RequestCtx struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
// Incoming request.
|
||||
//
|
||||
|
@ -1003,7 +1002,7 @@ func (ctx *RequestCtx) PostArgs() *Args {
|
|||
return ctx.Request.PostArgs()
|
||||
}
|
||||
|
||||
// MultipartForm returns requests's multipart form.
|
||||
// MultipartForm returns request's multipart form.
|
||||
//
|
||||
// Returns ErrNoMultipartForm if request's content-type
|
||||
// isn't 'multipart/form-data'.
|
||||
|
@ -1234,7 +1233,7 @@ func (ctx *RequestCtx) RemoteAddr() net.Addr {
|
|||
|
||||
// SetRemoteAddr sets remote address to the given value.
|
||||
//
|
||||
// Set nil value to resore default behaviour for using
|
||||
// Set nil value to restore default behaviour for using
|
||||
// connection remote address.
|
||||
func (ctx *RequestCtx) SetRemoteAddr(remoteAddr net.Addr) {
|
||||
ctx.remoteAddr = remoteAddr
|
||||
|
@ -1480,7 +1479,7 @@ func (ctx *RequestCtx) SetBodyStream(bodyStream io.Reader, bodySize int) {
|
|||
// SetBodyStreamWriter registers the given stream writer for populating
|
||||
// response body.
|
||||
//
|
||||
// Access to RequestCtx and/or its' members is forbidden from sw.
|
||||
// Access to RequestCtx and/or its members is forbidden from sw.
|
||||
//
|
||||
// This function may be used in the following cases:
|
||||
//
|
||||
|
@ -1864,7 +1863,7 @@ func (s *Server) Serve(ln net.Listener) error {
|
|||
// When Shutdown is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil.
|
||||
// Make sure the program doesn't exit and waits instead for Shutdown to return.
|
||||
//
|
||||
// Shutdown does not close keepalive connections so its recommended to set ReadTimeout and IdleTimeout to something else than 0.
|
||||
// Shutdown does not close keepalive connections so it's recommended to set ReadTimeout and IdleTimeout to something else than 0.
|
||||
func (s *Server) Shutdown() error {
|
||||
return s.ShutdownWithContext(context.Background())
|
||||
}
|
||||
|
@ -1875,7 +1874,7 @@ func (s *Server) Shutdown() error {
|
|||
// When ShutdownWithContext is called, Serve, ListenAndServe, and ListenAndServeTLS immediately return nil.
|
||||
// Make sure the program doesn't exit and waits instead for Shutdown to return.
|
||||
//
|
||||
// ShutdownWithContext does not close keepalive connections so its recommended to set ReadTimeout and IdleTimeout to something else than 0.
|
||||
// ShutdownWithContext does not close keepalive connections so it's recommended to set ReadTimeout and IdleTimeout to something else than 0.
|
||||
func (s *Server) ShutdownWithContext(ctx context.Context) (err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
@ -1950,12 +1949,12 @@ func acceptConn(s *Server, ln net.Listener, lastPerIPErrorTime *time.Time) (net.
|
|||
|
||||
if tc, ok := c.(*net.TCPConn); ok && s.TCPKeepalive {
|
||||
if err := tc.SetKeepAlive(s.TCPKeepalive); err != nil {
|
||||
tc.Close() //nolint:errcheck
|
||||
_ = tc.Close()
|
||||
return nil, err
|
||||
}
|
||||
if s.TCPKeepalivePeriod > 0 {
|
||||
if err := tc.SetKeepAlivePeriod(s.TCPKeepalivePeriod); err != nil {
|
||||
tc.Close() //nolint:errcheck
|
||||
_ = tc.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -2226,7 +2225,7 @@ func (s *Server) serveConn(c net.Conn) (err error) {
|
|||
|
||||
// Reading Headers.
|
||||
//
|
||||
// If we have pipline response in the outgoing buffer,
|
||||
// If we have pipeline response in the outgoing buffer,
|
||||
// we only want to try and read the next headers once.
|
||||
// If we have to wait for the next request we flush the
|
||||
// outgoing buffer first so it doesn't have to wait.
|
||||
|
|
|
@ -338,8 +338,8 @@ func (d *TCPDialer) tryDial(network string, addr *net.TCPAddr, deadline time.Tim
|
|||
dialer.LocalAddr = d.LocalAddr
|
||||
}
|
||||
|
||||
ctx, cancel_ctx := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancel_ctx()
|
||||
ctx, cancelCtx := context.WithDeadline(context.Background(), deadline)
|
||||
defer cancelCtx()
|
||||
conn, err := dialer.DialContext(ctx, network, addr.String())
|
||||
if err != nil && ctx.Err() == context.DeadlineExceeded {
|
||||
return nil, ErrDialTimeout
|
||||
|
|
|
@ -18,7 +18,7 @@ func initTimer(t *time.Timer, timeout time.Duration) *time.Timer {
|
|||
func stopTimer(t *time.Timer) {
|
||||
if !t.Stop() {
|
||||
// Collect possibly added time from the channel
|
||||
// if timer has been stopped and nobody collected its' value.
|
||||
// if timer has been stopped and nobody collected its value.
|
||||
select {
|
||||
case <-t.C:
|
||||
default:
|
||||
|
@ -44,7 +44,7 @@ func AcquireTimer(timeout time.Duration) *time.Timer {
|
|||
// 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
|
||||
// Do not access the released time.Timer or read from its channel otherwise
|
||||
// data races may occur.
|
||||
func ReleaseTimer(t *time.Timer) {
|
||||
stopTimer(t)
|
||||
|
|
|
@ -40,7 +40,7 @@ var uriPool = &sync.Pool{
|
|||
//
|
||||
// URI instance MUST NOT be used from concurrently running goroutines.
|
||||
type URI struct {
|
||||
noCopy noCopy //nolint:unused,structcheck
|
||||
noCopy noCopy
|
||||
|
||||
pathOriginal []byte
|
||||
scheme []byte
|
||||
|
@ -217,11 +217,11 @@ func (u *URI) SetSchemeBytes(scheme []byte) {
|
|||
lowercaseBytes(u.scheme)
|
||||
}
|
||||
|
||||
func (u *URI) isHttps() bool {
|
||||
func (u *URI) isHTTPS() bool {
|
||||
return bytes.Equal(u.scheme, strHTTPS)
|
||||
}
|
||||
|
||||
func (u *URI) isHttp() bool {
|
||||
func (u *URI) isHTTP() bool {
|
||||
return len(u.scheme) == 0 || bytes.Equal(u.scheme, strHTTP)
|
||||
}
|
||||
|
||||
|
@ -504,7 +504,7 @@ func unescape(s []byte, mode encoding) ([]byte, error) {
|
|||
// appearing in a URL string, according to RFC 3986.
|
||||
//
|
||||
// Please be informed that for now shouldEscape does not check all
|
||||
// reserved characters correctly. See golang.org/issue/5684.
|
||||
// reserved characters correctly. See https://github.com/golang/go/issues/5684.
|
||||
//
|
||||
// Based on https://github.com/golang/go/blob/8ac5cbe05d61df0a7a7c9a38ff33305d4dcfea32/src/net/url/url.go#L100
|
||||
func shouldEscape(c byte, mode encoding) bool {
|
||||
|
@ -631,7 +631,6 @@ func normalizePath(dst, src []byte) []byte {
|
|||
|
||||
if filepath.Separator == '\\' {
|
||||
// remove \.\ parts
|
||||
b = dst
|
||||
for {
|
||||
n := bytes.Index(b, strBackSlashDotBackSlash)
|
||||
if n < 0 {
|
||||
|
@ -652,7 +651,8 @@ func normalizePath(dst, src []byte) []byte {
|
|||
if nn < 0 {
|
||||
nn = 0
|
||||
}
|
||||
n += len(strSlashDotDotBackSlash) - 1
|
||||
nn++
|
||||
n += len(strSlashDotDotBackSlash)
|
||||
copy(b[nn:], b[n:])
|
||||
b = b[:len(b)-n+nn]
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
package fasthttp
|
||||
|
||||
func addLeadingSlash(dst, src []byte) []byte {
|
||||
// zero length and "C:/" case
|
||||
if len(src) == 0 || (len(src) > 2 && src[1] != ':') {
|
||||
// zero length 、"C:/" and "a" case
|
||||
isDisk := len(src) > 2 && src[1] == ':'
|
||||
if len(src) == 0 || (!isDisk && src[0] != '/') {
|
||||
dst = append(dst, '/')
|
||||
}
|
||||
|
||||
|
|
|
@ -3,5 +3,8 @@
|
|||
*.swp
|
||||
/bin/
|
||||
cover.out
|
||||
cover-*.out
|
||||
/.idea
|
||||
*.iml
|
||||
/cmd/bbolt/bbolt
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
language: go
|
||||
go_import_path: go.etcd.io/bbolt
|
||||
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.15
|
||||
|
||||
before_install:
|
||||
- go get -v golang.org/x/sys/unix
|
||||
- go get -v honnef.co/go/tools/...
|
||||
- go get -v github.com/kisielk/errcheck
|
||||
|
||||
script:
|
||||
- make fmt
|
||||
- make test
|
||||
- make race
|
||||
# - make errcheck
|
|
@ -2,35 +2,62 @@ BRANCH=`git rev-parse --abbrev-ref HEAD`
|
|||
COMMIT=`git rev-parse --short HEAD`
|
||||
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
||||
|
||||
race:
|
||||
@TEST_FREELIST_TYPE=hashmap go test -v -race -test.run="TestSimulate_(100op|1000op)"
|
||||
@echo "array freelist test"
|
||||
@TEST_FREELIST_TYPE=array go test -v -race -test.run="TestSimulate_(100op|1000op)"
|
||||
TESTFLAGS_RACE=-race=false
|
||||
ifdef ENABLE_RACE
|
||||
TESTFLAGS_RACE=-race=true
|
||||
endif
|
||||
|
||||
TESTFLAGS_CPU=
|
||||
ifdef CPU
|
||||
TESTFLAGS_CPU=-cpu=$(CPU)
|
||||
endif
|
||||
TESTFLAGS = $(TESTFLAGS_RACE) $(TESTFLAGS_CPU) $(EXTRA_TESTFLAGS)
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
!(gofmt -l -s -d $(shell find . -name \*.go) | grep '[a-z]')
|
||||
|
||||
# go get honnef.co/go/tools/simple
|
||||
gosimple:
|
||||
gosimple ./...
|
||||
|
||||
# go get honnef.co/go/tools/unused
|
||||
unused:
|
||||
unused ./...
|
||||
|
||||
# go get github.com/kisielk/errcheck
|
||||
errcheck:
|
||||
@errcheck -ignorepkg=bytes -ignore=os:Remove go.etcd.io/bbolt
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
TEST_FREELIST_TYPE=hashmap go test -timeout 20m -v -coverprofile cover.out -covermode atomic
|
||||
# Note: gets "program not an importable package" in out of path builds
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ./cmd/bbolt
|
||||
@echo "hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
|
||||
@echo "array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} ./cmd/bbolt
|
||||
|
||||
@TEST_FREELIST_TYPE=array go test -timeout 20m -v -coverprofile cover.out -covermode atomic
|
||||
# Note: gets "program not an importable package" in out of path builds
|
||||
@TEST_FREELIST_TYPE=array go test -v ./cmd/bbolt
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
@echo "hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v -timeout 30m \
|
||||
-coverprofile cover-freelist-hashmap.out -covermode atomic
|
||||
|
||||
@echo "array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v -timeout 30m \
|
||||
-coverprofile cover-freelist-array.out -covermode atomic
|
||||
|
||||
.PHONY: gofail-enable
|
||||
gofail-enable: install-gofail
|
||||
gofail enable .
|
||||
|
||||
.PHONY: gofail-disable
|
||||
gofail-disable:
|
||||
gofail disable .
|
||||
|
||||
.PHONY: install-gofail
|
||||
install-gofail:
|
||||
go install go.etcd.io/gofail
|
||||
|
||||
.PHONY: test-failpoint
|
||||
test-failpoint:
|
||||
@echo "[failpoint] hashmap freelist test"
|
||||
TEST_FREELIST_TYPE=hashmap go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
|
||||
@echo "[failpoint] array freelist test"
|
||||
TEST_FREELIST_TYPE=array go test -v ${TESTFLAGS} -timeout 30m ./tests/failpoint
|
||||
|
||||
.PHONY: race fmt errcheck test gosimple unused
|
||||
|
|
|
@ -26,7 +26,7 @@ and setting values. That's it.
|
|||
[gh_ben]: https://github.com/benbjohnson
|
||||
[bolt]: https://github.com/boltdb/bolt
|
||||
[hyc_symas]: https://twitter.com/hyc_symas
|
||||
[lmdb]: http://symas.com/mdb/
|
||||
[lmdb]: https://www.symas.com/symas-embedded-database-lmdb
|
||||
|
||||
## Project Status
|
||||
|
||||
|
@ -78,14 +78,23 @@ New minor versions may add additional features to the API.
|
|||
### Installing
|
||||
|
||||
To start using Bolt, install Go and run `go get`:
|
||||
|
||||
```sh
|
||||
$ go get go.etcd.io/bbolt/...
|
||||
$ go get go.etcd.io/bbolt@latest
|
||||
```
|
||||
|
||||
This will retrieve the library and install the `bolt` command line utility into
|
||||
your `$GOBIN` path.
|
||||
This will retrieve the library and update your `go.mod` and `go.sum` files.
|
||||
|
||||
To run the command line utility, execute:
|
||||
```sh
|
||||
$ go run go.etcd.io/bbolt/cmd/bbolt@latest
|
||||
```
|
||||
|
||||
Run `go install` to install the `bbolt` command line utility into
|
||||
your `$GOBIN` path, which defaults to `$GOPATH/bin` or `$HOME/go/bin` if the
|
||||
`GOPATH` environment variable is not set.
|
||||
```sh
|
||||
$ go install go.etcd.io/bbolt/cmd/bbolt@latest
|
||||
```
|
||||
|
||||
### Importing bbolt
|
||||
|
||||
|
@ -933,7 +942,7 @@ Below is a list of public, open source projects that use Bolt:
|
|||
* [ipxed](https://github.com/kelseyhightower/ipxed) - Web interface and api for ipxed.
|
||||
* [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies
|
||||
* [Kala](https://github.com/ajvb/kala) - Kala is a modern job scheduler optimized to run on a single node. It is persistent, JSON over HTTP API, ISO 8601 duration notation, and dependent jobs.
|
||||
* [Key Value Access Langusge (KVAL)](https://github.com/kval-access-language) - A proposed grammar for key-value datastores offering a bbolt binding.
|
||||
* [Key Value Access Language (KVAL)](https://github.com/kval-access-language) - A proposed grammar for key-value datastores offering a bbolt binding.
|
||||
* [LedisDB](https://github.com/siddontang/ledisdb) - A high performance NoSQL, using Bolt as optional storage.
|
||||
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
|
||||
* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue